{-# LANGUAGE RecordWildCards #-}
module Web.Api.WebDriver.Monad.Test.Server.Page (
    HtmlTag(..)
  , Attr(..)
  , Document(Text, tag, attrs, children, elementId)
  , Page(url, contents)
  , buildPage
  , node
  , requestPage
  , CssSelector()
  , getElementById
  , cssMatchDocument
  , parseCss
  , tagIsClearable
  ) where

import Text.ParserCombinators.Parsec
import Data.Monoid
import Data.List




data HtmlTag
  = Html
  | Head
  | Title
  | Body
  | Div
  | P
  | Ol
  | Ul
  | Li
  | Form
  | Input
  | Button
  deriving Eq

instance Show HtmlTag where
  show t = case t of
    Html -> "html"; Head -> "head"; Title -> "title"; Body -> "body"
    Div -> "div"; P -> "p"; Ol -> "ol"; Ul -> "ul"; _ -> error "Show HtmlTag"

tagIsClearable :: HtmlTag -> Bool
tagIsClearable t = case t of
  Input -> True
  _ -> False

data Attr
  = Id
  | Class
  | Name
  deriving (Eq, Show)

data Document
  = Text String
  | Document
      { elementId :: String
      , tag :: HtmlTag
      , attrs :: [(Attr, Maybe String)]
      , children :: [Document]
      }
  deriving (Eq, Show)

attrHasValue :: Attr -> String -> Document -> Bool
attrHasValue _ _ (Text _) = False
attrHasValue a v Document{..} =
  case lookup a attrs of
    Just (Just val) -> v == val
    _ -> False

data Page = Page
  { contents :: Document
  , url :: String
  } deriving Show

node :: HtmlTag -> [(Attr, Maybe String)] -> [Document] -> Document
node tag attrs children =
  let elementId = "" in
  Document{..}


assignIds :: String -> Document -> Document
assignIds _ h@(Text str) = Text str
assignIds base h@Document{..} = h
  { elementId = base
  , children = zipWith prefix [1..] children
  }
  where
    prefix i child = assignIds (base ++ "." ++ show i) child

buildPage :: String -> Document -> Page
buildPage url doc =
  let contents = assignIds "" doc
  in Page{..}

test1 :: Page
test1 = buildPage "example.com" $
  node Html []
    [
    ]
    


getElementById :: String -> Page -> Maybe Document
getElementById str Page{..} = getFirst $ get contents
  where
    get :: Document -> First Document
    get (Text _) = First Nothing
    get d@Document{..} = if elementId == str
      then First (Just d)
      else mconcat $ map get children



data CssSelector
  = CssTag HtmlTag
  | CssClass HtmlTag String
  | CssHash HtmlTag String
  | CssAttr HtmlTag Attr String
  deriving Show

pHtmlTag :: Parser HtmlTag
pHtmlTag = choice $ map try
  [ string "html" >> return Html
  , string "head" >> return Head
  , string "title" >> return Title
  , string "body" >> return Body
  , string "div" >> return Div
  , string "p" >> return P
  , string "ol" >> return Ol
  , string "ul" >> return Ul
  , string "li" >> return Li
  , string "form" >> return Form
  , string "input" >> return Input
  , string "button" >> return Button
  ]

pAttr :: Parser Attr
pAttr = choice
  [ string "class" >> return Class
  , string "id" >> return Id
  ]

tokenchar :: Parser String
tokenchar = many1 $ oneOf $ ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ ['-','_']

pCssSelector :: Parser CssSelector
pCssSelector =
  choice
    [ try $ do
        tag <- pHtmlTag
        char '.'
        classname <- tokenchar
        return (CssClass tag classname)

    , try $ do
        tag <- pHtmlTag
        char '#'
        name <- tokenchar
        return (CssHash tag name)

    , try $ do
        tag <- pHtmlTag
        char '['
        attr <- pAttr
        char '='
        char '\''
        value <- tokenchar
        char '\''
        char ']'
        return (CssAttr tag attr value)

    ] <|> do
      tag <- pHtmlTag
      return (CssTag tag)

parseCss :: String -> Either ParseError CssSelector
parseCss str = parse pCssSelector "" str


cssMatchDocument :: CssSelector -> Document -> [Document]
cssMatchDocument _ (Text _) = []
cssMatchDocument selector d@Document{..} =
  let
    match = case selector of
      CssTag t -> t == tag
      CssClass t c -> t == tag && attrHasValue Class c d
      CssHash t h -> t == tag && attrHasValue Id h d
      CssAttr t a v -> t == tag && attrHasValue a v d
  in
    (if match then (d:) else id) $
      concatMap (cssMatchDocument selector) children

requestPage :: String -> [Page] -> Maybe Page
requestPage _ [] = Nothing
requestPage path (p@Page{..}:ps) =
  if url == path
    then Just p
    else requestPage path ps