{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# OPTIONS_GHC -Wall #-}
module NumHask.Algebra.Field
  ( Semifield
  , Field
  , ExpField(..)
  , QuotientField(..)
  , UpperBoundedField(..)
  , LowerBoundedField(..)
  , BoundedField
  , TrigField(..)
  ) where
import Data.Complex (Complex(..))
import NumHask.Algebra.Additive
import NumHask.Algebra.Multiplicative
import NumHask.Algebra.Ring
import NumHask.Algebra.Integral
import Data.Bool (bool)
import Prelude (Double, Float, Integer, (||))
import qualified Prelude as P
class (MultiplicativeInvertible a, MultiplicativeGroup a, Semiring a) =>
      Semifield a
instance Semifield Double
instance Semifield Float
instance (Semifield a, AdditiveGroup a) => Semifield (Complex a)
class (AdditiveGroup a, MultiplicativeGroup a, Ring a) =>
      Field a
instance Field Double
instance Field Float
instance (Field a) => Field (Complex a)
class (Field a) =>
      ExpField a where
  exp :: a -> a
  log :: a -> a
  logBase :: a -> a -> a
  logBase a b = log b / log a
  (**) :: a -> a -> a
  (**) a b = exp (log a * b)
  sqrt :: a -> a
  sqrt a = a ** (one / (one + one))
instance ExpField Double where
  exp = P.exp
  log = P.log
  (**) = (P.**)
instance ExpField Float where
  exp = P.exp
  log = P.log
  (**) = (P.**)
instance (P.Ord a, TrigField a, ExpField a) => ExpField (Complex a) where
  exp (rx :+ ix) = exp rx * cos ix :+ exp rx * sin ix
  log (rx :+ ix) = log (sqrt (rx * rx + ix * ix)) :+ atan2 ix rx
    where
      atan2 y x
        | x P.> zero = atan (y / x)
        | x P.== zero P.&& y P.> zero = pi / (one + one)
        | x P.< one P.&& y P.> one = pi + atan (y / x)
        | (x P.<= zero P.&& y P.< zero) || (x P.< zero) =
          negate (atan2 (negate y) x)
        | y P.== zero = pi 
        | x P.== zero P.&& y P.== zero = y 
        | P.otherwise = x + y 
class (P.Ord a, Field a, P.Eq b, Integral b, AdditiveGroup b, MultiplicativeUnital b) =>
      QuotientField a b where
  properFraction :: a -> (b, a)
  round :: a -> b
  round x = case properFraction x of
    (n,r) -> let
      m         = bool (n+one) (n-one) (r P.< zero)
      half_down = abs' r - (one/(one+one))
      abs' a
        | a P.< zero = negate a
        | P.otherwise = a
      in
        case P.compare half_down zero of
          P.LT -> n
          P.EQ -> bool m n (even n)
          P.GT -> m
  ceiling :: a -> b
  ceiling x = bool n (n+one) (r P.> zero)
    where (n,r) = properFraction x
  floor :: a -> b
  floor x = bool n (n-one) (r P.< zero)
    where (n,r) = properFraction x
instance QuotientField Float Integer where
  properFraction = P.properFraction
instance QuotientField Double Integer where
  properFraction = P.properFraction
class (Semifield a) =>
      UpperBoundedField a where
  infinity :: a
  infinity = one / zero
  nan :: a
  nan = zero / zero
instance UpperBoundedField Float
instance UpperBoundedField Double
class (Field a) =>
      LowerBoundedField a where
  negInfinity :: a
  negInfinity = negate (one / zero)
instance LowerBoundedField Float
instance LowerBoundedField Double
instance (AdditiveGroup a, UpperBoundedField a) =>
  UpperBoundedField (Complex a)
class (UpperBoundedField a, LowerBoundedField a) => BoundedField a
instance (UpperBoundedField a, LowerBoundedField a) => BoundedField a
class (Field a) =>
      TrigField a where
  pi :: a
  sin :: a -> a
  cos :: a -> a
  tan :: a -> a
  tan x = sin x / cos x
  asin :: a -> a
  acos :: a -> a
  atan :: a -> a
  sinh :: a -> a
  cosh :: a -> a
  tanh :: a -> a
  tanh x = sinh x / cosh x
  asinh :: a -> a
  acosh :: a -> a
  atanh :: a -> a
instance TrigField Double where
  pi = P.pi
  sin = P.sin
  cos = P.cos
  asin = P.asin
  acos = P.acos
  atan = P.atan
  sinh = P.sinh
  cosh = P.cosh
  asinh = P.sinh
  acosh = P.acosh
  atanh = P.atanh
instance TrigField Float where
  pi = P.pi
  sin = P.sin
  cos = P.cos
  asin = P.asin
  acos = P.acos
  atan = P.atan
  sinh = P.sinh
  cosh = P.cosh
  asinh = P.sinh
  acosh = P.acosh
  atanh = P.atanh