{-|

Rules to convert numbers to an expression language.

-}
module Text.Numeral.Rules
  ( -- * The Rule type
    Rule

    -- * Rule combinators
  , conditional
  , combine
  , mapRule
  , findRule

    -- * Rules
  , unknown

  , lit, lit1

  , pos, checkPos

  , add
  , mul, mul1
  , sub

  , mulScale_, mulScale, mulScale1
  , shortScale,  longScale,  pelletierScale
  , shortScale1, longScale1, pelletierScale1

  , mkStep, step, step1

    -- ** Grammar Rules
  , changeCase
  , changeGender
  , changeNumber
  ) where


-------------------------------------------------------------------------------
-- Imports
-------------------------------------------------------------------------------

import "base" Data.Function ( fix )
import           "this" Text.Numeral.Exp ( Side(L, R) )
import qualified "this" Text.Numeral.Exp as E
import qualified "this" Text.Numeral.Grammar as G
import           "this" Text.Numeral.Misc ( intLog )
import qualified "fingertree" Data.IntervalMap.FingerTree as FT
    ( Interval(Interval)
    , IntervalMap, empty, insert
    , search
    )


--------------------------------------------------------------------------------
-- The Rule type
--------------------------------------------------------------------------------

-- | A rule on how to convert a number into an expression
-- language. Notice how this type is similar to the type of the '$'
-- operator.
type Rule a = (a -> E.Exp) -> (a -> E.Exp)


--------------------------------------------------------------------------------
-- Rule combinators
--------------------------------------------------------------------------------


-- | The \'if-then-else\' concept for rules. Applies the first rule if
-- the predicate holds on the input value, otherwise applies the
-- second rule.
conditional :: (a -> Bool) -- ^ Predicate on input value (\"if\").
            -> Rule a -- ^ Rule to apply when predicate holds (\"then\").
            -> Rule a -- ^ Rule to apply when predicate does not hold (\"else\").
            -> Rule a
conditional p t e = \f n -> if p n
                           then t f n
                           else e f n

-- | Tries to apply the first rule, if that produces an 'E.Unknown'
-- value it applies the second rule.
combine :: Rule a
        -> Rule a
        -> Rule a
combine r1 r2 = \f n -> case r1 f n of
                          E.Unknown -> r2 f n
                          x         -> x

-- | Transform a value before it is given to a rule.
mapRule :: (a -> a) -> Rule a -> Rule a
mapRule g r = \f n -> r f (g n)

-- | Chooses which rule to apply to an input value based on a interval
-- list of rules.
findRule :: (Ord a, Num a)
         => (a, Rule a)   -- ^ First interval rule.
         -> [(a, Rule a)] -- ^ Interval rule list.
         -> a             -- ^ Upper bound of the last interval.
         -> Rule a
findRule x xs end = \f n -> case FT.search n xm of
                             [] -> E.Unknown
                             (_,r):_ -> r f n
    where
      xm = mkIntervalMap $ mkIntervalList x xs end


--------------------------------------------------------------------------------
-- Rules
--------------------------------------------------------------------------------

-- | A rule that always fails to convert a value. It constantly
-- produces the 'E.unknown' value.
--
-- >>> (fix unknown) (3 :: Integer) :: Exp
-- Unknown
unknown :: Rule a
unknown _ = const E.Unknown

-- | The literal rule. Converts its argument into a 'E.lit'eral
-- expression.
--
-- >>> lit (fix unknown) (3 :: Integer) :: Exp
-- Lit 3
--
-- In this example lit is applied to the nonsense rule \"'fix'
-- 'unknown'\". Lit ignores that function, which is why we can pass it
-- anything we want, including itself.
--
-- >>> lit (fix undefined) (3 :: Integer) :: Exp
-- Lit 3
-- >>> (fix lit) (3 :: Integer) :: Exp
-- Lit 3
lit :: (Integral a) => Rule a
lit = const $ E.Lit . fromIntegral

-- | A variant on the 'lit' rule which always multiplies its argument
-- with 1. Useful for languages which have numerals of the form \"one
-- hundred and three\" as opposed to \"hundred and three\".
--
-- >>> lit1 (fix unknown) (3 :: Integer) :: Exp
-- Mul (Lit 1) (Lit 3)
lit1 :: (Integral a) => Rule a
lit1 = const $ \n -> E.Lit 1 `E.Mul` E.Lit (fromIntegral n)

-- |
--
-- >>> (pos $ lit $ fix unknown) (3 :: Integer) :: Exp
-- Lit 3
-- >>> (pos $ lit $ fix unknown) (-3 :: Integer) :: Exp
-- Neg (Lit 3)
pos :: (Ord a, Num a) => Rule a
pos f n | n < 0     = E.Neg $ f (abs n)
        | n > 0     = f n
        | otherwise = E.Lit 0

-- |
--
-- >>> (checkPos $ lit $ fix unknown) (3 :: Integer) :: Exp
-- Lit 3
-- >>> (checkPos $ lit $ fix unknown) (-3 :: Integer) :: Exp
-- Unknown
checkPos :: (Ord a, Num a) => Rule a
checkPos f n | n < 0     = E.Unknown
             | n > 0     = f n
             | otherwise = E.Lit 0

-- -- | Changes the inflection of a subexpression.
-- inflection :: (E.Inflection b) => (Inflection -> Inflection) -> Rule a
-- inflection changeInf = \f n -> E.inflection changeInf $ f n

-- |
--
-- >>> (add 10 L $ lit $ fix unknown) (13 :: Integer) :: Exp
-- Add (Lit 3) (Lit 10)
add :: (Num a) => a -> Side -> Rule a
add val s = \f n -> (flipIfR s E.Add) (f $ n - val) (f val)

-- |
--
-- >>> (mul 10 R L $ lit $ fix unknown) (42 :: Integer) :: Exp
-- Add (Mul (Lit 4) (Lit 10)) (Lit 2)
mul :: (Integral a) => a -> Side -> Side -> Rule a
mul val aSide mSide =
    \f n -> let (m, a) = n `divMod` val
                mval = (flipIfR mSide E.Mul) (f m) (f val)
            in if a == 0
               then mval
               else (flipIfR aSide E.Add) (f a) mval

mul1 :: (Integral a) => a -> Side -> Side -> Rule a
mul1 val aSide mSide =
    \f n -> let (m, a) = n `divMod` val
                mval = if m == 1
                       then E.Lit 1  E.Lit (fromIntegral val)
                       else f m      E.Lit (fromIntegral val)
            in if a == 0
               then mval
               else (flipIfR aSide E.Add) (f a) mval
  where
     () = flipIfR mSide E.Mul

-- |
--
-- >>> (sub 20 $ lit $ fix unknown) (18 :: Integer) :: Exp
-- Sub (Lit 2) (Lit 20)
sub :: (Integral a) => a -> Rule a
sub val = \f n -> E.Sub (f $ val - n) (f val)

mkStep :: (Integral a)
       => Rule a                        -- ^ lit rule
       -> (a -> Side -> Rule a)         -- ^ add rule
       -> (a -> Side -> Side -> Rule a) -- ^ mul rule
       -> a -> a -> Side -> Side -> Rule a
mkStep lr ar mr val r aSide mSide
       f n | n <  val   = E.Unknown
           | n == val   = lr                 f n
           | n <  val*2 = ar val aSide       f n
           | n <  val*r = mr val aSide mSide f n
           | otherwise  = E.Unknown

step :: (Integral a) => a -> a -> Side -> Side -> Rule a
step = mkStep lit add mul

step1 :: (Integral a) => a -> a -> Side -> Side -> Rule a
step1 = mkStep lit1 add mul1

mulScale_ :: forall a. (Integral a)
          => ( (a -> E.Exp) -- Parent rule.
             -> a       -- First multiplication value (not converted).
             -> E.Exp   -- Second multiplication value (scale step,
                        -- already converted).
             -> Side    -- Multiplication side.
             -> E.Exp
             )      -- ^ Performs the multiplication.
          -> a      -- ^ Base.
          -> a      -- ^ Offset.
          -> Side   -- ^ Add side.
          -> Side   -- ^ Mul side.
          -> Rule a -- ^ Big num rule.
          -> Rule a
mulScale_ doMul base offset aSide mSide bigNumRule =
    \f n -> let rank = (intLog n - offset) `div` base

                base' :: Integer
                base' = fromIntegral base

                offset' :: Integer
                offset' = fromIntegral offset

                rankExp :: E.Exp
                rankExp = (fix bigNumRule) rank

                m, a :: a
                (m, a) = n `divMod` E.evalScale base offset rank

                scale' :: E.Exp
                scale' = E.Scale base' offset' rankExp

                mval = doMul f m scale' mSide

            in case rankExp of
                 E.Unknown -> E.Unknown
                 _ -> if a == 0
                      then mval
                      else (flipIfR aSide E.Add) (f a) mval

mulScale :: (Integral a)
         => a      -- ^ Base.
         -> a      -- ^ Offset.
         -> Side   -- ^ Add side.
         -> Side   -- ^ Mul side.
         -> Rule a -- ^ Big num rule.
         -> Rule a
mulScale = mulScale_ $ \f m scale' mSide ->
                         case m of
                           1 -> scale'
                           _ -> (flipIfR mSide E.Mul) (f m) scale'

mulScale1 :: (Integral a)
          => a      -- ^ Base.
          -> a      -- ^ Offset.
          -> Side   -- ^ Add side.
          -> Side   -- ^ Mul side.
          -> Rule a -- ^ Big num rule.
          -> Rule a
mulScale1 = mulScale_ $ \f m scale' mSide -> (flipIfR mSide E.Mul) (f m) scale'

shortScale :: (Integral a)
           => Side   -- ^ Add side.
           -> Side   -- ^ Mul side.
           -> Rule a -- ^ Big num rule.
           -> Rule a
shortScale = mulScale 3 3

shortScale1 :: (Integral a)
            => Side   -- ^ Add side.
            -> Side   -- ^ Mul side.
            -> Rule a -- ^ Big num rule.
            -> Rule a
shortScale1 = mulScale1 3 3

longScale :: (Integral a)
          => Side   -- ^ Add side.
          -> Side   -- ^ Mul side.
          -> Rule a -- ^ Big num rule.
          -> Rule a
longScale = mulScale 6 0

longScale1 :: (Integral a)
           => Side   -- ^ Add side.
           -> Side   -- ^ Mul side.
           -> Rule a -- ^ Big num rule.
           -> Rule a
longScale1 = mulScale1 6 0

pelletierScale :: (Integral a)
               => Side   -- ^ Add side.
               -> Side   -- ^ Mul side.
               -> Rule a -- ^ Big num rule.
               -> Rule a
pelletierScale aSide mSide bigNumRule =
    conditional (\n -> even $ intLog n `div` 3)
                (mulScale 6 0 aSide mSide bigNumRule)
                (mulScale 6 3 aSide mSide bigNumRule)

pelletierScale1 :: (Integral a)
                => Side   -- ^ Add side.
                -> Side   -- ^ Mul side.
                -> Rule a -- ^ Big num rule.
                -> Rule a
pelletierScale1 aSide mSide bigNumRule =
    conditional (\n -> even $ intLog n `div` 3)
                (mulScale1 6 0 aSide mSide bigNumRule)
                (mulScale1 6 3 aSide mSide bigNumRule)


--------------------------------------------------------------------------------
-- Grammar Rules
--------------------------------------------------------------------------------

changeCase :: Maybe G.Case -> Rule a
changeCase mbCase = \f n -> E.ChangeCase mbCase $ f n

changeGender :: Maybe G.Gender -> Rule a
changeGender mbGender = \f n -> E.ChangeGender mbGender $ f n

changeNumber :: Maybe G.Number -> Rule a
changeNumber mbNumber = \f n -> E.ChangeNumber mbNumber $ f n

--------------------------------------------------------------------------------
-- Miscellaneous
--------------------------------------------------------------------------------

flipIfR :: Side -> (a -> a -> a) -> (a -> a -> a)
flipIfR L = id
flipIfR R = flip

mkIntervalList :: (Num a) => (a, b) -> [(a, b)] -> a -> [((a, a), b)]
mkIntervalList (k, r) krs end = go k r krs
    where
      go k1 r1 []            = [((k1, end), r1)]
      go k1 r1 ((k2, r2):xs) = ((k1, k2-1), r1) : go k2 r2 xs

mkIntervalMap :: (Ord v) => [((v, v), a)] -> FT.IntervalMap v a
mkIntervalMap = foldr ins FT.empty
  where ins ((lo, hi), n) = FT.insert (FT.Interval lo hi) n