{-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE UnliftedFFITypes #-}

module System.AbstractFilePath.Internal where

import {-# SOURCE #-} System.AbstractFilePath
    ( isValid )
import System.AbstractFilePath.Types
import System.OsString.Internal
import qualified System.OsString.Internal as OS

import Control.Monad.Catch
    ( MonadThrow )
import Data.ByteString
    ( ByteString )
import Language.Haskell.TH
import Language.Haskell.TH.Quote
    ( QuasiQuoter (..) )
import Language.Haskell.TH.Syntax
    ( Lift (..), lift )
import System.IO
    ( TextEncoding )
import System.AbstractFilePath.Encoding ( EncodingException(..) )



-- | Convert a String.
--
-- On windows this encodes as UTF16, which is a pretty good guess.
-- On unix this encodes as UTF8, which is a good guess.
--
-- Throws a 'EncodingException' if encoding fails.
toAbstractFilePathUtf :: MonadThrow m => String -> m AbstractFilePath
toAbstractFilePathUtf = toOsStringUtf

-- | Like 'toAbstractFilePathUtf', except allows to provide encodings.
toAbstractFilePathEnc :: TextEncoding  -- ^ unix text encoding
                      -> TextEncoding  -- ^ windows text encoding
                      -> String
                      -> Either EncodingException AbstractFilePath
toAbstractFilePathEnc = toOsStringEnc

-- | Like 'toAbstractFilePathUtf', except on unix this uses the current
-- filesystem locale for encoding instead of always UTF8.
--
-- Looking up the locale requires IO. If you're not worried about calls
-- to 'setFileSystemEncoding', then 'unsafePerformIO' may be feasible (make sure
-- to deeply evaluate the result to catch exceptions).
--
-- Throws 'EncodingException' if decoding fails.
toAbstractFilePathFS :: String -> IO AbstractFilePath
toAbstractFilePathFS = toOsStringFS


-- | Partial unicode friendly decoding.
--
-- On windows this decodes as UTF16-LE (which is the expected filename encoding).
-- On unix this decodes as UTF8 (which is a good guess). Note that
-- filenames on unix are encoding agnostic char arrays.
--
-- Throws a 'EncodingException' if decoding fails.
--
-- Note that filenames of different encodings may have the same @String@
-- representation, although they're not the same byte-wise.
fromAbstractFilePathUtf :: MonadThrow m => AbstractFilePath -> m String
fromAbstractFilePathUtf = fromOsStringUtf

-- | Like 'fromAbstractFilePathUtf', except on unix this uses the provided
-- 'TextEncoding' for decoding.
fromAbstractFilePathEnc :: TextEncoding  -- ^ unix text encoding
                        -> TextEncoding  -- ^ windows text encoding
                        -> AbstractFilePath
                        -> Either EncodingException String
fromAbstractFilePathEnc = fromOsStringEnc

-- | Like 'fromAbstractFilePathUtf', except on unix this uses the current
-- locale for decoding instead of always UTF8. On windows, uses UTF-16LE.
--
-- Looking up the locale requires IO. If you're not worried about calls
-- to 'setFileSystemEncoding', then 'unsafePerformIO' may be feasible (make sure
-- to deeply evaluate the result to catch exceptions).
--
-- Throws 'EncodingException' if decoding fails.
fromAbstractFilePathFS :: AbstractFilePath -> IO String
fromAbstractFilePathFS = fromOsStringFS


-- | Constructs an @AbstractFilePath@ from a ByteString.
--
-- On windows, this ensures valid UCS-2LE, on unix it is passed unchanged/unchecked.
--
-- Throws 'EncodingException' on invalid UCS-2LE on windows (although unlikely).
bytesToAFP :: MonadThrow m
           => ByteString
           -> m AbstractFilePath
bytesToAFP = OS.bytesToOsString


mkAbstractFilePath :: ByteString -> Q Exp
mkAbstractFilePath bs = 
  case bytesToAFP bs of
    Just afp' ->
      if isValid afp'
      then lift afp'
      else error "invalid filepath"
    Nothing -> error "invalid encoding"

-- | QuasiQuote an 'AbstractFilePath'. This accepts Unicode characters
-- and encodes as UTF-8 on unix and UTF-16 on windows. Runs 'filepathIsValid'
-- on the input.
afp :: QuasiQuoter
afp = qq mkAbstractFilePath


-- | Unpack an 'AbstractFilePath' to a list of 'OsChar'.
unpackAFP :: AbstractFilePath -> [OsChar]
unpackAFP = unpackOsString


-- | Pack a list of 'OsChar' to an 'AbstractFilePath'.
--
-- Note that using this in conjunction with 'unsafeFromChar' to
-- convert from @[Char]@ to 'AbstractFilePath' is probably not what
-- you want, because it will truncate unicode code points.
packAFP :: [OsChar] -> AbstractFilePath
packAFP = packOsString