module FormalLanguage.GrammarProduct.Op.Greibach.Proof where

import Control.Lens
import Control.Lens.Fold
import Control.Newtype ()
import Data.List (genericReplicate)
import Data.Monoid hiding ((<>))
import Data.Semigroup
import qualified Data.Set as S
import Text.Printf
import Data.List (groupBy)
import Data.Function (on)
import Data.Maybe
import Control.Applicative

import Text.PrettyPrint.ANSI.Leijen hiding ((<>))
import Text.Trifecta  --
import qualified Data.ByteString.Char8 as B
import           Control.Monad.Trans.State.Strict
import           Data.Default
import           Text.Trifecta.Delta

import FormalLanguage.CFG.Grammar
import FormalLanguage.CFG.PrettyPrint.ANSI
import FormalLanguage.CFG.PrettyPrint.LaTeX
import FormalLanguage.CFG.Parser

import FormalLanguage.GrammarProduct.Op.Greibach
import FormalLanguage.GrammarProduct.Op.Common



{-

-- * Proof of associativity of the 2-GNF.

-- | Run the 2-gnf grammar with the TwoGNF monoid which observes the 2 star
-- cases.

twoGNFassociativity :: (Grammar, Grammar, S.Set Rule, S.Set Rule, Bool)
twoGNFassociativity = ( l
                      , r
                      , (l^.rules) S.\\ (r^.rules)
                      , (r^.rules) S.\\ (l^.rules)
                      , l^.rules == r^.rules)  where
  l = runTwoGNF $ (TwoGNF g <>  TwoGNF g) <> TwoGNF g
  r = runTwoGNF $  TwoGNF g <> (TwoGNF g  <> TwoGNF g)
  g = twoGNFgrammar

twoGNFs = g where
  g = runTwoGNF $ (TwoGNF h <> TwoGNF h)
  h = twoGNFgrammar

assocHelper l r = ( l
                  , r
                  , (l^.rules) S.\\ (r^.rules)
                  , (r^.rules) S.\\ (l^.rules)
                  , l^.rules == r^.rules)

-- * Proof that the 2 star cases are actually needed. We loose associativity
-- without those. As this version does not preserve associativity, we keep it
-- here, instead of the general Greibach version.

newtype FailGNF = FailGNF { runFailGNF :: Grammar }

-- |
--
-- TODO check correctness

instance Semigroup FailGNF where
  (FailGNF g) <> (FailGNF h) = FailGNF $ Grammar ts ns es rs s (g^.name ++ h^.name) where
    ts = collectTerminals rs
    ns = collectNonTerminals rs
    es = g^.epsis <> h^.epsis
    rs = S.fromList
       . map starRemove
       . concat
       $ [ l <.> r
         | l <- S.toList $ g^.rules
         , r <- S.toList $ h^.rules
         ]
    s  = liftA2 (\l r -> Symb $ l^.symb ++ r^.symb) (g^.start) (h^.start)
    (<.>) :: Rule -> Rule -> [Rule]
    a <.> b | ((Just $ a^.lhs)==g^.start) `exactlyOne` ((Just $ b^.lhs)==h^.start) = []
    a <.> b
      | [s,m]   <- a^.rhs
      , [t,n,o] <- b^.rhs
      = [ Rule (Symb $ a^.lhs.symb ++ b^.lhs.symb)
          [""]
          [Symb $ s^.symb ++ t^.symb, Symb $ m^.symb ++ n^.symb, Symb $ stars (length $ m^.symb) ^.symb ++ o^.symb ]
        , Rule (Symb $ a^.lhs.symb ++ b^.lhs.symb)
          [""]
          [Symb $ s^.symb ++ t^.symb, Symb $ stars (length $ m^.symb) ^.symb ++ n^.symb, Symb $ m^.symb ++ o^.symb ]
        ]
      | [s,m,o] <- a^.rhs
      , [t,n]   <- b^.rhs
      = [ Rule (Symb $ a^.lhs.symb ++ b^.lhs.symb)
          [""]
          [ Symb $ s^.symb ++ t^.symb
          , Symb $ m^.symb ++ n^.symb
          , Symb $ o^.symb ++ stars (length $ t^.symb) ^.symb
          ]
        , Rule (Symb $ a^.lhs.symb ++ b^.lhs.symb)
          [""]
          [ Symb $ s^.symb ++ t^.symb
          , Symb $ m^.symb ++ stars (length $ t^.symb) ^.symb
          , Symb $ o^.symb ++ n^.symb
          ]
        ]
    a <.> b = [ Rule  (Symb $ a^.lhs.symb ++ b^.lhs.symb)
                      [""]
                      (take 3 $ zipWith (\l r -> Symb $ l^.symb ++ r^.symb) (a^.rhs ++ repeat (stars (gDim g)))
                                                                            (b^.rhs ++ repeat (stars (gDim h)))
                      )
              ]
    exactlyOne False True  = True
    exactlyOne True  False = True
    exactlyOne _     _     = False
    stars :: Int -> Symb
    stars k = Symb $ replicate k E
    -- | Remove star-online columns.
    starRemove :: Rule -> Rule
    starRemove = over rhs (filter (any (not . isEpsilon) . getSymbs))
    isEpsilon E = True
    isEpsilon _ = False


-- | Run the 2-gnf grammar without the star cases.

-- noStarFailure :: (S.Set Rule, S.Set Rule, 
noStarFailure = assocHelper l r where
  l = runFailGNF $ (FailGNF g <>  FailGNF g) <> FailGNF g
  r = runFailGNF $  FailGNF g <> (FailGNF g  <> FailGNF g)
  g = twoGNFgrammar

-- * The simple 2-gnf grammar to run the proof on.

-- | Very simple 2-gnf form for proofs.

twoGNFgrammar = case g of
  Success g' -> g'
  Failure f  -> error $ show f
  where
  g = parseGrammar "testGrammar" twoGNF
  twoGNF = unlines
    [ "Grammar: TwoGNF"
    , "N: A"
    , "N: B"
    , "N: C"
    , "N: D"
    , "T: a"
    , "T: b"
    , "T: c"
--    , "S: X"
    , "A -> three <<< a B C"
    , "A -> two   <<< b D"
    , "A -> one   <<< c"
    , "//"
    ]

-}