{-# LANGUAGE GeneralizedNewtypeDeriving,
    ScopedTypeVariables,
    BangPatterns,
    OverloadedStrings,
    TypeOperators,
    DeriveFunctor,
    DeriveFoldable,
    FlexibleInstances
    #-}


-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : non-portable (GNTD, DeriveFunctor, OverloadedStrings)
--
-- Lightweight parsing library based on partial functions.
--
-------------------------------------------------------------------------------------

module Data.Sparser (
        -- * Sparser
        SparserT,
        Sparser,
        asSparser,

        -- * Running
        runSparser,
        runSparserT,
        runSparserT',
        withState,

        -- * Primitives
        stateP,
        -- mapStateP,
        -- mapInputP,
        headP,
        splitP,
        gateP,
        atEnd,

        -- * Basic parsers
        char,         
        notChar,
        charIf,
        string,
        stringIf,
        space,
        integer,
        stringLiteral,
        brackets,
        braces,
        complete,
        ifState,

        -- * Combinators
        between,
        skipMany1,
        skipMany,
        many1,
        sepBy,
        sepBy1,
        sepEndBy1,
        sepEndBy,
        endBy1,
        endBy,
        count
) where

import Data.Char
import Data.Maybe -- DEBUG
import Data.Ratio -- DEBUG
import Data.String
import Data.Tree
import Data.Default
import Data.Pointed
import Data.Semigroup
import Data.Foldable(Foldable)
import Control.Applicative
import Control.Monad.Plus


newtype a ?-> b = PartialP { getPartialP :: a -> Maybe (a, b) }

instance Functor ((?->) r) where
    fmap f (PartialP g) = PartialP (fmap (fmap f) . g)

instance Monad ((?->) r) where
    return x = PartialP (\a -> Just (a, x))
    PartialP f >>= k = PartialP $ \r -> (f r >>= \(r1, x) -> getPartialP (k x) r1)

instance MonadPlus ((?->) r) where
    mzero = PartialP (const Nothing)
    PartialP f `mplus` PartialP g = PartialP $ \x -> f x `mplus` g x

instance Applicative ((?->) r) where
    pure  = return
    (<*>) = ap

instance Alternative ((?->) r) where
    empty = mzero
    (<|>) = mplus

instance Pointed ((?->) r) where
    point = return

instance Semigroup ((?->) a b) where
    (<>) = mplus

instance Monoid ((?->) a b) where
    mempty  = mzero
    mappend = mplus


----------

newtype SparserT s a b = SparserT { getSparserT :: (s, [a]) ?-> b }
    deriving (Semigroup, Monoid, Functor, Pointed, Applicative, Alternative, Monad, MonadPlus)

asSparser = id
asSparser :: Sparser a -> Sparser a

instance IsString (SparserT s Char String) where
    fromString = string

type Sparser = SparserT () Char

-- | 
-- Run a parser, returning the result.
-- 
runSparser :: Sparser a -> String -> Maybe a
runSparser p = runSparserT p ()

-- | 
-- Run a parser with a custom state, returning the result.
-- 
runSparserT :: SparserT s a b -> s -> [a] -> Maybe b
runSparserT = curry . fmap (fmap snd) . getPartialP . getSparserT

-- | 
-- Run a parser with a custom state.
--
-- This is the most general way to run a parser. It returns the final state,
-- remaining input and the result.
-- 
runSparserT' :: SparserT s a b -> s -> [a] -> Maybe (s, [a], b)
runSparserT' = curry . fmap (fmap untrip) . getPartialP . getSparserT
    where untrip ((a,b),c) = (a,b,c)

withState :: (s -> t) -> (t -> s) -> SparserT t a b -> SparserT s a b
withState setup teardown (SparserT (PartialP f)) = (SparserT (PartialP $ ws f))
    where
        ws f = fmap (first (first teardown)) . f . first setup


----------

-- | Return the state as result.
stateP :: SparserT s a s
stateP = (SparserT (PartialP st))
    where
        st = \(s, as) -> Just ((s, as), s)

{-
-- | Transform state.
mapStateP :: (s -> s) -> SparserT s a ()
mapStateP f = (SparserT (PartialP st))
    where
        st = \(s, as) -> Just ((f s, as), ())

-- | Transform input.
mapInputP :: ([a] -> [a]) -> SparserT s a ()
mapInputP f = (SparserT (PartialP st))
    where
        st = \(s, as) -> Just ((s, f as), ())
-}


-- | Consumes one input element.
--
--   Fails if the predicate fails, or if there is no more input.
--
headP :: (s -> a -> Bool) -> SparserT s a a
headP  = SparserT . PartialP . headP'


-- | Consume one or more input elements.
--
--   The given function receives the /entire/ remaining input, and must return
--   the number of consumed elements.
--
--   Fails if the predicate return 0 or less, or if there is no more input.
--
splitP :: (s -> [a] -> Int) -> SparserT s a [a]
splitP = SparserT . PartialP . splitP'

-- | Succeed based on predicate, but do not consume input.
--
--   The given function receives the /entire/ remaining input.
--
gateP :: (s -> [a] -> Bool) -> SparserT s a ()
gateP = SparserT . PartialP . gateP'

atEnd :: SparserT s a ()
atEnd = SparserT $ PartialP atEnd'


headP' :: (s -> a -> Bool) -> (s, [a]) -> Maybe ((s, [a]), a)
headP' p (s, [])     = Nothing
headP' p (s, (x:xs)) = if not (p s x) then Nothing else Just ((s, xs), x)

splitP' :: (s -> [a] -> Int) -> (s, [a]) -> Maybe ((s, [a]), [a])
splitP' p (s, []) = Nothing
splitP' p (s, ys) = let n = p s ys in if n < 1 then Nothing else Just ((s, drop n ys), take n ys)

gateP' :: (s -> [a] -> Bool) -> (s, [a]) -> Maybe ((s, [a]), ())
gateP' p (s, [])    = Nothing
gateP' p (s, xs)    = if not (p s xs) then Nothing else Just ((s, xs), ())


atEnd' :: (s, [a]) -> Maybe ((s, [a]), ())
atEnd' (s, []) = Just ((s, []), ())
atEnd' (s, xs) = Nothing

----------

complete :: SparserT s a b -> SparserT s a b
complete x = do
    res <- x
    atEnd
    return res


ifState :: (s -> Bool) -> SparserT s a b -> SparserT s a b
ifState p x = gateP (\s _ -> p s) >> x

-- char :: Char -> Sparser Char

char c = charIf (== c)

notChar c = charIf (/= c)

-- charIf :: (Char -> Bool) -> Sparser Char
charIf p = headP (const p)

-- string :: String -> Sparser String
string s = stringIf (length s) (== s)

-- stringIf :: Int -> (String -> Bool) -> Sparser String
stringIf n p = splitP (\_ xs -> if p (take n xs) then n else 0)

----------

-- Use applicative optional
between open close p
                        = do{ open; x <- p; close; return x }
skipMany1 p             = do{ p; skipMany p }
skipMany p              = scan
                        where
                          scan  = do{ p; scan } <|> return ()
many1 p                 = do{ x <- p; xs <- many p; return (x:xs) }
sepBy p sep             = sepBy1 p sep <|> return []
sepBy1 p sep            = do{ x <- p
                            ; xs <- many (sep >> p)
                            ; return (x:xs)
                            }
sepEndBy1 p sep         = do{ x <- p
                            ; do{ sep
                                ; xs <- sepEndBy p sep
                                ; return (x:xs)
                                }
                              <|> return [x]
                            }
sepEndBy p sep          = sepEndBy1 p sep <|> return []
endBy1 p sep            = many1 (do{ x <- p; sep; return x })
endBy p sep             = many (do{ x <- p; sep; return x })
count n p               | n <= 0    = return []
                        | otherwise = sequence (replicate n p)

----------

space   = many1 (charIf isSpace)
symbol  = many1 (charIf isAlphaNum)

integer :: SparserT s Char Integer
integer = read <$> many1 (charIf isDigit)

stringLiteral :: SparserT s Char String
stringLiteral = between (char '"') (char '"') $ many (notChar '"')

brackets = between (char '{') (char '}')
braces   = between (char '[') (char ']')

----------

-- Tests




test :: SparserT Int Char String
test = withState id id $ do
    ifState (== 0) $ string "name:"
    optional space        
    n <- symbol
    m <- withState (+ 10) (subtract 10) stateP
    optional space
    many1 (string ";")
    optional space
    return ("Name is " ++ n ++ ", state is " ++ show m)


data JSON
    = Object [(String, JSON)]
    | Array [JSON]
    | String String
    | Number Double
    | Boolean Bool
    |Null
    deriving (Eq, Ord, Show)

json :: SparserT s Char JSON
json = empty
    <|> (Object                   <$> members)
    <|> (Array                    <$> elements)
    <|> (String                   <$> stringLiteral)
    <|> ((Number . fromIntegral)  <$> integer)
    <|> (const (Boolean False)    <$> string "false")
    <|> (const (Boolean True)     <$> string "true")
    <|> (const Null               <$> string "null")     
    where  
        members  = brackets (pair `sepBy` (char ',' >> optional space))
        elements = braces (value `sepBy` (char ',' >> optional space))

        pair  = do
            n <- stringLiteral
            optional space
            string ":"
            optional space
            v <- json
            return (n, v)
            
        value = json

----------

type Duration = Double

data Rhythm a
    = Beat       !Duration a                     -- d is divisible by 2
    | Group      ![Rhythm a]                     -- normal note sequence
    | Dots       !Int !(Rhythm a)                 -- n > 0.
    | Tuplet     !Duration !(Rhythm a)            -- d is an emelent of 'tupletMods'.
    deriving (Eq, Show, Functor, Foldable)

rhTree :: Show a => Rhythm a -> Tree String
rhTree = go
    where
        go (Beat d a)   = Node (showDur d{- ++ ":" ++ show a-}) []
        go (Group as)   = Node "" (fmap rhTree as)
        go (Dots n a)   = Node ("dot:" ++ show n) [rhTree a]
        go (Tuplet d a) = Node ("tuplet:" ++ showDur d) [rhTree a]
        -- (realToFrac d :: Double)    
        
        showDur x = show (numerator (toRational x)) ++ "/"
                 ++ show (denominator (toRational x))

putRh :: Show a => Maybe (Rhythm a) -> IO () 
putRh = putStrLn . drawTree . rhTree . fromMaybe (error "Could not quantize")

instance Semigroup (Rhythm a) where
    (<>) = mappend
instance Monoid (Rhythm a) where
    mempty = Group []
    Group as `mappend` Group bs   =  Group (as <> bs)
    r        `mappend` Group bs   =  Group ([r] <> bs)
    Group as `mappend` r          =  Group (as <> [r])

type Quant s a = SparserT s (Duration, a) (Rhythm a)

data QuantState = QuantState {
        timeMod_ :: Duration,  
        recur_   :: Int
    }          
    deriving (Eq, Show)
instance Default QuantState where def = QuantState {
        timeMod_ = 1,
        recur_   = 0
    }

class HasTimeScale a where                          
    getTimeScale :: a -> Duration
    mapTimeScale :: (Duration -> Duration) -> a -> a
instance HasTimeScale () where
    mapTimeScale f  = id          
    getTimeScale () = 1       
instance HasTimeScale QuantState where
    getTimeScale = timeMod_
    mapTimeScale f (QuantState tm r) = QuantState (f tm) r

class HasRecur a where
    getRecur :: a -> Int
    mapRecur :: (Int -> Int) -> a -> a
    recur, unrecur :: a -> a
    recur = mapRecur succ
    unrecur = mapRecur pred
    guardRecur :: SparserT a m n -> SparserT a m n
    guardRecur = ifState (\x -> getRecur x < kMaxRecur)
instance HasRecur QuantState where
    getRecur                     = recur_
    mapRecur f (QuantState tm r) = QuantState tm (f r)

testQuant :: Quant QuantState () -> [Duration] -> Maybe (Rhythm ())
testQuant p = quant p . (`zip` repeat ())

quant :: Default s => Quant s a -> [(Duration, a)] -> Maybe (Rhythm a)
quant p = quant' p def

quant' :: Quant s a -> s -> [(Duration, a)] -> Maybe (Rhythm a)
quant' = runSparserT




allDivs :: (HasTimeScale s, HasRecur s) => Quant s a -> Quant s a
allDivs x = msum $ fmap (`scaleTime` x) divs
    where
        divs = [8,4,2,1] ++ fmap (recip.(2^)) [1..5] :: [Duration]


-- Tries to match 2.5, then shorter
rh5 :: (HasTimeScale s, HasRecur s) => Quant s a
rh5 = group [rh4,rh3] <|> group [rh3,rh4]

-- Tries to match 1, then shorter
rh4 :: (HasTimeScale s, HasRecur s) => Quant s a
rh4 = withState recur unrecur $guardRecur $ empty
    -- 1
    <|> note

    -- 1/4 1/4 1/4 1/4
    <|> (quarter (group [rh4, rh4, rh4, rh4]))

    -- 1/2 1/2
    <|> (half (group [rh4, rh4]))

    -- 1/4 1/2 1/4
    <|> (half (group [half rh4, rh4, half rh4]))

    -- dotted figures
    <|> (half (group [rh3, half rh4]))
    <|> (half (group [half rh4, rh3]))



-- Tries to match 1+1/2, then shorter
rh3 :: (HasTimeScale s, HasRecur s) => Quant s a
rh3 = withState recur unrecur $empty
    -- 1+1/2
    <|> dot note

    -- 1/2 1/2 1/2
    <|> (triple (half (group [rh4, rh4, rh4])))

    -- 1 1/2
    <|> (group [unit rh4, half rh4])

    -- 1/2 1
    <|> (group [half rh4, unit rh4])


-- Tries to match something in scale 1.5
dot, unit, double, half, triple, quarter :: (HasTimeScale s, HasRecur s) => Quant s a -> Quant s a
dot = fmap (Dots 1) . scaleTime (3/2)
unit = scaleTime (2/2)
double = scaleTime (2/1)
half = scaleTime (1/2)
quarter = scaleTime (1/4)
triple = scaleTime (1/3)

-- Tries to match 1 as a note
note :: (HasTimeScale s, HasRecur s) => Quant s a
note = noteIf (\s d x -> d / getTimeScale s == 1)

scaleTime :: (HasTimeScale s, HasRecur s) => Duration -> Quant s a -> Quant s a
scaleTime n = withState 
    (mapTimeScale (* n)) 
    (mapTimeScale (/ n))

group :: [Quant s a] -> Quant s a
group xs = Group <$> sequence xs

kMaxRecur = 6
-- 5
r = [2,2,1,1,1,1,  2,2,1,3, 0.5,0.5,1,1,1] :: [Duration]

-- 5
r2 = [2,2, 1,1,1,2,1,2, 1,3, 0.5,0.5,1,1,1] :: [Duration]

-- 4
r3 = [1,1,2,3,1]:: [Duration]


-- Mathes a single note whose duration is simple
-- note :: Quant s a
-- note = noteIf (\s d x -> isDivisibleBy 2 d)



noteIf :: (s -> Duration -> a -> Bool) -> Quant s a
noteIf p = uncurry beat <$> headP (\s (d,x) -> p s d x)
    where
        beat :: Duration -> a -> Rhythm a
        beat d x = Beat d x
        





-- As it sounds
isDivisibleBy :: Duration -> Duration -> Bool
isDivisibleBy n = (== 0.0) . snd . properFraction . logBaseR (toRational n) . toRational

logBaseR :: forall a . (RealFloat a, Floating a) => Rational -> Rational -> a
logBaseR k n
    | isInfinite (fromRational n :: a)      = logBaseR k (n/k) + 1
logBaseR k n
    | isDenormalized (fromRational n :: a)  = logBaseR k (n*k) - 1
logBaseR k n                         = logBase (fromRational k) (fromRational n)


----------

first f (a, b) = (f a, b)
single x = [x]
list z f xs = case xs of
    [] -> z
    ys -> f ys

-- [a,b,c,d,e,f,g,x,y,z,m,n,o,p,q,r] = undefined