{-# LANGUAGE DeriveFoldable         #-}
{-# LANGUAGE DeriveFunctor          #-}
{-# LANGUAGE DeriveTraversable      #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE LambdaCase             #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE NoImplicitPrelude      #-}
-- |
--
-- Types and functions for handling \\u0000 values in JSON.
--
module Waargonaut.Types.JChar.HexDigit4
  (
    -- * Types
    HexDigit4 (..)
  , HasHexDigit4 (..)

    -- * Parse / Build
  , parseHexDigit4

    -- * Conversion
  , hexDigit4ToChar
  , charToHexDigit4
  ) where

import           Prelude             (Eq, Ord (..), Show, otherwise, (||))

import           Control.Applicative ((<*>))
import           Control.Category    (id, (.))
import           Control.Lens        (Lens')
import           Control.Monad       ((=<<))

import           Control.Error.Util  (hush)

import           Data.List.NonEmpty  (NonEmpty ((:|)))

import           Data.Foldable       (Foldable)
import           Data.Function       (($))
import           Data.Functor        (Functor, fmap, (<$>))
import           Data.Traversable    (Traversable)

import           Data.Char           (Char, chr, ord)
import           Data.Either         (Either (..))
import           Data.Maybe          (Maybe (..))
import           Text.Parser.Char    (CharParsing)

import           Data.Digit          (HeXDigit, HeXaDeCiMaL)
import qualified Data.Digit          as D

-- $setup
-- >>> :set -XOverloadedStrings
-- >>> import Control.Monad (return)
-- >>> import Data.Either(Either (..), isLeft)
-- >>> import Data.Digit (HeXDigit(..))
-- >>> import qualified Data.Digit as D
-- >>> import Waargonaut.Decode.Error (DecodeError)
-- >>> import Utils
----

-- | JSON Characters may be single escaped UTF16 "\uab34".
data HexDigit4 d =
  HexDigit4 d d d d
  deriving (Eq, Show, Ord, Functor, Foldable, Traversable)

-- | Typeclass for things that contain a 'HexDigit4'.
class HasHexDigit4 c d | c -> d where
  hexDigit4 :: Lens' c (HexDigit4 d)

instance HasHexDigit4 (HexDigit4 d) d where
  hexDigit4 = id

hexHeX :: D.HexDigit -> D.HeXDigit
hexHeX = \case
  D.HexDigit0 -> D.HeXDigit0
  D.HexDigit1 -> D.HeXDigit1
  D.HexDigit2 -> D.HeXDigit2
  D.HexDigit3 -> D.HeXDigit3
  D.HexDigit4 -> D.HeXDigit4
  D.HexDigit5 -> D.HeXDigit5
  D.HexDigit6 -> D.HeXDigit6
  D.HexDigit7 -> D.HeXDigit7
  D.HexDigit8 -> D.HeXDigit8
  D.HexDigit9 -> D.HeXDigit9
  D.HexDigita -> D.HeXDigita
  D.HexDigitb -> D.HeXDigitb
  D.HexDigitc -> D.HeXDigitc
  D.HexDigitd -> D.HeXDigitd
  D.HexDigite -> D.HeXDigite
  D.HexDigitf -> D.HeXDigitf

-- | Convert a given 'HexDigit4' to a Haskell 'Char'.
hexDigit4ToChar :: HexDigit4 HeXDigit -> Char
hexDigit4ToChar (HexDigit4 a b c d) = chr (D._HeXDigitsIntegral (Right $ a :| [b,c,d]))

-- | Try to convert a Haskell 'Char' to a JSON acceptable character. NOTE: This
-- cannot preserve the upper or lower casing of any original 'Waargonaut.Types.Json.Json' data structure
-- inputs that may have been used to create this 'Char'. Also the JSON RFC
-- specifies a "limited" range of @U+0000@ to @U+FFFF@ as permissible as a six
-- character sequence: @\u0000@.
charToHexDigit4 :: Char -> Maybe (HexDigit4 HeXDigit)
charToHexDigit4 x
  | x < '\x0' || x > '\xffff' = Nothing
  | otherwise                 = toHexDig . fmap hexHeX =<< hush (D.integralHexDigits (ord x))
  where
    z = D.x0

    toHexDig (a :| [b,c,d]) = Just (HexDigit4 a b c d)
    toHexDig (  b :| [c,d]) = Just (HexDigit4 z b c d)
    toHexDig (    c :| [d]) = Just (HexDigit4 z z c d)
    toHexDig (     d :| []) = Just (HexDigit4 z z z d)
    toHexDig              _ = Nothing

{-# INLINE charToHexDigit4 #-}

-- | Parse a single 'HexDigit4'.
--
-- >>> testparse parseHexDigit4 "1234" :: Either DecodeError (HexDigit4 HeXDigit)
-- Right (HexDigit4 HeXDigit1 HeXDigit2 HeXDigit3 HeXDigit4)
--
-- >>> testparse parseHexDigit4 "12aF" :: Either DecodeError (HexDigit4 HeXDigit)
-- Right (HexDigit4 HeXDigit1 HeXDigit2 HeXDigita HeXDigitF)
--
-- >>> testparse parseHexDigit4 "aBcD" :: Either DecodeError (HexDigit4 HeXDigit)
-- Right (HexDigit4 HeXDigita HeXDigitB HeXDigitc HeXDigitD)
--
-- >>> testparsetheneof parseHexDigit4 "12aF" :: Either DecodeError (HexDigit4 HeXDigit)
-- Right (HexDigit4 HeXDigit1 HeXDigit2 HeXDigita HeXDigitF)
--
-- >>> testparsethennoteof parseHexDigit4 "12aFx" :: Either DecodeError (HexDigit4 HeXDigit)
-- Right (HexDigit4 HeXDigit1 HeXDigit2 HeXDigita HeXDigitF)
parseHexDigit4 ::
  ( CharParsing f, HeXaDeCiMaL digit ) =>
  f ( HexDigit4 digit )
parseHexDigit4 = HexDigit4
  <$> D.parseHeXaDeCiMaL
  <*> D.parseHeXaDeCiMaL
  <*> D.parseHeXaDeCiMaL
  <*> D.parseHeXaDeCiMaL
