```-- | The functions exposed by this module convert expressions of type 'Expr'
-- into different normal forms: negation normal form via 'toNNF', conjunctive
-- normal form via 'toCNF' and disjunctive normal form via 'toDNF'. All these
-- functions are total.
module Data.Logic.Propositional.NormalForms
( toNNF
, toCNF
, toDNF
) where

import Data.Logic.Propositional.Core

-- | The 'toNNF' function converts expressions to negation normal form. This
-- function is total: it's defined for all expressions, not just those which
-- only use negation, conjunction and disjunction, although all expressions in
-- negation normal form do in fact only use those connectives.
--
-- The conversion is carried out by replacing any condtitionals or
-- biconditionals with equivalent expressions using only negation, conjunction
-- and disjunction. Then de Morgan's laws are applied to convert negated
-- conjunctions and disjunctions into the conjunction or disjunction of the
-- negation of their conjuncts: @¬(φ ∧ ψ)@ is converted to @(¬φ ∨ ¬ψ)@
-- while @¬(φ ∨ ψ)@ becomes @(¬φ ∧ ¬ψ)@.
toNNF :: Expr -> Expr
toNNF expr@(Variable _)                    = expr
toNNF expr@(Negation (Variable _))         = expr
toNNF (Negation (Negation expr))           = expr

toNNF (Conjunction exp1 exp2)              = toNNF exp1 `conj` toNNF exp2
toNNF (Negation (Conjunction exp1 exp2))   = toNNF \$ neg exp1 `disj` neg exp2

toNNF (Disjunction exp1 exp2)              = toNNF exp1 `disj` toNNF exp2
toNNF (Negation (Disjunction exp1 exp2))   = toNNF \$ neg exp1 `conj` neg exp2

toNNF (Conditional exp1 exp2)              = toNNF \$ neg exp1 `disj` exp2
toNNF (Negation (Conditional exp1 exp2))   = toNNF \$ exp1 `conj` neg exp2

toNNF (Biconditional exp1 exp2)            = let a = exp1 `conj` exp2
b = neg exp1 `conj` neg exp2
in toNNF \$ a `disj` b
toNNF (Negation (Biconditional exp1 exp2)) = let a = exp1 `disj` exp2
b = neg exp1 `disj` neg exp2
in toNNF \$ a `conj` b

-- | The 'toCNF' function converts expressions to conjunctive normal form: a
-- conjunction of clauses, where a clause is a disjunction of literals
-- (variables and negated variables).
--
-- The conversion is carried out by first converting the expression into
-- negation normal form, and then applying the distributive law.
--
-- Because it first applies 'toNNF', it is a total function and can handle
-- expressions which include conditionals and biconditionals.
toCNF :: Expr -> Expr
toCNF = toCNF' . toNNF
where
toCNF' :: Expr -> Expr
toCNF' (Conjunction exp1 exp2) = toCNF' exp1 `conj` toCNF' exp2
toCNF' (Disjunction exp1 exp2) = toCNF' exp1 `dist` toCNF' exp2
toCNF' expr                    = expr

dist :: Expr -> Expr -> Expr
dist (Conjunction e11 e12) e2 = (e11 `dist` e2) `conj` (e12 `dist` e2)
dist e1 (Conjunction e21 e22) = (e1 `dist` e21) `conj` (e1 `dist` e22)
dist e1 e2                    = e1 `disj` e2

-- | The 'toDNF' function converts expressions to disjunctive normal form: a
-- disjunction of clauses, where a clause is a conjunction of literals
-- (variables and negated variables).
--
-- The conversion is carried out by first converting the expression into
-- negation normal form, and then applying the distributive law.
--
-- Because it first applies 'toNNF', it is a total function and can handle
-- expressions which include conditionals and biconditionals.
toDNF :: Expr -> Expr
toDNF = toDNF' . toNNF
where
toDNF' :: Expr -> Expr
toDNF' (Conjunction exp1 exp2) = toDNF' exp1 `dist` toDNF' exp2
toDNF' (Disjunction exp1 exp2) = toDNF' exp1 `disj` toDNF' exp2
toDNF' expr                    = expr

dist :: Expr -> Expr -> Expr
dist (Disjunction e11 e12) e2 = (e11 `dist` e2) `disj` (e12 `dist` e2)
dist e1 (Disjunction e21 e22) = (e1 `dist` e21) `disj` (e1 `dist` e22)
dist e1 e2                    = e1 `conj` e2

neg :: Expr -> Expr
neg = Negation

disj :: Expr -> Expr -> Expr
disj = Disjunction

conj :: Expr -> Expr -> Expr
conj = Conjunction
```