content: -- Copyright (c) 2016-present, Facebook, Inc.
  -- All rights reserved.
  --
  -- This source code is licensed under the BSD-style license found in the
  -- LICENSE file in the root directory of this source tree.
  
  
  {-# LANGUAGE GADTs #-}
  {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE NoRebindableSyntax #-}
  {-# LANGUAGE OverloadedStrings #-}
  
  module Duckling.Time.AR.Rules
    ( rules
    ) where
  
  import Data.Maybe
  import Data.Text (Text)
  import Prelude
  import Data.HashMap.Strict (HashMap)
  import qualified Data.HashMap.Strict as HashMap
  import qualified Data.Text as Text
  
  import Duckling.Dimensions.Types
  import Duckling.Duration.Helpers (duration)
  import Duckling.Numeral.Helpers (parseInt)
  import Duckling.Numeral.Types (NumeralData (..))
  import Duckling.Regex.Types
  import Duckling.Time.Computed
  import Duckling.Time.Helpers
  import Duckling.Time.Types (TimeData (..))
  import Duckling.Types
  import qualified Duckling.Numeral.Types as TNumeral
  import qualified Duckling.Ordinal.Types as TOrdinal
  import qualified Duckling.Time.Types as TTime
  import qualified Duckling.TimeGrain.Types as TG
  
  ruleIntersect :: Rule
  ruleIntersect = Rule
    { name = "intersect"
    , pattern =
      [ Predicate isNotLatent
      , Predicate isNotLatent
      ]
    , prod = \tokens -> case tokens of
        (Token Time td1:Token Time td2:_) -> Token Time <$> intersect td1 td2
        _ -> Nothing
    }
  
  ruleIntersectOf :: Rule
  ruleIntersectOf = Rule
    { name = "intersect by \",\", \"of\", \"from\", \"'s\""
    , pattern =
      [ Predicate isNotLatent
      , regex "من|,|،|لـ?|بـ?|الموافق( ل)?"
      , Predicate isNotLatent
      ]
    , prod = \tokens -> case tokens of
        (Token Time td1:_:Token Time td2:_) -> Token Time <$> intersect td1 td2
        _ -> Nothing
    }
  
  ruleAbsorbOnTime :: Rule
  ruleAbsorbOnTime = Rule
    { name = "on <date>"
    , pattern =
      [ regex "في"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:token:_) -> Just token
        _ -> Nothing
    }
  
  ruleAbsorbOnADOW :: Rule
  ruleAbsorbOnADOW = Rule
    { name = "on a <named-day>"
    , pattern =
      [ regex "يوم"
      , Predicate isADayOfWeek
      ]
    , prod = \tokens -> case tokens of
        (_:token:_) -> Just token
        _ -> Nothing
    }
  
  ruleAbsorbInMonth :: Rule
  ruleAbsorbInMonth = Rule
    { name = "in <named-month>"
    , pattern =
      [ regex "شهر"
      , Predicate isAMonth
      ]
    , prod = \tokens -> case tokens of
        (_:token:_) -> Just token
        _ -> Nothing
    }
  
  ruleAbsorbCommaTOD :: Rule
  ruleAbsorbCommaTOD = Rule
    { name = "absorption of , after named day"
    , pattern =
      [ Predicate isADayOfWeek
      , regex "[,،]"
      ]
    , prod = \tokens -> case tokens of
        (token:_) -> Just token
        _ -> Nothing
    }
  
  instants :: [(Text, String, TG.Grain, Int)]
  instants =
    [ ("right now", "الان|حالا|(في )?هذه اللحظ[ةه]", TG.Second, 0)
    , ("today", "اليوم", TG.Day, 0)
    , ("tomorrow", "((يوم )?(غدا?|الغد))|(بكر[اةه])", TG.Day, 1)
    -- TODO: add regex for more variation of yesterday
    , ("yesterday", "[أا]مس|(ال|ام)بارح[ةه]?", TG.Day, - 1)
    , ("end of month", "(نهاي[ةه] الشهر)", TG.Month, 1)
    , ("end of year", "(نهاي[ةه] (السن[ةه]|العام))", TG.Year, 1)
    ]
  
  ruleInstants :: [Rule]
  ruleInstants = map go instants
    where
      go (name, regexPattern, grain, n) = Rule
        { name = name
        , pattern = [regex regexPattern]
        , prod = \_ -> tt $ cycleNth grain n
        }
  
  ruleNow :: Rule
  ruleNow = Rule
    { name = "now"
    , pattern =
      [ regex "ال[اآ]ن|حالا"
      ]
    , prod = \_ -> tt now
    }
  
  ruleThisDOW :: Rule
  ruleThisDOW = Rule
    { name = "this <day-of-week>"
    , pattern =
      [ regex "هذا"
      , Predicate isADayOfWeek
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ predNth 0 True td
        _ -> Nothing
    }
  
  ruleDOWNext :: Rule
  ruleDOWNext = Rule
    { name = "<day-of-week> next"
    , pattern =
      [ Predicate isADayOfWeek
      , regex "القادم|التالي|ال[آا]تي?|القادم"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:_) -> tt $ predNth 0 True td
        _ -> Nothing
    }
  
  ruleDOWLast :: Rule
  ruleDOWLast = Rule
    { name = "<day-of-week> last"
    , pattern =
      [ Predicate isADayOfWeek
      , regex "السابق|الماضي|المنصرم|الفا[يئ]ت"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:_) -> tt $ predNth (- 1) True td
        _ -> Nothing
    }
  
  ruleThisTime :: Rule
  ruleThisTime = Rule
    { name = "this <time>"
    , pattern =
      [ regex "هذا"
      , Predicate $ and . sequence [isNotLatent, isOkWithThisNext]
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ predNth 0 False td
        _ -> Nothing
    }
  
  ruleNextTime :: Rule
  ruleNextTime = Rule
    { name = "<time> next"
    , pattern =
      [ Predicate $ and . sequence [isNotLatent, isOkWithThisNext]
      , regex "التالي"
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ predNth 0 True td
        _ -> Nothing
    }
  
  ruleLastTime :: Rule
  ruleLastTime = Rule
    { name = "last <time>"
    , pattern =
      [ regex "([اآ]خر)"
      , Predicate $ and . sequence [isNotLatent, isOkWithThisNext]
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ predNth (- 1) False td
        _ -> Nothing
    }
  
  -- to match 'عام سابق', 'عامان سابقان' or 'سابقة' and the same for 'منصرم' and 'ماضي'
  ruleLastTime2 :: Rule
  ruleLastTime2 = Rule
    { name = "<time> last"
    , pattern =
      [ Predicate $ and . sequence [isNotLatent, isOkWithThisNext]
      , regex "((ال)?ماضيت?(ان|ين|ة|ه)?|(ال)?منصرمت?(ان|ين|ة|ه)?|(ال)?سابقت?(ان|ين|ة|ه)?)"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:_) -> tt $ predNth (- 1) False td
        _ -> Nothing
    }
  
  ruleLastDOWOfTime :: Rule
  ruleLastDOWOfTime = Rule
    { name = "last <day-of-week> of <time>"
    , pattern =
      [ regex "[اآ]خر"
      , Predicate isADayOfWeek
      , regex "من|في"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td1:_:Token Time td2:_) ->
          tt $ predLastOf td1 td2
        _ -> Nothing
    }
  
  ruleDOWTheLastOfTime :: Rule
  ruleDOWTheLastOfTime = Rule
    { name = "<day-of-week> the last of <time>"
    , pattern =
      [ Predicate isADayOfWeek
      , regex "(ال[اأآ]خي?رة?) (من|في|ب|ل)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (Token Time td1:_:Token Time td2:_) ->
          tt $ predLastOf td1 td2
        _ -> Nothing
    }
  
  ruleLastCycleOfTime :: Rule
  ruleLastCycleOfTime = Rule
    { name = "last <cycle> of <time>"
    , pattern =
      [ regex "[اآ]خر"
      , dimension TimeGrain
      , regex "(من|في|ل|ب)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token TimeGrain grain:_:Token Time td:_) ->
          tt $ cycleLastOf grain td
        _ -> Nothing
    }
  
  ruleCycleLastOfTime :: Rule
  ruleCycleLastOfTime = Rule
    { name = "<cycle> last of <time>"
    , pattern =
      [ regex "ال"
      , dimension TimeGrain
      , regex "ال[اأ]خيرة? (من|في|ل|ب)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token TimeGrain grain:_:Token Time td:_) ->
          tt $ cycleLastOf grain td
        _ -> Nothing
    }
  
  ruleNthTimeOfTime :: Rule
  ruleNthTimeOfTime = Rule
    { name = "nth <time> of <time>"
    , pattern =
      [ dimension Ordinal
      , dimension Time
      , regex "في|من|ب"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
          predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
        _ -> Nothing
    }
  
  ruleTimeNthOfTime :: Rule
  ruleTimeNthOfTime = Rule
    { name = "<time> nth of <time>"
    , pattern =
      [ dimension Time
      , dimension Ordinal
      , regex "من|في"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (Token Time td1:Token Ordinal od:_:Token Time td2:_) -> Token Time .
          predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
        _ -> Nothing
    }
  
  ruleNthTimeAfterTime :: Rule
  ruleNthTimeAfterTime = Rule
    { name = "nth <time> after <time>"
    , pattern =
      [ dimension Ordinal
      , dimension Time
      , regex "بعد"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (Token Ordinal od:Token Time td1:_:Token Time td2:_) ->
          tt $ predNthAfter (TOrdinal.value od - 1) td1 td2
        _ -> Nothing
    }
  
  ruleTimeNthAfterTime :: Rule
  ruleTimeNthAfterTime = Rule
    { name = "<time> nth after <time>"
    , pattern =
      [ dimension Time
      , dimension Ordinal
      , regex "بعد"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (Token Time td1:Token Ordinal od:_:Token Time td2:_) ->
          tt $ predNthAfter (TOrdinal.value od - 1) td1 td2
        _ -> Nothing
    }
  
  ruleYear :: Rule
  ruleYear = Rule
    { name = "year"
    , pattern = [Predicate $ isIntegerBetween 1000 2100]
    , prod = \tokens -> case tokens of
        (token:_) -> do
          n <- getIntValue token
          tt $ year n
        _ -> Nothing
    }
  
  ruleYearPastLatent :: Rule
  ruleYearPastLatent = Rule
   { name = "past year (latent)"
   , pattern =
     [ Predicate $ isIntegerBetween 25 999
     ]
   , prod = \tokens -> case tokens of
       (token:_) -> do
         n <- getIntValue token
         tt . mkLatent $ year n
       _ -> Nothing
   }
  
  ruleYearFutureLatent :: Rule
  ruleYearFutureLatent = Rule
   { name = "future year (latent)"
   , pattern = [Predicate $ isIntegerBetween 2101 10000]
   , prod = \tokens -> case tokens of
       (token:_) -> do
         n <- getIntValue token
         tt . mkLatent $ year n
       _ -> Nothing
   }
  
  ruleDOMLatent :: Rule
  ruleDOMLatent = Rule
    { name = "<day-of-month> (ordinal)"
    , pattern = [Predicate isDOMOrdinal]
    , prod = \tokens -> case tokens of
        (token:_) -> do
          n <- getIntValue token
          tt . mkLatent $ dayOfMonth n
        _ -> Nothing
    }
  
  ruleNamedDOMOrdinal :: Rule
  ruleNamedDOMOrdinal = Rule
    { name = "<named-month>|<named-day> <day-of-month> (ordinal)"
    , pattern =
      [ Predicate $ or . sequence [isAMonth, isADayOfWeek]
      , Predicate isDOMOrdinal
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:token:_) -> Token Time <$> intersectDOM td token
        _ -> Nothing
    }
  
  ruleMonthDOMNumeral :: Rule
  ruleMonthDOMNumeral = Rule
    { name = "<named-month> <day-of-month> (non ordinal)"
    , pattern =
      [ Predicate isAMonth
      , Predicate isDOMInteger
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:token:_) -> Token Time <$> intersectDOM td token
        _ -> Nothing
    }
  
  ruleDOMOfMonth :: Rule
  ruleDOMOfMonth = Rule
    { name = "<day-of-month> (ordinal or number) of <named-month>"
    , pattern =
      [ Predicate isDOMValue
      , regex "(من|في|بـ?)"
      , Predicate isAMonth
      ]
    , prod = \tokens -> case tokens of
        (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
        _ -> Nothing
    }
  
  ruleDOMMonth :: Rule
  ruleDOMMonth = Rule
    { name = "<day-of-month> (ordinal or number) <named-month>"
    , pattern =
      [ Predicate isDOMValue
      , Predicate isAMonth
      ]
    , prod = \tokens -> case tokens of
        (token:Token Time td:_) -> Token Time <$> intersectDOM td token
        _ -> Nothing
    }
  
  ruleDOMOrdinalMonthYear :: Rule
  ruleDOMOrdinalMonthYear = Rule
    { name = "<day-of-month>(ordinal) <named-month> year"
    , pattern =
      [ Predicate isDOMOrdinal
      , Predicate isAMonth
      , regex "(\\d{2,4})"
      ]
    , prod = \tokens -> case tokens of
        (token:Token Time td:Token RegexMatch (GroupMatch (match:_)):_) -> do
          intVal <- parseInt match
          dom <- intersectDOM td token
          Token Time <$> intersect dom (year intVal)
        _ -> Nothing
    }
  
  ruleDOMOrdinalDayMonthYear :: Rule
  ruleDOMOrdinalDayMonthYear = Rule
    { name = "<day-of-month>(ordinal) <named-month> year"
    , pattern =
      [ Predicate isDOMOrdinal
      , regex "يوم( من| في| بـ?)?"
      , Predicate isAMonth
      , regex "(\\d{2,4})"
      ]
    , prod = \tokens -> case tokens of
        (token:_:Token Time td:Token RegexMatch (GroupMatch (match:_)):_) -> do
          intVal <- parseInt match
          dom <- intersectDOM td token
          Token Time <$> intersect dom (year intVal)
        _ -> Nothing
    }
  
  ruleTODIntegerLatent :: Rule
  ruleTODIntegerLatent = Rule
    { name = "time-of-day integer (latent)"
    , pattern =
      [ Predicate $ isIntegerBetween 0 23
      ]
    , prod = \tokens -> case tokens of
        (token:_) -> do
          n <- getIntValue token
          tt . mkLatent $ hour True n
        _ -> Nothing
    }
  
  ruleTODOrdinalLatent :: Rule
  ruleTODOrdinalLatent = Rule
    { name = "time-of-day ordinal (latent)"
    , pattern =
      [ Predicate $ isOrdinalBetween 1 24
      ]
    , prod = \tokens -> case tokens of
        (token:_) -> do
          n <- getIntValue token
          tt . mkLatent $ hour True n
        _ -> Nothing
    }
  
  ruleHourTOD :: Rule
  ruleHourTOD = Rule
    { name = "at <time-of-day>"
    , pattern =
      [ regex "(ال)?ساع[ةه]"
      , Predicate isATimeOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  ruleAtHourTOD :: Rule
  ruleAtHourTOD = Rule
    { name = "at <time-of-day>"
    , pattern =
      [ regex "عند ((ال)?ساع[ةه])?"
      , Predicate isATimeOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  ruleHODAndInteger :: Rule
  ruleHODAndInteger = Rule
    { name = "<hour-of-day> and integer"
    , pattern =
      [ Predicate $ and . sequence [isNotLatent, isAnHourOfDay]
      , regex "و"
      , Predicate $ isIntegerBetween 0 60
      ]
    , prod = \tokens -> case tokens of
        (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_:Token Numeral NumeralData{TNumeral.value = v}:_) ->
          tt $ hourMinute is12H hours (floor v)
        _ -> Nothing
    }
  
  ruleHODAndIntegerMinutes :: Rule
  ruleHODAndIntegerMinutes = Rule
    { name = "<hour-of-day> and integer minutes"
    , pattern =
      [ Predicate isAnHourOfDay
      , regex "و"
      , Predicate $ isIntegerBetween 0 60
      , regex "دقيق[ةه]|دقا[ئي]ق"
      ]
    , prod = \tokens -> case tokens of
        (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_:Token Numeral NumeralData{TNumeral.value = v}:_) ->
          tt $ hourMinute is12H hours (floor v)
        _ -> Nothing
    }
  
  ruleHHMM :: Rule
  ruleHHMM = Rule
    { name = "hh:mm"
    , pattern = [regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)"]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
          h <- parseInt hh
          m <- parseInt mm
          tt $ hourMinute True h m
        _ -> Nothing
    }
  
  ruleHHMMLatent :: Rule
  ruleHHMMLatent = Rule
    { name = "hhmm (latent)"
    , pattern =
      [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
          h <- parseInt hh
          m <- parseInt mm
          tt . mkLatent $ hourMinute True h m
        _ -> Nothing
    }
  
  ruleHHMMSS :: Rule
  ruleHHMMSS = Rule
    { name = "hh:mm:ss"
    , pattern = [regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
          h <- parseInt hh
          m <- parseInt mm
          s <- parseInt ss
          tt $ hourMinuteSecond True h m s
        _ -> Nothing
    }
  
  ruleTODAMPM :: Rule
  ruleTODAMPM = Rule
    { name = "<time-of-day> am|pm"
    , pattern =
      [ Predicate isATimeOfDay
      , regex "([ap])(\\s|\\.)?m?\\.?"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:Token RegexMatch (GroupMatch (_:ap:_)):_) ->
          tt $ timeOfDayAMPM (Text.toLower ap == "a") td
        _ -> Nothing
    }
  
  ruleHONumeral :: Rule
  ruleHONumeral = Rule
    { name = "<hour-of-day> <integer>"
    , pattern =
      [ Predicate $ and . sequence [isNotLatent, isAnHourOfDay]
      , Predicate $ isIntegerBetween 1 59
      ]
    , prod = \tokens -> case tokens of
        (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
         token:
         _) -> do
          n <- getIntValue token
          tt $ hourMinute is12H hours n
        _ -> Nothing
    }
  
  ruleHODHalf :: Rule
  ruleHODHalf = Rule
    { name = "<hour-of-day> half"
    , pattern =
      [ Predicate isAnHourOfDay
      , regex "(و ?)?نصف?( ساع[ةه])?"
      ]
    , prod = \tokens -> case tokens of
        (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
          tt $ hourMinute is12H hours 30
        _ -> Nothing
    }
  
  ruleHODThird :: Rule
  ruleHODThird = Rule
    { name = "<hour-of-day> third"
    , pattern =
      [ Predicate isAnHourOfDay
      , regex "(و ?)?ثلث( ساع[ةه])?"
      ]
    , prod = \tokens -> case tokens of
        (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
          tt $ hourMinute is12H hours 20
        _ -> Nothing
    }
  
  ruleHODQuarter :: Rule
  ruleHODQuarter = Rule
    { name = "<hour-of-day> quarter"
    , pattern =
      [ Predicate isAnHourOfDay
      , regex "(و ?)?ربع( ساع[ةه])?"
      ]
    , prod = \tokens -> case tokens of
        (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
          tt $ hourMinute is12H hours 15
        _ -> Nothing
    }
  
  ruleThirdToHOD :: Rule
  ruleThirdToHOD = Rule
    { name = "<hour-of-day> till third"
    , pattern =
      [ Predicate isAnHourOfDay
      , regex "[إا]لا ثلثا?"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:_) -> Token Time <$> minutesBefore 20 td
        _ -> Nothing
    }
  
  ruleQuarterToHOD :: Rule
  ruleQuarterToHOD = Rule
    { name = "<hour-of-day> till quarter"
    , pattern =
      [ Predicate isAnHourOfDay
      , regex "[إا]لا ربعا?"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:_) -> Token Time <$> minutesBefore 15 td
        _ -> Nothing
    }
  
  ruleHODAndNumeralAfter :: Rule
  ruleHODAndNumeralAfter = Rule
    { name = "<hour-of-day>"
    , pattern =
      [ Predicate isAnHourOfDay
      , regex "و"
      , Predicate $ isIntegerBetween 1 59
      , regex "(دقيق[ةه]|دقا[ئي]ق)"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:token:_) -> do
          n <- getIntValue token
          Token Time <$> minutesAfter n td
        _ -> Nothing
    }
  
  ruleNumeralAfterHOD :: Rule
  ruleNumeralAfterHOD = Rule
    { name = "integer after <hour-of-day>"
    , pattern =
      [ Predicate $ isIntegerBetween 1 59
      , regex "(دقيق[ةه]|دقا[ئي]ق) بعد"
      , Predicate isAnHourOfDay
      ]
    , prod = \tokens -> case tokens of
        (token:_:Token Time td:_) -> do
          n <- getIntValue token
          Token Time <$> minutesAfter n td
        _ -> Nothing
    }
  
  ruleHalfAfterHOD :: Rule
  ruleHalfAfterHOD = Rule
    { name = "half after <hour-of-day>"
    , pattern =
      [ regex "نصف? ساع[ةه] بعد"
      , Predicate isAnHourOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> Token Time <$> minutesAfter 30 td
        _ -> Nothing
    }
  
  ruleThirdAfterHOD :: Rule
  ruleThirdAfterHOD = Rule
    { name = "third after <hour-of-day>"
    , pattern =
      [ regex "ثلث ساع[ةه] بعد"
      , Predicate isAnHourOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> Token Time <$> minutesAfter 20 td
        _ -> Nothing
    }
  
  ruleQuarterAfterHOD :: Rule
  ruleQuarterAfterHOD = Rule
    { name = "quarter after <hour-of-day>"
    , pattern =
      [ regex "ربع ساع[ةه] بعد"
      , Predicate isAnHourOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> Token Time <$> minutesAfter 15 td
        _ -> Nothing
    }
  
  ruleDDMM :: Rule
  ruleDDMM = Rule
    { name = "dd/mm"
    , pattern =
      [ regex "(3[01]|[12]\\d|0?[1-9])\\s?[/-]\\s?(1[0-2]|0?[1-9])"
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
          d <- parseInt dd
          m <- parseInt mm
          tt $ monthDay m d
        _ -> Nothing
    }
  
  ruleDDMMYYYY :: Rule
  ruleDDMMYYYY = Rule
    { name = "dd/mm/yyyy"
    , pattern =
      [ regex "(3[01]|[12]\\d|0?[1-9])[/-](1[0-2]|0?[1-9])[-/](\\d{2,4})"
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (dd:mm:yy:_)):_) -> do
          y <- parseInt yy
          d <- parseInt dd
          m <- parseInt mm
          tt $ yearMonthDay y m d
        _ -> Nothing
    }
  
  ruleMMYYYY :: Rule
  ruleMMYYYY = Rule
    { name = "mm/yyyy"
    , pattern =
      [ regex "(0?[1-9]|1[0-2])[/-](\\d{4})"
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (mm:yy:_)):_) -> do
          y <- parseInt yy
          m <- parseInt mm
          tt $ yearMonthDay y m 1
        _ -> Nothing
    }
  
  ruleYYYYMMDD :: Rule
  ruleYYYYMMDD = Rule
    { name = "yyyy-mm-dd"
    , pattern = [regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
          y <- parseInt yy
          m <- parseInt mm
          d <- parseInt dd
          tt $ yearMonthDay y m d
        _ -> Nothing
    }
  
  ruleNoonMidnightEOD :: Rule
  ruleNoonMidnightEOD = Rule
    { name = "noon|midnight|EOD|end of day"
    , pattern = [regex "(ليلا|مساء|نهاية (ال)يوم)|منتصف الليل"]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):_) -> tt . hour False $
          if match == "منتصف الليل" then 12 else 0
        _ -> Nothing
    }
  
  parOfDaysMap :: HashMap Text (Int, Int)
  parOfDaysMap = HashMap.fromList
    [ ( "فجر",    (3, 6)   )
    , ( "صبح",    (0, 12)  )
    , ( "صباح",   (0, 12)  )
    , ( "نهار",   (6, 17)  )
    , ( "ظهر",    (11, 15) )
    , ( "ظهر",    (11, 15) )
    , ( "ظهيرة",  (11, 15) )
    , ( "عصر",    (15, 18) )
    , ( "مغرب",   (17, 21) )
    , ( "غداء",   (13, 16) )
    , ( "عشاء",   (18, 3)  )
    , ( "ليلة",   (18, 3)  )
    , ( "ليله",   (18, 3)  )
    , ( "مساء",   (12, 24)  )
    ]
  
  rulePartOfDays :: Rule
  rulePartOfDays = Rule
    { name = "part of days"
    , pattern =
      [ regex "(ال)?(صبا?ح|فجر|ظهر|ظهيرة|فطور|إفطار|عصر|مغرب|مساء|عشاء|ليل[ةه]|غداء)ا?"
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (_:match:_)):_) -> do
          (start, end) <- HashMap.lookup (Text.toLower match) parOfDaysMap
          td <- interval TTime.Open (hour False start) (hour False end)
          tt . partOfDay $ mkLatent td
        _ -> Nothing
    }
  
  ruleAfterPartOfDays :: Rule
  ruleAfterPartOfDays = Rule
    { name = "after part of days"
    , pattern =
      [ regex "بعد ال(فجر|ظهر|ظهيرة|فطور|إفطار|عصر|مغرب|عشاء|ليل|غداء)"
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):_) -> do
          (start, end) <- HashMap.lookup (Text.toLower match) parOfDaysMap
          td <- interval TTime.Open (hour False start) (hour False (end + 1))
          tt . partOfDay $ notLatent td
        _ -> Nothing
    }
  
  ruleBeforePartOfDays :: Rule
  ruleBeforePartOfDays = Rule
    { name = "before part of days"
    , pattern =
      [ regex "قبل ال(فجر|ظهر|ظهيرة|فطور|إفطار|عصر|مغرب|عشاء|ليل|غداء)"
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):_) -> do
          (start, end) <- HashMap.lookup (Text.toLower match) parOfDaysMap
          td <- interval TTime.Open (hour False (start - 1)) (hour False end)
          tt . partOfDay $ notLatent td
        _ -> Nothing
    }
  
  ruleEarlyMorning :: Rule
  ruleEarlyMorning = Rule
    { name = "early morning"
    , pattern =
      [ regex "(ال)?صبا?ح (((ال)?باكر)|بدري)"
      ]
    , prod = \_ -> Token Time . partOfDay . mkLatent <$>
        interval TTime.Open (hour False 4) (hour False 9)
    }
  
  rulePODIn :: Rule
  rulePODIn = Rule
    { name = "in|during the <part-of-day>"
    , pattern =
      [ regex "(في|خلال|في خلال|عند)(وقت )?( ال)?|وقت ال"
      , Predicate isAPartOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  rulePODThis :: Rule
  rulePODThis = Rule
    { name = "this <part-of-day>"
    , pattern =
      [ regex "هذ[اه](ال)?"
      , Predicate isAPartOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> Token Time . partOfDay . notLatent <$>
          intersect today td
        _ -> Nothing
    }
  
  ruleTonight :: Rule
  ruleTonight = Rule
    { name = "tonight"
    , pattern = [regex "(هذه )?الليل[ةه]"]
    , prod = \_ -> do
        evening <- interval TTime.Open (hour False 18) (hour False 0)
        Token Time . partOfDay . notLatent <$> intersect today evening
    }
  
  ruleAfterPartofday :: Rule
  ruleAfterPartofday = Rule
    { name = "after lunch/work/school"
    , pattern =
      [ regex "بعد[\\s-]?((ال)?غداء|(ال)?عمل|(ال)?شغل|(ال)?مدرس[ةه])"
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):_) -> do
          (start, end) <- case Text.toLower match of
            "(ال)?غداء"  -> Just (hour False 13, hour False 17)
            "(ال)?عمل|(ال)?شغل"   -> Just (hour False 17, hour False 21)
            "(ال)?مدرس[ةه]" -> Just (hour False 15, hour False 21)
            _        -> Nothing
          td <- interval TTime.Open start end
          Token Time . partOfDay . notLatent <$> intersect today td
        _ -> Nothing
    }
  
  -- Since part of days are latent, general time intersection is blocked
  ruleTimePOD :: Rule
  ruleTimePOD = Rule
    { name = "<time> <part-of-day>"
    , pattern =
      [ dimension Time
      , Predicate isAPartOfDay
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:Token Time pod:_) -> Token Time <$> intersect pod td
        _ -> Nothing
    }
  
  ruleWeekend :: Rule
  ruleWeekend = Rule
    { name = "week-end"
    , pattern =
      [ regex "(عطل[ةه] )?نهاي[ةه] ال[اأ]سبوع"
      ]
    , prod = \_ -> tt weekend
    }
  
  ruleSeasons :: [Rule]
  ruleSeasons = mkRuleSeasons
    [ ("summer", "صيف|الصيف"  , monthDay  6 21, monthDay  9 23)
    , ("fall"  , "الخريف|خريف", monthDay  9 23, monthDay 12 21)
    , ("winter", "شتاء|الشتاء", monthDay 12 21, monthDay  3 20)
    , ("spring", "ربيع|الربيع", monthDay  3 20, monthDay  6 21)
    ]
  
  ruleTODPrecision :: Rule
  ruleTODPrecision = Rule
    { name = "<time-of-day> sharp|exactly"
    , pattern =
      [ Predicate isATimeOfDay
      , regex "(تقريبا)"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  rulePrecisionTOD :: Rule
  rulePrecisionTOD = Rule
    { name = "about|exactly <time-of-day>"
    , pattern =
      [ regex "(حول|حوالي?|تقريبا|قراب[ةه]|(ب|في )حدود)"
      , Predicate $ isGrainFinerThan TG.Year
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  ruleIntervalMonthDDDD :: Rule
  ruleIntervalMonthDDDD = Rule
    { name = "<month> dd-dd (interval)"
    , pattern =
      [ Predicate isAMonth
      , Predicate isDOMValue
      , regex "\\-|[اإ]لى|لـ?|حتى"
      , Predicate isDOMValue
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:
         token1:
         _:
         token2:
         _) -> do
          dom1 <- intersectDOM td token1
          dom2 <- intersectDOM td token2
          Token Time <$> interval TTime.Closed dom1 dom2
        _ -> Nothing
    }
  
  ruleIntervalDDDDMonth :: Rule
  ruleIntervalDDDDMonth = Rule
    { name = "dd-dd <month> (interval)"
    , pattern =
      [ Predicate isDOMValue
      , regex "\\-|[اإ]لى|لـ?|حتى"
      , Predicate isDOMValue
      , Predicate isAMonth
      ]
    , prod = \tokens -> case tokens of
        (token1:
         _:
         token2:
         Token Time td:
         _) -> do
          dom1 <- intersectDOM td token1
          dom2 <- intersectDOM td token2
          Token Time <$> interval TTime.Closed dom1 dom2
        _ -> Nothing
    }
  
  ruleIntervalFromMonthDDDD :: Rule
  ruleIntervalFromMonthDDDD = Rule
    { name = "from <month> dd-dd (interval)"
    , pattern =
      [ regex "من"
      , Predicate isAMonth
      , Predicate isDOMValue
      , regex "\\-|[اإ]لى|لـ?|حتى"
      , Predicate isDOMValue
      ]
    , prod = \tokens -> case tokens of
        (_:
         Token Time td:
         token1:
         _:
         token2:
         _) -> do
          dom1 <- intersectDOM td token1
          dom2 <- intersectDOM td token2
          Token Time <$> interval TTime.Closed dom1 dom2
        _ -> Nothing
    }
  
  ruleIntervalFromDDDDMonth :: Rule
  ruleIntervalFromDDDDMonth = Rule
    { name = "from <day-of-month> (ordinal or number) to <day-of-month> (ordinal or number) <named-month> (interval)"
    , pattern =
      [ regex "من|بين"
      , Predicate isDOMValue
      , regex "(و ?)?(\\-|[اإ]لى|لـ?|حتى)"
      , Predicate isDOMValue
      , Predicate isAMonth
      ]
    , prod = \tokens -> case tokens of
        (_:
         token1:
         _:
         token2:
         Token Time td:
         _) -> do
          dom1 <- intersectDOM td token1
          dom2 <- intersectDOM td token2
          Token Time <$> interval TTime.Closed dom1 dom2
        _ -> Nothing
    }
  
  ruleIntervalFromDDDDInMonth :: Rule
  ruleIntervalFromDDDDInMonth = Rule
    { name = "from <day-of-month> (ordinal or number) to <day-of-month> (ordinal or number) in <named-month> (interval)"
    , pattern =
      [ regex "من|بين"
      , Predicate isDOMValue
      , regex "(و ?)?(\\-|[اإ]لى|لـ?|حتى)"
      , Predicate isDOMValue
      , regex "من|في"
      , Predicate isAMonth
      ]
    , prod = \tokens -> case tokens of
        (_:
         token1:
         _:
         token2:
         _:
         Token Time td:
         _) -> do
          dom1 <- intersectDOM td token1
          dom2 <- intersectDOM td token2
          Token Time <$> interval TTime.Closed dom1 dom2
        _ -> Nothing
    }
  
  -- Blocked for :latent time. May need to accept certain latents only, like hours
  ruleIntervalDash :: Rule
  ruleIntervalDash = Rule
    { name = "<datetime> - <datetime> (interval)"
    , pattern =
      [ Predicate isNotLatent
      , regex "(و ?)?(\\-|[اإ]لى|حتى)"
      , Predicate isNotLatent
      ]
    , prod = \tokens -> case tokens of
        (Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleIntervalFrom :: Rule
  ruleIntervalFrom = Rule
    { name = "from <datetime> - <datetime> (interval)"
    , pattern =
      [ regex "من"
      , Predicate isNotLatent
      , regex "(و ?)?(\\-|[اإ]لى|لـ?|حتى)"
      , Predicate isNotLatent
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleIntervalBetween :: Rule
  ruleIntervalBetween = Rule
    { name = "between <time> and <time>"
    , pattern =
      [ regex "بين ?((ال)?ساع[ةه])?"
      , dimension Time
      , regex "و ?(بين)? ?((ال)?ساع[ةه])?"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  -- Specific for time-of-day, to help resolve ambiguities
  ruleIntervalTODDash :: Rule
  ruleIntervalTODDash = Rule
    { name = "<time-of-day> - <time-of-day> (interval)"
    , pattern =
      [ Predicate $ and . sequence [isNotLatent, isATimeOfDay]
      , regex "(و ?)?(\\-|:|[اإ]لى|لـ?|حتى)"
      , Predicate isATimeOfDay
      ]
    , prod = \tokens -> case tokens of
        (Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleIntervalTODFrom :: Rule
  ruleIntervalTODFrom = Rule
    { name = "from <time-of-day> - <time-of-day> (interval)"
    , pattern =
      [ regex "(من|بين|)((ال)?ساع[ةه]?)"
      , Predicate isATimeOfDay
      , regex "(و ?)?(\\-|[اإ]لى|لـ?|حتى)((ال)?ساع[ةه]?)"
      , Predicate isATimeOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleIntervalTODBetween :: Rule
  ruleIntervalTODBetween = Rule
    { name = "between <time-of-day> and <time-of-day> (interval)"
    , pattern =
      [ regex "بين"
      , Predicate isATimeOfDay
      , regex "و"
      , Predicate isATimeOfDay
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleIntervalBy :: Rule
  ruleIntervalBy = Rule
    { name = "by <time>"
    , pattern =
      [ regex "حتى"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> Token Time <$> interval TTime.Open now td
        _ -> Nothing
    }
  
  ruleIntervalUntilTOD :: Rule
  ruleIntervalUntilTOD = Rule
    { name = "until <time-of-day>"
    , pattern =
      [ regex "([اأ]ي وقت)?(قبل|حتى|[إا]لى)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ withDirection TTime.Before td
        _ -> Nothing
    }
  
  ruleIntervalAfterTOD :: Rule
  ruleIntervalAfterTOD = Rule
    { name = "after <time-of-day>"
    , pattern =
      [ regex "([اأ]ي وقت)?(بعد)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ withDirection TTime.After td
        _ -> Nothing
    }
  
  ruleIntervalSinceTOD :: Rule
  ruleIntervalSinceTOD = Rule
    { name = "since <time-of-day>"
    , pattern =
      [ regex "منذ"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token Time td:_) -> tt $ withDirection TTime.After td
        _ -> Nothing
    }
  
  ruleDaysOfWeek :: [Rule]
  ruleDaysOfWeek = mkRuleDaysOfWeek
    [ ( "Monday"   , "(ال)?[اإ]ثنين"   )
    , ( "Tuesday"  , "(ال)?ثلاثاء?"     )
    , ( "Wednesday", "(ال)?[اأ]ربعاء?" )
    , ( "Thursday" , "(ال)?خميس"       )
    , ( "Friday"   , "(ال)?جمع[ةه]"    )
    , ( "Saturday" , "(ال)?سبت"        )
    , ( "Sunday"   , "(ال)?[اأ]حد"     )
    ]
  
  ruleMonths :: [Rule]
  ruleMonths = mkRuleMonths
    [ ( "January"  , "يناير|كانون (ال)ثاني?"       )
    , ( "February" , "فبراير|شباط"                 )
    , ( "March"    , "مارس|[اآ]ذار"                )
    , ( "April"    , "[اأ]بريل|نيسان"              )
    , ( "May"      , "مايو|[اأ]ي[اآ]ر"             )
    , ( "June"     , "يونيو|حزيران"                )
    , ( "July"     , "يوليو|تموز"                  )
    , ( "August"   , "[اأ]غسطس|[اآ]ب"              )
    , ( "September", "سي?بتمبر|[اأ]يلول"           )
    , ( "October"  , "[اأ]كتوبر|تشرين (ال)?[اأ]ول" )
    , ( "November" , "نوفمبر|تشرين (ال)?ثاني"      )
    , ( "December" , "ديسمبر|كانون (ال)?[اأ]ول"    )
    ]
  
  ruleMonthsNumeral :: Rule
  ruleMonthsNumeral = Rule
    { name = "month (integer)"
    , pattern =
      [ regex "شهر"
      , Predicate $ isIntegerBetween 1 12
      ]
    , prod = \tokens -> case tokens of
        (_:token:_) -> do
          n <- getIntValue token
          tt $ month n
        _ -> Nothing
    }
  
  ruleMonthsOrdinal :: Rule
  ruleMonthsOrdinal = Rule
    { name = "month (ordinal)"
    , pattern =
      [ regex "الشهر"
      , dimension Ordinal
      ]
    , prod = \tokens -> case tokens of
        (_:token:_) -> do
          n <- getIntValue token
          tt $ month n
        _ -> Nothing
    }
  
  ruleYearInteger :: Rule
  ruleYearInteger = Rule
    { name = "year (integer)"
    , pattern =
      [ regex "سنة|عام"
      , Predicate $ isIntegerBetween 1 3000
      ]
    , prod = \tokens -> case tokens of
        (_:token:_) -> do
          n <- getIntValue token
          tt $ year n
        _ -> Nothing
    }
  
  rulePartOfMonth :: Rule
  rulePartOfMonth = Rule
    { name = "part of <named-month>"
    , pattern =
      [ regex "([اأ]وائل|[اأ]واخر|نهاي[ةه])-?"
      , Predicate isAMonth
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):Token Time td:_) -> do
          (sd, (Just end)) <- case Text.toLower match of
            "اوائل" -> Just (1, intersect (dayOfMonth 10) td)
            "أوائل" -> Just (1, intersect (dayOfMonth 10) td)
            "اواخر" -> Just (21, Just $ cycleLastOf TG.Day td)
            "أواخر" -> Just (21, Just $ cycleLastOf TG.Day td)
            _       -> Nothing
          start <- intersect td $ dayOfMonth sd
          Token Time <$> interval TTime.Open start end
        _ -> Nothing
    }
  
  ruleDayOfMonth :: Rule
  ruleDayOfMonth = Rule
    { name = "day of <named-month>"
    , pattern =
      [ regex "(بداي[ةه]|منتصف|نصف?|[اآ]خر|نهاي[ةه])-?"
      , Predicate isAMonth
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):
         Token Time td:
         _) -> case Text.toLower match of
          "بداية" -> Token Time <$> intersect (dayOfMonth 1) td
          "بدايه" -> Token Time <$> intersect (dayOfMonth 1) td
          "منتصف" -> Token Time <$> intersect (dayOfMonth 15) td
          "نصف"   -> Token Time <$> intersect (dayOfMonth 15) td
          "نص"    -> Token Time <$> intersect (dayOfMonth 15) td
          _       -> tt $ cycleLastOf TG.Day td
        _ -> Nothing
    }
  
  ruleUSHolidays :: [Rule]
  ruleUSHolidays = mkRuleHolidays
    [ ( "Christmas"       , "(يوم |عطل[ةه] )?((ال)?كري?سماس)"           , monthDay 12 25)
    , ( "Christmas Eve"   , "(ليل[ةه] )((ال)?كري?سماس)"                 , monthDay 12 24)
    , ( "New Year's Eve"  , "(ليل[ةه] )(ر[اأ]س السن[ةه])"               , monthDay 12 31)
    , ( "New Year's Day"  , "(وم |عطل[ةه] )?(ر[اأ]س السن[ةه])"          , monthDay  1  1)
    , ( "Valentine's Day" , "(عيد|يوم|عطل[ةه])((ال)?حب|(ال)?فالنتا?ين)" , monthDay  2 12)
    , ( "Halloween"       , "(عيد |يوم |عطل[ةه] )?((ال)?هالوي?ين)"      , monthDay 10 31 )
    ]
  
  ruleThisLastNextCycle :: Rule
  ruleThisLastNextCycle = Rule
    { name = "this|last <cycle>"
    , pattern =
      [ regex "(هذا|هذه|اخر|آخر)"
      , dimension TimeGrain
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):
         Token TimeGrain grain:
         _) -> case Text.toLower match of
          "هذا" -> tt $ cycleNth grain 0
          "هذه" -> tt $ cycleNth grain 0
          "اخر" -> tt . cycleNth grain $ - 1
          "آخر" -> tt . cycleNth grain $ - 1
          _     -> Nothing
        _ -> Nothing
    }
  
  ruleThisLastNextCycle2 :: Rule
  ruleThisLastNextCycle2 = Rule
    { name = "this|last the <cycle>"
    , pattern =
      [ regex "(هذا|هذه|اخر|آخر)"
      , regex "(ال)"
      , dimension TimeGrain
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):
         _:
         Token TimeGrain grain:
         _) -> case Text.toLower match of
          "هذا" -> tt $ cycleNth grain 0
          "هذه" -> tt $ cycleNth grain 0
          "اخر" -> tt . cycleNth grain $ - 1
          "آخر" -> tt . cycleNth grain $ - 1
          _     -> Nothing
        _ -> Nothing
    }
  
  ruleCycleThisLastNext :: Rule
  ruleCycleThisLastNext = Rule
    { name = "<cycle> this|last|next"
    , pattern =
      [ regex "ال"
      , dimension TimeGrain
      , regex "(هذ|القادم|التالي|الحالي|المقبل|الجاي|السابق|الماضي?|الفا[ئي]ت|المنصرم)[اةه]?(ين|ان|ات)?"
      ]
    , prod = \tokens -> case tokens of
        (_:
         Token TimeGrain grain:
         Token RegexMatch (GroupMatch (match:_)):
         _) -> case Text.toLower match of
          "هذ"      -> tt $ cycleNth grain 0
          "الحالي"  -> tt $ cycleNth grain 0
          "القادم"  -> tt $ cycleNth grain 1
          "التالي"  -> tt $ cycleNth grain 1
          "المقبل"  -> tt $ cycleNth grain 1
          "الجاي"   -> tt $ cycleNth grain 1
          "السابق"  -> tt . cycleNth grain $ - 1
          "الماضي"  -> tt . cycleNth grain $ - 1
          "الماض"   -> tt . cycleNth grain $ - 1
          "المنصرم" -> tt . cycleNth grain $ - 1
          "الفائت"  -> tt . cycleNth grain $ - 1
          "الفايت"  -> tt . cycleNth grain $ - 1
          _         -> Nothing
        _ -> Nothing
    }
  
  ruleCycleTheAfterBeforeTime :: Rule
  ruleCycleTheAfterBeforeTime = Rule
    { name = "the <cycle> after|before <time>"
    , pattern =
      [ regex "ال"
      , dimension TimeGrain
      , regex "(الذ?ي |ال)(قبل|بعد)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:
         Token TimeGrain grain:
         Token RegexMatch (GroupMatch (_:match:_)):
         Token Time td:
         _) -> case Text.toLower match of
          "بعد" -> tt $ cycleNthAfter False grain 1 td
          _     -> tt $ cycleNthAfter False grain (-1) td
        _ -> Nothing
    }
  
  ruleCycleAfterBeforeTime :: Rule
  ruleCycleAfterBeforeTime = Rule
    { name = "<cycle> after|before <time>"
    , pattern =
      [ dimension TimeGrain
      , regex "(قبل|بعد)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (Token TimeGrain grain:
         Token RegexMatch (GroupMatch (match:_)):
         Token Time td:
         _) -> case Text.toLower match of
          "بعد" -> tt $ cycleNthAfter False grain 1 td
          _     -> tt $ cycleNthAfter False grain (-1) td
        _ -> Nothing
    }
  
  ruleCycleLastN :: Rule
  ruleCycleLastN = Rule
    { name = "last n <cycle>"
    , pattern =
      [ regex "([اآ]خر)"
      , Predicate $ isIntegerBetween 1 9999
      , dimension TimeGrain
      ]
    , prod = \tokens -> case tokens of
        (_:token:Token TimeGrain grain:_) -> do
          n <- getIntValue token
          tt . cycleN True grain $ - n
        _ -> Nothing
    }
  
  ruleCycleNextN :: Rule
  ruleCycleNextN = Rule
    { name = "<cycle> n next"
    , pattern =
      [ regex "ال"
      , dimension TimeGrain
      , regex "ال"
      , Predicate $ isIntegerBetween 1 9999
      , regex "(ال)(تالي|قادم)[ةه]?"
      ]
    , prod = \tokens -> case tokens of
        (_:Token TimeGrain grain:_:token:_) -> do
          n <- getIntValue token
          tt $ cycleN True grain n
        _ -> Nothing
    }
  
  ruleCycleOrdinalOfTime :: Rule
  ruleCycleOrdinalOfTime = Rule
    { name = "<ordinal> <cycle> of <time>"
    , pattern =
      [ dimension Ordinal
      , dimension TimeGrain
      , regex "في|من|بـ?"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (token:Token TimeGrain grain:_:Token Time td:_) -> do
          n <- getIntValue token
          tt $ cycleNthAfter True grain (n - 1) td
        _ -> Nothing
    }
  
  ruleCycleOrdinalDayOfTime :: Rule
  ruleCycleOrdinalDayOfTime = Rule
    { name = "<cycle> <ordinal> day? of <time>"
    , pattern =
      [ dimension TimeGrain
      , dimension Ordinal
      , regex "((ال)?يوم )?(بـ?|في|من)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (Token TimeGrain grain:token:_:Token Time td:_) -> do
          n <- getIntValue token
          tt $ cycleNthAfter True grain (n - 1) td
        _ -> Nothing
    }
  
  ruleTheCycleTheOrdinalOfTime :: Rule
  ruleTheCycleTheOrdinalOfTime = Rule
    { name = "the <cycle> the <ordinal> of <time>"
    , pattern =
      [ regex "ال"
      , dimension TimeGrain
      , dimension Ordinal
      , regex "في|من|بـ?"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token TimeGrain grain:token:_:Token Time td:_) -> do
          n <- getIntValue token
          tt $ cycleNthAfter True grain (n - 1) td
        _ -> Nothing
    }
  
  
  ruleCycleOrdinalAfterTime :: Rule
  ruleCycleOrdinalAfterTime = Rule
    { name = "<ordinal> <cycle> after <time>"
    , pattern =
      [ dimension Ordinal
      , dimension TimeGrain
      , regex "بعد"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (token:Token TimeGrain grain:_:Token Time td:_) -> do
          n <- getIntValue token
          tt $ cycleNthAfter True grain (n - 1) td
        _ -> Nothing
    }
  
  ruleDurationInWithinAfter :: Rule
  ruleDurationInWithinAfter = Rule
    { name = "in|within|after <duration>"
    , pattern =
      [ regex "(في|خلال|بعد)"
      , dimension Duration
      ]
    , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):
         Token Duration dd:
         _) -> case Text.toLower match of
           "خلال" -> Token Time <$> interval TTime.Open now (inDuration dd)
           "بعد" -> tt . withDirection TTime.After $ inDuration dd
           "في" -> tt $ inDuration dd
           _ -> Nothing
        _ -> Nothing
    }
  
  ruleDurationHenceAgo :: Rule
  ruleDurationHenceAgo = Rule
    { name = "<duration> hence|ago"
    , pattern =
      [ dimension Duration
      , regex "(مض[تى]|من الان)"
      ]
    , prod = \tokens -> case tokens of
        (Token Duration dd:
         Token RegexMatch (GroupMatch (match:_)):
         _) -> case Text.toLower match of
          "من الان" -> tt $ inDuration dd
          _        -> tt $ durationAgo dd
        _ -> Nothing
    }
  
  ruleBeforeDuration :: Rule
  ruleBeforeDuration = Rule
    { name = "before <duration>"
    , pattern =
      [ regex "من|قبل"
      , dimension Duration
      ]
    , prod = \case
        (_:Token Duration dd:_) ->
          tt $ durationAgo dd
        _ -> Nothing
    }
  
  ruleInNumeral :: Rule
  ruleInNumeral = Rule
    { name = "in <number> (implicit minutes)"
    , pattern =
      [ regex "في|خلال"
      , Predicate $ isIntegerBetween 0 60
      ]
    , prod = \tokens -> case tokens of
        (_:Token Numeral NumeralData{TNumeral.value = v}:_) ->
          tt . inDuration . duration TG.Minute $ floor v
        _ -> Nothing
    }
  
  ruleDurationAfterBeforeTime :: Rule
  ruleDurationAfterBeforeTime = Rule
    { name = "<duration> after|before|from <time>"
    , pattern =
      [ dimension Duration
      , regex "(من|قبل|بعد)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (Token Duration dd:
         Token RegexMatch (GroupMatch (match:_)):
         Token Time td:
         _) -> case Text.toLower match of
           "قبل" -> tt $ durationBefore dd td
           _     -> tt $ durationAfter dd td
        _ -> Nothing
    }
  
  ruleIntervalForDurationFrom :: Rule
  ruleIntervalForDurationFrom = Rule
    { name = "for <duration> from <time>"
    , pattern =
      [ regex "لمدة|ل"
      , dimension Duration
      , regex "(من بداي[ةه]|ابتداء ب|بداية من|من)"
      , dimension Time
      ]
    , prod = \tokens -> case tokens of
        (_:Token Duration dd:_:Token Time td1:_) ->
          Token Time <$> interval TTime.Open td1 (durationAfter dd td1)
        _ -> Nothing
  }
  
  ruleTimeForDuration :: Rule
  ruleTimeForDuration = Rule
    { name = "<time> for <duration>"
    , pattern =
      [ dimension Time
      , regex "لمدة|ل"
      , dimension Duration
      ]
    , prod = \tokens -> case tokens of
        (Token Time td1:_:Token Duration dd:_) ->
          Token Time <$> interval TTime.Open td1 (durationAfter dd td1)
        _ -> Nothing
  }
  
  ruleTimezone :: Rule
  ruleTimezone = Rule
    { name = "<time> timezone"
    , pattern =
      [ Predicate $ and . sequence [isNotLatent, isATimeOfDay]
      , regex "(بتوقيت |)?\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
      ]
    , prod = \tokens -> case tokens of
        (Token Time td:
         Token RegexMatch (GroupMatch (_:tz:_)):
         _) -> Token Time <$> inTimezone (Text.toUpper tz) td
        _ -> Nothing
    }
  
  rulePeriodicHolidays :: [Rule]
  rulePeriodicHolidays = mkRuleHolidays
    [ ( "عيد الميلاد" ,"عيد الميلاد", monthDay 12 25 )
    ]
  
  ruleComputedHolidays :: [Rule]
  ruleComputedHolidays = mkRuleHolidays
    [ ( "عيد الأضحى" ,"عيد الأضحى", eidalAdha )
    , ( "عيد الفطر" ,"عيد الفطر", eidalFitr )
    , ( "عيد الفصح" ,"عيد الفصح", easterSunday )
    , ( "رأس السنة الهجرية" ,"رأس السنة الهجرية", muharram )
    ]
  
  ruleComputedHolidays' :: [Rule]
  ruleComputedHolidays' = mkRuleHolidays'
    [ ( "رمضان", "رمضان"
      , let start = ramadan
            end = cycleNthAfter False TG.Day (-1) eidalFitr
          in interval TTime.Open start end )
    ]
  
  rules :: [Rule]
  rules =
    [ ruleIntersect
    , ruleIntersectOf
    , ruleAbsorbOnTime
    , ruleAbsorbOnADOW
    , ruleAbsorbInMonth
    , ruleAbsorbCommaTOD
    , ruleThisDOW
    , ruleDOWNext
    , ruleDOWLast
    , ruleThisTime
    , ruleNextTime
    , ruleLastTime
    , ruleLastTime2
    , ruleLastDOWOfTime
    , ruleDOWTheLastOfTime
    , ruleLastCycleOfTime
    , ruleCycleLastOfTime
    , ruleNthTimeOfTime
    , ruleTimeNthOfTime
    , ruleNthTimeAfterTime
    , ruleTimeNthAfterTime
    , ruleYear
    , ruleYearPastLatent
    , ruleYearFutureLatent
    , ruleYearInteger
    , ruleMonthsNumeral
    , ruleMonthsOrdinal
    , ruleDOMLatent
    , ruleNamedDOMOrdinal
    , ruleMonthDOMNumeral
    , ruleDOMMonth
    , ruleDOMOfMonth
    , ruleDOMOrdinalMonthYear
    , ruleDOMOrdinalDayMonthYear
    , ruleTODIntegerLatent
    , ruleTODOrdinalLatent
    , ruleAtHourTOD
    , ruleHODAndInteger
    , ruleHODAndIntegerMinutes
    , ruleHourTOD
    , ruleHHMM
    , ruleHHMMLatent
    , ruleHHMMSS
    , ruleTODAMPM
    , ruleHONumeral
    , ruleHODHalf
    , ruleHODThird
    , ruleHODQuarter
    , ruleThirdToHOD
    , ruleQuarterToHOD
    , ruleHODAndNumeralAfter
    , ruleNumeralAfterHOD
    , ruleHalfAfterHOD
    , ruleThirdAfterHOD
    , ruleQuarterAfterHOD
    , ruleDDMM
    , ruleDDMMYYYY
    , ruleYYYYMMDD
    , ruleMMYYYY
    , ruleNoonMidnightEOD
    , rulePartOfDays
    , ruleAfterPartOfDays
    , ruleBeforePartOfDays
    , ruleEarlyMorning
    , rulePODIn
    , rulePODThis
    , ruleTonight
    , ruleAfterPartofday
    , ruleTimePOD
    , ruleWeekend
    , ruleTODPrecision
    , rulePrecisionTOD
    , ruleIntervalFromMonthDDDD
    , ruleIntervalFromDDDDMonth
    , ruleIntervalFromDDDDInMonth
    , ruleIntervalMonthDDDD
    , ruleIntervalDDDDMonth
    , ruleIntervalDash
    , ruleIntervalFrom
    , ruleIntervalBetween
    , ruleIntervalTODDash
    , ruleIntervalTODFrom
    , ruleIntervalTODBetween
    , ruleIntervalBy
    , ruleIntervalUntilTOD
    , ruleIntervalAfterTOD
    , ruleIntervalSinceTOD
    , ruleThisLastNextCycle
    , ruleThisLastNextCycle2
    , ruleCycleThisLastNext
    , ruleCycleTheAfterBeforeTime
    , ruleCycleAfterBeforeTime
    , ruleCycleLastN
    , ruleCycleNextN
    , ruleTheCycleTheOrdinalOfTime
    , ruleCycleOrdinalOfTime
    , ruleCycleOrdinalDayOfTime
    , ruleCycleOrdinalAfterTime
    , ruleDurationInWithinAfter
    , ruleDurationHenceAgo
    , ruleBeforeDuration
    , ruleDurationAfterBeforeTime
    , ruleIntervalForDurationFrom
    , ruleTimeForDuration
    , ruleInNumeral
    , ruleTimezone
    , rulePartOfMonth
    , ruleDayOfMonth
    , ruleNow
    ]
  
    ++ ruleInstants
    ++ ruleDaysOfWeek
    ++ ruleMonths
    ++ ruleSeasons
    ++ ruleUSHolidays
    ++ rulePeriodicHolidays
    ++ ruleComputedHolidays
    ++ ruleComputedHolidays'
  