module Text.Format.ArgKey ( ArgKey (..), emptyKey ) where

import           Control.Arrow
import           Data.Char     (isDigit)
import qualified Data.List     as L


{-| A data type indicates key of format argument

==== The key syntax

  @
    key :: [(int | chars) {"!" (int | chars)}]
  @

  Note: See 'Format' to learn more about syntax description language

  Examples

    >>> read "0" :: ArgKey
    >>> read "country" :: ArgKey
    >>> read "coun!!try" :: ArgKey
    >>> read "country!name" :: ArgKey
    >>> read "country!cities!10!name" :: ArgKey
-}
data ArgKey = Index Int           -- ^ Refers to a positional argument or
                                  -- index in a list like argument.
            | Name String         -- ^ Refers to a named argument or a record
                                  -- name of an argument.
            | Nest ArgKey ArgKey  -- ^ Refers to a attribute (index or name) of
                                  -- an argument.
            deriving (Eq, Ord)

instance Read ArgKey where
  readsPrec _ "" = [ (emptyKey, "") ]
  readsPrec _ cs = [ parse cs ]
    where
      parse :: String -> (ArgKey, String)
      parse cs =
        case break cs of
          ("", cs1)  -> (undefined, cs1)
          (_, "!")   -> (undefined, "!")
          (cs1, "")  -> (parse1 cs1, "")
          (cs1, cs2) -> first (Nest $ parse1 cs1) (parse cs2)

      parse1 :: String -> ArgKey
      parse1 cs = if all isDigit cs then Index (read cs) else Name cs

      break :: String -> (String, String)
      break cs =
        case L.break (== '!') cs of
          (cs1, "")              -> (cs1, "")
          (cs1, "!")             -> (cs1, "!")
          (cs1, '!' : '!' : cs2) -> first ((cs1 ++ "!") ++) (break cs2)
          (cs1, '!' : cs2)       -> (cs1, cs2)

instance Show ArgKey where
  show k@(Index i) = if emptyKey == k then "" else show i
  show (Name s)    = escape s
    where
      escape :: String -> String
      escape ""         = ""
      escape ('!' : cs) = "!!" ++ escape cs
      escape (c : cs)   = (c : escape cs)
  show (Nest k1 k2)  = show k1 ++ "!" ++ show k2

emptyKey :: ArgKey
emptyKey = Index (-1)