    -- |
    -- Module      : Basement.String.Encoding.Encoding
    -- License     : BSD-style
    -- Maintainer  : Foundation
    -- Stability   : experimental
    -- Portability : portable
    --

    {-# LANGUAGE FlexibleContexts #-}

    module Basement.String.Encoding.Encoding
        ( Encoding(..)
        , convertFromTo
        ) where

    import           Basement.Compat.Base
    import           Basement.Types.OffsetSize
    import           Basement.Monad
    import           Basement.PrimType
    import           Basement.MutableBuilder
    import           Basement.Numerical.Additive
    import           Basement.UArray (UArray)
    import           Basement.UArray.Mutable (MUArray)
    import qualified Basement.UArray as Vec

    class Encoding encoding where
        -- | the unit element use for the encoding.
        -- i.e. Word8 for ASCII7 or UTF8, Word16 for UTF16...
        --
        type Unit encoding

        -- | define the type of error handling you want to use for the
        -- next function.
        --
        -- > type Error UTF8 = Either UTF8_Invalid
        --
        type Error encoding

        -- | consume an `Unit encoding` and return the Unicode point and the position
        -- of the next possible `Unit encoding`
        --
        encodingNext :: encoding
                          -- ^ only used for type deduction
                    -> (Offset (Unit encoding) -> Unit encoding)
                          -- ^ method to access a given `Unit encoding`
                          -- (see `unsafeIndexer`)
                    -> Offset (Unit encoding)
                          -- ^ offset of the `Unit encoding` where starts the
                          -- encoding of a given unicode
                    -> Either (Error encoding) (Char, Offset (Unit encoding)) -- ^ either successfully validated the `Unit encoding`
                          -- and returned the next offset or fail with an
                          -- `Error encoding`

        -- Write a unicode point encoded into one or multiple `Unit encoding`
        --
        -- > build 64 $ sequence_ (write UTF8) "this is a simple list of char..."
        --
        encodingWrite :: (PrimMonad st, Monad st)
                      => encoding
                          -- ^ only used for type deduction
                      -> Char
                          -- ^ the unicode character to encode
                      -> Builder (UArray (Unit encoding))
                                 (MUArray (Unit encoding))
                                 (Unit encoding) st err ()

    -- | helper to convert a given Array in a given encoding into an array
    -- with another encoding.
    --
    -- This is a helper to convert from one String encoding to another.
    -- This function is (quite) slow and needs some work.
    --
    -- ```
    -- let s16 = ... -- string in UTF16
    -- -- create s8, a UTF8 String
    -- let s8  = runST $ convertWith UTF16 UTF8 (toBytes s16)
    --
    -- print s8
    -- ```
    --
    convertFromTo :: ( PrimMonad st, Monad st
                     , Encoding input, PrimType (Unit input)
                     , Encoding output, PrimType (Unit output)
                     )
                  => input
                    -- ^ Input's encoding type
                  -> output
                    -- ^ Output's encoding type
                  -> UArray (Unit input)
                    -- ^ the input raw array
                  -> st (Either (Offset (Unit input), Error input) (UArray (Unit output)))
    convertFromTo inputEncodingTy outputEncodingTy bytes
        | Vec.null bytes = return . return $ mempty
        | otherwise      = Vec.unsafeIndexer bytes $ \t -> Vec.builderBuild 64 (loop azero t)
      where
        lastUnit = Vec.length bytes

        loop off getter
          | off .==# lastUnit = return ()
          | otherwise = case encodingNext inputEncodingTy getter off of
              Left err -> mFail (off, err)
              Right (c, noff) -> encodingWrite outputEncodingTy c >> loop noff getter