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.Numeral.KA.Rules
    ( rules
    ) where
  
  import Control.Applicative ((<|>))
  import Data.HashMap.Strict (HashMap)
  import Data.Maybe
  import Data.String
  import Data.Text (Text)
  import Prelude
  import qualified Data.HashMap.Strict as HashMap
  import qualified Data.Text as Text
  
  import Duckling.Dimensions.Types
  import Duckling.Numeral.Helpers
  import Duckling.Numeral.Types (NumeralData (..))
  import Duckling.Regex.Types
  import Duckling.Types
  import qualified Duckling.Numeral.Types as TNumeral
  
  zeroNineteenMap :: HashMap Text Integer
  zeroNineteenMap = HashMap.fromList
    [ ( "ნოლ"       , 0  )
    , ( "ნულ"       , 0  )
    , ( "ნული"      , 0  )
    , ( "ნოლ"       , 0  )
    , ( "ნოლი"      , 0  )
    , ( "ერთი"      , 1  )
    , ( "ორი"       , 2  )
    , ( "ორ"        , 2  )
    , ( "სამი"      , 3  )
    , ( "სამ"       , 3  )
    , ( "ოთხი"      , 4  )
    , ( "ოთხ"       , 4  )
    , ( "ხუთი"      , 5  )
    , ( "ხუთ"       , 5  )
    , ( "ექვსი"     , 6  )
    , ( "ექვს"      , 6  )
    , ( "შვიდი"     , 7  )
    , ( "შვიდ"      , 7  )
    , ( "რვა"       , 8  )
    , ( "რვ"        , 8  )
    , ( "ცხრა"      , 9  )
    , ( "ცხრ"       , 9  )
    , ( "ათი"       , 10 )
    , ( "აათი"      , 10 )
    , ( "თერთმეტი"  , 11 )
    , ( "თერთმეტ"   , 11 )
    , ( "თორმეტი"   , 12 )
    , ( "თორმეტ"    , 12 )
    , ( "ცამეტი"    , 13 )
    , ( "ცამეტ"     , 13 )
    , ( "თოთხმეტი"  , 14 )
    , ( "თოთხმეტ"   , 14 )
    , ( "თხუთმეტი"  , 15 )
    , ( "თხუთმეტ"   , 15 )
    , ( "თექვსმეტი" , 16 )
    , ( "თექვსმეტ"  , 16 )
    , ( "ჩვიდმეტი"  , 17 )
    , ( "ჩვიდმეტ"   , 17 )
    , ( "თვრამეტი"  , 18 )
    , ( "თვრამეტ"   , 18 )
    , ( "ცხრამეტი"  , 19 )
    , ( "ცხრამეტ"   , 19 )
    ]
  
  informalMap :: HashMap Text Integer
  informalMap = HashMap.fromList
    [ ( "ერთი"       , 1 )
    , ( "წყვილი"     , 2 )
    , ( "წყვილები"   , 2 )
    , ( "ცოტა"       , 3 )
    , ( "რამდენიმე"  , 3 )
    , ( "რამოდენიმე" , 3 )
    ]
  
  ruleToNineteen :: Rule
  ruleToNineteen = Rule
    { name = "integer (0..19)"
    , pattern =
      [ regex "(წყვილ(ებ)?ი|ცოტა|რამდენიმე|რამოდენიმე|ნოლი?|ნული?|ერთი|ორი?|სამი?|ოთხი?|ხუთი?|ექვსი?|შვიდი?|რვა|თერთმეტი?|თორმეტი?|ცამეტი?|თოთხმეტი?|თხუთმეტი?|თექვსმეტი?|ჩვიდმეტი?|თვრამეტი?|ცხრამეტი?|ცხრა|ა?ათი)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):_) ->
          let x = Text.toLower match in
          (HashMap.lookup x zeroNineteenMap >>= integer) <|>
          (HashMap.lookup x informalMap >>= integer >>= notOkForAnyTime)
        _ -> Nothing
    }
  
  tensMap :: HashMap Text Integer
  tensMap = HashMap.fromList
    [ ( "ოცი"         , 20 )
    , ( "ოცდა"        , 20 )
    , ( "ოც"          , 20 )
    , ( "ოცდაათ"      , 30 )
    , ( "ოცდაათი"     , 30 )
    , ( "ორმოც"       , 40 )
    , ( "ორმოცი"      , 40 )
    , ( "ორმოცდა"     , 40 )
    , ( "ორმოცდაათ"   , 50 )
    , ( "ორმოცდაათი"  , 50 )
    , ( "სამოც"       , 60 )
    , ( "სამოცი"      , 60 )
    , ( "სამოცდა"     , 60 )
    , ( "სამოცდაათ"   , 70 )
    , ( "სამოცდაათი"  , 70 )
    , ( "ოთხმოც"      , 80 )
    , ( "ოთხმოცი"     , 80 )
    , ( "ოთხმოცდა"    , 80 )
    , ( "ოთხმოცდაათ"  , 90 )
    , ( "ოთხმოცდაათი" , 90 )
    ]
  
  ruleTens :: Rule
  ruleTens = Rule
    { name = "integer (20..90)"
    , pattern =
      [ regex "(ოცდაათი?|ორმოცდაათი?|სამოცდაათი?|ოთხმოცდაათი?|ოცდა|ორმოცდა|სამოცდა|ოთხმოცდა|ოცი?|ორმოცი?|სამოცი?|ოთხმოცი?)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):_) ->
          HashMap.lookup (Text.toLower match) tensMap >>= integer
        _ -> Nothing
    }
  
  hundredsMap :: HashMap Text Integer
  hundredsMap = HashMap.fromList
    [ ( "ასი"      , 100 )
    , ( "ორასი"    , 200 )
    , ( "სამასი"   , 300 )
    , ( "ოთხასი"   , 400 )
    , ( "ხუთასი"   , 500 )
    , ( "ექვსასი"  , 600 )
    , ( "შვიდასი"  , 700 )
    , ( "რვაასი"   , 800 )
    , ( "ცხრაასი"  , 900 )
    , ( "ას"       , 100 )
    , ( "ორას"     , 200 )
    , ( "სამას"    , 300 )
    , ( "ოთხას"    , 400 )
    , ( "ხუთას"    , 500 )
    , ( "ექვსას"   , 600 )
    , ( "შვიდას"   , 700 )
    , ( "რვაას"    , 800 )
    , ( "ცხრაას"   , 900 )
    , ( "ორ ას"    , 200 )
    , ( "სამ ას"   , 300 )
    , ( "ოთხ ას"   , 400 )
    , ( "ხუთ ას"   , 500 )
    , ( "ექვს ას"  , 600 )
    , ( "შვიდ ას"  , 700 )
    , ( "რვა ას"   , 800 )
    , ( "ცხრა ას"  , 900 )
    , ( "ორ ასი"   , 200 )
    , ( "სამ ასი"  , 300 )
    , ( "ოთხ ასი"  , 400 )
    , ( "ხუთ ასი"  , 500 )
    , ( "ექვს ასი" , 600 )
    , ( "შვიდ ასი" , 700 )
    , ( "რვა ასი"  , 800 )
    , ( "ცხრა ასი" , 900 )
    ]
  
  ruleHundreds :: Rule
  ruleHundreds = Rule
    { name = "integer (800..900)"
    , pattern =
      [ regex "(ასი?|ორ ?ასი?|სამ ?ასი?|ოთხ ?ასი?|ხუთ ?ასი?|ექვს ?ასი?|შვიდ ?ასი?|რვა ?ასი?|ცხრა ?ასი?)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):_) ->
          HashMap.lookup (Text.toLower match) hundredsMap >>= integer
        _ -> Nothing
    }
  
  rulePowersOfTen :: Rule
  rulePowersOfTen = Rule
    { name = "powers of tens"
    , pattern =
      [ regex "(ათასი?|მილიონი?|მილიარდი?)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
          "ათასი" -> double 1e3 >>= withGrain 3 >>= withMultipliable
          "ათას" -> double 1e3 >>= withGrain 3 >>= withMultipliable
          "მილიონი"  -> double 1e6 >>= withGrain 6 >>= withMultipliable
          "მილიონ"  -> double 1e6 >>= withGrain 6 >>= withMultipliable
          "მილიარდი"  -> double 1e9 >>= withGrain 9 >>= withMultipliable
          "მილიარდ"  -> double 1e9 >>= withGrain 9 >>= withMultipliable
          _          -> Nothing
        _ -> Nothing
    }
  
  ruleCompositeTens :: Rule
  ruleCompositeTens = Rule
    { name = "integer 21..99"
    , pattern =
      [ oneOf [20,40..90]
      , Predicate $ numberBetween 1 20
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = tens}:
         Token Numeral NumeralData{TNumeral.value = units}:
         _) -> double $ tens + units
        _ -> Nothing
    }
  
  ruleCompositeHundreds :: Rule
  ruleCompositeHundreds = Rule
    { name = "integer 100..999"
    , pattern =
      [ oneOf [100,200..900]
      , oneOf [20,40..90]
      , Predicate $ numberBetween 1 20
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = hundreds}:
         Token Numeral NumeralData{TNumeral.value = tens}:
         Token Numeral NumeralData{TNumeral.value = units}:
         _) -> double $ hundreds + tens + units
        _ -> Nothing
    }
  
  ruleCompositeHundredsAndUnits :: Rule
  ruleCompositeHundredsAndUnits = Rule
    { name = "integer 100..999"
    , pattern =
      [ oneOf [100,200..900]
      , Predicate $ numberBetween 1 20
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = hundreds}:
         Token Numeral NumeralData{TNumeral.value = units}:
         _) -> double $ hundreds + units
        _ -> Nothing
    }
  
  ruleCompositeHundredsAndTens :: Rule
  ruleCompositeHundredsAndTens = Rule
    { name = "integer 100..999"
    , pattern =
      [ oneOf [100,200..900]
      , oneOf [10,20..90]
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = hundreds}:
         Token Numeral NumeralData{TNumeral.value = tens}:
         _) -> double $ hundreds + tens
        _ -> Nothing
    }
  
  ruleDotSpelledOut :: Rule
  ruleDotSpelledOut = Rule
    { name = "one point 2"
    , pattern =
      [ dimension Numeral
      , regex "წერტილი|მთელი"
      , Predicate $ not . hasGrain
      ]
    , prod = \case
        (Token Numeral nd1:_:Token Numeral nd2:_) ->
          double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
        _ -> Nothing
    }
  
  ruleLeadingDotSpelledOut :: Rule
  ruleLeadingDotSpelledOut = Rule
    { name = "point 77"
    , pattern =
      [ regex "წერტილი|მთელი"
      , Predicate $ not . hasGrain
      ]
    , prod = \case
        (_:Token Numeral nd:_) -> double . decimalsToDouble $ TNumeral.value nd
        _ -> Nothing
    }
  
  ruleDecimals :: Rule
  ruleDecimals = Rule
    { name = "decimal number"
    , pattern =
      [ regex "(\\d*\\.\\d+)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
        _ -> Nothing
    }
  
  ruleCommas :: Rule
  ruleCommas = Rule
    { name = "comma-separated numbers"
    , pattern =
      [ regex "(\\d+(,\\d\\d\\d)+(\\.\\d+)?)"
      ]
    , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):_) ->
          parseDouble (Text.replace "," Text.empty match) >>= double
        _ -> Nothing
    }
  
  ruleSuffixes :: Rule
  ruleSuffixes = Rule
    { name = "suffixes (K,M,G))"
    , pattern =
      [ dimension Numeral
      , regex "(k|m|g)(?=[\\W$€¢£]|$)"
      ]
    , prod = \case
        (Token Numeral nd : Token RegexMatch (GroupMatch (match : _)):_) -> do
          x <- case Text.toLower match of
            "k" -> Just 1e3
            "m" -> Just 1e6
            "g" -> Just 1e9
            _ -> Nothing
          double $ TNumeral.value nd * x
        _ -> Nothing
    }
  
  ruleNegative :: Rule
  ruleNegative = Rule
    { name = "negative numbers"
    , pattern =
      [ regex "(-|მინუს|მინ)(?!\\s*-)"
      , Predicate isPositive
      ]
    , prod = \case
        (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
        _ -> Nothing
    }
  
  ruleSum :: Rule
  ruleSum = Rule
    { name = "intersect 2 numbers"
    , pattern =
      [ Predicate $ and . sequence [hasGrain, isPositive]
      , Predicate $ and . sequence [not . isMultipliable, isPositive]
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = val1, TNumeral.grain = Just g}:
         Token Numeral NumeralData{TNumeral.value = val2}:
         _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
        _ -> Nothing
    }
  
  ruleSumAnd :: Rule
  ruleSumAnd = Rule
    { name = "intersect 2 numbers (with and)"
    , pattern =
      [ Predicate $ and . sequence [hasGrain, isPositive]
      , regex "და"
      , Predicate $ and . sequence [not . isMultipliable, isPositive]
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = val1, TNumeral.grain = Just g}:
         _:
         Token Numeral NumeralData{TNumeral.value = val2}:
         _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
        _ -> Nothing
    }
  
  ruleMultiply :: Rule
  ruleMultiply = Rule
    { name = "compose by multiplication"
    , pattern =
      [ dimension Numeral
      , Predicate isMultipliable
      ]
    , prod = \case
        (token1:token2:_) -> multiply token1 token2
        _ -> Nothing
    }
  
  rules :: [Rule]
  rules =
    [ ruleToNineteen
    , ruleHundreds
    , ruleCompositeHundreds
    , ruleCompositeHundredsAndUnits
    , ruleCompositeHundredsAndTens
    , ruleTens
    , rulePowersOfTen
    , ruleCompositeTens
    , ruleDotSpelledOut
    , ruleLeadingDotSpelledOut
    , ruleDecimals
    , ruleCommas
    , ruleSuffixes
    , ruleNegative
    , ruleSum
    , ruleSumAnd
    , ruleMultiply
    ]
  