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

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


-- | ArgKey indicates a key of format argument
--
--  There are two kinds of basic key, named and indexed,
--  and a composed key indicates a key which is a attribute of
--  an argument.
--
--  When read from a String, the sytax is as followings:
--
--  1. if all chars are digits, it means an indexed key,
--
--  2. if there is a __"!"__, it means a nested key,
--     the chars before __"!"__ is parent key,
--     and the chars after are child key,
--
--  3. if you want to use literal __"!"__ in the key, you can write it doublely,
--     __"!!"__,
--
--  4. if there are not all digits, it's a named key.
--
--  Examples:
--
--  >>> read "country" :: ArgKey
--  Name "country"
--
--  >>> read "123" :: ArgKey
--  Index 123
--
--  >>> read "country!name" :: ArgKey
--  Nest (Name "country") (Name "name")
--
--  >>> read "country!cities!10" :: ArgKey
--  Nest (Name "country") (Nest (Name "cities") (Index 10))
--
--  >>> read "coun!!try" :: ArgKey
--  Name "coun!try"
--
data ArgKey = Index Int
            | Name String
            | Nest ArgKey ArgKey
            deriving (Show, Eq, Ord)

instance Read ArgKey where
  readsPrec _ "" = [ (Index (-1), "") ]
  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)