{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving, FlexibleInstances,
             FlexibleContexts, UndecidableInstances, MultiParamTypeClasses,
             TypeFamilies #-}
module Types ( GorsvetState(..), Repository(..)
             , GorsvetT(..), Gorsvet, IO', UUID(..), ObjectMap(..), FixedSize(..)
             , repo, pristine, shadow, inventory, order, editRepository, editRepositoryM
             , editInventory, editOrder, editShadow ) where

import Prelude hiding ( readFile, writeFile )
import Control.Applicative( (<$>) )
import Control.Monad.State ( gets, modify )
import Control.Monad.IO.Class( MonadIO(..) )
import Control.Monad.CatchIO ( MonadCatchIO(..) )
import Control.Monad.Trans ( MonadTrans(..) )
import System.FSLib.IO ( IO', FsRO(..), FsRW(..) )
import System.FSLib.Hash ( Hash )
import System.FSLib.StoreMonad ( LoadMonad )
import Data.Patch.ObjectMap ( ObjectMap(..), UUID )
import Data.Patch.ApplyMonad ( ApplyMonad(..), ApplyState, OmSemantics, ApplyDictionary )
import Data.ByteString ( ByteString )
import Data.Map ( Map )
import Data.Path ( Absolute )
import Control.Monad.State( StateT(..), MonadState )

data GorsvetState = GorsvetState { gsRepository :: Repository Gorsvet
                                 , gsRemotes :: [Absolute]
                                 }

data Repository m = Repository { rPristine :: ObjectMap m
                               , rShadow :: ObjectMap m
                               , rInventory :: Map Hash Hash -- from hashPI to patch
                               , rOrder :: [Hash] -- of hashPI
                               }

instance (MonadTrans t, Functor (t m), MonadCatchIO (t m), FsRO m) => FsRO (t m) where
  getCurrentDirectory = lift getCurrentDirectory
  setCurrentDirectory = lift . setCurrentDirectory
  doesDirectoryExist = lift . doesDirectoryExist
  doesFileExist = lift . doesFileExist
  doesExist = lift . doesExist
  readFile = lift . readFile
  readFileBL = lift . readFileBL
  readFileBS = lift . readFileBS
  readFileLazily = lift . readFileLazily
  readDirectory = lift . readDirectory
  getFileStatus = lift . getFileStatus

instance (MonadTrans t,  Functor (t m),MonadCatchIO (t m), FsRW m) => FsRW (t m) where
  createDirectory = lift . createDirectory
  removeRecursively = lift . removeRecursively
  writeFile x y = lift $ writeFile x y
  writeFileBL x y = lift $ writeFileBL x y
  writeFileBS x y = lift $ writeFileBS x y

  rename x y = lift $ rename x y
  renameFile x y = lift $ renameFile x y
  renameDirectory x y = lift $ renameDirectory x y

type instance ApplyState IO' OmSemantics = ObjectMap IO'
type instance ApplyState Gorsvet OmSemantics = ObjectMap Gorsvet

newtype GorsvetT m a = GorsvetT {
  runGorsvetT :: StateT GorsvetState m a
} deriving (Functor, Monad, MonadIO, MonadCatchIO, MonadState GorsvetState, MonadTrans)

type Gorsvet = GorsvetT IO'

repo :: Gorsvet (Repository Gorsvet)
repo = gets gsRepository

pristine :: Gorsvet (ObjectMap Gorsvet)
pristine = rPristine <$> repo

shadow :: Gorsvet (ObjectMap Gorsvet)
shadow = rShadow <$> repo

inventory :: Gorsvet (Map Hash Hash)
inventory = rInventory <$> repo

order :: Gorsvet [Hash]
order = rOrder <$> repo

class FixedSize v where
  fixedSize :: v -> Int
  toBS :: v -> ByteString
  fromBS :: ByteString -> v

editShadow id obj = do put <- omPut <$> shadow
                       shadow' <- put id obj
                       editRepository $ \x -> x { rShadow = shadow' }

editRepository :: (Repository Gorsvet -> Repository Gorsvet) -> Gorsvet ()
editRepository f = modify $ \gs -> gs { gsRepository = f $ gsRepository gs }

editRepositoryM :: (Repository Gorsvet -> Gorsvet (Repository Gorsvet)) -> Gorsvet ()
editRepositoryM f = do repo' <- f =<< repo
                       modify $ \gs -> gs { gsRepository = repo' }

editInventory :: (Map Hash Hash -> Map Hash Hash) -> Gorsvet ()
editInventory f =
  do inv' <- f <$> rInventory <$> repo
     editRepository $ \r -> r { rInventory = inv' }

editOrder :: ([Hash] -> [Hash]) -> Gorsvet ()
editOrder f =
  do ord' <- f <$> rOrder <$> repo
     editRepository $ \r -> r { rOrder = ord' }


