{-|
Module      : Game.GoreAndAsh.Actor.Collection.Data
Description : Handling dynamic collections of actors
Copyright   : (c) Anton Gushcha, 2015-2016
License     : BSD3
Maintainer  : ncrashed@gmail.com
Stability   : experimental
Portability : POSIX
-}
module Game.GoreAndAsh.Actor.Collection.Data(
    DynCollection(..)
  , ElementWithId(..)
  , rightsDynColl
  ) where

import Control.Monad 
import Control.Wire
import Data.Either (isRight)
import Data.Filterable 
import Data.Hashable 
import Data.List (nub)
import GHC.Exts
import Prelude hiding ((.), id)
import qualified Data.Foldable as F 
import qualified Data.HashMap.Strict as H 
import qualified Data.Sequence as S 

-- | Dynamic collection for control wire that automates handling collections of
-- FRP actors. The class defines minimum set of actions that collection should support
-- to be used as base for collection of actors.
class (Filterable c, F.Foldable c, Functor c, Traversable c) => DynCollection c where
  -- | Instance specific constraint for appending function
  type DynConsConstr c o :: Constraint 
  type DynConsConstr c o = ()

  -- | Concat of two collections
  concatDynColl :: c a -> c a -> c a
  -- | Unzipping of collection
  unzipDynColl :: c (a , b) -> (c a, c b)
  -- | Ziping collection
  zipDynColl :: c a -> c b -> c (a, b)
  -- | Getting empty collection
  emptyDynColl :: c a 
  -- | Adding element to the begining of collection
  consDynColl :: DynConsConstr c a => a -> c a -> c a 

instance DynCollection [] where
  concatDynColl = (++)
  unzipDynColl = unzip 
  zipDynColl = zip 
  emptyDynColl = []
  consDynColl = (:)

instance DynCollection S.Seq where 
  concatDynColl = (S.><)
  unzipDynColl = F.foldl' (\(as, bs) (a, b) -> (as S.|> a, bs S.|> b)) (S.empty, S.empty)
  zipDynColl = S.zip 
  emptyDynColl = S.empty
  consDynColl = (S.<|)

-- | Elements that contains id 
class (Hashable i, Eq i) => ElementWithId a i where
  elementId :: a -> i 

-- | Order of elements is not preserved
instance (Eq k, Hashable k) => DynCollection (H.HashMap k) where
  type DynConsConstr (H.HashMap k) o = ElementWithId o k

  concatDynColl = H.union
  unzipDynColl = H.foldlWithKey' (\(as, bs) k (a, b) -> (H.insert k a as, H.insert k b bs)) (H.empty, H.empty) 
  zipDynColl as bs = F.foldl' mrg H.empty $ nub $ H.keys as ++ H.keys bs
    where 
    mrg acc k = case (H.lookup k as, H.lookup k bs) of 
      (Just a, Just b) -> H.insert k (a, b) acc
      _ -> acc

  emptyDynColl = H.empty
  consDynColl a = H.insert (elementId a) a

-- | Helper to filter out lefts
rightsDynColl :: (FilterConstraint c (Either e a), DynCollection c) 
  => c (Either e a) -> c a 
rightsDynColl = fmap fromRight . fFilter isRight
  where
  fromRight e = case e of 
    Left _ -> error "rightsDynColl: left (impossible)"
    Right a -> a