{-# LANGUAGE OverloadedStrings #-}
-- |control center for external using
module AI.Heukarya.Center(
  module G
  ,module S
  ,EcoConfig(..)
  ,EcoUnWritable(..)
  ,EcoSystem(..)
  ,initGeneration
  ,nextGeneration
  ,evolveGeneration
  ,evolveCheckGeneration
  ,readEcoSystem
  ,writeEcoSystem
  ,defaultEcoConfig
) where
import Prelude hiding (map)
import System.Random
import AI.Heukarya.Gene as G(Tree,evalTreeGene)
import AI.Heukarya.Jungle
import Data.Text(Text,unpack)
import Data.Sequence(mapWithIndex)
import Data.Sequence as S(Seq)

smap x = mapWithIndex (\_->x)

-- |configuration of ecosystem
data EcoConfig = EcoConfig {
  num         :: Int    -- ^the number of Heukarya contained in EcoSystem
  ,depth      :: Int    -- ^depth of Heukaryas' tree structure
  ,maxWinProb :: Double -- ^probability of Eukarya who has bigger fitness win the game
  ,mutateProb :: Double -- ^probability of mutating
  ,crossProb  :: Double -- ^probability of pairing crossover
  ,outType    :: Text   -- ^Heukaryas' root's type, represented by Text
} deriving (Show, Read)

-- |storing the data which are not writable to file
data EcoUnWritable g d = EcoUnWritable {
  rndGen      :: g      -- ^Random Generator
  ,genes      :: [d]    -- ^genes for constructing Heukarya
}

defaultEcoConfig = EcoConfig{num=2000, depth=20, maxWinProb=0.6, mutateProb=0.4, crossProb=0.8, outType="Double"} 

-- |EcoSystem consists of ecosystem's configuration and Jungle.
--  `Tree rep` is sometimes used for repsentation of Heukarya by `Tree String`
data EcoSystem g d rep = EcoSystem { ecoUnW :: EcoUnWritable g d ,ecoConfig :: EcoConfig, ecoJungle :: Seq (Tree rep) } 

-- |initial a EcoSystem
initGeneration unW cfg = EcoSystem 
  unW{rndGen = g1} 
  cfg  
  (genJungle g2 (depth cfg) (genes unW) (outType cfg) (num cfg) )
  where
    (g1,g2) = split (rndGen unW) 

-- |evolve a EcoSystem one step
--  Parameters: EcoSystem , fitness function
nextGeneration ecoSys@(EcoSystem unW cfg jg) fitness =
  EcoSystem unW{rndGen = g1} cfg mutatedJg
  where
    (g1,g2) = split (rndGen unW) 
    (g3,g4) = split g2
    (g5,g6) = split g4
    choicedJg = choiceJungle g3 fitness jg (maxWinProb cfg)
    crossedJg = crossJungle  g5 (depth cfg) choicedJg (crossProb cfg)
    mutatedJg = mutateJungle g6 (depth cfg) (genes unW) crossedJg (mutateProb cfg)

-- |evolving until condiction suffered or max number of Generations hitted
--  Parameters : max number of generations, EcoSystem, stop evolving's Condiction ,
--               fitness function, generation counter(placed by zero when used)
evolveGeneration maxGeneration ecoSys@(EcoSystem unW cfg jg) stopCondict fitnessFunc cnt = 
  if stopCondict jg || cnt >= maxGeneration then ecoSys else
    evolveGeneration
      maxGeneration
      (nextGeneration ecoSys $ smap fitnessFunc jg)
      stopCondict
      fitnessFunc
      cnt

-- |evolving until condiction suffered or max number of Generations hitted
--  save checkpoint per period
--  Parameters : max number of generations, checkpoint's file path,
--               save checkpoint per x generations, 
--               EcoSystem, stop evolving's Condiction ,                
--               fitness function, generation counter(placed by zero when used)
evolveCheckGeneration maxGeneration filepath checksPer 
  ecoSys@(EcoSystem unW cfg jg) stopCondict fitnessFunc cnt = do
  if cnt `rem` checksPer == 0 then writeEcoSystem filepath ecoSys else return ()
  if stopCondict jg || cnt >= maxGeneration 
    then writeEcoSystem filepath ecoSys >> return ecoSys 
    else 
      evolveCheckGeneration
        maxGeneration
        filepath
        checksPer
        (nextGeneration ecoSys $ smap fitnessFunc jg)
        stopCondict
        fitnessFunc
        cnt
      
-- |read a EcoSystem from file and addon UnWritable data
--  Parameters : filepath for reading, UnWritable data
readEcoSystem filepath unW@(EcoUnWritable g geneList) = do
  (cfg,strJg) <- readFile filepath >>= (return.read) :: IO (EcoConfig,Seq (Tree String))
  jg <- return $ smap (fmap (\x->head $ filter (\s -> show s == x) geneList)) strJg
  return (EcoSystem unW cfg jg)

-- |write a EcoSystem into file without UnWritable data
--  Parameters : filepath for saving, EcoSystem saved
writeEcoSystem filepath (EcoSystem unW cfg jg) = 
  writeFile filepath (show $ (cfg,smap (fmap show) jg))