{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Rank2Types #-}

-- | This module provides a symbolic representation of real number
-- expressions, as well as a type class of things that can be
-- converted to arbitrary precision real numbers.
module Quantum.Synthesis.SymReal where

import Quantum.Synthesis.ArcTan2

import Control.Monad
import Data.Char (isAlpha, isAlphaNum, isDigit)
import Data.Number.FixedPrec
import Text.ParserCombinators.ReadP
import Data.Ratio

-- ----------------------------------------------------------------------
-- * Symbolic real number expressions

-- | A type to represent symbolic expressions for real numbers.
-- 
-- Caution: equality '==' at this type denotes symbolic equality of
-- expressions, not equality of the defined real numbers.
data SymReal =
  Const Integer             -- ^ An integer constant.
  | Decimal Rational String -- ^ A decimal constant. This has a rational value and a string representation.
  | Plus SymReal SymReal    -- ^ /x/ @+@ /y/.
  | Minus SymReal SymReal   -- ^ /x/ @−@ /y/.
  | Times SymReal SymReal   -- ^ /x/ @*@ /y/.
  | Div SymReal SymReal     -- ^ /x/ @\/@ /y/.
  | Negate SymReal          -- ^ \−/x/.
  | Abs SymReal             -- ^ |/x/|.
  | Signum SymReal          -- ^ signum(/x/).
  | Recip SymReal           -- ^ 1\//x/.
  | Pi                      -- ^ π.
  | Euler                   -- ^ /e/.
  | Exp SymReal             -- ^ \[exp /x/].
  | Sqrt SymReal            -- ^ √/x/.
  | Log SymReal             -- ^ log /x/.
  | Power SymReal SymReal   -- ^ /x/[sup /y/].
  | Sin SymReal             -- ^ sin /x/.
  | Tan SymReal             -- ^ cos /x/.
  | Cos SymReal             -- ^ cos /x/.
  | ASin SymReal            -- ^ asin /x/.
  | ATan SymReal            -- ^ atan /x/.
  | ACos SymReal            -- ^ acos /x/.
  | Sinh SymReal            -- ^ sinh /x/.
  | Tanh SymReal            -- ^ tanh /x/.
  | Cosh SymReal            -- ^ cosh /x/.
  | ASinh SymReal           -- ^ asinh /x/.
  | ATanh SymReal           -- ^ atanh /x/.
  | ACosh SymReal           -- ^ acosh /x/.
  | ArcTan2 SymReal SymReal -- ^ arctan2 /x/ /y/.
    deriving (Eq)

instance Show SymReal where
  showsPrec d (Const x)     = showsPrec d x
  showsPrec d (Decimal x s) = showString s
  showsPrec d (Plus x y)    = showParen (d > 6) $ showsPrec 6 x . showString "+" . showsPrec 6 y
  showsPrec d (Minus x y)   = showParen (d > 6) $ showsPrec 6 x . showString "-" . showsPrec 7 y
  showsPrec d (Times x y)   = showParen (d > 7) $ showsPrec 7 x . showString "*" . showsPrec 7 y
  showsPrec d (Div x y)     = showParen (d > 7) $ showsPrec 7 x . showString "/" . showsPrec 8 y
  showsPrec d (Power x y)   = showParen (d > 8) $ showsPrec 9 x . showString "**" . showsPrec 9 y
  showsPrec d (Negate x)    = showParen (d > 5) $ showString "-" . showsPrec 7 x
  showsPrec d (Abs x)       = showParen (d > 10) $ showString "abs " . showsPrec 11 x
  showsPrec d (Signum x)    = showParen (d > 10) $ showString "signum " . showsPrec 11 x
  showsPrec d (Recip x)     = showParen (d > 7) $ showString "1/" . showsPrec 8 x
  showsPrec d Pi            = showString "pi"
  showsPrec d Euler         = showString "e"
  showsPrec d (Exp x)       = showParen (d > 10) $ showString "exp " . showsPrec 11 x
  showsPrec d (Sqrt x)      = showParen (d > 10) $ showString "sqrt " . showsPrec 11 x
  showsPrec d (Log x)       = showParen (d > 10) $ showString "log " . showsPrec 11 x
  showsPrec d (Sin x)       = showParen (d > 10) $ showString "sin " . showsPrec 11 x
  showsPrec d (Tan x)       = showParen (d > 10) $ showString "tan " . showsPrec 11 x
  showsPrec d (Cos x)       = showParen (d > 10) $ showString "cos " . showsPrec 11 x
  showsPrec d (ASin x)      = showParen (d > 10) $ showString "asin " . showsPrec 11 x
  showsPrec d (ATan x)      = showParen (d > 10) $ showString "atan " . showsPrec 11 x
  showsPrec d (ACos x)      = showParen (d > 10) $ showString "acos " . showsPrec 11 x
  showsPrec d (Sinh x)      = showParen (d > 10) $ showString "sinh " . showsPrec 11 x
  showsPrec d (Tanh x)      = showParen (d > 10) $ showString "tanh " . showsPrec 11 x
  showsPrec d (Cosh x)      = showParen (d > 10) $ showString "cosh " . showsPrec 11 x
  showsPrec d (ASinh x)     = showParen (d > 10) $ showString "asinh " . showsPrec 11 x
  showsPrec d (ATanh x)     = showParen (d > 10) $ showString "atanh " . showsPrec 11 x
  showsPrec d (ACosh x)     = showParen (d > 10) $ showString "acosh " . showsPrec 11 x
  showsPrec d (ArcTan2 y x) = showParen (d > 10) $ showString "arctan2 " . showsPrec 11 y . showString " " . showsPrec 11 x

instance Num SymReal where
  (+) = Plus
  (*) = Times
  (-) = Minus
  negate = Negate
  abs = Abs
  signum = Signum
  fromInteger = Const
  
instance Fractional SymReal where
  (/) = Div
  recip = Recip
  fromRational x = Const (numerator x) `Div` Const (denominator x)
  
instance Floating SymReal where
  pi = Pi
  exp = Exp
  sqrt = Sqrt
  log = Log
  (**) = Power
  logBase x y = log y / log x
  sin = Sin
  tan = Tan
  cos = Cos
  asin = ASin
  atan = ATan
  acos = ACos
  sinh = Sinh
  tanh = Tanh
  cosh = Cosh
  asinh = ASinh
  atanh = ATanh
  acosh = ACosh

instance ArcTan2 SymReal where
  arctan2 y x = ArcTan2 y x

-- ----------------------------------------------------------------------
-- * Conversion to real number types

-- | A type class for things that can be converted to a real number at
-- arbitrary precision.
class ToReal a where
  to_real :: (Floating r, ArcTan2 r) => a -> r

instance ToReal SymReal where
  to_real (Const x) = fromInteger x
  to_real (Decimal x s) = fromRational x
  to_real (Plus x y) = to_real x + to_real y
  to_real (Minus x y) = to_real x - to_real y
  to_real (Times x y) = to_real x * to_real y
  to_real (Negate x) = -(to_real x)
  to_real (Abs x) = abs (to_real x)
  to_real (Signum x) = signum (to_real x)
  to_real (Div x y) =  to_real x / to_real y
  to_real (Recip x) = recip (to_real x)
  to_real Pi = pi
  to_real Euler = exp 1
  to_real (Exp x) = exp (to_real x)
  to_real (Sqrt x) = sqrt (to_real x)
  to_real (Log x) = log (to_real x)
  to_real (Power x y) = to_real x ** to_real y
  to_real (Sin x) = sin (to_real x)
  to_real (Tan x) = tan (to_real x)
  to_real (Cos x) = cos (to_real x)
  to_real (ASin x) = asin (to_real x)
  to_real (ATan x) = atan (to_real x)
  to_real (ACos x) = acos (to_real x)
  to_real (Sinh x) = sinh (to_real x)
  to_real (Tanh x) = tanh (to_real x)
  to_real (Cosh x) = cosh (to_real x)
  to_real (ASinh x) = asinh (to_real x)
  to_real (ATanh x) = atanh (to_real x)
  to_real (ACosh x) = acosh (to_real x)
  to_real (ArcTan2 y x) = arctan2 (to_real y) (to_real x)
  
instance ToReal Rational where
  to_real = fromRational
  
instance ToReal Integer where
  to_real = fromInteger
  
instance ToReal Int where
  to_real = fromIntegral
  
instance ToReal Double where
  to_real = fromRational . toRational

instance ToReal Float where
  to_real = fromRational . toRational

instance (Precision e) => ToReal (FixedPrec e) where
  to_real = fromRational . toRational

instance ToReal String where
  to_real x = case parse_SymReal x of
    Just n -> to_real n
    Nothing -> error "ToReal String: string does not parse"

-- ----------------------------------------------------------------------
-- ** Dynamic conversion to FixedPrec

-- | It would be useful to have a function for converting a symbolic
-- real number to a fixed-precision real number with a chosen
-- precision, such that the precision /e/ depends on a parameter /d/:
-- 
-- > to_fixedprec :: (ToReal r) => Integer -> r -> FixedPrec e
-- > to_fixedprec d x = ...
--  
-- However, since /e/ is a type, /d/ is a term, and Haskell is not
-- dependently typed, this cannot be done directly.
-- 
-- The function 'dynamic_fixedprec' is the closest thing we have to a
-- workaround. The call @dynamic_fixedprec@ /d/ /f/ /x/ calls
-- /f/(/x/'), where /x/' is the value /x/ converted to /d/ digits of
-- precision.  In other words, we have
-- 
-- > dynamic_fixedprec d f x = f (to_fixedprec d x),
-- 
-- with the restriction that the precision /e/ cannot occur freely in
-- the result type of /f/.
dynamic_fixedprec :: forall a r.(ToReal r) => Integer -> (forall e.(Precision e) => FixedPrec e -> a) -> r -> a
dynamic_fixedprec d f x = loop d (undefined :: P0)
  where 
    loop :: forall e.(Precision e) => Integer -> e -> a
    loop d e
      | d >= 1000 = loop (d-1000) (undefined :: PPlus1000 e)
      | d >= 100  = loop (d-100)  (undefined :: PPlus100 e)
      | d >= 10   = loop (d-10) (undefined :: PPlus10 e)
      | d > 0     = loop (d-1) (undefined :: PPlus1 e)
      | otherwise = f (to_real x :: FixedPrec e)

-- | Like 'dynamic_fixedprec', but take two real number arguments. In
-- terms of the fictitious function @to_fixedprec@, we have:
-- 
-- > dynamic_fixedprec2 d f x y = f (to_fixedprec d x) (to_fixedprec d y).
dynamic_fixedprec2 :: forall a r s.(ToReal r, ToReal s) => Integer -> (forall e.(Precision e) => FixedPrec e -> FixedPrec e -> a) -> r -> s -> a
dynamic_fixedprec2 d f x y = loop d (undefined :: P0)
  where 
    loop :: forall e.(Precision e) => Integer -> e -> a
    loop d e
      | d >= 1000 = loop (d-1000) (undefined :: PPlus1000 e)
      | d >= 100  = loop (d-100)  (undefined :: PPlus100 e)
      | d >= 10   = loop (d-10) (undefined :: PPlus10 e)
      | d > 0     = loop (d-1) (undefined :: PPlus1 e)
      | otherwise = f (to_real x :: FixedPrec e) (to_real y :: FixedPrec e)

-- ----------------------------------------------------------------------
-- * A parser for real number expressions
  
-- ----------------------------------------------------------------------
-- ** Grammar specification

-- $ Each function in this section corresponds to a production rule
-- for a context-free grammar. The type of each function is 'ReadP'
-- /a/, where /a/ is the type of the semantic value produced by the
-- grammar for that expression.
-- 
-- The parser uses simple precedences. 
-- 
-- * Unary \"+\" and \"−\" have precedence 6. 
-- 
-- * Binary \"+\" and \"−\" have precedence 6 and are left
-- associative.
-- 
-- * Binary \"*\" and \"\/\" have precedence 7 and are left
-- associative.
-- 
-- * Binary \"**\" and \"^\" have precedence 8 and are right
-- associative.
-- 
-- * All unary operators other than \"+\" and \"−\" have precedence
-- 10.
-- 
-- We use /exp6/ to denote an expression whose
-- top-level operator has precedence 6 or higher, /exp7/ to denote an
-- expression whose top-level operator has precedence 7 or higher, and
-- so on.
-- 
-- We also allow whitespace between lexicographic entities. For
-- simplicity, whitespace is not shown in the production rules,
-- although it appears in the code.

-- | /integer/ ::= /digit/ /digit/*.
integer :: ReadP SymReal
integer = do
  s <- munch1 isDigit
  let n = read s
  return (Const (fromInteger n))

-- | /float/ ::= /digit/* \".\" /digit/*.
-- 
-- There must be at least one digit, either before or after the decimal point.
float :: ReadP SymReal
float = do
  (s1, _) <- gather $ do
    munch isDigit
  char '.'
  (s2, _) <- gather $ do
    munch isDigit
  when (length s1 == 0 && length s2 == 0) $ do
    pfail
  let num = read (s1++s2) :: Integer
  let denom = 10^(length s2)
  let s1' = if s1 == [] then "0" else s1
  let s2' = if s2 == [] then "0" else s2
  return (Decimal (num % denom) (s1' ++ "." ++ s2'))

-- | /const_pi/ ::= \"pi\".
const_pi :: ReadP SymReal
const_pi = do
  string "pi"
  return Pi

-- | /const_e/ ::= \"e\".
const_e :: ReadP SymReal
const_e = do
  string "e"
  return Euler

-- | /negative/ ::= \"−\".
negative :: ReadP (SymReal -> SymReal)
negative = do
  string "-"
  skipSpaces
  return Negate

-- | /positive/ ::= \"+\".
positive :: ReadP (SymReal -> SymReal)
positive = do
  string "+"
  skipSpaces
  return id

-- | /plus_term/ ::= \"+\" /exp7/.
plus_term :: ReadP (SymReal -> SymReal)
plus_term = do
  skipSpaces
  string "+"
  skipSpaces
  n2 <- exp7
  return (\n1 -> Plus n1 n2)

-- | /minus_term/ ::= \"−\" /exp7/.
minus_term :: ReadP (SymReal -> SymReal)
minus_term = do
  skipSpaces
  string "-"
  skipSpaces
  n2 <- exp7
  return (\n1 -> Minus n1 n2)

-- | /times_term/ ::= \"*\" /exp8/.
times_term :: ReadP (SymReal -> SymReal)
times_term = do
  skipSpaces
  string "*"
  skipSpaces
  n2 <- exp8
  return (\n1 -> Times n1 n2)

-- | /div_term/ ::= \"\/\" /exp8/.
div_term :: ReadP (SymReal -> SymReal)
div_term = do
  skipSpaces
  string "/"
  skipSpaces
  n2 <- exp8
  return (\n1 -> Div n1 n2)

-- | /power_term/ ::= /exp10/ \"**\" | /exp10/ \"^\".
power_term :: ReadP (SymReal -> SymReal)
power_term = do
  n1 <- exp10
  skipSpaces
  string "**" +++ string "^"
  skipSpaces
  return (\n2 -> Power n1 n2)

-- | /unary_fun/ ::= /unary_op/ /exp10/.
unary_fun :: ReadP SymReal
unary_fun = do
  skipSpaces
  op <- unary_op
  skipSpaces
  n <- exp10
  return (op n)

-- | /unary_op/ ::= \"abs\" | \"signum\" | ...
unary_op :: ReadP (SymReal -> SymReal)
unary_op = 
  choice [ do { string s; return op } | (s, op) <- ops ]
  where 
    ops = [ ("abs", Abs),
            ("signum", Signum),
            ("recip", Recip),
            ("exp", Exp),
            ("sqrt", Sqrt),
            ("log", Log),
            ("sin", Sin),
            ("tan", Tan),
            ("cos", Cos),
            ("asin", ASin),
            ("atan", ATan),
            ("acos", ACos),
            ("sinh", Sinh),
            ("tanh", Tanh),
            ("cosh", Cosh),
            ("asinh", ASinh),
            ("atanh", ATanh),
            ("acosh", ACosh) ]

-- | /binary_fun/ ::= /binary_op/ /exp10/ /exp10/.
binary_fun :: ReadP SymReal
binary_fun = do
  skipSpaces
  op <- binary_op
  skipSpaces
  n <- exp10
  skipSpaces
  m <- exp10
  return (op n m)

-- | /binary_op/ ::= \"abs\" | \"signum\" | ...
binary_op :: ReadP (SymReal -> SymReal -> SymReal)
binary_op = 
  choice [ do { string s; return op } | (s, op) <- ops ]
  where 
    ops = [ ("arctan2", ArcTan2) ]

-- | /exp6/ ::= (/negative/ | /positive/)? /exp7/ ( /plus_term/ | /minus_term/ )*.
-- 
-- An expression whose top-level operator has precedence 6 or
-- above. The operators of precedence 6 are \"+\" and \"−\".
exp6 :: ReadP SymReal
exp6 = do
  sign <- option id (negative +++ positive)
  n1 <- exp7
  ops <- many $ do
    plus_term +++ minus_term
  return (foldl (\x f -> f x) (sign n1) ops)

-- | /exp7/ ::= /exp8/ ( /times_term/ | /div_term/ )*.
-- 
-- An expression whose top-level operator has precedence 7 or
-- above. The operators of precedence 6 are \"*\" and \"\/\".
exp7 :: ReadP SymReal
exp7 = do
  n1 <- exp8
  ops <- many $ do
    times_term +++ div_term
  return (foldl (\x f -> f x) n1 ops)

-- | /exp8/ ::= ( /power_term/ )* /exp10/
-- 
-- An expression whose top-level operator has precedence 8 or
-- above. The operators of precedence 6 are \"**\" and \"^\".
exp8 :: ReadP SymReal
exp8 = do
  ops <- many $ do
    power_term
  n2 <- exp10
  return (foldr (\f x -> f x) n2 ops)

-- | /exp10/ ::= /parenthesized/ | /const_pi/ | /const_e/ | /integer/ | /float/ | /unary_fun/ | /binary_fun/.
-- 
-- An expression whose top-level operator has precedence 10 or
-- above. Such expressions are constants, applications of unary
-- operators (except unary \"−\" and \"+\"), and parenthesized
-- expressions.
exp10 :: ReadP SymReal
exp10 = parenthesized +++ const_pi +++ const_e +++ integer +++ float +++ unary_fun +++ binary_fun

-- | /parenthesized/ ::= \"(\" /exp6/ \")\".
parenthesized :: ReadP SymReal
parenthesized = do
  string "("
  skipSpaces
  n <- exp6
  skipSpaces
  string ")"
  return n

-- | /expression/ ::= /exp6/ /end-of-line/.
--   
-- This is a top-level expression.
expression :: ReadP SymReal
expression = do
  skipSpaces
  s <- exp6
  skipSpaces
  eof
  return s

-- ----------------------------------------------------------------------
-- ** Top-level parser

-- | Parse a symbolic real number expression. Typical strings that can
-- be parsed are @\"1.0\"@, @\"pi\/128\"@, @\"(1+sin(pi\/3))^2\"@, etc. If
-- the expression cannot be parsed, return 'Nothing'.
parse_SymReal :: String -> Maybe SymReal
parse_SymReal s =
  case readP_to_S expression s of
    (h, ""):_ -> Just h
    _ -> Nothing
