{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE GADTs #-}

module Zsyntax.Labelled.Rule.Frontier where
  -- ( GoalNSequent(..)
  -- , Rule
  -- , initialSequentsAndRules
  -- ) where

import Data.Maybe (mapMaybe)
import Data.Constraint (Dict(..))
import Otter (unRule, Subsumable(..))
import Data.MultiSet (MultiSet)
import Data.Foldable (toList)
import Data.Set (Set)
import qualified Data.Set as S (singleton, fromList)
import Data.Function (on)
import Control.Monad (join)

import Zsyntax.Labelled.Rule.BipoleRelation
import Zsyntax.Labelled.Rule.Interface
import Zsyntax.Labelled.Formula

data DecoratedFormula :: * -> * -> * where
  Unrestr :: LAxiom a l -> DecoratedFormula a l
  LinNeg :: LFormula KImpl a l -> DecoratedFormula a l
  LinPos :: Opaque a l -> DecoratedFormula a l

dfLabel :: DecoratedFormula a l -> Label a l
dfLabel (Unrestr ax) = L (axLabel ax)
dfLabel (LinNeg f) = label f
dfLabel (LinPos (O f)) = label f

instance (Eq l, Eq a) => Eq (DecoratedFormula a l) where
  (==) = on (==) dfLabel

instance (Ord l, Ord a) => Ord (DecoratedFormula a l) where
  compare = on compare dfLabel

--------------------------------------------------------------------------------
-- Goal and result sequents.

-- | Type of goal sequents.
--
-- A goal sequent is characterized by an unrestricted context of axioms, a
-- (non-empty) neutral context, and a consequent formula of unspecified formula
-- kind (i.e., an opaque formula).
data GoalNSequent a l = GNS
  { _gnsUC :: Set (LAxiom a l)
  , _gnsLC :: MultiSet (Neutral a l) -- NonEmptyMultiSet (Neutral a l)
  , _gnsConcl :: Opaque a l -- Opaque (LFormula' cty l l)
  }
  deriving Show

instance (Ord a, Ord l) => Subsumable (GoalNSequent a l) where
  subsumes  (GNS uc lc fr) (GNS uc' lc' fr') =
    fr == fr' && lc == lc' && null (_scOnOnlyFirst (uc `subCtxtOf` uc'))

toGoalSequent :: LSequent a l -> GoalNSequent a l
toGoalSequent (LS uc lc _ c) = GNS uc lc c

--------------------------------------------------------------------------------
 -- Frontier computation

filterImpl :: [Neutral a l] -> [LFormula 'KImpl a l]
filterImpl = mapMaybe aux
  where
    aux :: Neutral a l -> Maybe (LFormula 'KImpl a l)
    aux (N (f :: LFormula k a l)) =
      either (const Nothing) (\Dict -> Just f) (decideNeutral @k)

frNeg :: (Ord l, Ord a) => Neutral a l -> Set (DecoratedFormula a l)
frNeg = switchN (const mempty) (\(Impl a _ _ b _) -> foc a <> act b)

frPos, foc, act :: (Ord l, Ord a) => LFormula k a l -> Set (DecoratedFormula a l)
frPos f = case f of
  Atom _ -> mempty
  Conj {} -> foc f
  Impl a _ _ b _ -> mconcat [act a, frPos b, S.singleton (LinPos (O b))]
foc f = case f of
  Atom _ -> mempty
  Conj a b _ -> foc a <> foc b
  Impl {} -> S.singleton (LinPos (O f)) <> frPos f
act f = case f of
  Atom _ -> mempty
  Conj f1 f2 _ -> act f1 <> act f2
  Impl {} -> S.singleton (LinNeg f) <> frPos f

-- | Computes the frontier of a sequent.
frontier :: (Ord l, Ord a) => GoalNSequent a l -> Set (DecoratedFormula a l)
frontier (GNS uc lc goal@(O fgoal)) =
  mconcat
    [ toplevelUC, toplevelLC
    , ucFrontier, linFrontier
    , goalFrontier, S.singleton (LinPos goal)
    ]
  where
    lcList = toList lc
    toplevelUC = S.fromList . map Unrestr . toList $ uc
    toplevelLC = S.fromList . fmap LinNeg . filterImpl $ lcList
    ucFrontier = mconcat . fmap (frNeg . N . axToFormula) . toList $ uc
    linFrontier = mconcat . fmap frNeg $ lcList
    goalFrontier = frPos fgoal

--------------------------------------------------------------------------------
-- Generation of initial rules from the frontier.

generateRule :: (Ord a, Ord l) => DecoratedFormula a l -> BipoleRule a l
generateRule (Unrestr axiom) = copyRule axiom
generateRule (LinNeg impl) = implLeft impl
generateRule (LinPos (O f)) =
  case f of { Atom _ -> focus f ; Conj {} -> focus f ; Impl {} -> implRight f }

--------------------------------------------------------------------------------
-- Main function

-- | Type of proper rules of the formal system, i.e. 'BipoleRule's that take at
-- least one premise.
type ProperRule a l = AnnLSequent a l -> BipoleRule a l

-- | Computes the set of initial rules from the frontier of a specified goal
-- sequent.
initialRules :: (Ord a, Ord l) => GoalNSequent a l -> [BipoleRule a l]
initialRules = fmap generateRule . toList . frontier

mayProperRule :: BipoleRule a l -> Maybe (ProperRule a l)
mayProperRule = join . fmap (either (const Nothing) Just) . unRule

maySequent :: BipoleRule a l -> Maybe (AnnLSequent a l)
maySequent = join . fmap (either Just (const Nothing)) . unRule