{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GADTs #-}

module ACME.PreCure.Monad
  ( PreCureMonad
  , EpisodeConfig(lineIntervalMicroSec)
  , defaultEpisodeConfig
  , speak
  , say
  , runPreCureMonad
  , composeEpisode
  , printEpisode
  , printEpisodeWith
  , hPrintEpisode
  , hPrintEpisodeWith
  , transform
  , purify
  , purifyWithoutItem
  ) where


import qualified Control.Arrow as Arrow
import qualified Control.Concurrent as Concurrent
import qualified Control.Monad as Monad
import           Control.Monad.Skeleton
                   ( Skeleton
                   , MonadView((:>>=), Return)
                   , bone
                   , debone
                   )
import qualified Data.DList as DList
import           Data.Monoid ((<>))
import qualified System.IO as IO

import           ACME.PreCure.Types


data PreCureMonadBase x where
  Speak :: [String] -> PreCureMonadBase ()


type PreCureMonad = Skeleton PreCureMonadBase


newtype EpisodeConfig =
  EpisodeConfig
    { lineIntervalMicroSec :: Int }


defaultEpisodeConfig :: EpisodeConfig
defaultEpisodeConfig =
  EpisodeConfig $ 1 * 1000 * 1000


speak :: [String] -> PreCureMonad ()
speak =
  bone . Speak


say :: String -> PreCureMonad ()
say =
  speak . (:[])


composeEpisode :: PreCureMonad a -> [String]
composeEpisode =
  snd . runPreCureMonad


-- TODO: Write test
runPreCureMonad :: PreCureMonad a -> (a, [String])
runPreCureMonad =
    Arrow.second DList.toList . runPreCureMonadD


runPreCureMonadD :: PreCureMonad a -> (a, DList.DList String)
runPreCureMonadD a =
  case debone a of
       Speak ss :>>= k ->
         let (x, ss') = runPreCureMonadD $ k ()
         in
           (x, DList.fromList ss <> ss')
       Return x -> (x, DList.empty)


printEpisode :: PreCureMonad a -> IO ()
printEpisode =
  printEpisodeWith defaultEpisodeConfig


hPrintEpisode :: IO.Handle -> PreCureMonad a -> IO ()
hPrintEpisode =
  flip hPrintEpisodeWith defaultEpisodeConfig


printEpisodeWith :: EpisodeConfig -> PreCureMonad a -> IO ()
printEpisodeWith =
  hPrintEpisodeWith IO.stdout


hPrintEpisodeWith :: IO.Handle -> EpisodeConfig -> PreCureMonad a -> IO ()
hPrintEpisodeWith h cfg a = do
  let interval = lineIntervalMicroSec cfg
  Monad.forM_ (composeEpisode a) $ \s -> do
    IO.hPutStrLn h s 
    IO.hFlush h
    Concurrent.threadDelay interval


transform
  :: (Transformation g' i')
  => g' -> i' -> (Style g' i' -> PreCureMonad x) -> PreCureMonad x
transform g i a = do
  speak $ transformationSpeech g i
  a $ transformedStyle g i


purify
  :: (Purification p' i')
  => p' -> i' -> PreCureMonad ()
purify p =
  speak . purificationSpeech p


purifyWithoutItem
  :: (NonItemPurification p')
  => p' -> PreCureMonad ()
purifyWithoutItem =
  speak . nonItemPurificationSpeech