{-# LANGUAGE ConstraintKinds #-}

-- | Defining all the classes which represent knowledge-about-knowledge.
module Language.Drasil.Classes (
    -- * Classes
    -- ** Chunks
    NamedIdea(term)
  , Idea(getA)
  , CommonIdea(abrv)
  , Concept
  , Definition(defn)
  , ConceptDomain(cdom)
  , Quantity
  , HasUnitSymbol(usymb)
  , HasReasVal(reasVal)
  , Constrained(constraints)
  , HasAdditionalNotes(getNotes)
    -- the unsorted rest
  , IsUnit(udefn, getUnits)
  , UnitEq(uniteq)
    -- ** Expr and expressions
  , Express(express)
  , DefiningExpr(defnExpr)
  ) where

import Control.Lens (Lens')

import Drasil.Database (UID)

-- some classes are so 'core' that they are defined elsewhere
-- also helps with cycles...
import Language.Drasil.Symbol (HasSymbol)
import Language.Drasil.Chunk.NamedIdea (Idea(..), NamedIdea(..))
import Language.Drasil.Constraint (ConstraintE)
import Language.Drasil.UnitLang (UDefn, USymb)
import Language.Drasil.Expr.Lang (Expr)
import Language.Drasil.ExprClasses (Express(express))
import Language.Drasil.Space (HasSpace)
import Language.Drasil.Sentence (Sentence)

-- TODO: conceptual typeclass?
-- TODO: I was thinking of splitting QDefinitions into Definitions with 2 type variables
--       Can we change this name from "Definition" to anything else? "NaturalDefinition"?
-- | Defines a chunk.
class Definition c where
  -- | Provides (a 'Lens' to) the definition for a chunk.
  defn :: Lens' c Sentence

-- TODO: conceptual typeclass?
-- Temporary hack to avoid loss of information
-- | Records any additional notes needed to avoid losing information
class HasAdditionalNotes c where
  -- | Provides a 'Lens' to the notes.
  getNotes :: Lens' c [Sentence]

-- TODO: `drasil-database`-related typeclass? UIDs should be  moved to `drasil-database` too.
-- | Some concepts have a domain (related information encoded in 'UID's to other chunks).
class ConceptDomain c where
  -- | Provides Getter for the concept domain tags for a chunk
  cdom :: c -> [UID]
  -- ^ /cdom/ should be exported for use by the
  -- Drasil framework, but should not be exported beyond that.

-- TODO: conceptual type synonym?
-- | Concepts are 'Idea's with definitions and domains.
type Concept c = (Idea c, Definition c, ConceptDomain c)
-- TODO: Would the below make this a bit better to work with?
--        type Concept = forall c. (Idea c, Definition c, ConceptDomain c) => c

-- | CommonIdea is a 'NamedIdea' with the additional
-- constraint that it __must__ have an abbreviation. This is the main
-- distinction between getA and abrv, where getA may return Nothing,
-- while abrv will always return the abbreviation.
class NamedIdea c => CommonIdea c where
  -- | Introduces abrv which necessarily provides an abbreviation.
  abrv :: c -> String

-- | The Constrained class is a 'Quantity' that has value constraints.
-- It does not enforce 'Quantity' at this point.
class Constrained c where
  -- | Provides a 'Lens' to the 'Constraint's.
  constraints :: Lens' c [ConstraintE]

-- | A 'Quantity' that could have a reasonable value.
class HasReasVal c where
  -- | Provides a 'Lens' to the possible reasonable value.
  reasVal     :: Lens' c (Maybe Expr)

-- | A Quantity is an 'Idea' with a 'Space' and a 'Symbol'.
-- In theory, it should also restrict to being a part of 'MayHaveUnit', but that causes
-- all sorts of import cycles (or lots of orphans).
class (Idea c, HasSpace c, HasSymbol c) => Quantity c where

-- TODO: potential alternative design for "Quantity"?
--
--      type Quantity2 = forall c. (Idea c, HasSpace c, HasSymbol c) => c

--  An UncertainQuantity is just a Quantity with some uncertainty associated to it.
-- This uncertainty is represented as a decimal value between 0 and 1 (percentage).
--
-- class Quantity c => UncertainQuantity c where
--   uncert :: Lens' c (Uncertainty)
--   replaced with HasUncertainty

-----------------------------------------------------
-- Below are for units only
-- | Some chunks store a unit symbol.
class HasUnitSymbol u where
  -- | Provides the ability to hold a unit symbol ('USymb').
  usymb :: u -> USymb

-- | Units are 'Idea's with a 'Definition' which store a unit symbol.
-- They must also be explicitly declared to be instances of IsUnit.
class (Idea u, Definition u, HasUnitSymbol u) => IsUnit u where
  -- | May have a unit definition.
  udefn :: u -> Maybe UDefn
  -- | Holds units as a list of 'UID'.
  getUnits :: u -> [UID]

-- Investigate (TODO): is this really needed?
-- | Contains a 'UDefn'
class UnitEq u where
  -- | Provides the 'Lens' to a unit definition.
  uniteq :: Lens' u UDefn

-----------------------------------------------------

class DefiningExpr c where
  -- | Provides a 'Lens' to the expression.
  --   TODO: Well, technically, `e` doesn't need to be an "expression" of any sorts.
  --         It just needs to be _something_, and it would have approximately have same meaning.
  defnExpr :: Lens' (c e) e