{-# LANGUAGE CPP                #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings  #-}
-- | ICalendar types, based on RFC5545.
module Text.ICalendar.Types
    ( module Text.ICalendar.Types
    ) where

import           Codec.MIME.Type            (MIMEType)
import           Data.ByteString.Lazy.Char8 (ByteString)
import           Data.CaseInsensitive       (CI)
import           Data.Default
import           Data.Map                   (Map)
import qualified Data.Map                   as M
import           Data.Set                   (Set)
import           Data.Semigroup             as Sem
import           Data.Text.Lazy             (Text, pack)
import           Data.Time
import           Data.Typeable              (Typeable)
import           Data.Version               (Version (..), showVersion)
import           Network.URI                (URI)

import Paths_iCalendar (version)

-- | Language.
newtype Language = Language (CI Text) -- TODO: RFC5646 types and parser.
                   deriving (Eq, Show, Ord, Typeable)

type CalAddress = URI

-- | One other parameter, either x-param or iana-param.
data OtherParam = OtherParam (CI Text) [Text]
                  deriving (Show, Eq, Ord, Typeable)

-- | Other parameters, either x-param or other iana-param.
data OtherParams = OtherParams (Set OtherParam)
                   deriving (Show, Eq, Ord, Typeable)

instance Default OtherParams where
    def = OtherParams def

-- | VCalendar component. 3.4.
data VCalendar = VCalendar
    { vcProdId     :: ProdId
    , vcVersion    :: ICalVersion
    , vcScale      :: Scale
    , vcMethod     :: Maybe Method
    , vcOther      :: Set OtherProperty
    , vcTimeZones  :: Map Text VTimeZone
    -- ^ Map TZID-value VTimeZone
    , vcEvents     :: Map (Text, Maybe (Either Date DateTime)) VEvent
    -- ^ Map (UID-value, Maybe RecurrenceID-value) VEvent
    , vcTodos      :: Map (Text, Maybe (Either Date DateTime)) VTodo
    -- ^ Map (UID-value, Maybe RecurrenceID-value) VTodo
    , vcJournals   :: Map (Text, Maybe (Either Date DateTime)) VJournal
    -- ^ Map (UID-value, Maybe RecurrenceID-value) VJournal
    , vcFreeBusys  :: Map Text VFreeBusy
    -- ^ Map UID-value VFreeBusy
    , vcOtherComps :: Set VOther
    } deriving (Show, Eq, Ord, Typeable)

instance Default VCalendar where
    def = VCalendar (ProdId ("-//haskell.org/NONSGML iCalendar-" <>
                             pack (showVersion version) <> "//EN") def)
                    (MaxICalVersion (Version [2,0] []) def)
                    def Nothing def def def def def def def

-- | 'vcMethod' is ignored at the moment.
--
-- Picks the left in most cases.
--
-- On UID/RecurrenceId/TZID clash, picks the 'VEvent's, 'VTodo's and
-- 'VJournal's with the highest ('Sequence', 'DTStamp'), the 'VTimeZone's
-- with the highest 'LastModified', and 'VFreeBusy' with the highest 'DTStamp'.
--
-- If the Sequence, DTStamp or LastModified is the same, picks the left.
instance Sem.Semigroup VCalendar where
    a <> b = VCalendar { vcProdId     = vcProdId a
                       , vcVersion    = vcVersion a
                       , vcScale      = vcScale a
                       , vcMethod     = vcMethod a
                       , vcOther      = vcOther a <> vcOther b
                       , vcTimeZones  = merge tz (vcTimeZones a)
                                                 (vcTimeZones b)
                       , vcEvents     = merge ev (vcEvents a) (vcEvents b)
                       , vcTodos      = merge td (vcTodos a) (vcTodos b)
                       , vcJournals   = merge jo (vcJournals a)
                                                 (vcJournals b)
                       , vcFreeBusys  = merge fb (vcFreeBusys a)
                                                 (vcFreeBusys b)
                       , vcOtherComps = vcOtherComps a <> vcOtherComps b
                       }
      where merge f = M.mergeWithKey (((Just .) .) . const f) id id
            tz c d = if vtzLastMod c >= vtzLastMod d then c else d
            ev c d = if (veSeq c, veDTStamp c) >= (veSeq d, veDTStamp d)
                        then c else d
            td c d = if (vtSeq c, vtDTStamp c) >= (vtSeq d, vtDTStamp d)
                        then c else d
            jo c d = if (vjSeq c, vjDTStamp c) >= (vjSeq d, vjDTStamp d)
                        then c else d
            fb c d = if vfbDTStamp c >= vfbDTStamp d then c else d


instance Monoid VCalendar where
    mempty = def
#if !(MIN_VERSION_base(4,11,0))
    mappend = (<>)
#endif

-- | Product Identifier. 3.7.3.
data ProdId = ProdId
    { prodIdValue :: Text
    , prodIdOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Version. 3.7.4.
data ICalVersion
    = MaxICalVersion
    { versionMax   :: Version
    , versionOther :: OtherParams
    }
    | MinMaxICalVersion
    { versionMax   :: Version
    , versionMin   :: Version
    , versionOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Calendar Scale. 3.7.1.
data Scale = Scale
    { scaleValue :: CI Text
    , scaleOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

instance Default Scale where
    def = Scale "GREGORIAN" def

-- | Method. 3.7.2.
data Method = Method
    { methodValue :: CI Text -- TODO: iTIP, RFC5546
    , methodOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Event Component. 3.6.1.
data VEvent = VEvent
    { veDTStamp       :: DTStamp
    , veUID           :: UID
    , veClass         :: Class -- ^ 'def' = 'Public'
    , veDTStart       :: Maybe DTStart
    , veCreated       :: Maybe Created
    , veDescription   :: Maybe Description
    , veGeo           :: Maybe Geo
    , veLastMod       :: Maybe LastModified
    , veLocation      :: Maybe Location
    , veOrganizer     :: Maybe Organizer
    , vePriority      :: Priority -- ^ 'def' = 0
    , veSeq           :: Sequence -- ^ 'def' = 0
    , veStatus        :: Maybe EventStatus
    , veSummary       :: Maybe Summary
    , veTransp        :: TimeTransparency -- ^ 'def' = 'Opaque'
    , veUrl           :: Maybe URL
    , veRecurId       :: Maybe RecurrenceId
    , veRRule         :: Set RRule
    , veDTEndDuration :: Maybe (Either DTEnd DurationProp)
    , veAttach        :: Set Attachment
    , veAttendee      :: Set Attendee
    , veCategories    :: Set Categories
    , veComment       :: Set Comment
    , veContact       :: Set Contact
    , veExDate        :: Set ExDate
    , veRStatus       :: Set RequestStatus
    , veRelated       :: Set RelatedTo
    , veResources     :: Set Resources
    , veRDate         :: Set RDate
    , veAlarms        :: Set VAlarm
    , veOther         :: Set OtherProperty
    } deriving (Show, Eq, Ord, Typeable)

-- | To-Do Component. 3.6.2
data VTodo = VTodo
    { vtDTStamp     :: DTStamp
    , vtUID         :: UID
    , vtClass       :: Class -- ^ 'def' = 'Public'
    , vtCompleted   :: Maybe Completed
    , vtCreated     :: Maybe Created
    , vtDescription :: Maybe Description
    , vtDTStart     :: Maybe DTStart
    , vtGeo         :: Maybe Geo
    , vtLastMod     :: Maybe LastModified
    , vtLocation    :: Maybe Location
    , vtOrganizer   :: Maybe Organizer
    , vtPercent     :: Maybe PercentComplete
    , vtPriority    :: Priority -- ^ 'def' = 0
    , vtRecurId     :: Maybe RecurrenceId
    , vtSeq         :: Sequence -- ^ 'def' = 0
    , vtStatus      :: Maybe TodoStatus
    , vtSummary     :: Maybe Summary
    , vtUrl         :: Maybe URL
    , vtRRule       :: Set RRule
    , vtDueDuration :: Maybe (Either Due DurationProp)
    , vtAttach      :: Set Attachment
    , vtAttendee    :: Set Attendee
    , vtCategories  :: Set Categories
    , vtComment     :: Set Comment
    , vtContact     :: Set Contact
    , vtExDate      :: Set ExDate
    , vtRStatus     :: Set RequestStatus
    , vtRelated     :: Set RelatedTo
    , vtResources   :: Set Resources
    , vtRDate       :: Set RDate
    , vtAlarms      :: Set VAlarm
    , vtOther       :: Set OtherProperty
    } deriving (Show, Eq, Ord, Typeable)

-- | Journal Component. 3.6.3
data VJournal = VJournal
    { vjDTStamp     :: DTStamp
    , vjUID         :: UID
    , vjClass       :: Class -- ^ 'def' = 'Public'
    , vjCreated     :: Maybe Created
    , vjDTStart     :: Maybe DTStart
    , vjLastMod     :: Maybe LastModified
    , vjOrganizer   :: Maybe Organizer
    , vjRecurId     :: Maybe RecurrenceId
    , vjSeq         :: Sequence -- ^ 'def' = 0
    , vjStatus      :: Maybe JournalStatus
    , vjSummary     :: Maybe Summary
    , vjUrl         :: Maybe URL
    , vjRRule       :: Set RRule
    , vjAttach      :: Set Attachment
    , vjAttendee    :: Set Attendee
    , vjCategories  :: Set Categories
    , vjComment     :: Set Comment
    , vjContact     :: Set Contact
    , vjDescription :: Set Description
    , vjExDate      :: Set ExDate
    , vjRelated     :: Set RelatedTo
    , vjRDate       :: Set RDate
    , vjRStatus     :: Set RequestStatus
    , vjOther       :: Set OtherProperty
    } deriving (Show, Eq, Ord, Typeable)

-- | Free/Busy Component. 3.6.4
data VFreeBusy = VFreeBusy
    { vfbDTStamp   :: DTStamp
    , vfbUID       :: UID
    , vfbContact   :: Maybe Contact
    , vfbDTStart   :: Maybe DTStart
    , vfbDTEnd     :: Maybe DTEnd
    , vfbOrganizer :: Maybe Organizer
    , vfbUrl       :: Maybe URL
    , vfbAttendee  :: Set Attendee
    , vfbComment   :: Set Comment
    , vfbFreeBusy  :: Set FreeBusy
    , vfbRStatus   :: Set RequestStatus
    , vfbOther     :: Set OtherProperty
    } deriving (Show, Eq, Ord, Typeable)

-- | Time Zone Component. 3.6.5.
data VTimeZone = VTimeZone
    { vtzId        :: TZID
    , vtzLastMod   :: Maybe LastModified
    , vtzUrl       :: Maybe TZUrl
    , vtzStandardC :: Set TZProp
    , vtzDaylightC :: Set TZProp
    , vtzOther     :: Set OtherProperty
    } deriving (Show, Eq, Ord, Typeable)

-- | Time zone property, also 3.6.5.
data TZProp = TZProp
    { tzpDTStart      :: DTStart
    , tzpTZOffsetTo   :: UTCOffset
    , tzpTZOffsetFrom :: UTCOffset
    , tzpRRule        :: Set RRule -- SHOULD NOT have multiple RRules.
    , tzpComment      :: Set Comment
    , tzpRDate        :: Set RDate
    , tzpTZName       :: Set TZName
    , tzpOther        :: Set OtherProperty
    } deriving (Show, Eq, Ord, Typeable)

-- | VAlarm component. 3.6.6.
data VAlarm
    = VAlarmAudio
    { vaTrigger     :: Trigger
    , vaRepeat      :: Repeat -- ^ 'def' = 0
    , vaDuration    :: Maybe DurationProp
    , vaAudioAttach :: Maybe Attachment
    , vaOther       :: Set OtherProperty
    , vaActionOther :: OtherParams
    }
    | VAlarmDisplay
    { vaDescription :: Description
    , vaTrigger     :: Trigger
    , vaRepeat      :: Repeat
    , vaDuration    :: Maybe DurationProp
    , vaOther       :: Set OtherProperty
    , vaActionOther :: OtherParams
    }
    | VAlarmEmail
    { vaDescription :: Description
    , vaTrigger     :: Trigger
    , vaSummary     :: Summary
    , vaAttendee    :: Set Attendee
    , vaRepeat      :: Repeat
    , vaDuration    :: Maybe DurationProp
    , vaMailAttach  :: Set Attachment
    , vaOther       :: Set OtherProperty
    , vaActionOther :: OtherParams
    }
    | VAlarmX
    { vaAction      :: CI Text
    , vaTrigger     :: Trigger
    , vaActionOther :: OtherParams
    , vaOther       :: Set OtherProperty
    } deriving (Show, Eq, Ord, Typeable)

-- | Any other component not recognized.
data VOther = VOther
    { voName  :: CI Text
    , voProps :: Set OtherProperty
    } deriving (Show, Eq, Ord, Typeable)

-- | Attachment. 3.8.1.1.
data Attachment
    = UriAttachment
    { attachFmtType :: Maybe MIMEType
    , attachUri     :: URI
    , attachOther   :: OtherParams
    }
    | BinaryAttachment
    { attachFmtType :: Maybe MIMEType
    , attachContent :: ByteString
    , attachOther   :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Categories. 3.8.1.2.
data Categories = Categories
    { categoriesValues   :: Set Text
    , categoriesLanguage :: Maybe Language
    , categoriesOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Classification. 3.8.1.3.
data Class = Class
    { classValue :: ClassValue
    , classOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

instance Default Class where
    def = Class def def

-- | Classification value. 3.8.1.3.
-- Unrecognized ClassValueX MUST be treated as Private.
data ClassValue
    = Public
    | Private
    | Confidential
    | ClassValueX (CI Text)
      deriving (Show, Eq, Ord, Typeable)

instance Default ClassValue where
    def = Public

-- | Date-Time Completed. 3.8.2.1.
data Completed = Completed
    { completedValue :: DateTime
    , completedOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Comment. 3.8.1.4.
data Comment = Comment
    { commentValue    :: Text
    , commentAltRep   :: Maybe URI
    , commentLanguage :: Maybe Language
    , commentOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Description. 3.8.1.5.
data Description = Description
    { descriptionValue    :: Text
    , descriptionAltRep   :: Maybe URI
    , descriptionLanguage :: Maybe Language
    , descriptionOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Geographic Position. 3.8.1.6.
data Geo = Geo
    { geoLat   :: Float
    , geoLong  :: Float
    , geoOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Location. 3.8.1.7.
data Location = Location
    { locationValue    :: Text
    , locationAltRep   :: Maybe URI
    , locationLanguage :: Maybe Language
    , locationOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Percent complete. 3.8.1.8.
data PercentComplete = PercentComplete
    { percentCompleteValue :: Int
    , percentCompleteOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Priority. 3.8.1.9.
data Priority = Priority
    { priorityValue :: Int
    , priorityOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

instance Default Priority where
    def = Priority 0 def

-- | Resources. 3.8.1.10.
data Resources = Resources
    { resourcesValue    :: Set Text
    , resourcesAltRep   :: Maybe URI
    , resourcesLanguage :: Maybe Language
    , resourcesOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Status, but only for Events. 3.8.1.11.
data EventStatus
    = TentativeEvent { eventStatusOther :: OtherParams }
    | ConfirmedEvent { eventStatusOther :: OtherParams }
    | CancelledEvent { eventStatusOther :: OtherParams }
      deriving (Show, Eq, Ord, Typeable)

-- | Status, but only for TODOs. 3.8.1.11.
data TodoStatus
    = TodoNeedsAction { todoStatusOther :: OtherParams }
    | CompletedTodo   { todoStatusOther :: OtherParams }
    | InProcessTodo   { todoStatusOther :: OtherParams }
    | CancelledTodo   { todoStatusOther :: OtherParams }
      deriving (Show, Eq, Ord, Typeable)

-- | Status, but only for Journals. 3.8.1.11.
data JournalStatus
    = DraftJournal     { journalStatusOther :: OtherParams }
    | FinalJournal     { journalStatusOther :: OtherParams }
    | CancelledJournal { journalStatusOther :: OtherParams }
      deriving (Show, Eq, Ord, Typeable)

-- | Summary. 3.8.1.12.
data Summary = Summary
    { summaryValue    :: Text
    , summaryAltRep   :: Maybe URI
    , summaryLanguage :: Maybe Language
    , summaryOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Date. 3.3.4
data Date = Date
    { dateValue :: Day
    } deriving (Show, Eq, Ord, Typeable)

-- | Date-Time value. 3.3.5.
data DateTime
    = FloatingDateTime
    { dateTimeFloating :: LocalTime
    }
    | UTCDateTime
    { dateTimeUTC :: UTCTime
    }
    | ZonedDateTime
    { dateTimeFloating :: LocalTime
    , dateTimeZone     :: Text
    } deriving (Show, Eq, Ord, Typeable)

-- | Date-Time End. 3.8.2.2.
data DTEnd
    = DTEndDateTime
    { dtEndDateTimeValue :: DateTime
    , dtEndOther         :: OtherParams
    }
    | DTEndDate
    { dtEndDateValue :: Date
    , dtEndOther     :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Date-Time Due. 3.8.2.3.
data Due
    = DueDateTime
    { dueDateTimeValue :: DateTime
    , dueOther         :: OtherParams
    }
    | DueDate
    { dueDateValue :: Date
    , dueOther     :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Date-Time Start. 3.8.2.4.
data DTStart
    = DTStartDateTime
    { dtStartDateTimeValue :: DateTime
    , dtStartOther         :: OtherParams
    }
    | DTStartDate
    { dtStartDateValue :: Date
    , dtStartOther     :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Duration value. 3.3.6.
data Duration -- TODO(?): Convert to DiffTime?
    = DurationDate
    { durSign   :: Sign -- ^ 'def' = 'Positive'
    , durDay    :: Int
    , durHour   :: Int
    , durMinute :: Int
    , durSecond :: Int
    }
    | DurationTime
    { durSign   :: Sign
    , durHour   :: Int
    , durMinute :: Int
    , durSecond :: Int
    }
    | DurationWeek
    { durSign :: Sign
    , durWeek :: Int
    } deriving (Show, Eq, Ord, Typeable)

-- | Sign.
data Sign = Positive | Negative
            deriving (Show, Eq, Ord, Typeable)

instance Default Sign where
    def = Positive

-- | Duration property. 3.8.2.5.
data DurationProp = DurationProp
    { durationValue :: Duration
    , durationOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

data FreeBusy = FreeBusy
    { freeBusyType    :: FBType
    , freeBusyPeriods :: Set UTCPeriod
    , freeBusyOther   :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Period of time. 3.3.9.
data Period
    = PeriodDates    DateTime DateTime
    | PeriodDuration DateTime Duration
      deriving (Show, Eq, Ord, Typeable)

-- | Period of time which must be UTC, as in FreeBusy. 3.3.9.
data UTCPeriod
    = UTCPeriodDates    UTCTime UTCTime
    | UTCPeriodDuration UTCTime Duration
      deriving (Show, Eq, Ord, Typeable)

-- | Free/Busy Time Type. 3.2.9.
--
-- Unrecognized FBTypeX MUST be treated as Busy.
data FBType
    = Free
    | Busy
    | BusyUnavailable
    | BusyTentative
    | FBTypeX (CI Text)
      deriving (Show, Eq, Ord, Typeable)

instance Default FBType where
    def = Busy

-- | Time Transparency. 3.8.2.7.
data TimeTransparency
    = Opaque      { timeTransparencyOther :: OtherParams }
    | Transparent { timeTransparencyOther :: OtherParams }
      deriving (Show, Eq, Ord, Typeable)

instance Default TimeTransparency where
    def = Opaque def

-- | Time Zone Identifier. 3.8.3.1.
data TZID = TZID
    { tzidValue  :: Text
    , tzidGlobal :: Bool
    , tzidOther  :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Time Zone Name. 3.8.3.2.
data TZName = TZName
    { tzNameValue    :: Text
    , tzNameLanguage :: Maybe Language
    , tzNameOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | UTC Offset. 3.3.14, 3.8.3.4, and 3.8.3.3. (unified-ish)
data UTCOffset = UTCOffset
    { utcOffsetValue :: Int -- ^ Number of seconds away from UTC
    , utcOffsetOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Time Zone URL. 3.8.3.5.
data TZUrl = TZUrl
    { tzUrlValue :: URI
    , tzUrlOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Attendee. 3.8.4.1.
data Attendee = Attendee
    { attendeeValue    :: CalAddress
    , attendeeCUType   :: CUType -- ^ 'def' = 'Individual'
    , attendeeMember   :: Set CalAddress
    , attendeeRole     :: Role -- ^ 'def' = 'ReqParticipant'
    , attendeePartStat :: PartStat -- ^ 'def' = 'PartStatNeedsAction'
    , attendeeRSVP     :: Bool
    , attendeeDelTo    :: Set CalAddress
    , attendeeDelFrom  :: Set CalAddress
    , attendeeSentBy   :: Maybe CalAddress
    , attendeeCN       :: Maybe Text
    , attendeeDir      :: Maybe URI
    , attendeeLanguage :: Maybe Language
    , attendeeOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Calendar User Type. 3.2.3.
--
-- Unrecognized CUTypeX MUST be treated as Unknown.
data CUType
    = Individual
    | Group
    | Resource
    | Room
    | Unknown
    | CUTypeX (CI Text)
      deriving (Show, Eq, Ord, Typeable)

instance Default CUType where
    def = Individual

-- | Role. 3.2.16.
data Role = Chair
          | ReqParticipant
          | OptParticipant
          | NonParticipant
          | RoleX (CI Text)
            deriving (Show, Eq, Ord, Typeable)

instance Default Role where
    def = ReqParticipant

-- | Participation Status. 3.2.12.
data PartStat -- Splitting requires splitting attendee too...
    = PartStatNeedsAction
    | Accepted
    | Declined
    | Tentative
    | Delegated
    | PartStatCompleted
    | InProcess
    | PartStatX (CI Text)
      deriving (Show, Eq, Ord, Typeable)

instance Default PartStat where
    def = PartStatNeedsAction

-- | Contact. 3.8.4.2.
data Contact = Contact
    { contactValue    :: Text
    , contactAltRep   :: Maybe URI
    , contactLanguage :: Maybe Language
    , contactOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Organizer. 3.8.4.3.
--
-- TODO: CAL-ADDRESS-related properties.
data Organizer = Organizer
    { organizerValue    :: CalAddress
    , organizerCN       :: Maybe Text
    , organizerDir      :: Maybe URI
    , organizerSentBy   :: Maybe CalAddress
    , organizerLanguage :: Maybe Language
    , organizerOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Recurrence ID. 3.8.4.4.
data RecurrenceId
    = RecurrenceIdDate
    { recurrenceIdDate  :: Date
    , recurrenceIdRange :: Maybe Range
    , recurrenceIdOther :: OtherParams
    }
    | RecurrenceIdDateTime
    { recurrenceIdDateTime :: DateTime
    , recurrenceIdRange    :: Maybe Range
    , recurrenceIdOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Recurrence Identifier Range. 3.2.13
data Range = ThisAndFuture | ThisAndPrior
             deriving (Show, Eq, Ord, Typeable)

-- | Related To. 3.8.4.5.
data RelatedTo = RelatedTo
    { relatedToValue :: Text
    , relatedToType  :: RelationshipType
    , relatedToOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Relationship Type. 3.2.15.
--
-- Unrecognized RelationshipTypeX values MUST be treated as Parent.
data RelationshipType = Parent | Child | Sibling | RelationshipTypeX (CI Text)
                        deriving (Show, Eq, Ord, Typeable)

instance Default RelationshipType where
    def = Parent

-- | Uniform Resource Locator. 3.8.4.6.
data URL = URL
    { urlValue :: URI
    , urlOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Unique Identifier. 3.8.4.7.
data UID = UID
    { uidValue :: Text
    , uidOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Exception Date-Times. 3.8.5.1.
data ExDate
    = ExDates
    { exDates     :: Set Date
    , exDateOther :: OtherParams
    }
    | ExDateTimes
    { exDateTimes :: Set DateTime
    , exDateOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Recurrence Date-Times. 3.8.5.2.
data RDate
    = RDateDates
    { rDateDates :: Set Date
    , rDateOther :: OtherParams
    }
    | RDateDateTimes
    { rDateDateTimes :: Set DateTime
    , rDateOther     :: OtherParams
    }
    | RDatePeriods
    { rDatePeriods :: Set Period
    , rDateOther   :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Frequency in recurrences. 3.3.10.
data Frequency
    = Secondly
    | Minutely
    | Hourly
    | Daily
    | Weekly
    | Monthly
    | Yearly
      deriving (Show, Eq, Ord, Typeable)

-- | Weekday, in recurrences. 3.3.10.
data Weekday = Sunday | Monday | Tuesday | Wednesday | Thursday
                      | Friday | Saturday
               deriving (Show, Eq, Ord, Bounded, Enum, Typeable)

-- | Recur value. 3.3.10.
data Recur = Recur
    { recurFreq       :: Frequency
    , recurUntilCount :: Maybe (Either (Either Date DateTime) Int)
    , recurInterval   :: Int
    , recurBySecond   :: [Int]
    , recurByMinute   :: [Int]
    , recurByHour     :: [Int]
    , recurByDay      :: [Either (Int, Weekday) Weekday]
    , recurByMonthDay :: [Int]
    , recurByYearDay  :: [Int]
    , recurByWeekNo   :: [Int]
    , recurByMonth    :: [Int]
    , recurBySetPos   :: [Int]
    , recurWkSt       :: Weekday
    } deriving (Show, Eq, Ord, Typeable)

-- | Recurrence Rule. 3.8.5.3.
data RRule = RRule
    { rRuleValue :: Recur
    , rRuleOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Repeat count. 3.8.6.2.
data Repeat = Repeat
    { repeatValue :: Integer
    , repeatOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

instance Default Repeat where
    def = Repeat 0 def

-- | Alarm Trigger Relationship. 3.2.14.
data AlarmTriggerRelationship = Start | End
                                deriving (Show, Eq, Ord, Typeable)

instance Default AlarmTriggerRelationship where
    def = Start

-- | Trigger. 3.8.6.3.
data Trigger
    = TriggerDuration
    { triggerDuration :: Duration
    , triggerRelated  :: AlarmTriggerRelationship -- ^ 'def' = 'Start'
    , triggerOther    :: OtherParams
    }
    | TriggerDateTime
    { triggerDateTime :: UTCTime
    , triggerOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Date-Time Created. 3.8.7.1.
data Created = Created
    { createdValue :: UTCTime
    , createdOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Date-Time Stamp. 3.8.7.2.
data DTStamp = DTStamp
    { dtStampValue :: UTCTime
    , dtStampOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Last Modified. 3.8.7.3.
data LastModified = LastModified
    { lastModifiedValue :: UTCTime
    , lastModifiedOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Sequence number. 3.8.7.4.
data Sequence = Sequence
    { sequenceValue :: Integer
    , sequenceOther :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

instance Default Sequence where
    def = Sequence 0 def

-- | Request Status. 3.8.8.3.
data RequestStatus = RequestStatus
    { requestStatusCode     :: [Int]
    , requestStatusDesc     :: Text
    , requestStatusLanguage :: Maybe Language
    , requestStatusExt      :: Maybe Text
    , requestStatusOther    :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)

-- | Any other property.
data OtherProperty = OtherProperty
    { otherName   :: CI Text
    , otherValue  :: ByteString
    , otherParams :: OtherParams
    } deriving (Show, Eq, Ord, Typeable)