{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnicodeSyntax #-}
{-# OPTIONS_HADDOCK show-extensions #-}

-- |
-- Module      :  HBooru.Parsers.FieldParsers
-- Copyright   :  (c) Mateusz Kowalczyk 2014
-- License     :  GPL-3
--
-- Maintainer  :  fuuzetsu@fuuzetsu.co.uk
-- Stability   :  experimental
--
-- A collection of arrow parsers for known fields. We can use these
-- (and write new ones) by composing these parsers together for each site we
-- want to parse. As we carry the field information with us, this can later be
-- used when trying to extract the parsed information from the sites into a
-- homogenous list. Currently this moduel only deals with parsing out XML
-- attributes.
module HBooru.Parsers.FieldParsers where

import Control.Applicative
import Control.Exception.Base (Exception)
import Control.Monad
import Control.Monad.Error
import Data.Maybe
import Data.Monoid
import Data.Typeable
import Data.Vinyl
import HBooru.Types
import Prelude
import Text.Read (readMaybe)
import Text.XML.HXT.Core hiding (mkName, (<+>))

import Data.Vinyl.TyFun

newtype E = E String deriving (Show, Eq, Typeable)
instance Exception E where

-- | Alias for the common constraint blob
type ParseArrow cat = (Functor (cat XmlTree), ArrowXml cat)

-- | Alias for named fields
type Field cat s = cat XmlTree (Parse (R '[s]))

-- | Helper that provides better error messages when a 'read' fails.
readAttr  (ArrowXml cat, Read (App ElF s), Functor (cat XmlTree))  String
          sing s  Field cat s
readAttr s f = readAttrWith s f readMaybe

readAttrWith  (ArrowXml cat, Functor (cat XmlTree))  String  sing s
              (String  Maybe (App ElF s))  Field cat s
readAttrWith s f r = readCustom s f (\x  handler x $ r x)
  where
    handler inp Nothing = throwError . PF $ mconcat [s, ": '", inp, "'"]
    handler _ (Just x) = return x

readCustom  (ArrowXml cat, Functor (cat XmlTree))  String
            sing s  (String  Parse (App ElF s))  Field cat s
readCustom s f h = fmap (f =:) . h <$> getAttrValue s

readNormalAttr  (ArrowXml cat, (App ElF s) ~ String, Functor (cat XmlTree))
                String  sing s  Field cat s
readNormalAttr s f = return . (f =:) <$> getAttrValue s

-- * Individual attribute parsers

-- | Parser arrow for a "height" XML attribute.
heightA  ParseArrow cat  Field cat "height"
heightA = readAttr "height" height

-- | Parser arrow for a "score" XML attribute.
scoreA  ParseArrow cat  Field cat "score"
scoreA = readAttr "score" score

-- | Parser arrow for a "file_url" XML attribute.
file_urlA  ParseArrow cat  Field cat "file_url"
file_urlA = readNormalAttr "file_url" file_url

-- | Parser arrow for a "parent_id" XML attribute.
parent_idA  ParseArrow cat  Field cat "parent_id"
parent_idA = readCustom "parent_id" parent_id handler
  where
    handler = return . readMaybe

-- | Parser arrow for a "sample_url" XML attribute.
sample_urlA  ParseArrow cat  Field cat "sample_url"
sample_urlA = readNormalAttr "sample_url" sample_url

-- | Parser arrow for a "sample_width" XML attribute.
sample_widthA  ParseArrow cat  Field cat "sample_width"
sample_widthA = readAttr "sample_width" sample_width

-- | Parser arrow for a "sample_height" XML attribute.
sample_heightA  ParseArrow cat  Field cat "sample_height"
sample_heightA = readAttr "sample_height" sample_height

-- | Parser arrow for a "preview_url" XML attribute.
preview_urlA  ParseArrow cat  Field cat "preview_url"
preview_urlA = readNormalAttr "preview_url" preview_url

-- | Parser arrow for a "rating" XML attribute.
ratingA  ParseArrow cat  Field cat "rating"
ratingA = readAttrWith "rating" rating parseRating

-- | Parser arrow for a "tags" XML attribute.
tagsA  ParseArrow cat  Field cat "tags"
tagsA = readCustom "tags" tags (return . parseTags)

-- | Parser arrow for a "id" XML attribute.
idA  ParseArrow cat  Field cat "id"
idA = readAttr "id" HBooru.Types.id

-- | Parser arrow for a "width" XML attribute.
widthA  ParseArrow cat  Field cat "width"
widthA = readAttr "width" width

-- | Parser arrow for a "change" XML attribute.
changeA  ParseArrow cat  Field cat "change"
changeA = readAttr "change" change

-- | Parser arrow for a "md5" XML attribute.
md5A  ParseArrow cat  Field cat "md5"
md5A = readNormalAttr "md5" md5

-- | Parser arrow for a "creator_id" XML attribute.
creator_idA  ParseArrow cat  Field cat "creator_id"
creator_idA = readAttr "creator_id" creator_id

-- | Parser arrow for a "has_children" XML attribute.
has_childrenA  ParseArrow cat  Field cat "has_children"
has_childrenA = readAttrWith "has_children" has_children parseBool

-- | Parser arrow for a "created_at" XML attribute.
created_atA  ParseArrow cat  Field cat "created_at"
created_atA = readNormalAttr "created_at" created_at

-- | Parser arrow for a "status" XML attribute.
statusA  ParseArrow cat  Field cat "status"
statusA = readNormalAttr "status" status

-- | Parser arrow for a "source" XML attribute.
sourceA  ParseArrow cat  Field cat "source"
sourceA = readNormalAttr "source" source

-- | Parser arrow for a "has_notes" XML attribute.
has_notesA  ParseArrow cat  Field cat "has_notes"
has_notesA = readAttrWith "has_notes" has_notes (return . parseBool)

-- | Parser arrow for a "has_comments" XML attribute.
has_commentsA  ParseArrow cat  Field cat "has_comments"
has_commentsA = readAttrWith "has_comments" has_comments (return . parseBool)

-- | Parser arrow for a "preview_width" XML attribute.
preview_widthA  ParseArrow cat  Field cat "preview_width"
preview_widthA = readAttr "preview_width" preview_width

-- | Parser arrow for a "preview_height" XML attribute.
preview_heightA  ParseArrow cat  Field cat "preview_height"
preview_heightA = readAttr "preview_height" preview_height

-- | Parser arrow for a "author" XML attribute.
authorA  ParseArrow cat  Field cat "author"
authorA = readNormalAttr "author" author

-- | Parser arrow for a "actual_preview_height" XML attribute.
actual_preview_heightA  ParseArrow cat  Field cat "actual_preview_height"
actual_preview_heightA = readAttr "actual_preview_height" actual_preview_height

-- | Parser arrow for a "actual_preview_width" XML attribute.
actual_preview_widthA  ParseArrow cat  Field cat "actual_preview_width"
actual_preview_widthA = readAttr "actual_preview_width" actual_preview_width

-- | Parser arrow for a "frames" XML attribute.
framesA  ParseArrow cat  Field cat "frames"
framesA = readNormalAttr "frames" frames

-- | Parser arrow for a "frames_pending" XML attribute.
frames_pendingA  ParseArrow cat  Field cat "frames_pending"
frames_pendingA = readNormalAttr "frames_pending" frames_pending

-- | Parser arrow for a "frames_pending_string" XML attribute.
frames_pending_stringA  ParseArrow cat  Field cat "frames_pending_string"
frames_pending_stringA =
  readNormalAttr "frames_pending_string" frames_pending_string

-- | Parser arrow for a "frames_string" XML attribute.
frames_stringA  ParseArrow cat  Field cat "frames_string"
frames_stringA = readNormalAttr "frames_string" frames_string

-- | Parser arrow for a "is_held" XML attribute.
is_heldA  ParseArrow cat  Field cat "is_held"
is_heldA = readAttrWith "is_held" is_held parseBool

-- | Parser arrow for a "is_shown_in_index" XML attribute.
is_shown_in_indexA  ParseArrow cat  Field cat "is_shown_in_index"
is_shown_in_indexA =
  readAttrWith "is_shown_in_index" is_shown_in_index parseBool

-- | Parser arrow for a "jpeg_file_size" XML attribute.
jpeg_file_sizeA  ParseArrow cat  Field cat "jpeg_file_size"
jpeg_file_sizeA = readAttr "jpeg_file_size" jpeg_file_size

-- | Parser arrow for a "jpeg_height" XML attribute.
jpeg_heightA  ParseArrow cat  Field cat "jpeg_height"
jpeg_heightA = readAttr "jpeg_height" jpeg_height

-- | Parser arrow for a "jpeg_url" XML attribute.
jpeg_urlA  ParseArrow cat  Field cat "jpeg_url"
jpeg_urlA = readNormalAttr "jpeg_url" jpeg_url

-- | Parser arrow for a "jpeg_width" XML attribute.
jpeg_widthA  ParseArrow cat  Field cat "jpeg_width"
jpeg_widthA = readAttr "jpeg_width" jpeg_width

-- | Parser arrow for a "sample_file_size" XML attribute.
sample_file_sizeA  ParseArrow cat  Field cat "sample_file_size"
sample_file_sizeA = readAttr "sample_file_size" sample_file_size

-- | Parser arrow for a "file_size" XML attribute.
file_sizeA  ParseArrow cat  Field cat "file_size"
file_sizeA = readAttr "file_size" file_size

-- * Parsing helpers

-- | Parses a string returned from a Gelbooru-like site into
-- one of the commonly used 'Rating's. Note that this is a partial function
-- so you should make sure that the site in question only ever returns the
-- values in a format specified in the function
parseRating  String  Maybe Rating
parseRating "e" = Just Explicit
parseRating "s" = Just HBooru.Types.Safe
parseRating "q" = Just Questionable
parseRating _ = Nothing

-- | Splits returned tag string into separate 'Tag's. For Gelbooru-like
-- sites, this is just the question of splitting on whitespace.
parseTags  String  [Tag]
parseTags = words

-- | Reads a lowercase 'Bool' string representation into its Haskell type. If we
-- can't parse the boolean, return 'Nothing'.
parseBool  String  Maybe Bool
parseBool "false" = Just False
parseBool "true" = Just True
parseBool _ = Nothing

infixr 5 <:+>
-- | A little helper that lifts '<+>' into 'Arrow' which allows us to
-- compose parsers returning records very easily.
(<:+>)  Arrow cat  cat b (Parse (R as))
        cat b (Parse (R bs))
        cat b (Parse (R (as ++ bs)))
x <:+> y = arr (uncurry pnd) <<< x &&& y
  where
    pnd  Parse (R a)  Parse (R b)  Parse (R (a ++ b))
    pnd (Left x') _ = Left x'
    pnd _ (Left y') = Left y'
    pnd (Right x') (Right y') = Right (x' <+> y')