-- ParserFunction provides utilities for parsing and evaluating mathematical expressions. The central parsing 
-- function in this package is stringToExpr, which parses a string-expression and returns a maybe expression tree.
--
-- EXAMPLE:
-- > stringToExpr "e^(1-x)*cos(pi*y)"
-- > Just (Mul (Pow (Var "e") (Sub (Num 1.0) (Var "x"))) (Cos (Mul (Var "pi") (Var "y"))))
--
-- This type is suitable for performing symbolic manipulation.
-- 
-- Expressions can then be evaluated using the function evalExpr. 
--
-- EXAMPLE:
-- > evalExpr ((Mul (Pow (Var "e") (Sub (Num 1.0) (Var "x"))) (Cos (Mul (Var "pi") (Var "y"))))) [("x",1),("y",0)]
-- > Just (1.0 :+ 0.0)
--
-- If you wish to evaluate a string-expression without any intermediate operations, simply use the function evalString.
--
-- EXAMPLE:
-- > evalString "e^(1-x)*cos(pi*y)" [("x",1),("y",0)]
-- > Just (1.0 :+ 0.0)
--
-- EXAMPLE:
-- > evalString "e^(-pi*i)+1" []
-- > Just (0.0 :+ (-1.2246467991473532e-16))
--

module Text.ParserCombinators.Parsec.ParserFunction
  (Expr,evalString,evalExpr,stringToExpr,buildExpr,eval) where

import Text.ParserCombinators.Parsec.Expr 
import Text.ParserCombinators.Parsec
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
import Data.List (isInfixOf)
import Data.Char (toLower)
import Data.Complex

type Variable = String

-- |The Expr data type provides a basis for ordering mathematical operations.
data Expr = 
  Num Double    | Var String    | Sub Expr Expr |
  Div Expr Expr | Pow Expr Expr | Log Expr      |
  Abs Expr      | Sqrt Expr     | Cbrt Expr     |
  ArcSinh Expr  | ArcCosh Expr  | ArcTanh Expr  |
  ArcSin Expr   | ArcCos Expr   | ArcTan Expr   |
  Sinh Expr     | Cosh Expr     | Tanh Expr     |
  Sin Expr      | Cos Expr      | Tan Expr      |
  ArcSech Expr  | ArcCsch Expr  | ArcCoth Expr  |
  ArcSec Expr   | ArcCsc Expr   | ArcCot Expr   |
  Sech Expr     | Csch Expr     | Coth Expr     |
  Sec Expr      | Csc Expr      | Cot Expr      |
  Mul Expr Expr | Add Expr Expr | Exp Expr      deriving (Show, Eq)

-- |@evalExpr@ evaluates an expression tree using a list of variable definitions with values. 
evalExpr :: Expr -> [(Variable,Complex Double)] -> Maybe (Complex Double)
evalExpr e m = eval (M.fromAscList $ caseMap m) (Just e)
  where caseMap x = fmap (\(a,b)->(map toLower a, b)) x

-- |@evalString@ evaluates a string-expression using a list of variable definitions with values. 
evalString :: String -> [(Variable,Complex Double)] -> Maybe (Complex Double)
evalString s m = eval (M.fromAscList $ caseMap m) (stringToExpr s)
  where caseMap x = fmap (\(a,b)->(map toLower a, b)) x

-- |@stringToExpr@ parses a string-expression and returns a maybe expression tree.
stringToExpr :: String -> Maybe Expr
stringToExpr xs =
  if null xs || any (==True) (symbols failingSymbols xs)
  then Nothing
  else either (const Nothing) (Just) (parse buildExpr "" handleString)
  where
    handleString   = "(" ++ (map toLower $ filter (/=' ') xs) ++ ")"
    symbols [] y   = []
    symbols x  y   = [isInfixOf (head x) y] ++ (symbols (drop 1 x) y)
    failingSymbols = [
      "^^","^*","^/","^+","^-","*^","**","*/","*+","*-",
      "/^","/*","//","/+","/-","+^","+*","+/","++","+-",
      "-^","-*","-/","-+","--","()"]

buildExpr :: Parser Expr
buildExpr = buildExpressionParser expressionTable factor

expressionTable :: [[Operator Char st Expr]]
expressionTable =  [
  [pr "arcsinh" ArcSinh, pr "arcsin" ArcSin, pr "sinh" Sinh, pr "sin" Sin],
  [pr "arccosh" ArcCosh, pr "arccos" ArcCos, pr "cosh" Cosh, pr "cos" Cos],
  [pr "arctanh" ArcTanh, pr "arctan" ArcTan, pr "tanh" Tanh, pr "tan" Tan],
  [pr "arcsech" ArcSech, pr "arcsec" ArcSec, pr "sech" Sech, pr "sec" Sec],
  [pr "arccsch" ArcCsch, pr "arccsc" ArcCsc, pr "csch" Csch, pr "csc" Csc],
  [pr "arccoth" ArcCoth, pr "arccot" ArcCot, pr "coth" Coth, pr "cot" Cot],
  [pr "log" Log, pr "abs" Abs,pr "exp" Exp],
  [pr "sqrt" Sqrt, pr "cbrt" Cbrt],
  [op "^" Pow AssocRight],
  [op "*" Mul AssocLeft, op "/" Div AssocLeft],
  [op "+" Add AssocLeft, op "-" Sub AssocLeft]]
  where
    op s f assoc = Infix  (do{ string s; return f}) assoc
    pr s f       = Prefix (try (string s) >> return f)

factor :: Parser Expr
factor = do
  char '('
  e <- buildExpr
  char ')'
  return e
  <|> variables

variables :: Parser Expr
variables = do
  ds <- many1 letter
  return $ Var ds
  <|> number

number :: Parser Expr
number = do
    br  <- many digit
    let d :: Double
        d = fromInteger (foldl ((. ch2num) . (+) . (*10)) 0 br)
    option (Num (d)) (try (do
        char '.'
        ar <- many1 digit
        return $ (Num (d + foldr (fd) 0 ar)) ))
        where
            fd a b = (fromInteger (ch2num a) + b) / 10
            fe = toInteger . fromEnum
            ch2num = (subtract $ fe '0') . fe

-- |@eval@ takes a map of variable definitions and values, and a maybe expression tree, to produce maybe a numerical value.
eval :: M.Map Variable (Complex Double) -> Maybe Expr -> Maybe (Complex Double)
eval m expr =
  case expr of
    Just (Num d)      -> Just $ d :+ 0
    Just (Var "pi")   -> Just $ pi
    Just (Var "i")    -> Just $ 0 :+ 1
    Just (Var "e")    -> Just $ exp 1
    Just (Var c)      -> M.lookup c m
    Just (Add e1 e2)  -> factorMaybe2 (eval m $ Just e1) (eval m $ Just e2) (+)
    Just (Sub e1 e2)  -> factorMaybe2 (eval m $ Just e1) (eval m $ Just e2) (-)
    Just (Mul e1 e2)  -> factorMaybe2 (eval m $ Just e1) (eval m $ Just e2) (*)
    Just (Div e1 e2)  -> factorMaybe2 (eval m $ Just e1) (eval m $ Just e2) (/)
    Just (Pow e1 e2)  -> factorMaybe2 (eval m $ Just e1) (eval m $ Just e2) (**)
    Just (Exp e1)     -> factorMaybe1 (eval m $ Just e1) (exp)
    Just (Sqrt e1)    -> factorMaybe1 (eval m $ Just e1) (\x->x**(0.5))
    Just (Cbrt e1)    -> factorMaybe1 (eval m $ Just e1) (\x->x**(1/3))
    Just (Log e1)     -> factorMaybe1 (eval m $ Just e1) (log)
    Just (Abs e1)     -> factorMaybe1 (eval m $ Just e1) (abs)
    Just (Sin e1)     -> factorMaybe1 (eval m $ Just e1) (sin)
    Just (Cos e1)     -> factorMaybe1 (eval m $ Just e1) (cos)
    Just (Tan e1)     -> factorMaybe1 (eval m $ Just e1) (tan)
    Just (Sec e1)     -> factorMaybe1 (eval m $ Just e1) (\x->1/sin x)
    Just (Csc e1)     -> factorMaybe1 (eval m $ Just e1) (\x->1/cos x)
    Just (Cot e1)     -> factorMaybe1 (eval m $ Just e1) (\x->1/tan x)
    Just (Sinh e1)    -> factorMaybe1 (eval m $ Just e1) (sinh)
    Just (Cosh e1)    -> factorMaybe1 (eval m $ Just e1) (cosh)
    Just (Tanh e1)    -> factorMaybe1 (eval m $ Just e1) (tanh)
    Just (Sech e1)    -> factorMaybe1 (eval m $ Just e1) (\x->1/sinh x)
    Just (Csch e1)    -> factorMaybe1 (eval m $ Just e1) (\x->1/cosh x)
    Just (Coth e1)    -> factorMaybe1 (eval m $ Just e1) (\x->1/tanh x)
    Just (ArcSin e1)  -> factorMaybe1 (eval m $ Just e1) (asin)
    Just (ArcCos e1)  -> factorMaybe1 (eval m $ Just e1) (acos)
    Just (ArcTan e1)  -> factorMaybe1 (eval m $ Just e1) (atan)
    Just (ArcSec e1)  -> factorMaybe1 (eval m $ Just e1) (\x->1/asin x)
    Just (ArcCsc e1)  -> factorMaybe1 (eval m $ Just e1) (\x->1/acos x)
    Just (ArcCot e1)  -> factorMaybe1 (eval m $ Just e1) (\x->1/atan x)
    Just (ArcSinh e1) -> factorMaybe1 (eval m $ Just e1) (asinh)
    Just (ArcCosh e1) -> factorMaybe1 (eval m $ Just e1) (acosh)
    Just (ArcTanh e1) -> factorMaybe1 (eval m $ Just e1) (atanh)
    Just (ArcSech e1) -> factorMaybe1 (eval m $ Just e1) (\x->1/asinh x)
    Just (ArcCsch e1) -> factorMaybe1 (eval m $ Just e1) (\x->1/acosh x)
    Just (ArcCoth e1) -> factorMaybe1 (eval m $ Just e1) (\x->1/atanh x)
    _                      -> Nothing
    where
      factorMaybe1 :: Maybe a -> (a -> a) -> Maybe a
      factorMaybe1 (Just x) f = Just $ f x
      factorMaybe1 _        _ = Nothing
      factorMaybe2 :: Maybe a -> Maybe a -> (a -> a -> a) -> Maybe a
      factorMaybe2 (Just x) (Just y) f = Just $ f x y
      factorMaybe2 _        _        _ = Nothing