{-# LANGUAGE OverloadedStrings #-}

import Control.Exception (evaluate)
import Control.Monad
import qualified Data.Aeson as Json
import qualified Data.ByteString as B
import qualified Data.ByteString.Builder as BBuilder
import Data.Csv
import Data.Maybe (fromJust, isJust, isNothing)
import qualified Data.Vector as V
import Epidemic
import qualified Epidemic.BDSCOD as BDSCOD
import qualified Epidemic.BirthDeath as BD
import qualified Epidemic.BirthDeathSamplingCatastropheOccurrence as BDSCO
import qualified Epidemic.BirthDeathSamplingOccurrence as BDSO
import qualified Epidemic.InhomogeneousBDS as InhomBDS
import Epidemic.Types.Events
import Epidemic.Types.Parameter
import Epidemic.Types.Population
import Epidemic.Utility
import Statistics.Sample
import qualified System.Random.MWC as MWC
import Test.Hspec

-- | y is within n% of x from x.
withinNPercent n x y = x - d < y && y < x + d
  where
    d = n * x / 100

p1 = Person 1

p2 = Person 2

p3 = Person 3

p4 = Person 4

p5 = Person 5

p6 = Person 6

p7 = Person 7

-- | The first set of test data does not have any catastrophe events.
demoFullEvents01 =
  [ Infection 1 p1 p2
  , Infection 2 p1 p3
  , Sampling 3 p1
  , Infection 4 p2 p4
  , Infection 5 p2 p5
  , Sampling 6 p4
  , Infection 7 p3 p6
  , Occurrence 8 p2
  , Removal 9 p3
  , Infection 10 p5 p7
  , Occurrence 11 p6
  , Sampling 12 p5
  , Removal 13 p7
  ]

demoSampleEvents01 =
  [ Infection 1 p1 p2
  , Sampling 3 p1
  , Infection 4 p2 p4
  , Sampling 6 p4
  , Occurrence 8 p2
  , Occurrence 11 p6
  , Sampling 12 p5
  ]

-- | The second set of test data is the same as the first but includes a
-- catastrophe event.
demoFullEvents02 =
  [ Infection 1 p1 p2
  , Infection 2 p1 p3
  , Sampling 3 p1
  , Infection 4 p2 p4
  , Infection 5 p2 p5
  , Sampling 6 p4
  , Infection 7 p3 p6
  , Occurrence 8 p2
  , Removal 9 p3
  , Infection 10 p5 p7
  , Catastrophe 11 (asPeople [p5])
  , Occurrence 12 p6
  , Removal 13 p7
  ]

demoSampleEvents02 =
  [ Infection 1 p1 p2
  , Sampling 3 p1
  , Infection 4 p2 p4
  , Sampling 6 p4
  , Occurrence 8 p2
  , Catastrophe 11 (asPeople [p5])
  , Occurrence 12 p6
  ]

-- | Another test set to test that catastrophes are handled correctly.
demoFullEvents03 =
  [ Infection 1 p1 p4
  , Infection 2 p1 p2
  , Sampling 3 p1
  , Infection 4 p2 p3
  , Infection 5 p4 p5
  , Catastrophe 6 (asPeople [p2, p3, p4])
  ]

demoSampleEvents03 =
  [ Infection 1 p1 p4
  , Infection 2 p1 p2
  , Sampling 3 p1
  , Infection 4 p2 p3
  , Catastrophe 6 (asPeople [p2, p3, p4])
  ]

-- | Another test to make sure that disasters are handled.
demoFullEvents04 =
  [ Infection 1 p1 p4
  , Infection 2 p1 p2
  , Sampling 3 p1
  , Infection 4 p2 p3
  , Infection 5 p4 p5
  , Catastrophe 6 (asPeople [p2, p3, p4])
  , Infection 7 p5 p6
  , Infection 8 p5 p7
  , Disaster 9 (asPeople [p5, p6])
  ]

demoSampleEvents04 =
  [ Infection 1 p1 p4
  , Infection 2 p1 p2
  , Sampling 3 p1
  , Infection 4 p2 p3
  , Catastrophe 6 (asPeople [p2, p3, p4])
  , Disaster 9 (asPeople [p5, p6])
  ]

eventHandlingTests = do
  describe "Post-simulation processing" $ do
    it "Extracting observed events" $ do
      (demoSampleEvents01 == BDSO.observedEvents demoFullEvents01) `shouldBe`
        True
      (demoSampleEvents02 == BDSO.observedEvents demoFullEvents02) `shouldBe`
        True
      let demoEvents = [Catastrophe 0.5  (asPeople []) -- Because the first event is a null event it can be ignored!
                       ,Infection 1.0 p1 p2
                       ,Catastrophe 1.5 (asPeople [])
                       ,Catastrophe 2.0 (asPeople [p1,p2])]
      (length demoEvents == 4) `shouldBe` True
      ((length <$> BDSCOD.observedEvents (tail demoEvents)) == (Just 2)) `shouldBe` True
      ((length <$> BDSCOD.observedEvents (demoEvents)) == (Just 2)) `shouldBe` True
      (BDSCOD.observedEvents (demoEvents) == BDSCOD.observedEvents (tail demoEvents)) `shouldBe` True
      (maybeEpidemicTree (demoEvents) == maybeEpidemicTree (tail demoEvents)) `shouldBe` True
  describe "Catastrophe definitions" $ do
    it "Check we can find a catastrophe" $ do
      (noScheduledEvent 0 1 (Timed [])) `shouldBe` True
      (noScheduledEvent 0 1 (Timed [(2, 0.5)])) `shouldBe` True
      (noScheduledEvent 0 1 (Timed [(0.5, 0.5)])) `shouldBe` False
      (noScheduledEvent 0 1 (Timed [(2, 0.6), (0.5, 0.5)])) `shouldBe` False
    it "Check we can find a particular catastrophe" $ do
      (firstScheduled 1 (Timed [])) `shouldBe` Nothing
      (firstScheduled 1 (Timed [(2, 0.5)])) `shouldBe` Just (2, 0.5)
      (firstScheduled 1 (Timed [(0.5, 0.5)])) `shouldBe` Nothing
      (firstScheduled 1 (Timed [(2, 0.6), (0.5, 0.5)])) `shouldBe` Just (2, 0.6)
      isNothing (asTimed [(2, 0.6 :: Rate), (0.5, 0.5), (1.5, 0.4)]) `shouldBe` True
      (firstScheduled 1 (Timed [(2, 0.6), (0.5, 0.5), (1.5, 0.4)])) `shouldBe`
        Just (2, 0.6)
    it "Works on a very specific case it seems to not like" $ do
      (noScheduledEvent 2.28 (2.28 + 0.42) (Timed [(2.3, 0.9)])) `shouldBe` False
    it "Catastrophes are handled correctly" $ do
      (demoSampleEvents03 == BDSCO.observedEvents demoFullEvents03) `shouldBe`
        True
    it "Catastrophes can be simulated" $ do
      demoSim <-
        simulation False
          (fromJust (BDSCO.configuration 4 (1.3, 0.1, 0.1, ([(3, 0.5)]), 0.2)))
          BDSCO.allEvents
      length demoSim > 1 `shouldBe` True
  describe "Disaster definitions" $ do
    it "Disasters are handled correctly" $ do
      (demoSampleEvents04 == fromJust (BDSCOD.observedEvents demoFullEvents04)) `shouldBe`
        True
    it "Disasters can be simulated" $ do
      demoSim <-
        simulation False
          (fromJust (BDSCOD.configuration 4 (1.3, 0.1, 0.1, [(3, 0.5)], 0.2, [(3.5, 0.5)])))
          BDSCOD.allEvents
      length demoSim > 1 `shouldBe` True

birthDeathTests = do
  describe "BirthDeath module tests" $ do
    it "Construct a simulation configuration" $ do
      (isJust (BD.configuration 1 (1, 1))) `shouldBe` True
      (isJust (BD.configuration (-1) (1, 1))) `shouldBe` False
      (isJust (BD.configuration 1 ((-1), 1))) `shouldBe` False
      (isJust (BD.configuration 1 (1, (-1)))) `shouldBe` False
      (isJust (BD.configuration 1 ((-1), (-1)))) `shouldBe` False
    it "Mean behaviour is approximately correct" $
      let mean xs = fromIntegral (sum xs) / (fromIntegral $ length xs)
          meanFinalSize = exp ((2.1 - 0.2) * 1.5)
          randomBDEvents =
            simulationWithSystemRandom False
              (fromJust $ BD.configuration 1.5 (2.1, 0.2))
              BD.allEvents
          numRepeats = 3000
       in do finalSizes <- replicateM numRepeats (finalSize <$> randomBDEvents)
             (withinNPercent 5 (mean finalSizes) meanFinalSize) `shouldBe` True

helperFuncTests = do
  describe "Helpers in Utility" $ do
    it "the isAscending function works" $ do
      (isAscending ([] :: [Time])) `shouldBe` True
      (isAscending [-1.0]) `shouldBe` True
      (isAscending [1.0]) `shouldBe` True
      (isAscending [1.0, 2.0]) `shouldBe` True
      (isAscending [1.0, 2.0, 3.0]) `shouldBe` True
      (isAscending [1.0, -2.0]) `shouldBe` False
      (isAscending [1.0, -2.0, 3.0]) `shouldBe` False
      (isAscending [1.0, 2.0, -3.0]) `shouldBe` False
    it "the asTimed function works" $ do
      (isJust $ asTimed []) `shouldBe` True
      (isJust $ asTimed [(0, 1)]) `shouldBe` True
      (isJust $ asTimed [(0, 1), (1, 3)]) `shouldBe` True
      (isJust $ asTimed [(0, 3), (1, 1)]) `shouldBe` True
      (isJust $ asTimed [(1, 3), (0, 1)]) `shouldBe` False
    let demoTimed = fromJust $ asTimed [(0, 1.2), (1, 3.1), (2, 2.7)]
     in do it "the cadlagValue function works" $ do
             (isJust $ cadlagValue demoTimed (-1.0)) `shouldBe` False
             ((== 1.2) . fromJust $ cadlagValue demoTimed 0.0) `shouldBe` True
             ((== 1.2) . fromJust $ cadlagValue demoTimed 0.5) `shouldBe` True
             ((== 3.1) . fromJust $ cadlagValue demoTimed 1.5) `shouldBe` True
           it "the diracDeltaValue function works" $ do
             ((== 1.2) . fromJust $ diracDeltaValue demoTimed 0) `shouldBe` True
             (isJust $ diracDeltaValue demoTimed 1) `shouldBe` True
             (isJust $ diracDeltaValue demoTimed 0.9) `shouldBe` False
             (isJust $ diracDeltaValue demoTimed 1.1) `shouldBe` False
           it "the hasTime function works" $ do
             (hasTime demoTimed 0) `shouldBe` True
             (hasTime demoTimed 0.5) `shouldBe` False
             (hasTime demoTimed 1) `shouldBe` True
             (hasTime demoTimed 1.5) `shouldBe` False
           it "the nextTime function works" $ do
             (0 == (fromJust $ nextTime demoTimed (-1))) `shouldBe` True
             (1 == (fromJust $ nextTime demoTimed (0))) `shouldBe` True
             (1 == (fromJust $ nextTime demoTimed (0.5))) `shouldBe` True
           it "the nextTime function handles the last time correctly" $ do
             isJust (nextTime demoTimed 1.9) `shouldBe` True
             isJust (nextTime demoTimed 2.0) `shouldBe` True
             isJust (nextTime demoTimed 2.1) `shouldBe` True
             isJust (nextTime demoTimed 10.0) `shouldBe` True
    it "shifted times work" $
      let sf = fromJust $ asTimed [(-1.0,2.0),(1,3.0)]
          val1 = cadlagValue sf 0
          val2 = cadlagValue sf (-2.0)
          val3 = cadlagValue sf 1.5
       in do
        isJust val1 `shouldBe` True
        val1 == Just 2.0 `shouldBe` True
        (not $ isJust val2) `shouldBe` True
        isJust val3 `shouldBe` True
        val3 == Just 3.0 `shouldBe` True
    it "the asTimed function returns nothing as expected" $ do
      (isJust $ asTimed [(0.0,-1)]) `shouldBe` True
      (isJust $ asTimed [(0.0,1),(1.0,-1)]) `shouldBe` True
      (isJust $ InhomBDS.inhomBDSRates [(0.0,1),(1.0,-1)] 0.5 0.5) `shouldBe` False



readwriteTests =
  do
    describe "Change Event read/write" $ do
      it "check we can writte an event" $
        let demoPerson = Person 3
            demoPersonField = toField demoPerson
            demoPersonField' = "3"
            demoEvent = Removal 1.0 demoPerson
            demoRecord = toRecord demoEvent
            demoRecord' = V.fromList ["removal", "1.0", "3", "NA"] :: Record
            (Right demoEvent') =
              runParser (parseRecord demoRecord) :: Either String EpidemicEvent
            demoRecord2 =
              toRecord (Catastrophe 1.0 (asPeople [p2, p3]))
            (Right demoEvent2@(Catastrophe _ people2)) =
              runParser (parseRecord demoRecord2) :: Either String EpidemicEvent
            demoRecord2' = toRecord demoEvent2
         in do (demoPersonField' == demoPersonField) `shouldBe` True
               (demoRecord' == demoRecord) `shouldBe` True
               (demoEvent' == demoEvent) `shouldBe` True
               (demoRecord2' == demoRecord2) `shouldBe` True
               (numPeople people2 == 2) `shouldBe` True


inhomExpTests =
  describe "Test the inhomogeneous exponential variate generator" $
  let rate1 = 2.0
      sF1 = fromJust $ asTimed [(0, rate1)]
      mean1 = 1 / rate1
      var1 = 1 / (rate1 ** 2.0)
      sF2 = fromJust $ asTimed [(0, 1e-10),(1, rate1)]
      mean2 = 1 / rate1 + 1
      var2 = var1
      genAction = MWC.createSystemRandom
   in do it "check we can get a positive variate out" $
           do
             gen <- genAction
             u1 <- MWC.uniform gen :: IO Double
             (u1 > 0) `shouldBe` True
             x1 <- inhomExponential sF1 gen
             (x1 > 0) `shouldBe` True
             (x1 < 100) `shouldBe` True
             True `shouldBe` True
         it "check the mean and variance look sensible" $
           do gen <- genAction
              x <- V.replicateM 20000 (inhomExponential sF1 gen)
              withinNPercent 5 (mean x) mean1 `shouldBe` True
              withinNPercent 5 (variance x) var1 `shouldBe` True
         it "check the mean and variance look sensible with delay" $
           do gen <- genAction
              x <- V.replicateM 20000 (inhomExponential sF2 gen)
              withinNPercent 5 (mean x) mean2 `shouldBe` True
              withinNPercent 5 (variance x) var2 `shouldBe` True


illFormedTreeTest =
  describe "Prevent the simulator returning a broken tree" $ do
  let simDuration = 0.2
      simLambda = 3.2
      simMu = 0.3
      simPsi = 0.3
      simRho = 0.15
      simRhoTime = 2.6
      simOmega = 0.3
      simNu = 0.15
      simNuTime = 3.0
      simParams = (simLambda, simMu, simPsi, [(simRhoTime,simRho)], simOmega, [(simNuTime,simNu)])
      simConfig = BDSCOD.configuration simDuration simParams
    in it "stress testing the observed events function" $
       do
         null (BDSCOD.observedEvents []) `shouldBe` True
         simEvents <- simulation True (fromJust simConfig) BDSCOD.allEvents
         any isReconTreeLeaf simEvents `shouldBe` True
         (length (fromJust $ BDSCOD.observedEvents simEvents) > 1) `shouldBe` True





inhomogeneousBDSTest =
  describe "InhomogeneousBDS module tests" $ do
    it "Check the observedEvents filters out removals" $
      let demoAllEvents = [Infection 0.1 p1 p2
                          ,Sampling 0.2 p1
                          ,Removal 0.3 p3
                          ,Sampling 0.4 p2]
          demoObsEvents = [Infection 0.1 p1 p2
                          ,Sampling 0.2 p1
                          ,Sampling 0.4 p2]
          compObsEvents = InhomBDS.observedEvents demoAllEvents
       in do
        (compObsEvents == demoObsEvents) `shouldBe` True


helperTypeTests = do
  describe "Helpers for working with the types" $ do
    it "the isAscending function works" $ do
      (isAscending ([] :: [Time])) `shouldBe` True
      (isAscending [-1.0]) `shouldBe` True
      (isAscending [1.0]) `shouldBe` True
      (isAscending [1.0, 2.0]) `shouldBe` True
      (isAscending [1.0, 2.0, 3.0]) `shouldBe` True
      (isAscending [1.0, -2.0]) `shouldBe` False
      (isAscending [1.0, -2.0, 3.0]) `shouldBe` False
      (isAscending [1.0, 2.0, -3.0]) `shouldBe` False
    it "the asTimed function works" $ do
      (isJust $ asTimed []) `shouldBe` True
      (isJust $ asTimed [(0, 1)]) `shouldBe` True
      (isJust $ asTimed [(0, 1), (1, 3)]) `shouldBe` True
      (isJust $ asTimed [(0, 3), (1, 1)]) `shouldBe` True
      (isJust $ asTimed [(1, 3), (0, 1)]) `shouldBe` False
    let demoTimed = fromJust $ asTimed [(0, 1.2), (1, 3.1), (2, 2.7)]
     in do it "the cadlagValue function works" $ do
             (isJust $ cadlagValue demoTimed (-1.0)) `shouldBe` False
             ((== 1.2) . fromJust $ cadlagValue demoTimed 0.0) `shouldBe` True
             ((== 1.2) . fromJust $ cadlagValue demoTimed 0.5) `shouldBe` True
             ((== 3.1) . fromJust $ cadlagValue demoTimed 1.5) `shouldBe` True
           it "the diracDeltaValue function works" $ do
             ((== 1.2) . fromJust $ diracDeltaValue demoTimed 0) `shouldBe` True
             (isJust $ diracDeltaValue demoTimed 1) `shouldBe` True
             (isJust $ diracDeltaValue demoTimed 0.9) `shouldBe` False
             (isJust $ diracDeltaValue demoTimed 1.1) `shouldBe` False
           it "the hasTime function works" $ do
             (hasTime demoTimed 0) `shouldBe` True
             (hasTime demoTimed 0.5) `shouldBe` False
             (hasTime demoTimed 1) `shouldBe` True
             (hasTime demoTimed 1.5) `shouldBe` False
           it "the nextTime function works" $ do
             (0 == (fromJust $ nextTime demoTimed (-1))) `shouldBe` True
             (1 == (fromJust $ nextTime demoTimed (0))) `shouldBe` True
             (1 == (fromJust $ nextTime demoTimed (0.5))) `shouldBe` True
    it "shifted times work" $
      let sf = fromJust $ asTimed [(-1.0, 2.0), (1, 3.0)]
          val1 = cadlagValue sf 0
          val2 = cadlagValue sf (-2.0)
          val3 = cadlagValue sf 1.5
       in do isJust val1 `shouldBe` True
             val1 == Just 2.0 `shouldBe` True
             (not $ isJust val2) `shouldBe` True
             isJust val3 `shouldBe` True
             val3 == Just 3.0 `shouldBe` True

jsonTests = do
  describe "Converting to and from JSON" $ do
    it "Conversion of Timed Rate" $ do
      let demoObj = Timed [(0.0, 1.0), (1.0, 1.0)] :: Timed Rate
          (Timed demoVals) = demoObj
          demoJson = "[[0,1],[1,1]]"
          encodedObj = Json.encode demoObj
          decodedJson = Json.decode demoJson :: Maybe (Timed Rate)
       in do True `shouldBe` True
             let (Timed foo) = demoObj in demoVals == foo `shouldBe` True
             encodedObj == demoJson `shouldBe` True
             isJust decodedJson `shouldBe` True
             let (Timed bar) = fromJust decodedJson in demoVals == bar `shouldBe` True

equalBuilders :: BBuilder.Builder -> BBuilder.Builder -> Bool
equalBuilders a b = BBuilder.toLazyByteString a == BBuilder.toLazyByteString b


newickTests =
  let p1 = Person 1
      p2 = Person 2
      p3 = Person 3
      ps = asPeople [p1,p2]
      maybeEpiTree = maybeEpidemicTree [Infection 1 p1 p2,Infection 2 p2 p3,Catastrophe 3 (asPeople [p1,p3]),Removal 4 p2]
      maybeEpiTree' = maybeEpidemicTree [Infection 1 p1 p2,Infection 2 p2 p3,Catastrophe 3 (asPeople [p1,p3]),Sampling 4 p2]
      maybeEpiTree'' = maybeEpidemicTree [Infection 1 p1 p2,Infection 2 p2 p3,Disaster 3 (asPeople [p1,p3]),Sampling 4 p2]
    in
    describe "Writing to Newick" $ do
    it "equalBuilders works as expected" $ do
      equalBuilders (BBuilder.charUtf8 ':') (BBuilder.charUtf8 ':') `shouldBe` True
      equalBuilders (BBuilder.charUtf8 'a') (BBuilder.charUtf8 ':') `shouldBe` False
    it "derivedFrom works as expected" $ do
      let p1 = Person 1
      let p2 = Person 2
      let p3 = Person 3
      let e = [Infection 0.3 p1 p2]
      derivedFrom p1 e == derivedFrom p2 e `shouldBe` True
      derivedFrom p1 e /= derivedFrom p3 e `shouldBe` True
      derivedFrom p1 e /= [] `shouldBe` True
      null (derivedFrom p3 e) `shouldBe` True
      derivedFrom p1 e == e `shouldBe` True
      let foo = derivedFrom (Person 1) [Infection 0.3 (Person 1) (Person 2),Sampling 0.7 (Person 1)]
      let bar = derivedFrom (Person 2) [Infection 0.3 (Person 1) (Person 2),Sampling 0.7 (Person 1)]
      foo == bar `shouldBe` True
    it "maybeEpidemicTree works as expected: 1" $ do
      let e1 = Removal 1 (Person 1)
      maybeEpidemicTree [e1] == Just (Leaf e1) `shouldBe` True
      let t1 = maybeEpidemicTree [Infection 0.3 (Person 1) (Person 2),Sampling 0.6 (Person 2),Sampling 0.7 (Person 1)]
      let t2 = Just (Branch (Infection 0.3 (Person 1) (Person 2)) (Leaf (Sampling 0.7 (Person 1))) (Leaf (Sampling 0.6 (Person 2))))
      t1 == t2 `shouldBe` True
      maybeEpidemicTree [Infection 0.3 (Person 1) (Person 2)] == Just (Branch (Infection 0.3 (Person 1) (Person 2)) (Shoot (Person 1)) (Shoot (Person 2))) `shouldBe` True
      maybeEpidemicTree [Infection 0.3 (Person 1) (Person 2),Sampling 0.7 (Person 1)] == Just (Branch (Infection 0.3 (Person 1) (Person 2)) (Leaf (Sampling 0.7 (Person 1))) (Shoot (Person 2))) `shouldBe` True
      let trickyEvents = [Infection 0.3 (Person 1) (Person 2),Infection 0.4 (Person 2) (Person 3),Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)]
      isJust (maybeEpidemicTree trickyEvents) `shouldBe` True
    it "maybeEpidemicTree works as expected: 2" $ do
      let p1 = Person 1
          p2 = Person 2
          demoEvents = [Catastrophe 0.5 (asPeople []) -- Because the first event is a null event it can be ignored!
                       ,Infection 1.0 p1 p2
                       ,Catastrophe 1.5 (asPeople [])
                       ,Catastrophe 2.0 (asPeople [p1,p2])]
      (length demoEvents == 4) `shouldBe` True
      (maybeEpidemicTree demoEvents == maybeEpidemicTree (tail demoEvents)) `shouldBe` True
    it "asNewickString works for EpidemicTree" $ do
      let trickyEvents = [Infection 0.3 (Person 1) (Person 2),Infection 0.4 (Person 2) (Person 3),Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)]
      let maybeNewickPair = asNewickString (0, Person 1) =<< maybeEpidemicTree trickyEvents
      let newickTarget = BBuilder.stringUtf8 "(1:0.39999999999999997,(2:Infinity,3:0.19999999999999996):0.10000000000000003):0.3"
      let maybeReconTree = maybeReconstructedTree =<< maybeEpidemicTree trickyEvents
      isJust maybeNewickPair `shouldBe` True
      [Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)] == snd (fromJust maybeNewickPair) `shouldBe` True
      equalBuilders newickTarget (fst $ fromJust maybeNewickPair) `shouldBe` True
      isJust maybeReconTree `shouldBe` True
    it "asNewickString works for ReconstructedTree" $ do
      isJust (asNewickString (0,Person 1) (RLeaf (Sampling 1 (Person 1)))) `shouldBe` True
      let trickyEvents = [Infection 0.3 (Person 1) (Person 2),Infection 0.4 (Person 2) (Person 3),Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)]
      let maybeNewickPair = asNewickString (0, Person 1) =<< maybeReconstructedTree =<< maybeEpidemicTree trickyEvents
      let newickTarget = BBuilder.stringUtf8 "(1:0.39999999999999997,3:0.3):0.3"
      isJust maybeNewickPair `shouldBe` True
      [Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)] == snd (fromJust maybeNewickPair) `shouldBe` True
      equalBuilders newickTarget (fst $ fromJust maybeNewickPair) `shouldBe` True
      let catasNewick = (asNewickString (0,Person 1) (RLeaf (Catastrophe 1 (asPeople [Person 1,Person 2]))))
      let catasTarget =  BBuilder.stringUtf8 "1&2:1.0"
      equalBuilders catasTarget (fst $ fromJust catasNewick) `shouldBe` True

main :: IO ()
main =
  hspec $ do
    eventHandlingTests
    birthDeathTests
    helperFuncTests
    readwriteTests
    inhomExpTests
    illFormedTreeTest
    inhomogeneousBDSTest
    helperTypeTests
    jsonTests
    newickTests