content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE RankNTypes #-}
  {-# LANGUAGE UnliftedFFITypes #-}
  {-# LANGUAGE TemplateHaskellQuotes #-}
  {-# LANGUAGE ViewPatterns #-}  -- needed to quote a view pattern
  
  module System.OsPath.Internal where
  
  import {-# SOURCE #-} System.OsPath
      ( isValid )
  import System.OsPath.Types
  import qualified System.OsString.Internal as OS
  
  import Control.Monad.Catch
      ( MonadThrow )
  import Data.ByteString
      ( ByteString )
  import Language.Haskell.TH.Quote
      ( QuasiQuoter (..) )
  import Language.Haskell.TH.Syntax
      ( Lift (..), lift )
  import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )
  
  import System.OsString.Internal.Types
  import System.OsPath.Encoding
  import Control.Monad (when)
  import System.IO
      ( TextEncoding )
  
  #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
  import qualified System.OsPath.Windows as PF
  import GHC.IO.Encoding.UTF16 ( mkUTF16le )
  #else
  import qualified System.OsPath.Posix as PF
  import GHC.IO.Encoding.UTF8 ( mkUTF8 )
  #endif
  import GHC.Stack (HasCallStack)
  
  
  
  -- | Partial unicode friendly encoding.
  --
  -- On windows this encodes as UTF16-LE (strictly), which is a pretty good guess.
  -- On unix this encodes as UTF8 (strictly), which is a good guess.
  --
  -- Throws an 'EncodingException' if encoding fails. If the input does not
  -- contain surrogate chars, you can use 'unsafeEncodeUtf'.
  encodeUtf :: MonadThrow m => FilePath -> m OsPath
  encodeUtf = OS.encodeUtf
  
  -- | Unsafe unicode friendly encoding.
  --
  -- Like 'encodeUtf', except it crashes when the input contains
  -- surrogate chars. For sanitized input, this can be useful.
  unsafeEncodeUtf :: HasCallStack => String -> OsString
  unsafeEncodeUtf = OS.unsafeEncodeUtf
  
  -- | Encode a 'FilePath' with the specified encoding.
  --
  -- Note: on windows, we expect a "wide char" encoding (e.g. UCS-2 or UTF-16). Anything
  -- that works with @Word16@ boundaries. Picking an incompatible encoding may crash
  -- filepath operations.
  encodeWith :: TextEncoding  -- ^ unix text encoding
             -> TextEncoding  -- ^ windows text encoding (wide char)
             -> FilePath
             -> Either EncodingException OsPath
  encodeWith = OS.encodeWith
  
  -- | Like 'encodeUtf', except this mimics the behavior of the base library when doing filesystem
  -- operations, which is:
  --
  -- 1. on unix, uses shady PEP 383 style encoding (based on the current locale,
  --    but PEP 383 only works properly on UTF-8 encodings, so good luck)
  -- 2. on windows does permissive UTF-16 encoding, where coding errors generate
  --    Chars in the surrogate range
  --
  -- 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).
  encodeFS :: FilePath -> IO OsPath
  encodeFS = OS.encodeFS
  
  
  -- | Partial unicode friendly decoding.
  --
  -- On windows this decodes as UTF16-LE (strictly), which is a pretty good guess.
  -- On unix this decodes as UTF8 (strictly), which is a good guess.
  --
  -- Throws a 'EncodingException' if decoding fails.
  decodeUtf :: MonadThrow m => OsPath -> m FilePath
  decodeUtf = OS.decodeUtf
  
  -- | Decode an 'OsPath' with the specified encoding.
  decodeWith :: TextEncoding  -- ^ unix text encoding
             -> TextEncoding  -- ^ windows text encoding
             -> OsPath
             -> Either EncodingException FilePath
  decodeWith = OS.decodeWith
  
  -- | Like 'decodeUtf', except this mimics the behavior of the base library when doing filesystem
  -- operations, which is:
  --
  -- 1. on unix, uses shady PEP 383 style encoding (based on the current locale,
  --    but PEP 383 only works properly on UTF-8 encodings, so good luck)
  -- 2. on windows does permissive UTF-16 encoding, where coding errors generate
  --    Chars in the surrogate range
  --
  -- 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).
  decodeFS :: OsPath -> IO FilePath
  decodeFS = OS.decodeFS
  
  
  -- | Constructs an @OsPath@ 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).
  fromBytes :: MonadThrow m
            => ByteString
            -> m OsPath
  fromBytes = OS.fromBytes
  
  
  
  -- | QuasiQuote an 'OsPath'. This accepts Unicode characters
  -- and encodes as UTF-8 on unix and UTF-16LE on windows. Runs 'isValid'
  -- on the input. If used as a pattern, requires turning on the @ViewPatterns@
  -- extension.
  osp :: QuasiQuoter
  osp = QuasiQuoter
  #if defined(mingw32_HOST_OS) || defined(__MINGW32__)
    { quoteExp = \s -> do
        osp' <- either (fail . show) (pure . OsString) . PF.encodeWith (mkUTF16le ErrorOnCodingFailure) $ s
        when (not $ isValid osp') $ fail ("filepath not valid: " ++ show osp')
        lift osp'
    , quotePat = \s -> do
        osp' <- either (fail . show) (pure . OsString) . PF.encodeWith (mkUTF16le ErrorOnCodingFailure) $ s
        when (not $ isValid osp') $ fail ("filepath not valid: " ++ show osp')
        [p|((==) osp' -> True)|]
    , quoteType = \_ ->
        fail "illegal QuasiQuote (allowed as expression or pattern only, used as a type)"
    , quoteDec  = \_ ->
        fail "illegal QuasiQuote (allowed as expression or pattern only, used as a declaration)"
    }
  #else
    { quoteExp = \s -> do
        osp' <- either (fail . show) (pure . OsString) . PF.encodeWith (mkUTF8 ErrorOnCodingFailure) $ s
        when (not $ isValid osp') $ fail ("filepath not valid: " ++ show osp')
        lift osp'
    , quotePat = \s -> do
        osp' <- either (fail . show) (pure . OsString) . PF.encodeWith (mkUTF8 ErrorOnCodingFailure) $ s
        when (not $ isValid osp') $ fail ("filepath not valid: " ++ show osp')
        [p|((==) osp' -> True)|]
    , quoteType = \_ ->
        fail "illegal QuasiQuote (allowed as expression or pattern only, used as a type)"
    , quoteDec  = \_ ->
        fail "illegal QuasiQuote (allowed as expression or pattern only, used as a declaration)"
    }
  #endif
  
  
  -- | Unpack an 'OsPath' to a list of 'OsChar'.
  unpack :: OsPath -> [OsChar]
  unpack = OS.unpack
  
  
  -- | Pack a list of 'OsChar' to an 'OsPath'.
  --
  -- Note that using this in conjunction with 'unsafeFromChar' to
  -- convert from @[Char]@ to 'OsPath' is probably not what
  -- you want, because it will truncate unicode code points.
  pack :: [OsChar] -> OsPath
  pack = OS.pack
  
  