{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveDataTypeable #-}
module System.FilePath.Wrapper where

import Data.Data
import Data.Typeable
import Control.Applicative
import Control.Monad.State
import Control.Monad.Trans
import qualified System.FilePath as F hiding ((</>))
import qualified Data.Map as M
import Data.Monoid
import Text.Printf

data FileT h a = FileT h a
  deriving(Show,Eq,Ord,Data,Typeable)

-- | Convert File back to FilePath
-- toFilePath :: (FileT h FilePath) -> FilePath
-- toFilePath (FileT _ f) = f

fromFilePath :: h -> FilePath -> FileT h FilePath
fromFilePath h f = FileT h f

-- instance (Monoid a, Monoid h) => Monoid (FileT h a) where
--   mempty = FileT mempty mempty
--   mappend (FileT h1 a) (FileT h2 b) = FileT (a`mappend`b)

class FileLike a where
  -- fromFilePath :: FilePath -> a
  combine :: a -> String -> a
  takeDirectory :: a -> a
  takeBaseName :: a -> String
  takeFileName :: a -> String
  makeRelative :: a -> a -> a
  replaceExtension :: a -> String -> a
  takeExtension :: a -> String
  takeExtensions :: a -> String
  dropExtensions :: a -> a
  dropExtension :: a -> a
  splitDirectories :: a -> [String]

instance (Monad m, FileLike (FileT h a)) => FileLike (m (FileT h a)) where
  combine mx s = mx >>= \x -> return $ combine x s
  takeDirectory mx = mx >>= return . takeDirectory
  takeBaseName mx = error "takeBaseName with monadic argument"
  takeFileName mx = error "takeFileName with monadic argument"
  makeRelative mx my = mx >>= \x -> my >>= \y -> return $ makeRelative x y
  replaceExtension mx s = mx >>= \x -> return $ replaceExtension x s
  takeExtension mx = error "takeExtension with monadic argument"
  takeExtensions mx = error "takeExtensions with monadic argument"
  dropExtensions mx = mx >>= return . dropExtensions
  dropExtension mx = mx >>= return . dropExtension
  splitDirectories mx = error "splitDirectories with monadic argument"

-- | Redefine standard @</>@ operator to work with Files
(</>) :: (FileLike a) => a -> String -> a
(</>) = combine

-- | Alias for replaceExtension
(.=) :: (FileLike a) => a -> String -> a
(.=) = replaceExtension

instance (Eq h, Show h, FileLike a) => FileLike (FileT h a) where
  -- fromFilePath fp = FileT (fromFilePath fp)
  combine (FileT h a) b = FileT h (combine a b)
  takeBaseName (FileT _ a) = takeBaseName a
  takeFileName (FileT _ a) = takeFileName a
  takeExtension (FileT _ a) = takeExtension a
  takeExtensions (FileT _ a) = takeExtensions a
  makeRelative (FileT h1 a) (FileT h2 b)
    | h1 == h2 = FileT h1 (makeRelative a b)
    | otherwise = error $ "makeRelative: FileT, hints are different: " ++ (show h1) ++ " <> " ++ (show h2)
  replaceExtension (FileT h a) ext = FileT h (replaceExtension a ext)
  takeDirectory (FileT h a) = FileT h (takeDirectory a)
  dropExtensions (FileT h a) = FileT h (dropExtensions a)
  dropExtension (FileT h a) = FileT h (dropExtension a)
  splitDirectories (FileT _ a) = splitDirectories a

instance FileLike FilePath where
  -- fromFilePath = id
  combine = F.combine
  takeBaseName = F.takeBaseName
  takeFileName = F.takeFileName
  makeRelative = F.makeRelative
  replaceExtension = F.replaceExtension
  takeDirectory = F.takeDirectory
  takeExtension = F.takeExtension
  takeExtensions = F.takeExtensions
  dropExtensions = F.dropExtensions
  dropExtension = F.dropExtension
  splitDirectories = F.splitDirectories