{-# LANGUAGE TupleSections #-}
-----------------------------------------------------------------------------
-- 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)
--
-- Here we implemented the diagnosis for the magictrick exercise
--
-----------------------------------------------------------------------------

module Task.MagicTrick.Recognizer where

import Control.Applicative
import Control.Arrow
import Control.Monad
import Data.List
import qualified Data.List.NonEmpty as N
import Data.Maybe
import Domain.Math.Data.Relation
import Domain.Math.Expr hiding (pExpr, fromDouble)
import Ideas.Common.Id
import Ideas.Utils.Uniplate
import Recognize.Data.Approach
import Recognize.Data.Attribute
import Recognize.Data.Math
import Recognize.Data.Op
import Recognize.Data.Diagnosis
import Recognize.Data.Step
import Recognize.Expr.Functions
import Recognize.Expr.Normalform
import Recognize.Parsing.Parse
import Recognize.Parsing.Derived
import Recognize.Parsing.MathParser      (parseMath)
import Recognize.Data.MathParserOptions
import Recognize.Strategy.Applications
import Recognize.SubExpr.SEParser
import Util.List
import Recognize.Data.MathStoryProblem
import Recognize.Data.MathParserOutput
import Recognize.Recognizer
import Task.MagicTrick.Assess
import Task.Network.MagicTrick
import Bayes.Probability ( fromDouble, Probability )
import Bayes.Network ( Node(..) )
import Bayes.Evidence ( Evidence, evidenceOfAbsence, nodeNotSet, nodeIsSet, nodeSetTo, (.=~) )

magicTrick :: MathStoryProblem
magicTrick = mathStoryProblem
   { problemId   = newId "magictrick"
   , analyzers   = [(newId "05", ana)]
   , inputFile   = Just "input/magictrick.csv"
   , networkFile = Just "networks/MagicTrick.xdsl"
   , singleNetwork = network
   }
 where
   ana = analyzer
      { recognizer = myrecognize
      , collector  = fillInMissedSteps . evidenceOfAbsence ans1 False . whereDidWeGoWrong . assess'
      }
   myrecognize po = seRecognizer pDiagnosis $ mathParserOutput po


{-
This module contains some postprocessing steps as a compromise to address a
number of issues we currently have:

1. There is an overwhelming bias toward positive rather than negative evidence.
2. There is not enough evidence in general, because the domain reasoner
allegedly produces evidence that is too shallow for the Bayesian networks to
work with. That is, it does not make "deep" judgments, like drawing conclusions
from steps that the student has skipped, or inferring that steps have been
taken from contextual clues.

Ideally, these problems would be solved by changing the way that the domain
reasoner works. However, that is infeasible in the current timeframe. Instead,
we will made judgment like these in a more ad-hoc manner: by transforming the
evidence, adding soft positive and negative evidence based on the hard, mostly
positive evidence found earlier.
-}

probHigh, probMediumLow, probLow :: [Probability]
probHigh = [fromDouble 0.8, fromDouble 0.2, fromDouble 0]
probMediumLow = [fromDouble 0.4, fromDouble 0.6, fromDouble 0]
probLow = [fromDouble 0.2, fromDouble 0.8, fromDouble 0]

-- | Postprocessing step on the found evidence: When there is no final answer,
-- but there are intermediate steps, we boldly assume that the step after the last
-- intermediate step was the culprit and we set its evidence to negative. If
-- there were no intermediate steps, we set the first step of each strategy to
-- negative.
whereDidWeGoWrong :: Evidence -> Evidence
whereDidWeGoWrong ev =
   if nodeIsSet ans1 ev
   then ev
   else mappend ev . additionalEvidence . flip map [strat1,strat2,strat3,strat4] $ \strat ->
      if studentTried strat ev
      then whereDidItGoWrong strat
      else Nothing

   where

   additionalEvidence nodes = mconcat $ case catMaybes nodes of
      [] -> map (.=~ probMediumLow) [ans1Strat1Step1, ans1Strat2Step1, ans1Strat3Step1, ans1Strat4Step1]
      other -> map (.=~ probLow) other

   studentTried :: [Node a] -> Evidence -> Bool
   studentTried strat ev' = any (flip nodeIsSet ev') strat

   whereDidItGoWrong :: [Node a] -> Maybe (Node a)
   whereDidItGoWrong strat = listToMaybe . reverse . takeWhile (flip nodeNotSet ev) . reverse $ strat

   strat1 = [ans1Strat1Step1, ans1Strat1Step2, ans1Strat1Step3, ans1Strat1Step4, ans1Strat1Step5, ans1Strat1Step6]
   strat2 = [ans1Strat2Step1, ans1Strat2Step2, ans1Strat2Step3, ans1Strat2Step4, ans1Strat2Step5, ans1Strat2Step6, ans1Strat2Step7]
   strat3 = [ans1Strat3Step1, ans1Strat3Step2]
   strat4 = [ans1Strat4Step1, ans1Strat4Step2, ans1Strat4Step3, ans1Strat4Step4, ans1Strat4Step5, ans1Strat4Step6, ans1Strat4Step7]


-- | Postprocessing steps on the found evidence. 
-- When all the nodes on the LHS are present in the evidence but none on the
-- RHS are, we set evidence to the nodes on the RHS. Note that it is evaluated
-- left-to-right, so later steps should appear first. 
fillInMissedSteps :: Evidence -> Evidence
fillInMissedSteps = applyTransformations
   [
   -- Every next step implies the previous step
      ( nodeSetTo ans1Strat1Step6 (Just True)
      , ans1Strat1Step5 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat1Step5 (Just True)
      , ans1Strat1Step4 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat1Step4 (Just True)
      , ans1Strat1Step3 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat1Step3 (Just True)
      , ans1Strat1Step2 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat1Step2 (Just True)
      , ans1Strat1Step1 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step7 (Just True)
      , ans1Strat2Step6 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step6 (Just True)
      , ans1Strat2Step5 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step5 (Just True)
      , ans1Strat2Step4 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step4 (Just True)
      , ans1Strat2Step3 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step3 (Just True)
      , ans1Strat2Step2 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step2 (Just True)
      , ans1Strat2Step1 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat3Step2 (Just True)
      , ans1Strat3Step1 .=~ probHigh )

   -- Choose variable is implied by every later step
   ,  ( nodeSetTo ans1Strat1Step3 (Just True)
      , ans1Strat1Step1 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat1Step4 (Just True)
      , ans1Strat1Step1 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step3 (Just True)
      , ans1Strat2Step1 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step5 (Just True)
      , ans1Strat2Step1 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step6 (Just True)
      , ans1Strat2Step1 .=~ probHigh )
   ,  ( nodeSetTo ans1Strat2Step7 (Just True)
      , ans1Strat2Step1 .=~ probHigh )
   ]

   where
   applyTransformations :: [(Evidence -> Bool, Evidence)] -> Evidence -> Evidence
   applyTransformations = flip $ foldl (flip applyTransformation)

   applyTransformation :: (Evidence -> Bool, Evidence) -> Evidence -> Evidence
   applyTransformation (ante, post) ev =
      case ante ev of
         True -> ev `mappend` post
         False -> ev



pDiagnosis :: SEParser Diagnosis
pDiagnosis = do
  sds <- pAttempts1
  -- TODO: Should perhaps also move the first algebraic attempt to the front
  return $ head sds

--Option that defines if we can take many buggy pars or only one
oTryManyBuggyPars :: Bool
oTryManyBuggyPars = True

--Option that defines if we can skip expressions
oSkipExpressions :: Bool
oSkipExpressions = True

-- wrapper around pAttempts; to only parse succesfull when at least one diagnosis has been matched.
pAttempts1 :: SEParser [Diagnosis]
pAttempts1 = do
  xs <- pAttempts
  when (null xs) empty
  return xs

-- | Tries to parse many attempts. If one fails then skip one input and try again until no input is left.
pAttempts :: SEParser [Diagnosis]                                 -- /--- a |> approach here, will cause Correct;garbage;garbage to process all possible fix first
pAttempts
  | oSkipExpressions = catMaybes <$> many (Just <$> pStdDiagnosis <|> Nothing <$ skip)
  | otherwise = many1 pStdDiagnosis

guessMagicExpr :: [Expr] -> [Expr]
guessMagicExpr [] = []
guessMagicExpr es@(e:_) = filter (matches es) z
   where
     z = nub $ [ x | Nat 8 :+: x <- universe e] -- niet efficient; maar willen prioriteit geven aan dit getal.
                              ++ [ x | x :+: Nat 8 <- universe e ] -- ,,
                              ++ concatMap f (universe e)
                              ++ [Nat 0]
     f v@(Nat _) = [v]
     f v@(Var _) = [v]
     f v@(Negate (Nat _)) = [v]
     f v@(Number _) = [v]
  -- function call
     f _ = []
     -- Tests whether the guessed magic expression is the actual magic expression
     -- we do this by looking for an expression of the form 8 + x or x + 8. The magic expression
     -- should then equal x. As a special case for x == 0, we also check for 8 * 3 and 3 * 8, in which
     -- case 0 is implicitly added to 8.
     -- Note that we only test against the first 3 following expressions. This is for 3 reasons:
     -- 1. Performance
     -- 2. Avoid overlapping with other attempts
     -- 3. Sometimes irrelevant expressions may be present
     matches es' m = all (all (\e' -> case e' of
       Nat 8 :+: x -> m == x
       x :+: Nat 8 -> m == x
       _ -> True) . universe) (take 3 es')

pMagicExpr :: SEParser Expr
pMagicExpr = withInputList (guessMagicExpr . mapMaybe getExpr)

pVars :: SEParser [String]
pVars = withInput (nub . concatMap vars . mapMaybe getExpr)

pAnnounce :: [String] -> Expr -> SEParser ()
pAnnounce vs x = () <$ many (() <$ pExpr x
                         <|> choice [ pEq $ Var v :==: x | v <-  vs ] )

pConclude :: [String] -> [Expr] -> Expr -> SEParser Expr
pConclude vs cs r =
    maybe r fst . uncons <$>
        many (choice ((7 <$ pExpr 7) : [ c <$ pEq (Var v :==: c) | v <- vs, c <- cs ]))

-- | Main diagnosis function.
--
-- First we guess some magic expression and then try to recognize the formula
pStdDiagnosis :: SEParser Diagnosis
pStdDiagnosis = do
  x <- pMagicExpr
  vs <- pVars
  -- pAnnounce vs x
  (a,st) <- pStepsFor x x (if x == 0 then taskIfZero else task x)

  res <- pConclude vs (nub [x,a]) a
  let attrs = map getAttributes st
  return Diagnosis
    { category = case x of { (Var _) -> Algebraic ; _ -> Numerical }
    , correctResult = res == 7 -- result is correct
    , resultIsSimplified = nf res == res -- nf result == result
    , parenthesisMismatch = any (elem NonMatchingParentheses) attrs
    , payload = Just x --The magic expression
    , steps = st
    , result = Just res
    }

task :: Expr -> [Op]
task me = [Add 8, Mul 3, Sub 4, Add me, Div 4, Add 2, Sub me]

taskIfZero :: [Op]
taskIfZero = [Add 8, Mul 3, Sub 4, Div 4, Add 2]

expandOps :: [Op] -> [Attribute]
expandOps = map Expand

taskExpand :: Expr -> [Attribute]
taskExpand = map Expand . task

taskForget :: Expr -> [Attribute]
taskForget = map Forget . task

-- | Pipeline for recognition
--
-- The pipeline consists of 4 phases:
--
-- * Parse correct steps
-- * Parse a simplification step
-- * Forget a step
-- * A mistake was made. Try fixing the step
--
-- These phases are passed through linearly, but a phase may go back to phase 1 before reaching the latter phases.
pStepsFor :: Expr -> -- magic expression
             Expr -> -- current expression
             [Op] -> -- remaining ops
             SEParser (Expr, [Step])
pStepsFor x a ops = do
  pLog $ "pStepsFor: " ++ show x ++ " | " ++ show a ++ " | " ++ show ops
  option $ satisfyWith (getExpr >=> (\e -> guard (a == e)))
  -- Try to parse as many correct steps as possible
  eth <- pStepsPhase1 x a ops
  case eth of
    Left (e, st, ops') -> do
      pLog $ "Fail after phase1 " ++ show x ++ "  " ++ show st ++ "  " ++ show e ++ "  " ++ show ops'
      ms <- pStepsPhase2 x e
      case ms of
        Just (e', st') -> do
          pLog $ "success after phase2 " ++ show x ++ "  " ++ show st' ++ "  " ++ show e'
          second (\st'' -> st ++ [st'] ++ st'') <$> pStepsFor x e' ops'

        Nothing -> do
          pLog "Fail after phase 2"
          -- Forget one step
          ma <- pStepsPhase3 x e ops'
          case ma of
            Just (e', st', ops'') -> do
              pLog $ "success after phase3 " ++ show x ++ "  " ++ show st' ++ "  " ++ show e' ++ "  " ++ show ops''
              -- After forgetting steps, we continue to try and parse correct steps
              second (\st'' -> st ++ [st'] ++ st'') <$> pStepsFor x e' ops''

            Nothing -> do
              pLog $ "Failed phase3: " ++ show st ++ "  " ++ show e ++ "  " ++ show ops'
                -- If we could not continue by forgetting then we try fixing, if this also fails then the parsing fails
              (e', st', ops'') <- pStepsPhase4 x e ops' st
              pLog $ "success after phase4 " ++ show x ++ "  " ++ show st' ++ "  " ++ show e' ++ "  " ++ show ops'
              second (\st'' -> st' ++ st'') <$> pStepsFor x e' ops''
    Right (e, st, _) -> do
      pLog $ "Success after phase1: " ++ show st ++ " | " ++ show e
      ms <- pStepsPhase2 x e
      case ms of
        Just (e', st') -> do
          pLog $ "success after phase2 " ++ show x ++ "  " ++ show st' ++ "  " ++ show e'
          second (\st'' -> st ++ [st'] ++ st'') <$> pStepsFor x e' []
        Nothing -> do
          pLog "done in StepsFor"
          return (e, st)

pStepsPhase1 :: Expr -> Expr -> [Op] -> SEParser (Either (Expr, [Step], [Op]) (Expr, [Step], [Op]))
pStepsPhase1 = pTask pStepOperators

pStepsPhase2 :: Expr -> Expr -> SEParser (Maybe (Expr, Step))
pStepsPhase2 = pTaskSimplify

pStepsPhase3 :: Expr -> Expr -> [Op] -> SEParser (Maybe (Expr, Step, [Op]))
pStepsPhase3 = pTaskErr

pStepsPhase4 :: Expr -> Expr -> [Op] -> [Step] -> SEParser (Expr, [Step], [Op])
pStepsPhase4 = pTaskForget

-- | Expands the current formula with a different number of operators.
--
-- Then attempts are made to recognize this formula.
pTask :: (Expr -> Expr -> [Op] -> SEParser (Expr, Step)) -- ^ to use recognition strategy
         -> Expr -- ^ magic expression
         -> Expr -- ^ current expression
         -> [Op] -- ^ remaining operators
         -> SEParser (Either (Expr, [Step], [Op]) (Expr, [Step], [Op]))
pTask _ _ a [] = return $ Right (a, [], [])
pTask strat x a xs = do
  pLog $ "pTask: " ++ show x ++ " | " ++ show a ++ " | " ++ show xs
  pTask'
  where
    pTask' =
      choice'
              [ do
                (b, as)  <- strat x a ops
                eth <- pTask strat x b ys
                case eth of
                  Left (c, cs, zs) -> return $ Left (c, as : cs, zs)
                  Right (c, cs, zs) -> return $ Right (c, as : cs, zs)
              | (ops, ys) <- splits xs, length ops > 0
              ]
      |> pIf (null xs)  (succeed $ Right (a, [], xs))
      |> succeed (Left (a, [], xs))

-- | tries to match the current formula to some simplified form of that formula
pTaskSimplify :: Expr -> Expr -> SEParser (Maybe (Expr, Step))
pTaskSimplify x a = do
  pLog $ "pTaskSimplify: " ++ show x ++ " | " ++ show a
  m <- withInput head
  if fst (simplify a) == a then return Nothing
   else do
    ma <- option $ pStepSimplify x a
    return $ ma >>= \(e, as) -> Just (e, smallStep (newId "simplify") (m,as))

-- | It's possible some operation was forgotten to be added
pTaskForget :: Expr -> Expr -> [Op] -> [Step] -> SEParser (Expr, [Step], [Op])
pTaskForget x a [Sub ope] stps -- special case necessary for if the last operator has been forgotten
  | x == ope || (isVar x && isVar ope) = return (a, modifyAt (length stps - 1) (addAttribute (Forget $ Sub x)) stps, [])
  | otherwise = empty
pTaskForget x a (op:op2:xs) stps = do
  (e,st) <- pStepForget x a op op2
  return (e, stps++[st], xs)
pTaskForget _ _ _ _ = empty

-- | See if an error was made given the next operation
pTaskErr :: Expr -> Expr -> [Op] -> SEParser (Maybe (Expr, Step, [Op]))
pTaskErr _ _ [] = empty
pTaskErr x a (op:xs) = option $ (\(e,st) -> (e,st,xs)) <$> pStepError x a [op]

pStepOperators :: Expr -- ^ magic expression
                -> Expr -- ^ the current expression
                -> [Op] -- ^ list of operators to be added
                -> SEParser (Expr, Step)
pStepOperators x a ops = do
  m <- withInput head
  (e,as) <- pStep x a ops
  let as' = map Expand ops ++ as
  return (e, smallStep (newId "") (m,as'))

pStepError :: Expr -- ^ magic expression
                -> Expr -- ^ the current expression
                -> [Op] -- ^ list of operators to be added
                -> SEParser (Expr, Step)
pStepError x a ops = do
  m <- withInput head
  (e,as) <- pStepErr x a ops
  return (e, smallStep (newId "") (m,as))

pStepForget :: Expr -- ^ magic expression
                -> Expr -- ^ the current expression
                -> Op -- ^ current step
                -> Op -- ^ next step
                -> SEParser (Expr, Step)
pStepForget x a op1 op2 = do
  m <- withInput head
  (e,as) <- pStep x a [op2]
  return (e, smallStep (newId "") (m,Forget op1 :as))

-- | Parse a step
--
-- Equation step, expression step or some erronuous step
pStep :: Expr -- ^ magic expression
      -> Expr -- ^ the current expression
      -> [Op] -- ^ list of operators to be added
      -> SEParser (Expr, [Attribute])
pStep x a ops =
  choice'
    [ do
      m <- peek
      guard $ isJust $ getEq m
      pStepEquation x a ops
    , do
      m <- peek
      guard $ isNothing $ getEq m
      pStepExpr x a ops
    , pBuggyStep x a ops
    ]

pStepErr :: Expr -> Expr -> [Op] -> SEParser (Expr, [Attribute])
pStepErr x a ops =
        -- pFixEqCom wil attempt to fix the equation to match the input
          pFixEqCom (b :==: b)
        |> choice [pFixEqCom (b :==: norm b) | norm <- [nfCom . nf]]
        |> pFixExprCom b
        |> choice [pFixExprCom (norm b) | norm <- [nfCom . nf]]
  where
    b = formExpr x a ops

pBuggyStep :: Expr -- ^ magic expression
           -> Expr -- ^ the current expression
           -> [Op] -- ^ list of operators to be added
           -> SEParser (Expr, [Attribute])
pBuggyStep x a ops =
  pIf (length ops >= 2) (
        ((,[IncorrectDistribution, Recovery])  . rightHandSide) <$> fst <$> pEqWith2 (\e -> (nfCom e,[])) (\e -> (nfCom $ nf e,[])) (b' :==: b)
     |> ((,[IncorrectDistribution] ) . rightHandSide) <$> fst <$> pEqWith2 (\e -> (nfCom e,[])) (\e -> (nfCom $ nf e,[])) (b' :==: b')
     |> (,[IncorrectDistribution]) <$> fst <$> pExprWith (\e -> (nfCom e,[])) b'
    )
  |> pIf (not $ null ops) (pNextStepBuggyPars a b)
 where
   b' = foldl (flip fromOp') a (map (substOp x) ops) -- b is a buggy parenthesis step
   b = formExpr x a ops

pStepSimplify :: Expr -- ^ magic expression
              -> Expr -- ^ the current expression
              -> SEParser (Expr, [Attribute])
pStepSimplify x a = do
  pLog $ "pStepSimplify: " ++ show x ++ " | " ++ show a
  choice'
    [ do
      m <- peek
      guard $ isJust $ getEq m
      (attr,e) <- pExplicitSimplifyEq x a
      return (attr,e)
    , do
      m <- peek
      guard $ isNothing $ getEq m
      (attr,e) <- pExplicitSimplifyExpr a
      return (attr,e)
    ]

pExplicitSimplifyEq :: Expr -- ^ magic expression
                    -> Expr -- ^ the current expression
                    -> SEParser (Expr, [Attribute])
pExplicitSimplifyEq x a = do
  pLog $ "pExplicitSimplifyEq: " ++ show x ++ " | " ++ show a
  choice
    [ first rightHandSide <$> pEqWith simplify (a :==: a)
    , pEqWithL simplify a
    ]

pExplicitSimplifyExpr :: Expr -- ^ the current expression
                      -> SEParser (Expr, [Attribute])
pExplicitSimplifyExpr = pExprWith simplify


{-
((x+8)*3-4+x)/4+2-x = 7
(3x +24 - 4 + x)/4+2-x = 7
-}

{-
((x+8)*3-4+x)/4+2-x = (3x +24 - 4 + x)/4+2-x
(3x + 24 - 4 + x)/4+2-x = (4x+20)/4+2-x
-}


pStepEquation :: Expr -- ^ magic expression
              -> Expr -- ^ the current expression
              -> [Op] -- ^ list of operators to be added
              -> SEParser (Expr, [Attribute])
pStepEquation x a ops = do
  pLog $ "pStepEquation: " ++ show x ++ " | " ++ show a ++ " | " ++ show ops
  choice
    [ first rightHandSide <$> pEqWith simplify (b :==: b)
    , pEqWithL simplify b
    ]
  |> pIf (length ops <= 3)
        (pFixEqCom (b :==: nfCom (nf b)))
  where
    b = formExpr x a ops

-- | pStepExpr probeert een expressie te parsen
-- probeer eerst zonder normaliseren (maar modulo comm/assoc/distr)
-- probeer dan met normaliseren (dan telt de stap/berekening als impliciet)
-- probeer dan met fout correctie.
pStepExpr :: Expr -- ^ magic expression
          -> Expr -- ^ the current expression
          -> [Op] -- ^ list of operators to be added
          -> SEParser (Expr, [Attribute])
pStepExpr x a ops = do
  pLog $ "In pStepExpr: " ++ show x ++ " | " ++ show a ++ " | " ++ show ops ++ " | " ++ show b
  pExprWith (\e -> (nfCom e, [])) b
  -- Be careful that you don't relax this condition too much. Otherwise any occurence of 7 can be seen as a fully expanded and simplified expression
  |> pIf (length ops < 4) (second (\attr -> (Implicit <$> ops)++attr) <$> pExprWith simplify b)
  where
    b = formExpr x a ops

pNextStepBuggyPars :: Expr -> Expr -> SEParser (Expr, [Attribute])
pNextStepBuggyPars _ b = let np = noPars b in choice'
   [ (lhs', [NonMatchingParentheses]) <$ pEqCom (lhs' :==: nf b)
     |> (lhs', [NonMatchingParentheses])  <$ pExprCom lhs'
     | lhs' <- np
   ]

splits :: [a] -> [([a], [a])]
splits xs = zip (inits xs) (tails xs)

pIf :: Alternative f => Bool -> f a -> f a
pIf b p = if b then p else empty

-- quick and dirty
noPars :: Expr -> [Expr]
noPars e = (if oTryManyBuggyPars then id else take 1) $ -- use take 1 to speed up the recognizer
            mapMaybe f (dropParensString (show e))
  where
    f s = case myparseExpr s of
      Just new | new /= e -> Just new
      _ -> Nothing

    myparseExpr s =
      case mapMaybe getExpr (snd $ parseMath mathParserOptions s) of
          [m] -> Just m
          _ -> Nothing


-- drop sets of corresponding parentheses
dropParensString :: String -> [String]
dropParensString = rec []
  where
    rec ps [] = [ "" | null ps ]
    rec ps (x:xs)
      | x == '('  = rec (False:ps) xs ++ map (x:) (rec (True:ps) xs)
      | x == ')'  = if null ps
                    then []
                    else if head ps
                          then map (x:) (rec (drop 1 ps) xs)
                          else rec (drop 1 ps) xs
      | otherwise = map (x:) (rec ps xs)

pFixEqCom :: Equation Expr -- ^ expected
          -> SEParser (Expr, [Attribute])
pFixEqCom e = peekEq >>= \eq -> pLog ("pFixEqCom: " ++ show e ++ " | " ++ show eq) >> pMatchEq e eq

pMatchEq :: Equation Expr -- ^ expected
         -> Equation Expr -- ^ provided
         -> SEParser (Expr, [Attribute])
pMatchEq (a :==: b) (x :==: y) =
      pIf (not (a === x) && any (=== x) (changeOp a) && y === x)
        (pRewrite (y, [OperatorMixedUp plusSymbol plusSymbol]))
  <|> pIf (a /= x) (pMatchExpr a x)
  <|> pIf (not (a === x) && b === y) (second (Recovery:) <$> pMatchExpr (nfCom a) (nfCom x))
  <|> pIf (not (a === x) && b == y) (pRewrite (y, [InvalidEquation x y, Recovery]))
  <|> pIf (a === x && (b /= y)) (pRewrite (y, [InvalidEquation x y]))

pFixExprCom :: Expr -> SEParser (Expr, [Attribute])
pFixExprCom e = peekExpr >>= pMatchExpr e

pMatchExpr :: Expr -- ^ expected
           -> Expr -- ^ provided
           -> SEParser (Expr, [Attribute])
pMatchExpr e p = choice
  [ do
    el <- maybeToParse $ getLeft e
    pl <- maybeToParse $ getLeft p
    let e' = replaceLeft pl e
    guard (e' == p)
    pRewrite (p, [AtomMixedUp pl el])
  , do
    er <- maybeToParse $ getRight e
    pr <- maybeToParse $ getRight p
    let e' = replaceRight pr e
    guard (e' == p)
    pRewrite (p, [AtomMixedUp pr er])
  , pIf (equivalentStructure e p
          && filter isAtom (universe e) == filter isAtom (universe p)
          && not (isAtom e)
         )
      (pRewrite (p, [OperatorMixedUp plusSymbol plusSymbol]))
  , pIf ((not.isAtom) e
          && equivalentStructure e p
          && length (changeSet e p) == 1
         )
      (pRewrite (p, [AtomMixedUp p e]))
  ]

pEqWith :: (Expr -> (Expr, [Attribute])) -> Equation Expr -> SEParser (Equation Expr, [Attribute])
pEqWith f x = do
  m <- satisfyWith Just
  pLog $ "pEqWith: " ++ show x ++ " | " ++ show m
  case getEq m of
    Just y -> do
      let xl = leftHandSide x
          yl = leftHandSide y
          (fxl,axl) = f xl
          (fxr,_)   = f (rightHandSide x)
          (fyl,ayl) = f yl
          (fyr,ayr) = f (rightHandSide y)
      guard (
        if xl == yl then fyl == fyr
                    else fxl == fyl && fyl == fyr && hasCommonality axl ayl && (hasCommonality ayl ayr || null ayr))
      pLog $ "pEqWith success: " ++ show fxl ++ "  " ++ show fxr ++ " | " ++ show fyl ++ " " ++ show fyr
      return (y, ayl \\ ayr)
    Nothing -> empty

pEqWith2 :: (Expr -> (Expr, [Attribute])) -> (Expr -> (Expr, [Attribute])) -> Equation Expr -> SEParser (Equation Expr, [Attribute])
pEqWith2 f g x = do
  m <- satisfyWith Just
  pLog $ "pEqWith2: " ++ show x ++ " | " ++ show m
  case getEq m of
    Just y -> do
      let (fx,_)   = f (leftHandSide x)
          (gx,_)   = g (rightHandSide x)
          (fy,aly) = f (leftHandSide y)
          (gy,ary) = g (rightHandSide y)
      guard (fx == fy && gx == gy)
      pLog $ "pEqWith2 success: " ++ show fx ++ " " ++ show fy ++ " | " ++ show gx ++ " " ++ show gy
      return (y, aly \\ ary)
    Nothing -> empty

{-|
Compares the left arguments of two subsequent equations

A = 7
B = 7
C = 7

In this case B should be a simplification of A and C a simplification of B.
-}
pEqWithL :: (Expr -> (Expr, [Attribute])) -> Expr -> SEParser (Expr, [Attribute])
pEqWithL f x = do
  m <- satisfyWith Just
  pLog $ "pEqWithL: " ++ show x ++ " | " ++ show m
  case getEq m of
    Just y -> do
      let (fx,ax)   = f x
          (fyl,ayl) = f (leftHandSide y)
          (fyr,_) = f (rightHandSide y)
      -- We require two things for x to match y.l:
      -- 1. The end result must be equal
      -- 2. There should be at least some common steps (ensures that two formulas don't match if they do not use the same magic variable)
      guard (fx == fyl && fyl == fyr && (hasCommonality ax ayl || null ayl)) -- ? ax ++
      pLog $ "pEqWith success: " ++ show fx ++ "  " ++ show fyl ++ " " ++ show fyr
      return (leftHandSide y, ax \\ ayl)
    Nothing -> empty

-- | Two expressions might simplify to the same expression, but their paths there are different.
--
-- This function will check that there was at least one common step in simplification.
hasCommonality :: [Attribute] -> [Attribute] -> Bool
hasCommonality xs ys =
  let xs' = map nfa xs
      ys' = map nfa ys
  in any (`elem` xs') ys'

  where
    nfa (ARule i a b) = ARule i (N.map nfComAssoc a) (nfComAssoc b)
    nfa r = r