{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}

{-|
Module      : Data.JoinSemilattice.Defined
Description : Values with differing levels of "definedness".
Copyright   : (c) Tom Harding, 2020
License     : MIT

The 'Defined' type simplifies the join semilattice-shaped knowledge down to its
simplest form, by saying there are only three possible states of knowledge:

- I don't know anything about this value.
- I know exactly what this value is.
- I'm getting conflicting information.

The simplicity of the type makes it incredibly helpful when we're trying to
lift regular computations into the world of propagators.
-}
module Data.JoinSemilattice.Defined where

import Control.Applicative (liftA2)
import Data.Hashable (Hashable)
import Data.Input.Config (Config (..), Input (..))
import Data.Kind (Type)
import Data.List.NonEmpty (unzip)
import Data.Monoid (Ap (..))
import GHC.Generics (Generic)
import Prelude hiding (unzip)

-- | Defines simple "levels of knowledge" about a value.
data Defined (x :: Type)
  = Unknown   -- ^ Nothing has told me what this value is.
  | Exactly x -- ^ Everyone who has told me this value agrees.
  | Conflict  -- ^ Two sources disagree on what this value should be.
  deriving stock (Eq, Ord, Show, Functor, Generic)
  deriving anyclass (Hashable)
  deriving (Bounded, Num) via (Ap Defined x)

instance Enum content => Enum (Defined content) where
  fromEnum = \case
    Exactly this -> fromEnum this
    _            -> error "fromEnum is undefined for non-exact values."

  toEnum = pure . toEnum

instance Applicative Defined where
  pure = Exactly

  Conflict <*> _ = Conflict
  _ <*> Conflict = Conflict

  Unknown <*> _ = Unknown
  _ <*> Unknown = Unknown

  Exactly f <*> Exactly x
    = Exactly (f x)

instance Eq content => Semigroup (Defined content) where
  Conflict <> _ = Conflict
  _ <> Conflict = Conflict

  this <> Unknown = this
  Unknown <> that = that

  Exactly this <> Exactly that
    | this == that = Exactly this
    | otherwise    = Conflict

instance Eq content => Monoid (Defined content) where
  mempty = Unknown

instance Real content => Real (Defined content) where
  toRational = \case
    Exactly this -> toRational this
    _            -> error "toRational is undefined for non-exact values."

instance Integral content => Integral (Defined content) where
  quotRem this that = unzip (liftA2 quotRem this that)

  toInteger = \case
    Exactly this -> toInteger this
    _            -> error "toInteger is undefined for non-exact values."

instance Fractional x => Fractional (Defined x) where
  (/) = liftA2 (/)

  fromRational = pure . fromRational
  recip        = fmap recip

instance Input (Defined content) where
  type Raw (Defined content) = content

  from count options = Config (replicate count Unknown) do
    pure . \case
      Unknown -> map Exactly options
      decided -> [ decided ]