{-# LANGUAGE FlexibleContexts #-}
-----------------------------------------------------------------------------
-- Copyright 2019, Advise-Me project team. This file is distributed under 
-- the terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- This module exports a set of functions that build upon the primitives from "Recognizer.Common.Parsing.Parse"
-- 
-----------------------------------------------------------------------------

module Recognize.Parsing.Derived
   ( choice, skip, peek, maybeToParse, peek2, choice'
   , choiceFor, choiceFor', succeedIf, pFoldAlt
   , few, satisfyEq, pInOrder, pRepeat, withInput
   , pInOrderAll, getInputSize, getBinary, many1, pSkipUntil
   , many', many1', pAnyExpr, pReplicate, pAnyOf, pSomewhere
   , pAnywhere, nonEmpty, pRewrite, peekExpr, peekEq, pExprWith
   , succeed, pEq, pEqCom, pExprCom, pExpr
   ) where

import Control.Applicative
import Control.Monad
import Data.List
import Data.Maybe
import Domain.Math.Data.Relation
import Domain.Math.Expr hiding (pExpr)
import Ideas.Common.Rewriting (getFunction)
import Ideas.Common.View
import Recognize.Data.Attribute
import Recognize.Data.Math
import Recognize.Expr.Normalform
import Recognize.Parsing.Parse

-- | Skips a single input
skip :: Parse m s => m s
skip = satisfy (const True)

-- | Synonym for `pure` and `return`
succeed :: Parse m s => a -> m a
succeed = pure

-- | Greedy version of `many`
many' :: Parse m s => m a -> m [a]
many' p = (:) <$> p <*> many' p
                  |> succeed []

-- | Perform one parsing followed by `many`
many1 :: Parse m s => m a -> m [a]
many1 p = (:) <$> p <*> many p

-- | Greedy version of `many1`
many1' :: Parse m s => m a -> m [a]
many1' p = (:) <$> p <*> many' p

-- | Alternative to `many` where the order of the branches is reversed.
--
-- Here the first branch is an always successful parsing, the 2nd branch does one parsing
--
-- The third does two parsings and so on..
--
-- A common usage:
--
-- >>> p >> few skip >> q
--
-- We want to parse @q@ and close as possible to @p@, but we do not mind skipping as much as necessary.
few :: Parse m s => m a -> m [a]
few p = return [] <|> ((:) <$> p >> few p)

-- | Fails if the resulting list is empty
nonEmpty :: Parse m s => m [a] -> m [a]
nonEmpty p = do xs <- p
                when (null xs) empty
                return xs

-- | Fold over <|>
choice :: Parse m s => [m a] -> m a
choice = foldl (<|>) empty

-- | Fold over |>
choice' :: Parse m s => [m a] -> m a
choice' = foldl (|>) empty

-- | Combination of choice and for
choiceFor :: Parse m s => [a] -> (a -> m b) -> m b
choiceFor as f = foldl (\acc a -> acc <|> f a) empty as

-- | Combination of choice' and for
choiceFor' :: Parse m s => [a] -> (a -> m b) -> m b
choiceFor' as f = foldl (\acc a -> acc |> f a) empty as

-- | Fails if predicate doesn't hold
succeedIf :: Parse m s => (a -> Bool) -> a -> m a
succeedIf p a | p a = return a
              | otherwise = empty

-- | Apply a function to the remaining input and return the result
--
-- Does not parse input.
withInput :: Parse m s => ([s] -> a) -> m a
withInput f = withInputList (return . f)

getInputSize :: Parse m s => m Int
getInputSize = withInput length

-- | Fails on Nothing. Succeeds on Just.
maybeToParse :: Parse m s => Maybe a -> m a
maybeToParse Nothing = empty
maybeToParse (Just a) = succeed a

-- | Returns the head of input. Fails if there is none.
peek :: Parse m s => m s
peek = do
  ms <- safePeek
  case ms of
    Nothing -> empty
    Just s -> return s

-- | Returns the top two inputs. Fails if there are less than 2 input remaining.
peek2 :: Parse m s => m (s, s)
peek2 = do
  ss <- withInput id
  case ss of
    (x:y:_) -> return (x,y)
    _ -> empty

-- | Continuously skip input until parsing succeeds.
pSkipUntil :: Parse m s => m a -> m (a,[s])
pSkipUntil p = ((\a -> (a,[])) <$> p) |> (skip >>= \s -> second (s:) <$> pSkipUntil p)

-- | Similar to `pFold`, except that we only return the last computed result
pFoldAlt' :: (Parse m s, Show a) => (a -> m a) -> a -> m a
pFoldAlt' f a = do
  a' <- f a
  pFoldAlt' f a' <|> return a'

-- | Only calls `pFoldAlt'` if the predicate is True. Otherwise parses once.
pFoldAlt :: (Parse m s, Show a) => Bool -> (a -> m a) -> a -> m a
pFoldAlt b f a
  | b = pFoldAlt' f a
  | otherwise = f a

-- | Skip input until a successful parsing
pSomewhere :: Parse m s => m a -> m a
pSomewhere p = p |> (skip *> pSomewhere p)

-- | Skip input until a successful parsing. Skip all remaining input.
pAnywhere :: Parse m s => m a -> m a
pAnywhere p = do
   x <- pSomewhere p
   _ <- many' skip
   return x

-- | Skip input until one of the parsers succeeds. Applies this procedure many times.
pAnyOf :: Parse m s => [m a] -> m [a]
pAnyOf ps = nonEmpty $ catMaybes <$> many' (Just <$> choice' ps |> Nothing <$ skip)

-- | Execute a parser N times.
pReplicate :: Parse m s => Int -> m a -> m [a]
pReplicate 0 _ = succeed []
pReplicate i p | i > 0 = (:) <$> p <*> pReplicate (i-1) p
               | otherwise = fail "Recognize.Parsing.Parse: replicate bounds below zero"

-- | Specialised version of `peek` for `Expr`
peekExpr :: Parse m Math => m Expr
peekExpr = peek >>= getExpr

-- | Specialised version of `peek` for `Equation Expr`
peekEq :: Parse m Math => m (Equation Expr)
peekEq = peek >>= getEq

-- | Parse an expression that has exactly two children
getBinary :: Parse m Math => Expr -> m (Expr, Expr)
getBinary e =
   case getFunction e of
      Just (_, [x, y]) -> return (x,y)
      _ -> empty

-- | Parse an input as an `Equation Expr` whose both sides must satisfy their respective predicates.
satisfyEq :: Parse m Math => (Expr -> Bool) -> (Expr -> Bool) -> m (Equation Expr)
satisfyEq f g = do
  (x :==: y) <- peek >>= getEq
  if f x && g y then skip >> return (x :==: y)
                else empty

-- | Skip an input and return a different value instead
pRewrite :: Parse m s => a -> m a
pRewrite a = skip >> return a

-- | Parses any `Expr`
pAnyExpr :: Parse m Math => m Expr
pAnyExpr = (peek >>= getExpr) <* skip

-- | Parse an `Expr` that is equal to the argument `Expr`
pExpr :: Parse m Math => Expr -> m ()
pExpr e = () <$ pExprWith (\a -> (a, [])) e

-- | Parse an `Expr` that is equal to the argument `Expr`.
--
-- Also takes a function that may be applied to both the argument and input `Expr`.
pExprWith :: Parse m Math => (Expr -> (Expr, [Attribute])) -> Expr -> m (Expr, [Attribute])
pExprWith f x = satisfyWith $ \m ->
    case getExpr m of
      Just y -> do
        let (fx,attr1) = f (toExpr x)
        let (fy,attr2) = f y
        guard $ fx == fy
        return (y, attr1 \\ attr2)
      Nothing -> Nothing

-- | Parse an `Equation` that is equal to the argument `Equation`
pEq :: Parse m Math => Equation Expr -> m ()
pEq e = () <$ pEqWith (\a -> (a, [])) e

-- | Parse an `Equation` that is equal to the argument `Equation`
--
-- Also takes a function that may be applied to both the argument and input `Expr`.
pEqWith :: Parse m Math => (Expr -> (Expr,[Attribute])) -> Equation Expr -> m (Equation Expr,[Attribute])
pEqWith f x = satisfyWith (\m ->
    case getEq m of
      Just y -> do
        let (fxl,_)   = f $ leftHandSide x
            (fxr,_)   = f $ rightHandSide x
            (fyl,ayl) = f $ leftHandSide y
            (fyr,ayr) = f $ rightHandSide y
        guard $ fxl == fyl && fxr == fyr
        return (y,ayl \\ ayr)
      Nothing -> Nothing)

-- | Parse an expression that matches the argument expression modulo commutativity
pExprCom :: Parse m Math => Expr -> m ()
pExprCom e = () <$ pExprWith (\a -> (nfCom a,[])) e

-- | Parse an equation that matches the argument expression modulo commutativity
pEqCom :: Parse m Math => Equation Expr -> m ()
pEqCom e = () <$ pEqWith (\a -> (nfCom a,[])) e

-- | This function is given a list of parsers that must all succeed in the order of the list.
--
-- Input may be skipped as much as necessary. The result of the previous parsing is also available.
pInOrderAll :: Parse m Math => [Maybe (Expr,[r]) -> m (Expr,[r])] -> m (Expr, [r],[Math])
pInOrderAll = rec Nothing 1 []
 where
   rec _ e sk [] = return (e, [], sk)
   rec m _ sk (x:xs) = do
      sk' <- few skip
      (e1, st) <- x m
      (e2, rs, ms) <- rec (Just (e1, st)) e1 (sk ++ sk') xs
      return (e2, st ++ rs, ms)

-- | This function is given a list of parsers that are executed in order of the list.
--
-- Input may be skipped as much as necessary. A parser may fail (none of the remaining input could be parsed) and thus is skipped.
--
-- The result of each previous successful parsing is available.
pInOrder :: (ParseLog m, Parse m Math) => [Maybe (Expr,[r]) -> m (Expr,[r])] -> m (Expr, [r], [Math])
pInOrder = rec Nothing 1 []
 where
   rec _ e sk [] = return (e, [], sk)
   rec m e sk (x:xs) = do
      n <- getInputSize
      pLog $ "I Size: " ++ show n
      choice
         [ do
            pLog "before skip"
            sk' <- return [] <|> (skip >> pLog "skipped" >> return [])
            -- let sk' = []
            -- s <- peek
            -- pLog ("pInOrder: " ++ show s)
            (e',st) <- x m
            pLog "was success"
            (e'',ss,sk'') <- rec (Just (e',st)) e' (sk++sk') xs
            return (e'',st ++ ss,sk'')
         , rec m e sk xs
         ]

-- | Repeatedly execute a parser until we can no longer find any input for a successful parsing.
--
-- Input may be skipped as much as necessary between executions.
--
-- The remaining input is determined by the remaining input after the last successful parsing.
pRepeat :: Parse m Math => m (Expr,[r]) -> m ([Expr],[r],[Math])
pRepeat p = do
  eth <- choice'
    [ do
      sk <- few skip
      (e,r) <- p
      return $ Right (e, r, sk)
    , return $ Left ([],[],[])
    ]

  case eth of
    Left s -> return s
    Right (e,r,sk) -> do
      (e',r',sk') <- pRepeat p
      return (e:e',r++r',sk++sk')