    {-# 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 :: forall (m :: * -> *). MonadThrow m => FilePath -> m OsString
    encodeUtf = FilePath -> m OsString
    forall (m :: * -> *). MonadThrow m => FilePath -> m OsString
    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 :: HasCallStack => FilePath -> OsString
    unsafeEncodeUtf = HasCallStack => FilePath -> OsString
    FilePath -> OsString
    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 :: TextEncoding
    -> TextEncoding -> FilePath -> Either EncodingException OsString
    encodeWith = TextEncoding
    -> TextEncoding -> FilePath -> Either EncodingException OsString
    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 :: FilePath -> IO OsString
    encodeFS = FilePath -> IO OsString
    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 :: forall (m :: * -> *). MonadThrow m => OsString -> m FilePath
    decodeUtf = OsString -> m FilePath
    forall (m :: * -> *). MonadThrow m => OsString -> m FilePath
    OS.decodeUtf

    -- | Decode an 'OsPath' with the specified encoding.
    decodeWith :: TextEncoding  -- ^ unix text encoding
               -> TextEncoding  -- ^ windows text encoding
               -> OsPath
               -> Either EncodingException FilePath
    decodeWith :: TextEncoding
    -> TextEncoding -> OsString -> Either EncodingException FilePath
    decodeWith = TextEncoding
    -> TextEncoding -> OsString -> Either EncodingException FilePath
    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 :: OsString -> IO FilePath
    decodeFS = OsString -> IO FilePath
    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 :: forall (m :: * -> *). MonadThrow m => ByteString -> m OsString
    fromBytes = ByteString -> m OsString
    forall (m :: * -> *). MonadThrow m => ByteString -> m OsString
    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
    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 :: FilePath -> Q Exp
    quoteExp = \FilePath
    s -> do
          osp' <- (EncodingException -> Q OsString)
    -> (PlatformString -> Q OsString)
    -> Either EncodingException PlatformString
    -> Q OsString
    forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
    either (FilePath -> Q OsString
    forall a. FilePath -> Q a
    forall (m :: * -> *) a. MonadFail m => FilePath -> m a
    fail (FilePath -> Q OsString)
    -> (EncodingException -> FilePath)
    -> EncodingException
    -> Q OsString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . EncodingException -> FilePath
    forall a. Show a => a -> FilePath
    show) (OsString -> Q OsString
    forall a. a -> Q a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (OsString -> Q OsString)
    -> (PlatformString -> OsString) -> PlatformString -> Q OsString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PlatformString -> OsString
    OsString) (Either EncodingException PlatformString -> Q OsString)
    -> (FilePath -> Either EncodingException PlatformString)
    -> FilePath
    -> Q OsString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . TextEncoding -> FilePath -> Either EncodingException PlatformString
    PF.encodeWith (CodingFailureMode -> TextEncoding
    mkUTF8 CodingFailureMode
    ErrorOnCodingFailure) (FilePath -> Q OsString) -> FilePath -> Q OsString
    forall a b. (a -> b) -> a -> b
    $ FilePath
    s
          when (not $ isValid osp') $ fail ("filepath not valid: " ++ show osp')
          lift osp'
      , quotePat :: FilePath -> Q Pat
    quotePat = \FilePath
    s -> do
          osp' <- (EncodingException -> Q OsString)
    -> (PlatformString -> Q OsString)
    -> Either EncodingException PlatformString
    -> Q OsString
    forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
    either (FilePath -> Q OsString
    forall a. FilePath -> Q a
    forall (m :: * -> *) a. MonadFail m => FilePath -> m a
    fail (FilePath -> Q OsString)
    -> (EncodingException -> FilePath)
    -> EncodingException
    -> Q OsString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . EncodingException -> FilePath
    forall a. Show a => a -> FilePath
    show) (OsString -> Q OsString
    forall a. a -> Q a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (OsString -> Q OsString)
    -> (PlatformString -> OsString) -> PlatformString -> Q OsString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PlatformString -> OsString
    OsString) (Either EncodingException PlatformString -> Q OsString)
    -> (FilePath -> Either EncodingException PlatformString)
    -> FilePath
    -> Q OsString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . TextEncoding -> FilePath -> Either EncodingException PlatformString
    PF.encodeWith (CodingFailureMode -> TextEncoding
    mkUTF8 CodingFailureMode
    ErrorOnCodingFailure) (FilePath -> Q OsString) -> FilePath -> Q OsString
    forall a b. (a -> b) -> a -> b
    $ FilePath
    s
          when (not $ isValid osp') $ fail ("filepath not valid: " ++ show osp')
          [p|((==) osp' -> True)|]
      , quoteType :: FilePath -> Q Type
    quoteType = \FilePath
    _ ->
          FilePath -> Q Type
    forall a. FilePath -> Q a
    forall (m :: * -> *) a. MonadFail m => FilePath -> m a
    fail FilePath
    "illegal QuasiQuote (allowed as expression or pattern only, used as a type)"
      , quoteDec :: FilePath -> Q [Dec]
    quoteDec  = \FilePath
    _ ->
          FilePath -> Q [Dec]
    forall a. FilePath -> Q a
    forall (m :: * -> *) a. MonadFail m => FilePath -> m a
    fail FilePath
    "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 :: OsString -> [OsChar]
    unpack = OsString -> [OsChar]
    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 :: [OsChar] -> OsString
    pack = [OsChar] -> OsString
    OS.pack