module Neet.Species (
                      Species(..)
                    , SpecScore(..)
                      
                    , newSpec
                      
                    , TestResult(..)
                    , runFitTest
                    , updateSpec
                      
                    , maxDist
                      
                    , validateSpecies
                    ) where
import Neet.Genome
import Neet.Parameters
import Data.MultiMap (MultiMap)
import qualified Data.MultiMap as MM
import Data.List (foldl')
import Data.Maybe
import Control.Applicative ((<$>), (<*>))
data Species =
  Species { specSize :: Int
          , specOrgs :: [Genome]    
          , specScore :: SpecScore
          , lastImprovement :: Int  
          }
data SpecScore = SpecScore { bestScore :: !Double, bestGen :: !Genome }
instance Show Species where
  show (Species siz _ (SpecScore scr _) lastImprov) =
    "Species {specSize = " ++ show siz ++
    ", specOrgs = <...>, bestScore = " ++ show scr ++
    ", bestGen = <...>" ++
    ", lastImprovement = " ++ show lastImprov ++ "}"
newSpec :: Genome -> [Genome] -> Species
newSpec gen gens = Species (length gens + 1) (gen:gens) (SpecScore 0 gen) 0
data TestResult =
  TR { trScores :: MultiMap Double Genome 
     , trSS     :: !SpecScore              
     , trAdj    :: !Double                 
     , trSol    :: !(Maybe Genome)           
     }
findMay :: (a -> Bool) -> [a] -> Maybe a
findMay _ [] = Nothing
findMay p (a:as)
  | p a = Just a
  | otherwise = findMay p as
runFitTest :: GenScorer a -> Species -> TestResult
runFitTest GS{..} Species{..} = TR mmap ss (totF / dubSize) msolution
  where dubSize = fromIntegral specSize :: Double
        (mmap, totF) = foldl' accumOne (MM.empty, 0) resses
        calcOne g = let !score = gScorer g in (score, g)
        resses = map calcOne specOrgs
        msolution = fmap snd . findMay (\pair -> winCriteria (fst pair)) $ resses
        accumOne (accM, accA) (score, g) = (MM.insert fit g accM, accA + fit)
          where fit = fitnessFunction score
        ss = case MM.findMaxWithValues mmap of
              Nothing -> error "(runFitTest) folding fitness resulted in empty map!"
              Just (scr, (x:_)) -> SpecScore scr x
              _       -> error "(runFitTest) MultiMap had a key with empty list!"
updateSpec :: SpecScore -> Species -> Species
updateSpec ss spec = spec { specScore = newScr
                          , lastImprovement = li
                          }
  where oldScr = specScore spec
        (newScr, li)
          | bestScore ss > bestScore oldScr = (ss, 0)
          | otherwise                       = (oldScr, lastImprovement spec + 1)
validateSpecies :: Species -> Maybe [String]
validateSpecies Species{..} = case orgErrs ++ goodSize of
                               [] -> Nothing
                               xs -> Just xs
  where orgErrs = concat $ mapMaybe validateGenome specOrgs
        goodSize
          | specSize == length specOrgs = []
          | otherwise = ["Species size differs from number of organisms"]
        
maxDist :: Parameters -> Species -> Double
maxDist ps Species{..} = maximum . map (uncurry (distance ps)) $ (,) <$> specOrgs <*> specOrgs