-- Copyright (c) 2010 John Millikin
--
-- Permission is hereby granted, free of charge, to any person
-- obtaining a copy of this software and associated documentation
-- files (the "Software"), to deal in the Software without
-- restriction, including without limitation the rights to use,
-- copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the
-- Software is furnished to do so, subject to the following
-- conditions:
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-- OTHER DEALINGS IN THE SOFTWARE.

module Data.XML.Types
	( -- * Types
	  -- ** Document prologue
	  Document (..)
	, Prologue (..)
	, Instruction (..)
	, Miscellaneous (..)
	
	-- ** Document body
	, Node (..)
	, Element (..)
	, Attribute (..)
	, Content (..)
	, Name (..)
	, Named (..)
	
	-- ** Doctypes
	, Doctype (..)
	, ExternalID (..)
	, InternalSubset
	
	-- * Predicates
	, isElement
	, isInstruction
	, isContent
	, isComment
	, isNamed
	
	-- * Filters
	, nodeChildren
	, elementChildren
	, hasAttribute
	, position
	) where
import Control.Monad ((>=>))
import Data.Text.Lazy (Text)

data Document = Document
	{ documentPrologue :: Prologue
	, documentRoot :: Element
	, documentEpilogue :: [Miscellaneous]
	}
	deriving (Show, Eq)

data Prologue = Prologue
	{ prologueBefore :: [Miscellaneous]
	, prologueDoctype :: Maybe Doctype
	, prologueAfter :: [Miscellaneous]
	}
	deriving (Show, Eq)

data Instruction = Instruction
	{ instructionTarget :: Text
	, instructionData :: Text
	}
	deriving (Show, Eq)

data Miscellaneous
	= MiscInstruction Instruction
	| MiscComment Text
	deriving (Show, Eq)

data Node
	= NodeElement Element
	| NodeInstruction Instruction
	| NodeContent Content
	| NodeComment Text
	deriving (Show, Eq)

data Element = Element
	{ elementName :: Name
	, elementAttributes :: [Attribute]
	, elementNodes :: [Node]
	}
	deriving (Show, Eq)

data Attribute = Attribute
	{ attributeName :: Name
	, attributeContent :: [Content]
	}
	deriving (Show, Eq)

data Content
	= ContentText Text
	| ContentEntity Text
	deriving (Show, Eq)

data Name = Name
	{ nameLocalName :: Text
	, nameNamespace :: Maybe Text
	, namePrefix :: Maybe Text
	}
	deriving (Show)

-- Ignore prefixes when comparing names
instance Eq Name where
	x == y = and
		[ nameLocalName x == nameLocalName y
		, nameNamespace x == nameNamespace y
		]

class Named a where
	getName :: a -> Name

instance Named Element where
	getName = elementName

instance Named Attribute where
	getName = attributeName

data Doctype = Doctype
	{ doctypeName :: Text
	, doctypeExternalID :: Maybe ExternalID
	, doctypeInternalSubsets :: [InternalSubset]
	}
	deriving (Show, Eq)

data ExternalID
	= SystemID Text
	| PublicID Text Text
	deriving (Show, Eq)

data InternalSubset = InternalSubset
	-- TODO
	deriving (Show, Eq)

isElement :: Node -> [Element]
isElement (NodeElement e) = [e]
isElement _ = []

isInstruction :: Node -> [Instruction]
isInstruction (NodeInstruction i) = [i]
isInstruction _ = []

isContent :: Node -> [Content]
isContent (NodeContent c) = [c]
isContent _ = []

isComment :: Node -> [Text]
isComment (NodeComment t) = [t]
isComment _ = []

isNamed :: Named a => Name -> a -> [a]
isNamed n x = [x | getName x == n]

elementChildren :: Element -> [Element]
elementChildren = elementNodes >=> isElement

nodeChildren :: Node -> [Node]
nodeChildren = isElement >=> elementNodes

position :: Integer -> (a -> [b]) -> a -> [b]
position n f e = safeHead n (f e)

safeHead :: Integer -> [a] -> [a]
safeHead _ [] = []
safeHead 0 (x:_) = [x]
safeHead n (_:xs) = safeHead (n - 1) xs

hasAttribute :: (Attribute -> [Attribute]) -> Element -> [Element]
hasAttribute f e = [e | not $ null (elementAttributes e >>= f)]