{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}

module Data.Foscam.File.DeviceIdCharacter(
  DeviceIdCharacter
, AsDeviceIdCharacter(..)
, deviceIdCharacter
, getDeviceIdCharacter
) where

import Control.Applicative(Applicative(pure))
import Control.Category(id, (.))
import Control.Lens(Optic', Choice, Getter, prism', re)
import Control.Monad(Monad(fail))
import Data.Char(Char)
import Data.Eq(Eq)
import Data.Functor(fmap)
import Data.Foscam.File.Internal(boolj, charP)
import Data.List(elem, (++))
import Data.Ord(Ord)
import Data.String(String)
import Data.Traversable(traverse)
import Text.Parser.Char(CharParsing)
import Text.Parser.Combinators((<?>))
import Prelude(Show)

-- $setup
-- >>> import Text.Parsec

newtype DeviceIdCharacter = 
  DeviceIdCharacter Char
  deriving (Eq, Ord, Show)

class AsDeviceIdCharacter p f s where
  _DeviceIdCharacter ::
    Optic' p f s DeviceIdCharacter

instance AsDeviceIdCharacter p f DeviceIdCharacter where
  _DeviceIdCharacter =
    id

instance (Choice p, Applicative f) => AsDeviceIdCharacter p f Char where
  _DeviceIdCharacter =
    prism'
      (\(DeviceIdCharacter c) -> c)
      (fmap DeviceIdCharacter . boolj (`elem` (['A'..'F'] ++ ['0'..'9'])))

instance (p ~ (->), Applicative f) => AsDeviceIdCharacter p f String where
  _DeviceIdCharacter =
    traverse . _DeviceIdCharacter

getDeviceIdCharacter ::
  Getter DeviceIdCharacter Char
getDeviceIdCharacter =
  re _DeviceIdCharacter

-- |
--
-- >>> parse deviceIdCharacter "test" "A"
-- Right (DeviceIdCharacter 'A')
-- 
-- >>> parse deviceIdCharacter "test" "0"
-- Right (DeviceIdCharacter '0')
-- 
-- >>> parse deviceIdCharacter "test" "0abc"
-- Right (DeviceIdCharacter '0')
-- 
-- >>> parse deviceIdCharacter "test" "a"
-- Left "test" (line 1, column 2):
-- not a device ID character: a
-- 
-- >>> parse deviceIdCharacter "test" "G"
-- Left "test" (line 1, column 2):
-- not a device ID character: G
--
-- >>> parse deviceIdCharacter "test" ""
-- Left "test" (line 1, column 1):
-- unexpected end of input
-- expecting device ID character    
deviceIdCharacter ::
  (Monad f, CharParsing f) =>
  f DeviceIdCharacter
deviceIdCharacter =
  charP (fail . ("not a device ID character: " ++) . pure) _DeviceIdCharacter <?> "device ID character"