module Hydrogen.Data.Parser where

import Hydrogen.Prelude hiding (left, many, right, (<|>))
import Hydrogen.Data.Types

import Hydrogen.Syntax.Types

import Hydrogen.Parsing

import qualified Data.Map as Map


parseData :: Parser POPs Data
parseData = either mkError Right . runIdentity . runParserT items () ""

items :: Monad m => ParsecT POPs u m Data
items = do

    let left  = Left <$> (keyValue <|> node)
        right = Right <$> value

    (keyValues, values) <- partitionEithers <$> sepBy (left <|> right) separator
    
    return (DNode (Map.fromList keyValues) values)

value :: Monad m => ParsecT POPs u m Data
value = val <|> link <|> (DConstant <$> matches "^[A-Z](_?[A-Z0-9])*$")
  where
      val = sourceToken $ \case
        Token AposString "" xs -> Just (DString xs)
        Token QuotString "" xs -> Just (DString xs)
        Token SomethingT "" xs -> firstJust [
            fmap DNumber . tryReadDecimal
          , fmap DNumber . tryReadRational
          , fmap DNumber . tryReadHex
          , fmap DBool . tryReadBool
          , fmap DVersion . tryReadVersion
          , fmap DUUID . tryReadUUID
          , fmap DDateTime . join . tryReadDateTime
          , fmap DDate . join . tryReadDate
          , fmap DTime . join . tryReadTime
          ] xs
        _ -> Nothing

link :: (Monad m) => ParsecT POPs u m Data
link = DLink <$> matches url
  where
    url = concat [
        "^[a-z](-?[a-z0-9])*(\\.[a-z](-?[a-z0-9])*)+"
      , "(/([a-z0-9_.=-]|%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})*)+"
      , "(\\?([a-z0-9_.=-]|%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})*)?$"
      ]

keyValue :: Monad m => ParsecT [(SourcePos, POP)] u m (String, Data)
keyValue = liftA2 (,) (init <$> matches "^[a-z](\\-?[a-z0-9])*:$") value

node :: Monad m => ParsecT [(SourcePos, POP)] u m (String, Data)
node = sourceToken $ \case
    Block Mustache key pops | not (null key) -> (key ,) <$> dataNode pops
    _ -> Nothing
  where
    dataNode :: POPs -> Maybe Data
    dataNode = either (const Nothing) Just . runIdentity . runParserT items () "-"

separator :: Monad m => ParsecT POPs u m String
separator = equals ";;" <|> equals ";"

noSeparator :: Monad m => ParsecT POPs u m POP
noSeparator = sourceToken $ \case
    Token SomethingT "" val | elem val [";;", ";"] -> Nothing
    t -> Just t

equals, matches :: Monad m => String -> ParsecT POPs u m String

equals string = sourceToken $ \case
    Token SomethingT "" val | val == string -> Just val
    _ -> Nothing

matches regex = sourceToken $ \case
    Token SomethingT "" val | val =~ regex -> Just val
    _ -> Nothing