{-# LANGUAGE OverloadedStrings #-}

module Funcons.Operations.Integers where

import Funcons.Operations.Internal hiding (isInt)
import Funcons.Operations.Types
import Funcons.Operations.Booleans (tobool)

import Data.Char
import Numeric

library :: HasValues t => Library t
library = libFromList [
    ("integers", NullaryExpr integers)
  , ("integers-from", UnaryExpr integers_from)
  , ("from", UnaryExpr integers_from)
  , ("integers-up-to", UnaryExpr integers_up_to)
  , ("up-to", UnaryExpr integers_up_to)
  , ("is-integer", UnaryExpr is_integer)
  , ("is-int", UnaryExpr is_integer)
  , ("int-add", NaryExpr integer_add_)
  , ("integer-add", NaryExpr integer_add_)
  , ("int-sub", BinaryExpr integer_subtract)
  , ("integer-subtract", BinaryExpr integer_subtract)
  , ("integer-sub", BinaryExpr integer_subtract)
  , ("integer-modulo", BinaryExpr stepMod)
  , ("integer-mod", BinaryExpr stepMod)
  , ("int-mod", BinaryExpr stepMod)
  , ("integer-multiply", NaryExpr integer_multiply_)
  , ("int-mul", NaryExpr integer_multiply_)
  , ("integer-divide", BinaryExpr integer_divide)
  , ("int-div", BinaryExpr integer_divide)
  , ("integer-power", BinaryExpr integer_power)
  , ("int-pow", BinaryExpr integer_power)
-- integer-negate is now generated
--  ,   ("integer-negate", ValueOp stepInteger_Negate)
--  ,   ("int-neg", ValueOp stepInteger_Negate)
  , ("integer-list", BinaryExpr integer_list)
  , ("integer-absolute-value", UnaryExpr integer_absolute_value)
  , ("decimal-natural", UnaryExpr decimal_natural)
  , ("decimal", UnaryExpr decimal_natural)
  , ("hexadecimal-natural", UnaryExpr hexadecimal_natural)
  , ("hexadecimal", UnaryExpr hexadecimal_natural)
  , ("binary-natural", UnaryExpr binary_natural)
  , ("binary", UnaryExpr binary_natural)
  , ("octal-natural", UnaryExpr octal_natural)
  , ("octal", UnaryExpr octal_natural)
  , ("natural-predecessor", UnaryExpr natural_predecessor)
  , ("nat-pred", UnaryExpr natural_predecessor)
  , ("natural-successor", UnaryExpr natural_successor)
  , ("nat-successor", UnaryExpr natural_successor)
  , ("nat-succ", UnaryExpr natural_successor)
  , ("integer-is-less", BinaryExpr is_less)
  , ("is-less", BinaryExpr is_less)
  , ("is-less-or-equal", BinaryExpr is_less_or_equal)
  , ("integer-is-less-or-equal", BinaryExpr is_less_or_equal)
  , ("is-greater", BinaryExpr is_greater)
  , ("integer-is-greater", BinaryExpr is_greater)
  , ("is-greater-or-equal", BinaryExpr is_greater_or_equal)
  , ("integer-is-greater-or-equal", BinaryExpr is_greater_or_equal)
  ]

integer_modulo_, integer_mod_ :: HasValues t => [OpExpr t] -> OpExpr t
integer_mod_ = binaryOp stepMod
integer_modulo_ = binaryOp stepMod

stepMod :: HasValues t => OpExpr t -> OpExpr t -> OpExpr t
stepMod = vBinaryOp "mod" op
  where op vx vy | (Int x, Int y)<- (upcastIntegers vx, upcastIntegers vy)
                 = if y == 0 then Normal $ inject null__
                             else Normal $ inject $ mk_integers $ x `mod` y
        op _ _ = SortErr "mod not applied to integers"

integers_ :: HasValues t => [OpExpr t] -> OpExpr t
integers_ = nullaryOp integers
integers :: HasValues t => OpExpr t
integers = vNullaryOp "integers" (Normal $ injectT Integers)

integers_from_ :: HasValues t => [OpExpr t] -> OpExpr t
integers_from_ = unaryOp integers_from
integers_from :: HasValues t => OpExpr t -> OpExpr t
integers_from = vUnaryOp "integers-from" op
  where op v | Int i <- upcastIntegers v = Normal $ injectT $ IntegersFrom i
             | otherwise = SortErr "integers-from not applied to an integer"

integers_up_to_ :: HasValues t => [OpExpr t] -> OpExpr t
integers_up_to_ = unaryOp integers_up_to
integers_up_to :: HasValues t => OpExpr t -> OpExpr t
integers_up_to = vUnaryOp "integers-up-to" op
  where op v | Int i <- upcastIntegers v = Normal $ injectT $ IntegersUpTo i
             | otherwise = SortErr "integers-up-to not applied to an integer"

is_integer_ :: HasValues t => [OpExpr t] -> OpExpr t
is_integer_ = unaryOp is_integer
is_integer :: HasValues t => OpExpr t -> OpExpr t
is_integer x = RewritesTo "is-integer" (type_member x (ValExpr (ComputationType (Type Integers)))) [x]

isInt :: Values t -> Bool
isInt x | Int i <- upcastIntegers x = True
        | otherwise = False

unInt x | Int i <- upcastIntegers x = i
        | otherwise = error "unInt"

integer_add_ :: HasValues t => [OpExpr t] -> OpExpr t
integer_add_ = vNaryOp "integer-add" op
  where op  xs | all isInt xs = Normal $ inject $ mk_integers $ sum (map unInt xs)
               | otherwise = SortErr "integer-add not applied to integers"

integer_multiply_ :: HasValues t => [OpExpr t] -> OpExpr t
integer_multiply_ = vNaryOp "integer-multiply" op
  where op  xs | all isInt xs = Normal $ inject $ mk_integers $ product (map unInt xs)
               | otherwise = SortErr "integer-multiply not applied to integers"


integer_subtract_ :: HasValues t => [OpExpr t] -> OpExpr t
integer_subtract_ = binaryOp integer_subtract
integer_subtract :: HasValues t => OpExpr t -> OpExpr t -> OpExpr t
integer_subtract = vBinaryOp "integer-subtract" op
  where op vx vy | Int x <- upcastIntegers vx
                 , Int y <- upcastIntegers vy = Normal $ inject $ mk_integers $ (x - y)
        op _ _ = SortErr "integer-subtract not applied to integers"

integer_divide_ :: HasValues t => [OpExpr t] -> OpExpr t
integer_divide_ = binaryOp integer_divide
integer_divide :: HasValues t => OpExpr t -> OpExpr t -> OpExpr t
integer_divide = vBinaryOp "integer-divide" op
  where op vx vy
          | (Int x,Int y) <- (upcastIntegers vx, upcastIntegers vy) =
              if y == 0
              then Normal $ inject null__
              else Normal $ inject $ mk_integers $ fromInteger (x `div` y)
          | otherwise = SortErr "integer-divide not applied to ints"

integer_power_ :: HasValues t => [OpExpr t] -> OpExpr t
integer_power_ = binaryOp integer_power
integer_power :: HasValues t => OpExpr t -> OpExpr t -> OpExpr t
integer_power = vBinaryOp "integer-power" op
  where op vx vy
          | (Int x, Int y) <- (upcastIntegers vx, upcastIntegers vy) =
              Normal $ inject $ mk_integers $ fromInteger $(x ^ y)
          | otherwise = SortErr "integer-power not applied to two integers"

natural_predecessor_, nat_pred_ :: HasValues t => [OpExpr t] -> OpExpr t
natural_predecessor_ = unaryOp natural_predecessor
nat_pred_ = unaryOp natural_predecessor
natural_predecessor :: HasValues t => OpExpr t -> OpExpr t
natural_predecessor = vUnaryOp "natural-predecessor" op
  where op x | Nat n <- upcastNaturals x =
          if n == 0 then Normal $ inject null__
                    else Normal $ inject $ Nat (n - 1)
             | otherwise = SortErr "natural-pred not applied to a natural number"

natural_successor_, nat_succ_ :: HasValues t => [OpExpr t] -> OpExpr t
natural_successor_ = nat_succ_
nat_succ_ = unaryOp natural_successor
natural_successor :: HasValues t => OpExpr t -> OpExpr t
natural_successor = vUnaryOp "natural-successor" op
  where op x | Nat n <- upcastNaturals x = Normal $ inject $ Nat (n + 1)
             | otherwise = SortErr "natural-succ not applied to a natural number"

integer_list_ :: HasValues t => [OpExpr t] -> OpExpr t
integer_list_ = binaryOp integer_list
integer_list :: HasValues t => OpExpr t -> OpExpr t -> OpExpr t
integer_list = vBinaryOp "integer-list" op
 where op vi1 vi2
        | (Int i1, Int i2) <- (upcastIntegers vi1, upcastIntegers vi2)
                = Normal $ inject $ ADTVal "list" (map (inject . Int) [i1.. i2])
        | otherwise = SortErr "integer-list not applied to two integers"

integer_absolute_value_ :: HasValues t => [OpExpr t] -> OpExpr t
integer_absolute_value_ = unaryOp integer_absolute_value
integer_absolute_value :: HasValues t => OpExpr t -> OpExpr t
integer_absolute_value = vUnaryOp "integer-absolute-value" op
  where op v | Int  i <- upcastIntegers v =
                Normal $ inject $ Int (Prelude.abs i)
             | otherwise = SortErr "sort check: integer-absolute-value(I1)"

decimal_natural_ :: HasValues t => [OpExpr t] -> OpExpr t
decimal_natural_ = unaryOp decimal_natural
decimal_natural :: HasValues t => OpExpr t -> OpExpr t
decimal_natural = vUnaryOp "decimal-natural" op
  where op :: HasValues t => Values t -> Result t
        op s | isString_ s = Normal $ inject $ Nat (read (unString s))
             | otherwise = SortErr "decimal-natural not applied to a string"

binary_natural_ :: HasValues t => [OpExpr t] -> OpExpr t
binary_natural_ = unaryOp decimal_natural
binary_natural :: HasValues t => OpExpr t -> OpExpr t
binary_natural = vUnaryOp "binary-natural" op
  where op  :: HasValues t => Values t -> Result t
        op s | isString_ s = case readInt 2 (`elem` ['0'..'1']) digitToInt (unString s) of
                [(i,"")] -> Normal $ inject $ Nat i
                _        -> SortErr "binary-natural not applied to a binary number"
             | otherwise = SortErr "binary-natural not applied to a string"

octal_natural_ :: HasValues t => [OpExpr t] -> OpExpr t
octal_natural_ = unaryOp decimal_natural
octal_natural :: HasValues t => OpExpr t -> OpExpr t
octal_natural = vUnaryOp "octal-natural" op
  where op  :: HasValues t => Values t -> Result t
        op s | isString_ s = case readInt 8 (`elem` ['0'..'7']) digitToInt (unString s) of
                [(i,"")] -> Normal $ inject $ Nat i
                _        -> SortErr "octal-natural not applied to a octal number"
             | otherwise = SortErr "octal-natural not applied to a string"

hexadecimal_natural_ :: HasValues t => [OpExpr t] -> OpExpr t
hexadecimal_natural_ = unaryOp hexadecimal_natural
hexadecimal_natural :: HasValues t => OpExpr t -> OpExpr t
hexadecimal_natural = vUnaryOp "hexadecimal-natural" op
  where op  :: HasValues t => Values t -> Result t
        op s | isString_ s = case readInt 16 (`elem` (['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F'])) digitToInt (unString s) of
                [(i,"")] -> Normal $ inject $ Nat i
                _        -> SortErr "hexadecimal-natural not applied to a hexadecimal number"
             | otherwise = SortErr "hexadecimal-natural not applied to a string"

is_less_ :: HasValues t => [OpExpr t] -> OpExpr t
is_less_ = binaryOp is_less
is_less :: HasValues t => OpExpr t -> OpExpr t -> OpExpr t
is_less = vBinaryOp "is-less" op
  where op vx vy
          | (Int x, Int y) <- (upcastIntegers vx, upcastIntegers vy)
              = Normal $ inject $ tobool (x < y)
          | otherwise = SortErr "is-less not applied to rationals"

is_less_or_equal_ :: HasValues t => [OpExpr t] -> OpExpr t
is_less_or_equal_ = binaryOp is_less_or_equal
is_less_or_equal ::  HasValues t => OpExpr t -> OpExpr t -> OpExpr t
is_less_or_equal = vBinaryOp "is-less-or-equal" op
 where op vx vy
         | (Int x, Int y) <- (upcastIntegers vx, upcastIntegers vy)
            = Normal $ inject $ tobool (x <= y)
         | otherwise = SortErr "is_less_or_equal not applied to two arguments"

is_greater_ :: HasValues t => [OpExpr t] -> OpExpr t
is_greater_ = binaryOp is_greater
is_greater :: HasValues t => OpExpr t -> OpExpr t -> OpExpr t
is_greater = vBinaryOp "is-greater" op
 where op vx vy
        | (Int x, Int y) <- (upcastIntegers vx, upcastIntegers vy)
            = Normal $ inject $ tobool (x > y)
        | otherwise = SortErr "is-greater not applied to two arguments"

is_greater_or_equal_ :: HasValues t => [OpExpr t] -> OpExpr t
is_greater_or_equal_ = binaryOp is_greater_or_equal
is_greater_or_equal :: HasValues t => OpExpr t -> OpExpr t -> OpExpr t
is_greater_or_equal = vBinaryOp "is-greater-or-equal" op
 where op vx vy | (Int x, Int y) <- (upcastIntegers vx, upcastIntegers vy)
                    = Normal $ inject $ tobool (x >= y)
                | otherwise = SortErr "is-greater-or-equal not applied to rationals"