{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}

module Zifter.Zift.Types where

import Prelude

import Control.Concurrent.STM
import Control.Monad.Catch (MonadThrow(..))
import Control.Monad.Fail as Fail
import Control.Monad.IO.Class
import Data.Validity
import Data.Validity.Path ()
import GHC.Generics
import Path
import System.Console.ANSI (SGR)

import Zifter.OptParse.Types

data ZiftToken =
    ZiftToken [LR]
              (Maybe ZiftOutput)
    deriving (Show, Eq, Generic)

data ZiftOutput = ZiftOutput
    { outputColors :: [SGR]
    , outputMessage :: String
    } deriving (Show, Eq, Generic)

data ZiftContext = ZiftContext
    { rootdir :: Path Abs Dir
    , tmpdir :: Path Abs Dir
    , settings :: Settings
    , printChan :: TChan ZiftToken
    , recursionList :: [LR] -- In reverse order
    } deriving (Generic)

data LR
    = L
    | R
    deriving (Show, Eq, Generic)

instance Validity ZiftContext where
    isValid = isValid . rootdir
#if MIN_VERSION_validity(0,4,0)
    validate zc = rootdir zc <?!> "rootdir"
#endif
data Zift a where
    ZiftPure :: a -> Zift a
    ZiftCtx :: Zift ZiftContext
    ZiftPrint :: ZiftOutput -> Zift ()
    ZiftFail :: String -> Zift a
    ZiftIO :: IO a -> Zift a
    ZiftFmap :: (a -> b) -> Zift a -> Zift b
    ZiftApp :: Zift (a -> b) -> Zift a -> Zift b
    ZiftBind :: Zift a -> (a -> Zift b) -> Zift b

instance Monoid a => Monoid (Zift a) where
    mempty = ZiftPure mempty
    mappend z1 z2 = mappend <$> z1 <*> z2

instance Functor Zift where
    fmap = ZiftFmap

-- | 'Zift' actions can be sequenced.
--
-- The implementation automatically parallelises the arguments of the
-- @(<*>)@ function. If any of the actions fails, the other is cancelled
-- and the result fails.
instance Applicative Zift where
    pure = ZiftPure
    (<*>) = ZiftApp

-- | 'Zift' actions can be composed.
instance Monad Zift where
    (>>=) = ZiftBind
    fail = Fail.fail

-- | A 'Zift' action can fail.
--
-- To make a Zift action fail, you can use the @fail :: String -> Zift a@
-- function.
--
-- The implementation uses the given string as the message that is shown at
-- the very end of the run.
instance MonadFail Zift where
    fail = ZiftFail
    -- fail s = Zift $ \_ -> pure $ ZiftFailed s

-- | Any IO action can be part of a 'Zift' action.
--
-- This is the most important instance for the end user.
--
-- > liftIO :: IO a -> Zift a
-- allows embedding arbitrary IO actions inside a 'Zift' action.
--
-- The implementation also ensures that exceptions are caught.
instance MonadIO Zift where
    liftIO = ZiftIO

instance MonadThrow Zift where
    throwM = ZiftIO . throwM

data ZiftResult a
    = ZiftSuccess a
    | ZiftFailed String
    deriving (Show, Eq, Generic)

instance Validity a => Validity (ZiftResult a) where
    isValid (ZiftSuccess a) = isValid a
    isValid _ = True

instance Monoid a => Monoid (ZiftResult a) where
    mempty = ZiftSuccess mempty
    mappend z1 z2 = mappend <$> z1 <*> z2

instance Functor ZiftResult where
    fmap f (ZiftSuccess a) = ZiftSuccess $ f a
    fmap _ (ZiftFailed s) = ZiftFailed s

instance Applicative ZiftResult where
    pure = ZiftSuccess
    (ZiftSuccess f) <*> (ZiftSuccess a) = ZiftSuccess $ f a
    (ZiftFailed e) <*> (ZiftSuccess _) = ZiftFailed e
    (ZiftSuccess _) <*> (ZiftFailed e) = ZiftFailed e
    (ZiftFailed e1) <*> (ZiftFailed e2) = ZiftFailed $ unwords [e1, e2]

instance Monad ZiftResult where
    (ZiftSuccess a) >>= fb = fb a
    (ZiftFailed e) >>= _ = ZiftFailed e

instance MonadFail ZiftResult where
    fail = ZiftFailed
-- -- | Internal: do not use yourself.
-- tryFlushZiftBuffer :: ZiftContext -> ZiftState -> IO ZiftState
-- tryFlushZiftBuffer ctx st =
--     if flushable $ recursionList ctx
--         then do
--             let zos = reverse $ bufferedOutput st
--                 st' = st {bufferedOutput = []}
--             atomically $ mapM_ (writeTChan $ printChan ctx) zos
--             pure st'
--         else pure st
--
-- -- The buffer is flushable when it's guaranteed to be the first in the in-order
-- -- of the evaluation tree.
-- flushable :: [LR] -> Bool
-- flushable = all (== L)