{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE TypeFamilies #-}
module Textphile.TCT.Read.Elem where

import Control.Applicative (Applicative(..), Alternative(..))
import Control.Monad (Monad(..))
import Data.Bool
import Data.Char (Char)
import Data.Eq (Eq(..))
import Data.Function (($), (.))
import Data.Functor ((<$>), (<$))
import Data.Maybe (Maybe(..))
import Data.Semigroup (Semigroup(..))
import qualified Data.Char as Char
import qualified Data.Char.Properties.XMLCharProps as XC
import qualified Data.Text.Lazy as TL
import qualified Symantic.XML as XML
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P

import Textphile.TCT.Debug
import Textphile.TCT.Elem
import Textphile.TCT.Tree
import Textphile.TCT.Read.Cell

-- * Word
p_Spaces :: P.Tokens s ~ TL.Text => Parser e s TL.Text
p_Spaces = P.takeWhileP (Just "Space") Char.isSpace
p_Spaces1 :: P.Tokens s ~ TL.Text => Parser e s TL.Text
p_Spaces1 = P.takeWhile1P (Just "Space") Char.isSpace
p_HSpaces :: P.Tokens s ~ TL.Text => Parser e s TL.Text
p_HSpaces = P.takeWhileP (Just "HSpace") (==' ')
p_Digits :: P.Tokens s ~ TL.Text => Parser e s TL.Text
p_Digits = P.takeWhileP (Just "Digit") Char.isDigit
p_Digits1 :: P.Tokens s ~ TL.Text => Parser e s TL.Text
p_Digits1 = P.takeWhile1P (Just "Digit1") Char.isDigit
p_AlphaNums :: P.Tokens s ~ TL.Text => Parser e s TL.Text
p_AlphaNums = P.takeWhileP (Just "AlphaNum") Char.isAlphaNum
p_AlphaNums1 :: P.Tokens s ~ TL.Text => Parser e s TL.Text
p_AlphaNums1 = P.takeWhile1P (Just "AlphaNum1") Char.isAlphaNum
{-
-- NOTE: could be done with TL.Text, which has a less greedy (<>).
p_Word :: Parser e Text Text
p_Word = debugParser "Word" $ P.try p_take <|> p_copy
	where
	p_take = do
		P.notFollowedBy $ P.satisfy $ not . Char.isAlphaNum
		w <- P.takeWhile1P (Just "Word") $ \c ->
			Char.isAlphaNum c ||
			c == '_' ||
			c == '-'
		guard $ Char.isAlphaNum $ Text.last w
		return w
	p_copy =
		(<>)
		 <$> p_AlphaNums1
		 <*> P.option "" (P.try $
			(<>)
			 <$> ((Text.pack <$>) $ P.some $ P.char '_' <|> P.char '-')
			 <*> p_copy)
-}

-- * Elem
p_ElemSingle :: P.Tokens s ~ TL.Text => Parser e s Pair
p_ElemSingle = debugParser "ElemSingle" $
	PairElem
	 <$  P.char '<'
	 <*> p_ElemName
	 <*> p_ElemAttrs
	 <*  P.string "/>"

p_ElemOpen :: P.Tokens s ~ TL.Text => Parser e s Pair
p_ElemOpen = debugParser "ElemOpen" $
	PairElem
	 <$  P.char '<'
	 <*> p_ElemName
	 <*> p_ElemAttrs
	 <*  P.char '>'

p_ElemName :: P.Tokens s ~ TL.Text => Parser e s ElemName
p_ElemName = P.label "NCName" $
	XML.NCName
	 <$  P.notFollowedBy (P.satisfy $ not . XC.isXmlNCNameStartChar)
	 <*> P.takeWhile1P Nothing XC.isXmlNCNameChar
 -- TODO: namespace

p_ElemClose :: P.Tokens s ~ TL.Text => Parser e s Pair
p_ElemClose = debugParser "ElemClose" $
	(`PairElem` [])
	 <$  P.string "</"
	 <*> p_ElemName
	 <*  P.char '>'

{-
p_ElemOpenOrSingle :: Parser e Text Pair
p_ElemOpenOrSingle =
	p_ElemOpen >>= \p ->
		P.char    '>' $> LexemePairOpen p <|>
		P.string "/>" $> LexemePairAny  p
-}

-- * 'ElemAttr'
p_ElemAttrs :: P.Tokens s ~ TL.Text => Parser e s [(White,ElemAttr)]
p_ElemAttrs = P.many $ P.try $ (,) <$> p_Spaces <*> p_ElemAttr
p_ElemAttr :: P.Tokens s ~ TL.Text => Parser e s ElemAttr
p_ElemAttr = P.try p_ElemAttrEq <|> p_ElemAttrName

p_ElemAttrEq :: P.Tokens s ~ TL.Text => Parser e s ElemAttr
p_ElemAttrEq =
	(\n (o,v,c) -> ElemAttr n ("="<>o) v c)
	 <$> p_ElemName
	 <*  P.char '='
	 <*> p_ElemAttrValue
p_ElemAttrName :: P.Tokens s ~ TL.Text => Parser e s ElemAttr
p_ElemAttrName =
	(\n -> ElemAttr n "" "" "")
	 <$> p_ElemName

p_ElemAttrValue :: P.Tokens s ~ TL.Text => Parser e s (TL.Text,TL.Text,TL.Text)
p_ElemAttrValue =
	p_ElemAttrValueQuote '\'' <|>
	p_ElemAttrValueQuote '"'  <|>
	p_ElemAttrValueWord

p_ElemAttrValueQuote :: Char -> P.Tokens s ~ TL.Text => Parser e s (TL.Text,TL.Text,TL.Text)
p_ElemAttrValueQuote q =
	(\o v c -> (TL.singleton o, v, TL.singleton c))
	 <$> P.char q
	 <*> P.takeWhile1P (Just "ElemAttrValueQuoted") (/=q)
	 <*> P.char q
p_ElemAttrValueWord :: P.Tokens s ~ TL.Text => Parser e s (TL.Text,TL.Text,TL.Text)
p_ElemAttrValueWord = do
	w <- P.takeWhile1P (Just "ElemAttrValueWord") $ \c ->
		Char.isPrint c &&
		not (Char.isSpace c) &&
		c /= '\'' &&
		c /= '"' &&
		c /= '=' &&
		c /= '/' &&
		c /= '<' &&
		c /= '>'
	return ("",w,"")