module MagicHaskeller.LibExcelStaged where
import MagicHaskeller
import Data.List
import Data.Char

default (Int, Integer, Double)

-- gcd in the latest library is total, but with older versions gcd 0 0 causes an error. 
totalGCD x y =  gcd' (abs x) (abs y)
  where gcd' a 0  =  a
        gcd' a b  =  gcd' b (a `rem` b)




curry2 = curry
curry3 :: ((a,b,c) -> d) -> a->b->c->d
curry3 f x y z = f(x,y,z)
curry4 :: ((a,b,c,d) -> e) -> a->b->c->d->e
curry4 f w x y z = f(w,x,y,z)
curry5 :: ((a,b,c,d,e) -> f) -> a->b->c->d->e->f
curry5 f v w x y z = f(v,w,x,y,z)
curry6 :: ((a,b,c,d,e,f) -> g) -> a->b->c->d->e->f->g
curry6 f u v w x y z = f(u,v,w,x,y,z)

{-
The Policy:
1. During synthesis, monomorphic types including Int and Double must be used by mkTrie &c.
2. fromIntegral and floor are available when synthesizing, but they are removed by ppExcel.
3. Because of 2., functions must be defined polymorphically here, or the generation of input-output examples by the CGI would cause type mismatch.
-}

iF :: (Bool, a, a) -> a
iF (True,  t, f) = t
iF (False, t, f) = f
upper = map toUpper
lower = map toLower
{- This definition unnecessarily trims the spaces.
proper = unwords . map proper' . words
proper' ""     = ""
proper' (h:ts) = toUpper h : map toLower ts
-}
-- I think the following definition is enough. PROPER capitalizes letters even after a hyphen or an apostrophe.
proper ""     = ""
proper (c:cs) | isAlpha c = toUpper c : case span isAlpha cs of (ts, ds) -> map toLower ts ++ proper ds
              | otherwise = c : proper cs

right, left :: ([b], Int) -> [b]
left1  = take 1
right1 str = right (str, 1)
left  (b, a)    = take a b
right (b, a)    = reverse (take a (reverse b))
dropLeft b a    = right(b, len(b) - a)
mid   (c, a, b) = take b (drop (a - 1) c)
len :: Num a => String -> a
len = fromIntegral . length
concatenate (a, b) = a ++ b
concatenatE (a,b,c) = a++b++c
concatenaTE (a,b,c,d) = a++b++c++d
concatenATE (a,b,c,d,e) = a++b++c++d++e
concateNATE (a,b,c,d,e,f) = a++b++c++d++e++f


{-
max = maximum
min= minimum
average= \a -> Prelude.sum a / fromIntegral (length a)
count= \a -> length (filter (/= 0) a)
sumif ms = Prelude.sum[x|Just x <- ms]
-}
cEILING, fLOOR, mround :: (Double, Double) -> Double
cEILING (_, 0) = 0
cEILING (a, b) = fromIntegral (Prelude.ceiling (a / b))*b
mround  (_, 0) = 0
mround  (a, b) = fromIntegral (Prelude.round   (a / b))*b
-- http://office.microsoft.com/en-us/excel-help/mround-HP005209185.aspx?CTT=5&origin=HP005204211

-- As for FLOOR, FLOOR(0,0) is 0, but FLOOR(x,0) is #DIV/0 for other x's. Also, if the second argument is negative, the result is $NUM.
-- Thus, we should prepare something different, defining  \a b -> IF(b > 0, FLOOR(a,b), 0).
-- The postprocessor expands it to FLOOR(a,b) if b is known to be positive.
fLOOR0 a b | b <= 0    = 0
           | otherwise = fromIntegral (Prelude.floor   (a / b))*b
 
-- We need fLOOR in order to generate I/O examples. This definition is not exact, but it alerts anyway if its second argument is not positive.
fLOOR (0, 0) = 0 
fLOOR (a, b) | b <= 0    = 0/0
             | otherwise = fLOOR0 a b

-- これらの第2引数は切り捨てで整数にされるみたい。なので、**ではなく^^を用いなければならない。
rOUND, roundup, rounddown :: (Double, Int) -> Double
rOUND (a, b) = mround (a, 0.1 ^^ b)
roundup (a, b) | a > 0     = cEILING (a, 0.1 ^^ b)
               | otherwise = fLOOR0 a (0.1 ^^ b)
rounddown (a, b) | a < 0     = cEILING (a, 0.1 ^^ b)
                 | otherwise = fLOOR0 a (0.1 ^^ b)

trim = unwords . words
fIND :: (Num b) => (String, String, Int) -> Maybe b
fIND (pat, xs, pos) = fmap (fromIntegral . fst) $ Data.List.find (isPrefixOf pat . snd) $ zip [1..] $ drop (pos-1) $ tails xs
ifERROR :: (Maybe a, a) -> a
ifERROR (mb, x) = maybe x id mb
aND (a, b) = a && b
oR  (a, b) = a || b
sign :: Num a => a -> a
sign = signum
power (a,b) | isNaN result || isInfinite result = Nothing 
            | otherwise                         = Just result
  where result = a ** b
sQRT x | x < 0     = Nothing
       | otherwise = Just $ sqrt x
lOG(a,b) | a<=0 || b<=0 = Nothing
         | otherwise    = Just $ logBase b a
ln a | a <= 0    = Nothing
     | otherwise = Just $ Prelude.log a
pI () = pi
aTAN2 (x,y) = atan2 y x

fact n | n < 0 = Nothing
       | otherwise = Just $ product [1..n]
combin (n,r) | (signum n + 1) * (signum r + 1) * (signum (r-n) + 1) == 0 = Nothing
             | otherwise = Just $ product [n-r+1 .. n] `div` product [1..r]

mOD    :: (Int, Int) -> Maybe Int
mOD(_,0) = Nothing
mOD(m,n) = Just $ m `mod` n
degrees = ((180/pi)*)
radians = ((pi/180)*)


gCD (m, n) = totalGCD (truncate m) (truncate n)

findIx c xs n = finD(char(7), sUBSTITUTE(concatenate(c,xs), c, char(7), 1+abs(n)))-1
finD(c, xs) = maybe undefined id $ fIND(c, xs, 1)
char = (:"") . chr


sUBsTITUTE :: (String, String, String) -> String
sUBsTITUTE(ts, "", _) = ts -- 実際やってみたらこうだった.
sUBsTITUTE([], _,  _) = []
sUBsTITUTE(text@(t:ts), old, new) = case old `stripPrefix` text of Nothing   -> t   :  sUBsTITUTE(ts,   old, new)
                                                                   Just rest -> new ++ sUBsTITUTE(rest, old, new)

sUBSTITUTE :: (String, String, String, Int) -> String
sUBSTITUTE(text, old, new, num) | num <= 0  = error "#NUM"
                                | otherwise = if null old then text
                                                          else sUB text old new num
sUB ""          _   _   _ = ""
sUB text@(t:ts) old new n = case old `stripPrefix` text of Nothing               -> t : sUB ts old new n
                                                           Just rest | n<=1      -> new ++ rest
                                                                     | otherwise -> old ++ sUB rest old new (n-1)

sUBST4 :: String -> String -> String -> Int -> String
sUBST4 text old new num = sUBSTITUTE(text, old, new, 1+abs(num))

countStr :: Num a => String -> String -> a
countStr x ""  = 0
countStr x str = fromIntegral $ (length(x)-length(sUBsTITUTE(x,str,""))) `div` length (str)
$(mkCurriedDecls "'2" [|curry2|] [|(left,right,concatenate,cEILING,mround,rOUND,roundup,rounddown,ifERROR,aND,oR,power,lOG,aTAN2,combin,mOD,gCD)|])
{- The above should generate:
left'2  = curry2 left
right'2 = curry2 right
...
-}

$(mkCurriedDecls "'3" [|curry3|] [|(iF,mid,fIND,sUBsTITUTE,concatenatE)|])
{- again,
iF'3    = curry3 iF
mid'3   = curry3 mid
...
-}

$(mkCurriedDecls "'4" [|curry4|] [|concatenaTE|])

$(mkCurriedDecls "'5" [|curry5|] [|concatenATE|])

$(mkCurriedDecls "'6" [|curry6|] [|concateNATE|])