{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}

module WykopTypes where

import Control.Applicative ((<$>), (<*>), pure, empty)
import Control.Monad (mzero)
import Data.Aeson
import Data.Text (unpack)
import Data.Time (LocalTime(..))
import Data.Time.Format (parseTime)
import System.Locale (defaultTimeLocale)

import qualified Data.HashMap.Strict as H
import qualified Data.Vector as V

type PostData = [(String, String)]
type GetData = PostData

type URL = String
type Userkey = String

data Keys = Keys {
      appKey    :: String
    , secretKey :: String
}
    deriving (Show)

data Link = Link {
      linkId              :: Int
    , linkTitle           :: String
    , linkDescription     :: String
    , linkTags            :: String
    , linkUrl             :: URL
    , linkSourceUrl       :: URL
    , linkVoteCount       :: Int
    , linkCommentCount    :: Int
    , linkReportCount     :: Int
    , linkRelatedCount    :: Int
    , linkDate            :: LocalTime
    , linkAuthor          :: String
    , linkAuthorAvatar    :: URL
    , linkAuthorAvatarBig :: URL
    , linkAuthorAvatarMed :: URL
    , linkAuthorAvatarLo  :: URL
    , linkAuthorGroup     :: Int
    , linkAuthorSex       :: String
    , linkPreview         :: Maybe URL
    , linkUserLists       :: [Int]
    , linkPlus18          :: Bool
    , linkStatus          :: Maybe String
    , linkCanVote         :: Bool
    , linkHasOwnContent   :: Bool
    , linkCategory        :: String
    , linkUserVote        :: Value -- false or "bury"
    , linkUserObserve     :: Maybe Bool
    , linkUserFavorite    :: Maybe Bool
    , linkIsHot           :: Bool
    , linkGroup           :: String
    , linkType            :: String
}
    deriving (Show)

instance FromJSON LocalTime where
    parseJSON (String v) =
        case parseTime defaultTimeLocale "%F %T" (unpack v) of
            Just time -> pure time
            _         -> empty

    parseJSON _ = mzero

instance FromJSON Link where
    parseJSON (Object v) =
        Link <$>
        v .: "id"                <*>
        v .: "title"             <*>
        v .: "description"       <*>
        v .: "tags"              <*>
        v .: "url"               <*>
        v .: "source_url"        <*>
        v .: "vote_count"        <*>
        v .: "comment_count"     <*>
        v .: "report_count"      <*>
        v .: "related_count"     <*>
        v .: "date"              <*>
        v .: "author"            <*>
        v .: "author_avatar"     <*>
        v .: "author_avatar_big" <*>
        v .: "author_avatar_med" <*>
        v .: "author_avatar_lo"  <*>
        v .: "author_group"      <*>
        v .: "author_sex"        <*>
        v .:? "preview"          <*>
        (v .: "user_lists" >>= parseJSONArray) <*>
        v .: "plus18"            <*>
        v .:? "status"           <*>
        v .: "can_vote"          <*>
        v .: "has_own_content"   <*>
        v .: "category"          <*>
        v .: "user_vote"         <*>
        v .:? "user_observe"     <*>
        v .:? "user_favorite"    <*>
        v .: "is_hot"            <*>
        v .: "group"             <*>
        v .: "type"

    parseJSON _ = mzero

data Profile = Profile {
      profLogin          :: String
    , profEmail          :: String
    , profPublicEmail    :: String
    , profName           :: String
    , profWWW            :: URL
    , profJabber         :: String
    , profGG             :: String -- int in API
    , profCity           :: String
    , profAbout          :: String
    , profAuthorGroup    :: Int
    , profLinksAdded     :: Maybe Int
    , profLinksPublished :: Maybe Int
    , profComments       :: Maybe Int
    , profRank           :: Maybe Int
    , profFollowers      :: Maybe Int
    , profFollowing      :: Maybe Int
    , profEntries        :: Maybe Int
    , profDiggs          :: Maybe Int
    , profBuries         :: Maybe Int -- int in API
    , profGroups         :: Maybe Int
    , profAvatar         :: URL
    , profAvatarBig      :: URL
    , profAvatarMed      :: URL
    , profAvatarLo       :: URL
    , profIsObserved     :: Maybe Bool
    , profSex            :: String -- undocumented
    , profUserKey        :: Maybe Userkey
}
    deriving (Show)

instance FromJSON Profile where
    parseJSON (Object v) =
        Profile <$>
        v .: "login"            <*>
        v .: "email"            <*>
        v .: "public_email"     <*>
        v .: "name"             <*>
        v .: "www"              <*>
        v .: "jabber"           <*>
        v .: "gg"               <*>
        v .: "city"             <*>
        v .: "about"            <*>
        v .: "author_group"     <*>
        v .:? "links_added"     <*>
        v .:? "links_published" <*>
        v .:? "comments"        <*>
        v .:? "rank"            <*>
        v .:? "followers"       <*>
        v .:? "following"       <*>
        v .:? "entries"         <*>
        v .:? "diggs"           <*>
        v .:? "buries"          <*>
        v .:? "groups"          <*>
        v .: "avatar"           <*>
        v .: "avatar_big"       <*>
        v .: "avatar_med"       <*>
        v .: "avatar_lo"        <*>
        v .:? "is_observed"     <*>
        v .: "sex"              <*>
        v .:? "userkey"

    parseJSON _ = mzero

data Comment = Comment {
      commId              :: Int
    , commDate            :: LocalTime
    , commAuthor          :: String
    , commAuthorAvatar    :: URL
    , commAuthorAvatarBig :: URL
    , commAuthorAvatarMed :: URL
    , commAuthorAvatarLo  :: URL
    , commAuthorGroup     :: Int
    , commVoteCount       :: Int
    , commBody            :: String
    , commParentId        :: Int
    , commStatus          :: Maybe String
    , commEmbed           :: Maybe Embed
    , commAuthorSex       :: String
    , commVoteCountPlus   :: Int
    , commVoteCountMinus  :: Maybe Int
    , commSource          :: Maybe String -- URL?
    , commCanVote         :: Bool
    , commUserVote        :: Maybe Bool
    , commBlocked         :: Bool
    , commDeleted         :: Bool
    , commLink            :: [Link]
}
    deriving (Show)

instance FromJSON Comment where
    parseJSON (Object v) =
        Comment <$>
        v .: "id"                   <*>
        v .: "date"                 <*>
        v .: "author"               <*>
        v .: "author_avatar"        <*>
        v .: "author_avatar_big"    <*>
        v .: "author_avatar_med"    <*>
        v .: "author_avatar_lo"     <*>
        v .: "author_group"         <*>
        v .: "vote_count"           <*>
        v .: "body"                 <*>
        v .: "parent_id"            <*>
        v .:? "status"              <*>
        (v .:? "embed" >>= parseMaybeJSON) <*>
        v .: "author_sex"           <*>
        v .: "vote_count_plus"      <*>
        v .:? "vote_count_minus"    <*>
        v .:? "source"              <*>
        v .: "can_vote"             <*>
        v .:? "user_vote"           <*>
        v .: "blocked"              <*>
        v .: "deleted"              <*>
        (v .: "link" >>= parseJSONArray)

    parseJSON _ = mzero

data Bury = Bury {
      buryReason          :: Int
    , buryAuthor          :: String
    , buryAuthorAvatar    :: URL
    , buryAuthorAvatarBig :: URL
    , buryAuthorAvatarMed :: URL
    , buryAuthorAvatarLo  :: URL
    , buryAuthorGroup     :: Int
}
    deriving (Show)

instance FromJSON Bury where
    parseJSON (Object v) =
        Bury <$>
        v .: "reason"            <*>
        v .: "author"            <*>
        v .: "author_avatar"     <*>
        v .: "author_avatar_big" <*>
        v .: "author_avatar_med" <*>
        v .: "author_avatar_lo"  <*>
        v .: "author_group"

    parseJSON _ = mzero

data Dig = Dig {
      digAuthor          :: String
    , digAuthorAvatar    :: URL
    , digAuthorAvatarBig :: URL
    , digAuthorAvatarMed :: URL
    , digAuthorAvatarLo  :: URL
    , digAuthorGroup     :: Int
}
    deriving (Show)

instance FromJSON Dig where
    parseJSON (Object v) =
        Dig <$>
        v .: "author"            <*>
        v .: "author_avatar"     <*>
        v .: "author_avatar_big" <*>
        v .: "author_avatar_med" <*>
        v .: "author_avatar_lo"  <*>
        v .: "author_group"

    parseJSON _ = mzero

data RelatedLink = RelatedLink {
      relLinkId         :: Int
    , relLinkUrl        :: URL
    , relLinkTitle      :: String
    , relLinkVoteCount  :: Int
    , relLinkEntryCount :: Int
    , relLinkUserVote   :: Maybe Int
}
    deriving (Show)

instance FromJSON RelatedLink where
    parseJSON (Object v) =
        RelatedLink <$>
        v .: "id"          <*>
        v .: "url"         <*>
        v .: "title"       <*>
        v .: "vote_count"  <*>
        v .: "entry_count" <*>
        v .:? "user_vote"

    parseJSON _ = mzero

data Entry = Entry {
      entryId                :: Int
    , entryAuthor            :: String
    , entryAuthorAvatar      :: URL
    , entryAuthorAvatarBig   :: URL
    , entryAuthorAvatarMed   :: URL
    , entryAuthorAvatarLo    :: URL
    , entryAuthorGroup       :: Int
    , entryDate              :: LocalTime
    , entryBody              :: String
    , entryUrl               :: URL
    , entryReceiver          :: Maybe String
    , entryReceiverAvatar    :: Maybe URL
    , entryReceiverAvatarBig :: Maybe URL
    , entryReceiverAvatarMed :: Maybe URL
    , entryReceiverAvatarLo  :: Maybe URL
    , entryReceiverGroup     :: Maybe Int
    , entryComments          :: [EntryComment]
    , entryVoteCount         :: Int
    , entryVoters            :: [Dig]
    , entryEmbed             :: Maybe Embed
}
    deriving (Show)

instance FromJSON Entry where
    parseJSON (Object v) =
        Entry <$>
        v .: "id"                            <*>
        v .: "author"                        <*>
        v .: "author_avatar"                 <*>
        v .: "author_avatar_big"             <*>
        v .: "author_avatar_med"             <*>
        v .: "author_avatar_lo"              <*>
        v .: "author_group"                  <*>
        v .: "date"                          <*>
        v .: "body"                          <*>
        v .: "url"                           <*>
        v .:? "receiver"                     <*>
        v .:? "receiver_avatar"              <*>
        v .:? "receiver_avatar_big"          <*>
        v .:? "receiver_avatar_med"          <*>
        v .:? "receiver_avatar_lo"           <*>
        v .:? "receiver_group"               <*>
        (v .: "comments" >>= parseJSONArray) <*>
        v .: "vote_count"                    <*>
        (v .: "voters" >>= parseJSONArray)   <*>
        (v .:? "embed" >>= parseMaybeJSON)

    parseJSON _ = mzero

--parseMaybeJSON :: (FromJSON a) => Maybe Value -> Parser (Maybe a)
parseMaybeJSON (Just x) = parseJSON x
parseMaybeJSON _        = pure Nothing

--parseJSONArray :: (FromJSON a) => Vector Value -> Parser [a]
parseJSONArray a = mapM parseJSON $ V.toList a

data EntryComment = EntryComment {
      entryCommId              :: Int
    , entryCommAuthor          :: String
    , entryCommAuthorAvatar    :: URL
    , entryCommAuthorAvatarBig :: URL
    , entryCommAuthorAvatarMed :: URL
    , entryCommAuthorAvatarLo  :: URL
    , entryCommAuthorGroup     :: Int
    , entryCommDate            :: LocalTime
    , entryCommBody            :: String
    , entryCommVoteCount       :: Int
    , entryCommVoters          :: [Dig]
    , entryCommEmbed           :: Maybe Embed
}
    deriving (Show)

instance FromJSON EntryComment where
    parseJSON (Object v) =
        EntryComment <$>
        v .: "id"                          <*>
        v .: "author"                      <*>
        v .: "author_avatar"               <*>
        v .: "author_avatar_big"           <*>
        v .: "author_avatar_med"           <*>
        v .: "author_avatar_lo"            <*>
        v .: "author_group"                <*>
        v .: "date"                        <*>
        v .: "body"                        <*>
        v .: "vote_count"                  <*>
        (v .: "voters" >>= parseJSONArray) <*>
        (v .:? "embed" >>= parseMaybeJSON)

    parseJSON _ = mzero

data Notification = Notification {
      notifId              :: Int
    , notifAuthor          :: String
    , notifAuthorAvatar    :: URL
    , notifAuthorAvatarBig :: URL
    , notifAuthorAvatarMed :: URL
    , notifAuthorAvatarLo  :: URL
    , notifAuthorGroup     :: Int
    , notifDate            :: LocalTime
    , notifBody            :: String
    , notifType            :: String
    , notifNew             :: Bool
    , notifUrl             :: URL
    , notifEntry           :: Maybe NotifEntry
    , notifLink            :: Maybe Link
    , notifComment         :: Maybe WykopID
}
    deriving (Show)

instance FromJSON Notification where
    parseJSON (Object v) =
        Notification <$>
        v .: "id"                            <*>
        v .: "author"                        <*>
        v .: "author_avatar"                 <*>
        v .: "author_avatar_big"             <*>
        v .: "author_avatar_med"             <*>
        v .: "author_avatar_lo"              <*>
        v .: "author_group"                  <*>
        v .: "date"                          <*>
        v .: "body"                          <*>
        v .: "type"                          <*>
        v .: "new"                           <*>
        v .: "url"                           <*>
        (v .:? "entry" >>= parseMaybeJSON)   <*>
        (v .:? "link" >>= parseMaybeJSON)    <*>
        (v .:? "comment" >>= parseMaybeJSON)

    parseJSON _ = mzero

data NotifEntry = NotifEntry {
      notEntId   :: Int
    , notEntBody :: String
    , notEntUrl  :: URL
}
    deriving (Show)

instance FromJSON NotifEntry where
    parseJSON (Object v) =
        NotifEntry <$>
        v .: "id"   <*>
        v .: "body" <*>
        v .: "url"

data Embed = Embed {
      embedType    :: String
    , embedPreview :: URL
    , embedUrl     :: URL
    , embedSource  :: URL
    , embedPlus18  :: Bool
}
    deriving (Show)

instance FromJSON Embed where
    parseJSON (Object v) =
        Embed <$>
        v .: "type"    <*>
        v .: "preview" <*>
        v .: "url"     <*>
        v .: "source"  <*>
        v .: "plus18"

    parseJSON _ = mzero

data MyWykop = MyWykop {
      myFeed :: [Either Link Entry]
}
    deriving (Show)

instance FromJSON MyWykop where
    parseJSON (Array v) =
        MyWykop <$>
        parseJSONArray v

    parseJSON _ = mzero

instance FromJSON (Either Link Entry) where
    parseJSON x@(Object v) =
        case H.lookup "type" v of
            Just "link"  -> Left  <$> parseJSON x
            Just "entry" -> Right <$> parseJSON x
            _ -> mzero

    parseJSON _ = mzero

data WykopID = WykopID Int
    deriving (Show)

instance FromJSON WykopID where
    parseJSON (Object v) =
        WykopID <$>
        v .: "id"

    parseJSON _ = mzero
