module Canopy.Adjudecate where

import Prelude

import Control.Bind (bind)
import Data.Foldable (class Foldable, any, foldMap, foldlDefault, foldrDefault)
import Data.List (List(..))
import Data.List as List
import Data.Tuple.Nested (type (/\), (/\))
import Partial.Unsafe (unsafeCrashWith)
import Prim.Row (class Cons)

type Index = Int

data Proposition a
  = False
  | True
  | Disj (Proposition a) (Proposition a)
  | Conj (Proposition a) (Proposition a)
  | Negation (Proposition a)
  | Other a

derive instance Functor Proposition

instance Apply Proposition where
  apply = ap

instance Applicative Proposition where
  pure = Other

instance Bind Proposition where
  bind (Other a) f = f a
  bind False _ = False
  bind True _ = True
  bind (Negation p) f = Negation (bind p f)
  bind (Conj l r) f = Conj (bind l f) (bind r f)
  bind (Disj l r) f = Disj (bind l f) (bind r f)

instance Foldable Proposition where
  foldMap f (Other a) = f a
  foldMap f (Disj l r) = foldMap f l <> foldMap f r
  foldMap f (Conj l r) = foldMap f l <> foldMap f r
  foldMap f (Negation p) = foldMap f p
  foldMap _ _ = mempty

  foldr = foldrDefault
  foldl = foldlDefault

instance Monad Proposition

derive instance Eq a => Eq (Proposition a)

simp :: forall a. Eq a => Proposition a -> Proposition a
simp (Conj False _) = False
simp (Conj _ False) = False
simp (Disj True _) = True
simp (Disj _ True) = True
simp (Negation True) = False
simp (Negation False) = True
simp (Negation (Negation p)) = simp p
simp (Conj l r) | l == r = simp l
simp (Disj l r) | l == r = simp l
simp (Negation (Disj l r)) = simp $ Conj (simp $ Negation l) (simp $ Negation r)
simp (Negation (Conj l r)) = simp $ Disj (simp $ Negation l) (simp $ Negation r)
simp (Conj l (Negation r)) | l == r = False
simp (Disj l (Negation r)) | l == r = True
simp (Negation p) = Negation (simp p)
simp (Conj l r) = Conj (simp l) (simp r)
simp (Disj l r) = Disj (simp l) (simp r)
simp o = o

-- Put this inside a proposition to have
-- two wildcard (self and target)
data BinaryWildcard = BSelf | BTarget | BRef Index

-- Put this inside a proposition to have
-- a single wildcard (target)
data UnaryWildcard = UTarget | URef Index

newtype Entry = Entry
  -- Expression which must be true for us to also be true.
  { requires :: Proposition Index

  -- We can add arbitrary logic to entries already in the graph
  , contributes :: List (Proposition BinaryWildcard)
  }

data MoveImplications
  = Empty
  | AddEntry Entry MoveImplications

referencesSelf :: Proposition UnaryWildcard -> Boolean
referencesSelf = any case _ of
    URef 0 -> true
    _ -> false

applyContributions :: List (Proposition UnaryWildcard) -> MoveImplications -> MoveImplications
applyContributions Nil Empty = Empty
applyContributions (Cons contribution rest) (AddEntry entry implications) = AddEntry entry' (applyContributions rest implications')
  where
  entry' = ?e
  implications' = ?i

  contributions = implications # List.partition referencesSelf

applyContributions _ _ = unsafeCrashWith "Different amounts of contributions and implications"


resolveMoveImplications :: MoveImplications -> List Boolean
resolveMoveImplications Empty = List.Nil
resolveMoveImplications (AddEntry (Entry { requires, contributes }) implications) =
  ?w
  where
  unaryContributions :: List (Proposition UnaryWildcard)
  unaryContributions = contributes <#> \proposition -> do
    wildcard <- proposition
    case wildcard of
      BTarget -> pure UTarget
      BRef i -> pure $ URef i
      BSelf -> map URef requires