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.TR.Rules
    ( rules
    ) where
  
  import Prelude
  import qualified Data.Text as Text
  
  import Duckling.Dimensions.Types
  import Duckling.Duration.Helpers (isGrain)
  import Duckling.Numeral.Helpers (parseInt)
  import Duckling.Ordinal.Types (OrdinalData(..))
  import Duckling.Regex.Types (GroupMatch(..))
  import Duckling.Time.Computed
  import Duckling.Time.Helpers
  import Duckling.Time.Types (TimeData(..), TimeIntervalType (Closed, Open))
  import Duckling.Types
  import qualified Duckling.Ordinal.Types as TOrdinal
  import qualified Duckling.Time.Types as TTime
  import qualified Duckling.TimeGrain.Types as TG
  
  ruleInstants :: [Rule]
  ruleInstants = mkRuleInstants
    [ ( "now"             , TG.Second,  0, "ş?imdi|şu(\\s)?an"                                )
    , ( "today"           , TG.Day   ,  0, "bugün"                                                         )
    , ( "tomorrow"        , TG.Day   ,  1, "yarın"                                                         )
    , ( "yesterday"       , TG.Day   , -1, "dün"                                                           )
    , ( "after tomorrow"  , TG.Day   ,  2, "(yarından\\ssonraki)\\s?(gün)?"                                 )
    , ( "before yesterday", TG.Day   , -2, "(dün\\sdeğil\\sevvelsi|dünden\\sönceki|öbürsü|öbürki)\\s(gün)?")
    , ( "EOM|End of day",   TG.Day ,    1, "gün\\s(sonu(na|ndan?)?|bitimi(ne|nden?)?)?"                    )
    , ( "EOM|End of month", TG.Month ,  1, "ay\\s(sonu(na|ndan?)?|bitimi(ne|nden?)?)?"                     )
    , ( "EOY|End of year" , TG.Year  ,  1, "yıl\\s(sonu(na|ndan?)?|bitimi(ne|nden?)?)?"                    )
    ]
  
  ruleDaysOfWeek :: [Rule]
  ruleDaysOfWeek = mkRuleDaysOfWeek
    [ ( "Pazartesi", "pazartesi'?(si|den|ye)?|pzts?")
    , ( "Salı"     , "salı?'?(sı|dan|ya)?"          )
    , ( "Çarşamba" , "çar(şamba)?'?(sı|dan|ya)?"    )
    , ( "Perşembe" , "per(şembe)?'?(si|den|ye)?"    )
    , ( "Cuma"     , "cuma?'?(sı|dan|ya)?"          )
    , ( "Cumartesi", "cumartesi'?(si|den|ye)?|cmt"  )
    , ( "Pazar"    , "paz(ar)?'?(ı|dan|a)?"         )
    ]
  
  ruleMonths :: [Rule]
  ruleMonths = mkRuleMonths
    [ ( "Ocak"   , "ocak?'?(ğın|tan|ğ?a)?"    )
    , ( "Şubat"  , "şub(at)?'?(a|ın|tan)?"    )
    , ( "Mart"   , "mart?'?(ın|a|tan)?"       )
    , ( "Nisan"  , "nis(an)?'?(ın|a|dan)?"    )
    , ( "Mayıs"  , "may(ıs)?'?(ın|a|tan)?"    )
    , ( "Haziran", "haz(iran)?'?(ın|a|dan)?"  )
    , ( "Temmuz" , "tem(muz)?'?(un|a|dan)?"   )
    , ( "Ağustos", "ağu(stos)?'?(un|a|tan)?"  )
    , ( "Eylül"  , "eyl(ül)?'?(ün|e|den)?"    )
    , ( "Ekim"   , "ekim?'?(in|den|e)?"       )
    , ( "Kasım"  , "kas(ım)?'?(ın|dan|a)?"    )
    , ( "Aralık" , "ara(lık?)?'?(ğın|ğa|tan)?")
    ]
  
  ruleSeasons :: [Rule]
  ruleSeasons = mkRuleSeasons
    [ ( "yaz",      "yaz(ın|a|dan)?",      monthDay 6  21, monthDay 9  23 )
    , ( "sonbahar", "sonbahar(ın|a|dan)?", monthDay 9  23, monthDay 12 21 )
    , ( "kış",      "kış(ın|a|tan)?",      monthDay 12 21, monthDay 3  20 )
    , ( "ilkbahar", "ilkbahar(ın|a|dan)?", monthDay 3  20, monthDay 6  21 )
    ]
  
  ruleHolidays :: [Rule]
  ruleHolidays = mkRuleHolidays
    [ ( "Yılbaşı"                                , "yılbaşı(ndan|na)?|yılbaşı(\\statili(nden|ne))?"                   , monthDay 1   1  )
    , ( "Ulusal Egemenlik ve Çocuk Bayramı"      , "(ulusal\\segemenlik\\sve\\s)?çocuk\\sbayramı"                     , monthDay 4   23 )
    , ( "Emek ve Dayanışma Günü"                 , "emek\\sve\\sdayanışma\\sgünü"                                     , monthDay 5   1  )
    , ( "Atatürk’ü Anma, Gençlik ve Spor Bayramı", "(gençlik\\sve\\sspor|spor|gençlik)\\sbayramı"                     , monthDay 5   19 )
    , ( "Zafer Bayramı"                          , "zafer\\sbayramı"                                                  , monthDay 8   30 )
    , ( "Cumhuriyet Bayramı"                     , "cumhuriyet\\sbayramı"                                             , monthDay 10  29 )
    ]
  
  ruleComputedHolidays:: [Rule]
  ruleComputedHolidays = mkRuleHolidays
    [ ("Kurban Bayramı",  "kurban\\sbayramı",  eidalAdha)
    , ("Ramazan Bayramı", "ramazan\\sbayramı", eidalFitr)
    ]
  
  rulePartOfDays :: Rule
  rulePartOfDays = Rule
    { name = "part of days"
    , pattern =
      [ regex "(sabah(ı|a|tan)?|öğlen?|akşam|gece|öğle\\syemeği)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):_) -> do
          let (start, end) = case Text.toLower match of
                "sabah"          -> (hour False 0, hour False 12 )
                "sabahı"         -> (hour False 0, hour False 12 )
                "sabaha"         -> (hour False 0, hour False 12 )
                "sabahtan"       -> (hour False 0, hour False 12 )
                "akşam"          -> (hour False 18, hour False 0 )
                "gece"           -> (hour False 18, hour False 0 )
                "öğlen"          -> (hour False 12, hour False 14)
                "öğle"           -> (hour False 12, hour False 14)
                "öğle yemeği"    -> (hour False 12, hour False 14)
                _                -> (hour False 12, hour False 19)
          td <- interval TTime.Open start end
          tt $ partOfDay $ mkLatent td
        _ -> Nothing
    }
  
  rulePrecisionTOD :: Rule
  rulePrecisionTOD = Rule
    { name = "about|exactly <time-of-day>"
    , pattern =
      [ Predicate $ isGrainFinerThan TG.Year
      , regex "gibi|civarı(nda)?"
      ]
    , prod = \case
        (Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  rulePrecisionTOD2 :: Rule
  rulePrecisionTOD2 = Rule
    { name = "about|exactly <time-of-day>"
    , pattern =
      [ regex "yaklaşık|tam(\\solarak)?"
      , Predicate $ isGrainFinerThan TG.Year
      ]
    , prod = \case
        (_:Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  ruleDatetimeDatetimeInterval :: Rule
  ruleDatetimeDatetimeInterval = Rule
    { name = "<datetime> - <datetime> (interval)"
    , pattern =
      [ Predicate isNotLatent
      , regex "\\-"
      , Predicate isNotLatent
      ]
    , prod = \case
        (Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleDateDateInterval :: Rule
  ruleDateDateInterval = Rule
    { name = "dd.(mm.)? - dd.mm.(yy[yy]?)? (interval)"
    , pattern =
      [ regex "(?:с\\s+)?(10|20|30|31|[012]?[1-9])\\.?((?<=\\.)(?:10|11|12|0?[1-9])(?:\\.?))?"
      , regex "\\-|/"
      , regex "(10|20|30|31|[012]?[1-9])\\.(10|11|12|0?[1-9])\\.?((?<=\\.)\\d{2,4})?"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (d1:"":_)):
         _:
         Token RegexMatch (GroupMatch (d2:m2:"":_)):
         _) -> do
            d1 <- parseInt d1
            d2 <- parseInt d2
            m2 <- parseInt m2
            Token Time <$> interval TTime.Closed (monthDay m2 d1) (monthDay m2 d2)
        (Token RegexMatch (GroupMatch (d1:"":_)):
         _:
         Token RegexMatch (GroupMatch (d2:m2:y:_)):
         _) -> do
            d1 <- parseInt d1
            d2 <- parseInt d2
            m2 <- parseInt m2
            y <- parseInt y
            Token Time <$> interval TTime.Closed (yearMonthDay y m2 d1) (yearMonthDay y m2 d2)
        (Token RegexMatch (GroupMatch (d1:m1:_)):
         _:
         Token RegexMatch (GroupMatch (d2:m2:"":_)):
         _) -> do
            d1 <- parseInt d1
            d2 <- parseInt d2
            m1 <- parseInt m1
            m2 <- parseInt m2
            Token Time <$> interval TTime.Closed (monthDay m1 d1) (monthDay m2 d2)
        (Token RegexMatch (GroupMatch (d1:m1:_)):
         _:
         Token RegexMatch (GroupMatch (d2:m2:y:_)):
         _) -> do
            d1 <- parseInt d1
            d2 <- parseInt d2
            m1 <- parseInt m1
            m2 <- parseInt m2
            y <- parseInt y
            Token Time <$> interval TTime.Closed (yearMonthDay y m1 d1) (yearMonthDay y m2 d2)
        _ -> Nothing
    }
  
  ruleInDuration :: Rule
  ruleInDuration = Rule
    { name = "in <duration>"
    , pattern =
      [ dimension Duration
      , regex "içinde|içerisinde"
      ]
    , prod = \case
        (Token Duration dd:_) ->
          tt $ inDuration dd
        _ -> Nothing
    }
  
  ruleDurationHence :: Rule
  ruleDurationHence = Rule
    { name = "<duration> hence"
    , pattern =
      [ dimension Duration
      , regex "sonra"
      ]
    , prod = \case
        (Token Duration dd:_) ->
          tt $ inDuration dd
        _ -> Nothing
    }
  
  ruleDurationFromNow :: Rule
  ruleDurationFromNow = Rule
    { name = "<duration> from now"
    , pattern =
      [ regex "(bugünden\\s)?sonra(ki)?"
      , dimension Duration
      ]
    , prod = \case
        (_:Token Duration dd:_) ->
          tt $ inDuration dd
        _ -> Nothing
    }
  
  
  ruleLastCycleOfTime :: Rule
  ruleLastCycleOfTime = Rule
    { name = "last <cycle> of <time>"
    , pattern =
      [ dimension Time
      , regex "son"
      , dimension TimeGrain
      ]
    , prod = \case
        (Token Time td:_:Token TimeGrain grain:_) ->
          tt $ cycleLastOf grain td
        _ -> Nothing
    }
  
  ruleYearLatent2 :: Rule
  ruleYearLatent2 = Rule
    { name = "year (latent)"
    , pattern =
      [ Predicate $ isIntegerBetween 2101 10000
      ]
    , prod = \case
        (token:_) -> do
          v <- getIntValue token
          tt $ mkLatent $ year v
        _ -> Nothing
    }
  
  ruleNoon :: Rule
  ruleNoon = Rule
    { name = "noon"
    , pattern =
      [ regex "öğle(n|den|ye)?"
      ]
    , prod = \_ -> tt $ hour False 12
    }
  
  ruleThisnextDayofweek :: Rule
  ruleThisnextDayofweek = Rule
    { name = "this|next <day-of-week>"
    , pattern =
      [ regex "bu|sonraki"
      , Predicate isADayOfWeek
      ]
    , prod = \case
        (_:Token Time td:_) ->
          tt $ predNth 0 True td
        _ -> Nothing
    }
  
  ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
  ruleBetweenTimeofdayAndTimeofdayInterval = Rule
    { name = "between <time-of-day> and <time-of-day> (interval)"
    , pattern =
      [ Predicate isATimeOfDay
      , regex "ile"
      , Predicate isATimeOfDay
      , regex "arası"
      ]
    , prod = \case
        (_:Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleNextCycle :: Rule
  ruleNextCycle = Rule
    { name = "next <cycle>"
    , pattern =
      [ regex "sonraki|önümüzdeki|gelecek"
      , dimension TimeGrain
      ]
    , prod = \case
        (_:Token TimeGrain grain:_) ->
          tt $ cycleNth grain 1
        _ -> Nothing
    }
  
  ruleLastCycle :: Rule
  ruleLastCycle = Rule
    { name = "last <cycle>"
    , pattern =
      [ regex "son|geçen|geçtiğimiz|önceki"
      , dimension TimeGrain
      ]
    , prod = \case
        (_:Token TimeGrain grain:_) ->
          tt $ cycleNth grain (-1)
        _ -> Nothing
    }
  
  ruleLunch :: Rule
  ruleLunch = Rule
    { name = "lunch"
    , pattern =
      [ regex "öğlen? (yemeği|arası)"
      ]
    , prod = \_ ->
        let from = hour False 12
            to = hour False 14
        in Token Time . mkLatent . partOfDay <$>
             interval TTime.Open from to
    }
  
  ruleAfternoon :: Rule
  ruleAfternoon = Rule
    { name = "afternoon"
    , pattern =
      [ regex "öğleden sonra"
      ]
    , prod = \_ ->
        let from = hour False 12
            to = hour False 19
        in Token Time . mkLatent . partOfDay <$>
             interval TTime.Open from to
    }
  
  ruleEvening :: Rule
  ruleEvening = Rule
    { name = "evening"
    , pattern =
      [ regex "akşam(a|dan)?"
      ]
    , prod = \_ ->
        let from = hour False 18
            to = hour False 0
        in Token Time . mkLatent . partOfDay <$>
             interval TTime.Open from to
    }
  
  ruleHourofdayIntegerAsRelativeMinutes :: Rule
  ruleHourofdayIntegerAsRelativeMinutes = Rule
    { name = "<hour-of-day> <integer> (as relative minutes)"
    , pattern =
      [ Predicate $ and . sequence [isNotLatent, isAnHourOfDay]
      , Predicate $ isIntegerBetween 1 59
      ]
    , prod = \case
        (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
         token:
         _) -> do
          n <- getIntValue token
          tt $ hourMinute is12H hours n
        _ -> Nothing
    }
  
  ruleDayofmonthordinalNamedmonth :: Rule
  ruleDayofmonthordinalNamedmonth = Rule
    { name = "<day-of-month>(ordinal) <named-month>"
    , pattern =
      [ Predicate isDOMOrdinal
      , Predicate isAMonth
      ]
    , prod = \case
        (token:Token Time td:_) -> Token Time <$> intersectDOM td token
        _ -> Nothing
    }
  
  ruleIntervalBy :: Rule
  ruleIntervalBy = Rule
    { name = "by <time>"
    , pattern =
      [ dimension Time
      ,  regex "kadar"
      ]
    , prod = \case
        (Token Time td:_) -> Token Time <$> interval TTime.Open now td
        _ -> Nothing
    }
  
  ruleIntersectBy :: Rule
  ruleIntersectBy = Rule
    { name = "intersect by ','"
    , pattern =
      [ Predicate isNotLatent
      , regex ","
      , Predicate isNotLatent
      ]
    , prod = \case
        (Token Time td1:_:Token Time td2:_) ->
          Token Time <$> intersect td1 td2
        _ -> Nothing
    }
  
  ruleNthTimeAfterTime :: Rule
  ruleNthTimeAfterTime = Rule
    { name = "nth <time> after <time>"
    , pattern =
      [ dimension Time
      , regex "sonra(ki)?"
      , dimension Ordinal
      , dimension Time
      ]
    , prod = \case
        (Token Time td1:
         _:
         Token Ordinal OrdinalData{TOrdinal.value = v}:
         Token Time td2:
         _) -> tt $ predNthAfter (v - 1) td1 td2
        _ -> Nothing
    }
  
  ruleMMDD :: Rule
  ruleMMDD = Rule
    { name = "mm/dd"
    , pattern =
      [ regex "([012]?[1-9]|10|20|30|31)\\.(10|11|12|0?[1-9])\\.?"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
          d <- parseInt m1
          m <- parseInt m2
          tt $ monthDay m d
        _ -> Nothing
    }
  
  ruleMMDDYYYY :: Rule
  ruleMMDDYYYY = Rule
    { name = "mm/dd/yyyy"
    , pattern =
      [ regex "(1[0-2]|0?[1-9])[-/\\s](3[01]|[12]\\d|0?[1-9])[-/\\s](\\d{2,4})"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (mm:dd:yy:_)):_) -> do
          y <- parseInt yy
          m <- parseInt mm
          d <- parseInt dd
          tt $ yearMonthDay y m d
        _ -> Nothing
    }
  
  ruleTimeofdayLatent :: Rule
  ruleTimeofdayLatent = Rule
    { name = "time-of-day (latent)"
    , pattern =
      [ Predicate $ isIntegerBetween 0 23
      ]
    , prod = \case
        (token:_) -> do
          n <- getIntValue token
          tt $ mkLatent $ hour (n < 12) n
        _ -> Nothing
    }
  
  ruleDurationAgo :: Rule
  ruleDurationAgo = Rule
    { name = "<duration> ago"
    , pattern =
      [ dimension Duration
      , regex "önce"
      ]
    , prod = \case
        (Token Duration dd:_:_) ->
          tt $ durationAgo dd
        _ -> Nothing
    }
  
  ruleIdesOfMonth :: Rule
  ruleIdesOfMonth = Rule
    { name = "the ides of <named-month>"
    , pattern =
      [ Predicate isAMonth
      , regex "ortası"
      ]
    , prod = \case
        (Token Time td@TimeData {TTime.form = Just (TTime.Month m)}:_) ->
          Token Time <$>
            intersect td (dayOfMonth $ if elem m [3, 5, 7, 10] then 15 else 13)
        _ -> Nothing
    }
  
  ruleLastNCycle :: Rule
  ruleLastNCycle = Rule
    { name = "last n <cycle>"
    , pattern =
      [ regex "son|geçen|geçtiğimiz|önceki"
      , Predicate $ isIntegerBetween 1 9999
      , dimension TimeGrain
      ]
    , prod = \case
        (_:token:Token TimeGrain grain:_) -> do
          n <- getIntValue token
          tt $ cycleN True grain (- n)
        _ -> Nothing
    }
  
  ruleMidnighteodendOfDay :: Rule
  ruleMidnighteodendOfDay = Rule
    { name = "midnight|EOD|end of day"
    , pattern =
      [ regex "gece\\syarısı|gün\\ssonu|gün\\sbitimi"
      ]
    , prod = \_ -> tt $ hour False 0
    }
  
  ruleDayofmonthNonOrdinalNamedmonth :: Rule
  ruleDayofmonthNonOrdinalNamedmonth = Rule
    { name = "<day-of-month> (non ordinal) <named-month>"
    , pattern =
      [ Predicate isDOMInteger
      , Predicate isAMonth
      ]
    , prod = \case
        (token:Token Time td:_) -> Token Time <$> intersectDOM td token
        _ -> Nothing
    }
  
  ruleIntersect :: Rule
  ruleIntersect = Rule
    { name = "intersect"
    , pattern =
      [ Predicate isNotLatent
      , Predicate isNotLatent
      ]
    , prod = \case
        (Token Time td1:Token Time td2:_) ->
          Token Time <$> intersect td1 td2
        _ -> Nothing
    }
  
  ruleUntilTimeofday :: Rule
  ruleUntilTimeofday = Rule
    { name = "until <time-of-day>"
    , pattern =
      [ dimension Time
      , regex "kadar"
      ]
    , prod = \case
        (_:Token Time td:_) -> tt $ withDirection TTime.Before td
        _ -> Nothing
    }
  
  ruleAtTimeofday :: Rule
  ruleAtTimeofday = Rule
    { name = "at <time-of-day>"
    , pattern =
      [ regex "saat"
      , Predicate isATimeOfDay
      ]
    , prod = \case
        (_:Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  ruleAtTimeofday2 :: Rule
  ruleAtTimeofday2 = Rule
    { name = "at <time-of-day>"
    , pattern =
      [ regex "saat"
      , Predicate isATimeOfDay
      , regex "'?(den|dan)?"
      ]
    , prod = \case
        (_:Token Time td:_) -> tt $ notLatent td
        _ -> Nothing
    }
  
  ruleNthTimeOfTime :: Rule
  ruleNthTimeOfTime = Rule
    { name = "nth <time> of <time>"
    , pattern =
      [ dimension Time
      , dimension Ordinal
      , dimension Time
      ]
    , prod = \case
        (Token Time td1:
         Token Ordinal OrdinalData{TOrdinal.value = v}:
         Token Time td2:
         _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
        _ -> Nothing
    }
  
  ruleTimePartofday :: Rule
  ruleTimePartofday = Rule
    { name = "<time> <part-of-day>"
    , pattern =
      [ dimension Time
      , Predicate isAPartOfDay
      ]
    , prod = \case
        (Token Time td1:Token Time td2:_) ->
          Token Time <$> intersect td1 td2
        _ -> Nothing
    }
  
  ruleWeek :: Rule
  ruleWeek = Rule
   { name = "week"
   , pattern =
     [ regex "((bu\\s)?hafta boyunca)|(bu hafta)|(haftanın geri kalanı)"
     ]
   , prod = \case
       (Token RegexMatch (GroupMatch (match:_)):_) ->
         let end = cycleNthAfter True TG.Day (-2) $ cycleNth TG.Week 1
             period = case Text.toLower match of
                        "hafta boyunca" -> interval Closed (cycleNth TG.Week 0) end
                        "haftanın geri kalanı" -> interval Open today end
                        "bu hafta" -> interval Open today end
                        _ -> Nothing
         in case Text.toLower match of
           "bu hafta" -> Token Time . mkLatent <$> period
           _ -> Token Time <$> period
       _ -> Nothing
   }
  
  
  ruleWeekend :: Rule
  ruleWeekend = Rule
    { name = "week-end"
    , pattern =
      [ regex "hafta sonu"
      ]
    , prod = \_ -> tt $ mkOkForThisNext weekend
    }
  
  ruleOrdinalQuarterYear :: Rule
  ruleOrdinalQuarterYear = Rule
    { name = "<ordinal> quarter <year>"
    , pattern =
      [ dimension Ordinal
      , Predicate $ isGrain TG.Quarter
      , dimension Time
      ]
    , prod = \case
        (Token Ordinal od:_:Token Time td:_) ->
          tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
        _ -> Nothing
    }
  
  ruleYYYYMMDD :: Rule
  ruleYYYYMMDD = Rule
    { name = "yyyy-mm-dd"
    , pattern =
      [ regex "(\\d{2,4})-(0?[1-9]|10|11|12)-([012]?[1-9]|10|20|30|31)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
          y <- parseInt m1
          m <- parseInt m2
          d <- parseInt m3
          tt $ yearMonthDay y m d
        _ -> Nothing
    }
  
  ruleNextNCycle :: Rule
  ruleNextNCycle = Rule
    { name = "next n <cycle>"
    , pattern =
      [ regex "önümüzdeki|sonraki"
      , Predicate $ isIntegerBetween 1 9999
      , dimension TimeGrain
      ]
    , prod = \case
        (_:token:Token TimeGrain grain:_) -> do
          v <- getIntValue token
          tt $ cycleN True grain v
        _ -> Nothing
    }
  
  ruleThisPartofday :: Rule
  ruleThisPartofday = Rule
    { name = "this <part-of-day>"
    , pattern =
      [ regex "bu"
      , Predicate isAPartOfDay
      ]
    , prod = \case
        (_:Token Time td:_) -> Token Time . partOfDay <$> intersect today td
        _ -> Nothing
    }
  
  ruleThisCycle :: Rule
  ruleThisCycle = Rule
    { name = "this <cycle>"
    , pattern =
      [ regex "bu"
      , dimension TimeGrain
      ]
    , prod = \case
        (_:Token TimeGrain grain:_) ->
          tt $ cycleNth grain 0
        _ -> Nothing
    }
  
  ruleThisTime :: Rule
  ruleThisTime = Rule
    { name = "this <time>"
    , pattern =
      [ regex "bu"
      , Predicate isOkWithThisNext
      ]
    , prod = \case
        (_:Token Time td:_) -> tt $ predNth 0 False td
        _ -> Nothing
    }
  
  ruleNextTime :: Rule
  ruleNextTime = Rule
    { name = "next <time>"
    , pattern =
      [ regex "önümüzdeki|gelecek|sonraki"
      , Predicate isOkWithThisNext
      ]
    , prod = \case
        (_:Token Time td:_) -> tt $ predNth 0 True td
        _ -> Nothing
    }
  
  ruleLastTime :: Rule
  ruleLastTime = Rule
    { name = "last <time>"
    , pattern =
      [ regex "geçen|önceki|geçtiğimiz|son"
      , Predicate isOkWithThisNext
      ]
    , prod = \case
        (_:Token Time td:_) -> tt $ predNth (- 1) False td
        _ -> Nothing
    }
  
  ruleYearLatent :: Rule
  ruleYearLatent = Rule
    { name = "year (latent)"
    , pattern =
      [ Predicate $ isIntegerBetween 25 999
      ]
    , prod = \case
        (token:_) -> do
          y <- getIntValue token
          tt $ mkLatent $ year y
        _ -> Nothing
    }
  
  ruleAfterTimeofday :: Rule
  ruleAfterTimeofday = Rule
    { name = "after <time-of-day>"
    , pattern =
      [ regex "sonraki|önümüzdeki"
      , Predicate isATimeOfDay
      ]
    , prod = \case
        (_:Token Time td:_) -> tt $ withDirection TTime.After td
        _ -> Nothing
    }
  
  ruleNight :: Rule
  ruleNight = Rule
    { name = "night"
    , pattern =
      [ regex "gece(ye|den)?"
      ]
    , prod = \_ ->
        let from = hour False 0
            to = hour False 4
        in Token Time . mkLatent . partOfDay <$>
             interval TTime.Open from to
    }
  
  ruleDayofmonthOrdinal :: Rule
  ruleDayofmonthOrdinal = Rule
    { name = "<day-of-month> (ordinal)"
    , pattern =
      [ Predicate isDOMOrdinal
      ]
    , prod = \case
        (Token Ordinal OrdinalData{TOrdinal.value = v}:_) ->
          tt $ dayOfMonth v
        _ -> Nothing
    }
  
  ruleOrdinalCycleOfTime :: Rule
  ruleOrdinalCycleOfTime = Rule
    { name = "<ordinal> <cycle> of <time>"
    , pattern =
      [ dimension Time
      , dimension Ordinal
      , dimension TimeGrain
      ]
    , prod = \case
        (Token Time td:Token Ordinal od:Token TimeGrain grain:_) ->
          tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
        _ -> Nothing
    }
  
  ruleHhmm :: Rule
  ruleHhmm = Rule
    { name = "hh:mm"
    , pattern =
      [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.ч]([0-5]\\d)(?:час(ов|а|у)?|ч)?"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
          h <- parseInt m1
          m <- parseInt m2
          tt $ hourMinute False h m
        _ -> Nothing
    }
  
  ruleYear :: Rule
  ruleYear = Rule
    { name = "year"
    , pattern =
      [ Predicate $ isIntegerBetween 1000 2100
      ]
    , prod = \case
        (token:_) -> do
          y <- getIntValue token
          tt $ year y
        _ -> Nothing
    }
  
  ruleNamedmonthDayofmonthNonOrdinal :: Rule
  ruleNamedmonthDayofmonthNonOrdinal = Rule
    { name = "<named-month> <day-of-month> (non ordinal)"
    , pattern =
      [ Predicate isAMonth
      , Predicate isDOMInteger
      ]
    , prod = \case
        (Token Time td:token:_) -> Token Time <$> intersectDOM td token
        _ -> Nothing
    }
  
  ruleHhmmMilitary :: Rule
  ruleHhmmMilitary = Rule
    { name = "hhmm (military)"
    , pattern =
      [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (h:m:_)):_) -> do
          hh <- parseInt h
          mm <- parseInt m
          tt $ mkLatent $ hourMinute False hh mm
        _ -> Nothing
    }
  
  ruleAbsorptionOfAfterNamedDay :: Rule
  ruleAbsorptionOfAfterNamedDay = Rule
    { name = "absorption of , after named day"
    , pattern =
      [ Predicate isADayOfWeek
      , regex ","
      ]
    , prod = \case
        (x:_) -> Just x
        _ -> Nothing
    }
  
  ruleLastDayofweekOfTime :: Rule
  ruleLastDayofweekOfTime = Rule
    { name = "last <day-of-week> of <time>"
    , pattern =
      [ dimension Time
      , regex "son"
      , Predicate isADayOfWeek
      ]
    , prod = \case
        (
          Token Time td1:
          _:
          Token Time td2:
          _) -> tt $ predLastOf td1 td2
        _ -> Nothing
    }
  
  ruleTimeofdayTimeofdayInterval :: Rule
  ruleTimeofdayTimeofdayInterval = Rule
    { name = "<time-of-day> - <time-of-day> (interval)"
    , pattern =
      [ Predicate $ and . sequence [isNotLatent, isATimeOfDay]
      , regex "\\-"
      , Predicate isATimeOfDay
      ]
    , prod = \case
        (Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleTimeofdayTimeofdayInterval2 :: Rule
  ruleTimeofdayTimeofdayInterval2 = Rule
    { name = "<time-of-day> - <time-of-day> (interval)"
    , pattern =
      [ Predicate isATimeOfDay
      , regex "\\-|/"
      , Predicate $ and . sequence [isNotLatent, isATimeOfDay]
      ]
    , prod = \case
        (Token Time td1:_:Token Time td2:_) ->
          Token Time <$> interval TTime.Closed td1 td2
        _ -> Nothing
    }
  
  ruleIntervalForDurationFrom :: Rule
  ruleIntervalForDurationFrom = Rule
    { name = "for <duration> from <time>"
    , pattern =
      [ dimension Time
      , regex "itibaren"
      , dimension Duration
      , regex "boyunca|süresince"
      ]
    , prod = \case
        (Token Time td1:_:Token Duration dd:_) ->
          Token Time <$> interval TTime.Closed td1 (durationAfter dd td1)
        _ -> Nothing
  }
  
  ruleDurationAfterTime :: Rule
  ruleDurationAfterTime = Rule
    { name = "<duration> after <time>"
    , pattern =
      [ dimension Time
      , regex "sonra(ki)?"
      , dimension Duration
      ]
    , prod = \case
        (Token Time td:_:Token Duration dd:_) ->
          tt $ durationAfter dd td
        _ -> Nothing
    }
  
  ruleDurationAfterTime2 :: Rule
  ruleDurationAfterTime2 = Rule
    { name = "<duration> after <time>"
    , pattern =
      [ dimension Time
      , dimension Duration
      , regex "sonra"
      ]
    , prod = \case
        (Token Time td:Token Duration dd:_:_) ->
          tt $ durationAfter dd td
        _ -> Nothing
    }
  
  ruleOrdinalQuarter :: Rule
  ruleOrdinalQuarter = Rule
    { name = "<ordinal> quarter"
    , pattern =
      [ dimension Ordinal
      , Predicate $ isGrain TG.Quarter
      ]
    , prod = \case
        (Token Ordinal OrdinalData{TOrdinal.value = v}:_) ->
          tt $ cycleNthAfter False TG.Quarter (v - 1) $ cycleNth TG.Year 0
        _ -> Nothing
    }
  
  ruleDurationBeforeTime :: Rule
  ruleDurationBeforeTime = Rule
    { name = "<duration> before <time>"
    , pattern =
      [ dimension Time
      , regex "önceki"
      , dimension Duration
      ]
    , prod = \case
        (Token Time td:_:Token Duration dd:_) ->
          tt $ durationBefore dd td
        _ -> Nothing
    }
  
  rulePartofdayOfTime :: Rule
  rulePartofdayOfTime = Rule
    { name = "<part-of-day> of <time>"
    , pattern =
      [ Predicate isAPartOfDay
      , dimension Time
      ]
    , prod = \case
        (Token Time td1:Token Time td2:_) ->
          Token Time <$> intersect td1 td2
        _ -> 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 = \case
        (Token Time td:
         Token RegexMatch (GroupMatch (tz:_)):
         _) -> Token Time <$> inTimezone (Text.toUpper tz) td
        _ -> Nothing
    }
  
  rules :: [Rule]
  rules =
    [ ruleAbsorptionOfAfterNamedDay
    , ruleAfterTimeofday
    , ruleAfternoon
    , ruleAtTimeofday
    , ruleAtTimeofday2
    , ruleBetweenTimeofdayAndTimeofdayInterval
    , ruleDatetimeDatetimeInterval
    , ruleDateDateInterval
    , ruleDayofmonthNonOrdinalNamedmonth
    , ruleDayofmonthOrdinal
    , ruleDayofmonthordinalNamedmonth
    , ruleDurationAfterTime
    , ruleDurationAfterTime2
    , ruleIntervalForDurationFrom
    , ruleDurationAgo
    , ruleDurationBeforeTime
    , ruleEvening
    , ruleHhmm
    , ruleHhmmMilitary
    , ruleHourofdayIntegerAsRelativeMinutes
    , ruleIdesOfMonth
    , ruleInDuration
    , ruleIntervalBy
    , ruleDurationHence
    , ruleDurationFromNow
    , ruleIntersect
    , ruleIntersectBy
    , ruleLastCycle
    , ruleLastCycleOfTime
    , ruleLastDayofweekOfTime
    , ruleLastNCycle
    , ruleLastTime
    , ruleThisTime
    , ruleNextTime
    , ruleLunch
    , ruleMidnighteodendOfDay
    , ruleMMDD
    , ruleMMDDYYYY
    , ruleYYYYMMDD
    , ruleNamedmonthDayofmonthNonOrdinal
    , ruleNextCycle
    , ruleNextNCycle
    , ruleNight
    , ruleNoon
    , ruleNthTimeAfterTime
    , ruleNthTimeOfTime
    , ruleOrdinalCycleOfTime
    , ruleOrdinalQuarter
    , ruleOrdinalQuarterYear
    , rulePartOfDays
    , rulePartofdayOfTime
    , rulePrecisionTOD
    , rulePrecisionTOD2
    , ruleThisCycle
    , ruleThisPartofday
    , ruleThisTime
    , ruleThisnextDayofweek
    , ruleTimePartofday
    , ruleTimeofdayLatent
    , ruleTimeofdayTimeofdayInterval
    , ruleTimeofdayTimeofdayInterval2
    , ruleUntilTimeofday
    , ruleWeek
    , ruleWeekend
    , ruleYear
    , ruleYearLatent
    , ruleYearLatent2
    , ruleTimezone
    ]
    ++ ruleInstants
    ++ ruleDaysOfWeek
    ++ ruleMonths
    ++ ruleSeasons
    ++ ruleHolidays
    ++ ruleComputedHolidays
  