{-# LANGUAGE TemplateHaskell #-}
-- | Defines types and functions to create a chunk database within Drasil.

-- Changes to ChunkDB should be reflected in the 'Creating Your Project 
-- in Drasil' tutorial found on the wiki:
-- https://github.com/JacquesCarette/Drasil/wiki/Creating-Your-Project-in-Drasil
module Database.Drasil.ChunkDB (
  -- * Types
  -- ** 'ChunkDB'
  -- | Main database type
  ChunkDB(CDB, symbolTable, termTable, defTable),
  -- ** Maps
  -- | Exported for external use.
  RefbyMap, TraceMap, UMap,
  -- * Functions
  -- ** Constructors
  cdb, idMap, termMap, conceptMap, traceMap, generateRefbyMap, -- idMap, termMap for docLang
  -- ** Lookup Functions
  asOrderedList, collectUnits,
  termResolve, defResolve, symbResolve,
  traceLookup, refbyLookup,
  datadefnLookup, insmodelLookup, gendefLookup, theoryModelLookup,
  conceptinsLookup, sectionLookup, labelledconLookup, refResolve,
  -- ** Lenses
  unitTable, traceTable, refbyTable,
  dataDefnTable, insmodelTable, gendefTable, theoryModelTable,
  conceptinsTable, sectionTable, labelledcontentTable, refTable
) where

import Language.Drasil
import Theory.Drasil (DataDefinition, GenDefn, InstanceModel, TheoryModel)

import Control.Lens ((^.), makeLenses)
import Data.List (sortOn)
import Data.Maybe (fromMaybe, mapMaybe)
import qualified Data.Map as Map
import Utils.Drasil (invert)

-- | The misnomers below (for the following Map types) are not actually a bad thing. We want to ensure data can't
-- be added to a map if it's not coming from a chunk, and there's no point confusing
-- what the map is for. One is for symbols + their units, and the others are for
-- what they state.
type UMap a = Map.Map UID (a, Int)

-- | A bit of a misnomer as it's really a map of all quantities, for retrieving
-- symbols and their units.
type SymbolMap  = UMap QuantityDict

-- | A map of all concepts, normally used for retrieving definitions.
type ConceptMap = UMap ConceptChunk

-- | A map of all the units used. Should be restricted to base units/synonyms.
type UnitMap = UMap UnitDefn

-- | Again a bit of a misnomer as it's really a map of all 'NamedIdea's.
-- Until these are built through automated means, there will
-- likely be some 'manual' duplication of terms as this map will contain all
-- quantities, concepts, etc.
type TermMap = UMap IdeaDict
-- | A traceability map, used to hold the relation between one 'UID' and a list of other 'UID's.
type TraceMap = Map.Map UID [UID]
-- | A reference map, used to hold a 'UID' and where it is referenced ('UID's).
type RefbyMap = Map.Map UID [UID]
-- | Data definitions map. Contains all data definitions ('DataDefinition').
type DatadefnMap = UMap DataDefinition
-- | Instance model map. Contains all instance models ('InstanceModel').
type InsModelMap = UMap InstanceModel
-- | General definitions map. Contains all general definitions ('GenDefn').
type GendefMap = UMap GenDefn
-- | Theory model map. Contains all theoretical models ('TheoryModel').
type TheoryModelMap = UMap TheoryModel
-- | Concept instance map. May hold similar information to a 'ConceptMap', but may also be referred to.
type ConceptInstanceMap = UMap ConceptInstance
-- | A map of all the different 'Section's.
type SectionMap = UMap Section
-- | A map of all 'LabelledContent's.
type LabelledContentMap = UMap LabelledContent
-- | A map of all 'Reference's.
type ReferenceMap = UMap Reference

-- | General chunk database map constructor. Creates a 'UMap' from a function that converts something with 'UID's into another type and a list of something with 'UID's.
cdbMap :: HasUID a => (a -> b) -> [a] -> Map.Map UID (b, Int)
cdbMap :: forall a b. HasUID a => (a -> b) -> [a] -> Map UID (b, Int)
cdbMap a -> b
fn = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (\(a
x,Int
y) -> (a
x forall s a. s -> Getting a s a -> a
^. forall c. HasUID c => Lens' c UID
uid, (a -> b
fn a
x, Int
y))) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a b. [a] -> [b] -> [(a, b)]
zip [Int
1..]

-- | Smart constructor for a 'SymbolMap'.
symbolMap :: (Quantity c, MayHaveUnit c) => [c] -> SymbolMap
symbolMap :: forall c. (Quantity c, MayHaveUnit c) => [c] -> SymbolMap
symbolMap = forall a b. HasUID a => (a -> b) -> [a] -> Map UID (b, Int)
cdbMap forall q. (Quantity q, MayHaveUnit q) => q -> QuantityDict
qw

-- | Smart constructor for a 'TermMap'.
termMap :: (Idea c) => [c] -> TermMap
termMap :: forall c. Idea c => [c] -> TermMap
termMap = forall a b. HasUID a => (a -> b) -> [a] -> Map UID (b, Int)
cdbMap forall c. Idea c => c -> IdeaDict
nw

-- | Smart constructor for a 'ConceptMap'.
conceptMap :: (Concept c) => [c] -> ConceptMap
conceptMap :: forall c. Concept c => [c] -> ConceptMap
conceptMap = forall a b. HasUID a => (a -> b) -> [a] -> Map UID (b, Int)
cdbMap forall c. Concept c => c -> ConceptChunk
cw

-- | Smart constructor for a 'UnitMap'.
unitMap :: (IsUnit u) => [u] -> UnitMap
unitMap :: forall u. IsUnit u => [u] -> UnitMap
unitMap = forall a b. HasUID a => (a -> b) -> [a] -> Map UID (b, Int)
cdbMap forall u. IsUnit u => u -> UnitDefn
unitWrapper

-- | General smart constructor for making a 'UMap' out of anything that has a 'UID'. 
idMap :: HasUID a => [a] -> Map.Map UID (a, Int)
idMap :: forall a. HasUID a => [a] -> Map UID (a, Int)
idMap = forall a b. HasUID a => (a -> b) -> [a] -> Map UID (b, Int)
cdbMap forall a. a -> a
id

-- | Smart constructor for a 'TraceMap' given a traceability matrix.
traceMap :: [(UID, [UID])] -> TraceMap
traceMap :: [(UID, [UID])] -> TraceMap
traceMap = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList

-- | Gets a unit if it exists, or Nothing.        
getUnitLup :: HasUID c => ChunkDB -> c -> Maybe UnitDefn
getUnitLup :: forall c. HasUID c => ChunkDB -> c -> Maybe UnitDefn
getUnitLup ChunkDB
m c
c = forall u. MayHaveUnit u => u -> Maybe UnitDefn
getUnit forall a b. (a -> b) -> a -> b
$ ChunkDB -> UID -> QuantityDict
symbResolve ChunkDB
m (c
c forall s a. s -> Getting a s a -> a
^. forall c. HasUID c => Lens' c UID
uid)

-- | Looks up a 'UID' in a 'UMap' table. If nothing is found, an error is thrown.
uMapLookup :: String -> String -> UID -> UMap a -> a
uMapLookup :: forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
tys String
ms UID
u UMap a
t = forall {b} {b}. Maybe (b, b) -> b
getFM forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup UID
u UMap a
t
  where getFM :: Maybe (b, b) -> b
getFM = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall a. HasCallStack => String -> a
error forall a b. (a -> b) -> a -> b
$ String
tys forall a. [a] -> [a] -> [a]
++ String
": " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show UID
u forall a. [a] -> [a] -> [a]
++ String
" not found in " forall a. [a] -> [a] -> [a]
++ String
ms) forall a b. (a, b) -> a
fst

-- | Looks up a 'UID' in the symbol table from the 'ChunkDB'. If nothing is found, an error is thrown.
symbResolve :: ChunkDB -> UID -> QuantityDict
symbResolve :: ChunkDB -> UID -> QuantityDict
symbResolve ChunkDB
m UID
x = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"Symbol" String
"SymbolMap" UID
x forall a b. (a -> b) -> a -> b
$ ChunkDB -> SymbolMap
symbolTable ChunkDB
m

-- | Looks up a 'UID' in the term table from the 'ChunkDB'. If nothing is found, an error is thrown.
termResolve :: ChunkDB -> UID -> IdeaDict
termResolve :: ChunkDB -> UID -> IdeaDict
termResolve ChunkDB
m UID
x = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"Term" String
"TermMap" UID
x forall a b. (a -> b) -> a -> b
$ ChunkDB -> TermMap
termTable ChunkDB
m

-- | Looks up a 'UID' in the reference table from the 'ChunkDB'. If nothing is found, an error is thrown.
refResolve :: UID -> ReferenceMap -> Reference
refResolve :: UID -> ReferenceMap -> Reference
refResolve = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"Reference" String
"ReferenceMap"

-- | Looks up a 'UID' in the unit table. If nothing is found, an error is thrown.
unitLookup :: UID -> UnitMap -> UnitDefn
unitLookup :: UID -> UnitMap -> UnitDefn
unitLookup = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"Unit" String
"UnitMap"

-- | Looks up a 'UID' in the definition table from the 'ChunkDB'. If nothing is found, an error is thrown.
defResolve :: ChunkDB -> UID -> ConceptChunk
defResolve :: ChunkDB -> UID -> ConceptChunk
defResolve ChunkDB
m UID
x = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"Concept" String
"ConceptMap" UID
x forall a b. (a -> b) -> a -> b
$ ChunkDB -> ConceptMap
defTable ChunkDB
m

-- | Looks up a 'UID' in the datadefinition table. If nothing is found, an error is thrown.
datadefnLookup :: UID -> DatadefnMap -> DataDefinition
datadefnLookup :: UID -> DatadefnMap -> DataDefinition
datadefnLookup = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"DataDefinition" String
"DatadefnMap"

-- | Looks up a 'UID' in the instance model table. If nothing is found, an error is thrown.
insmodelLookup :: UID -> InsModelMap -> InstanceModel
insmodelLookup :: UID -> InsModelMap -> InstanceModel
insmodelLookup = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"InstanceModel" String
"InsModelMap"

-- | Looks up a 'UID' in the general definition table. If nothing is found, an error is thrown.
gendefLookup :: UID -> GendefMap -> GenDefn
gendefLookup :: UID -> GendefMap -> GenDefn
gendefLookup = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"GenDefn" String
"GenDefnMap" 

-- | Looks up a 'UID' in the theory model table. If nothing is found, an error is thrown.
theoryModelLookup :: UID -> TheoryModelMap -> TheoryModel
theoryModelLookup :: UID -> TheoryModelMap -> TheoryModel
theoryModelLookup = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"TheoryModel" String
"TheoryModelMap"

-- | Looks up a 'UID' in the concept instance table. If nothing is found, an error is thrown.
conceptinsLookup :: UID -> ConceptInstanceMap -> ConceptInstance
conceptinsLookup :: UID -> ConceptInstanceMap -> ConceptInstance
conceptinsLookup = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"ConceptInstance" String
"ConceptInstanceMap"

-- | Looks up a 'UID' in the section table. If nothing is found, an error is thrown.
sectionLookup :: UID -> SectionMap -> Section
sectionLookup :: UID -> SectionMap -> Section
sectionLookup = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"Section" String
"SectionMap"

-- | Looks up a 'UID' in the labelled content table. If nothing is found, an error is thrown.
labelledconLookup :: UID -> LabelledContentMap -> LabelledContent
labelledconLookup :: UID -> LabelledContentMap -> LabelledContent
labelledconLookup = forall a. String -> String -> UID -> UMap a -> a
uMapLookup String
"LabelledContent" String
"LabelledContentMap"

-- | Gets an ordered list of @a@ from any @a@ that is of type 'UMap'.
asOrderedList :: UMap a -> [a]
asOrderedList :: forall a. UMap a -> [a]
asOrderedList = forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k a. Map k a -> [(k, a)]
Map.toList

-- | Our chunk databases. \Must contain all maps needed in an example.\
-- In turn, these maps must contain every chunk definition or concept 
-- used in its respective example, else an error is thrown.
data ChunkDB = CDB { ChunkDB -> SymbolMap
symbolTable           :: SymbolMap
                   , ChunkDB -> TermMap
termTable             :: TermMap 
                   , ChunkDB -> ConceptMap
defTable              :: ConceptMap
                   , ChunkDB -> UnitMap
_unitTable            :: UnitMap
                   , ChunkDB -> TraceMap
_traceTable           :: TraceMap
                   , ChunkDB -> TraceMap
_refbyTable           :: RefbyMap
                   , ChunkDB -> DatadefnMap
_dataDefnTable        :: DatadefnMap
                   , ChunkDB -> InsModelMap
_insmodelTable        :: InsModelMap
                   , ChunkDB -> GendefMap
_gendefTable          :: GendefMap
                   , ChunkDB -> TheoryModelMap
_theoryModelTable     :: TheoryModelMap
                   , ChunkDB -> ConceptInstanceMap
_conceptinsTable      :: ConceptInstanceMap
                   , ChunkDB -> SectionMap
_sectionTable         :: SectionMap
                   , ChunkDB -> LabelledContentMap
_labelledcontentTable :: LabelledContentMap
                   , ChunkDB -> ReferenceMap
_refTable             :: ReferenceMap
                   } -- TODO: Expand and add more databases
makeLenses ''ChunkDB

-- | Smart constructor for chunk databases. Takes in the following:
--
--     * ['Quantity'] (for 'SymbolMap'), 
--     * 'NamedIdea's (for 'TermMap'),
--     * 'Concept's (for 'ConceptMap'),
--     * Units (something that 'IsUnit' for 'UnitMap'),
--     * 'DataDefinition's (for 'DatadefnMap'),
--     * 'InstanceModel's (for 'InsModelMap'),
--     * 'GenDefn's (for 'GendefMap'),
--     * 'TheoryModel's (for 'TheoryModelMap'),
--     * 'ConceptInstance's (for 'ConceptInstanceMap'),
--     * 'Section's (for 'SectionMap'),
--     * 'LabelledContent's (for 'LabelledContentMap').
cdb :: (Quantity q, MayHaveUnit q, Idea t, Concept c, IsUnit u) =>
    [q] -> [t] -> [c] -> [u] -> [DataDefinition] -> [InstanceModel] ->
    [GenDefn] -> [TheoryModel] -> [ConceptInstance] -> [Section] ->
    [LabelledContent] -> [Reference] -> ChunkDB
cdb :: forall q t c u.
(Quantity q, MayHaveUnit q, Idea t, Concept c, IsUnit u) =>
[q]
-> [t]
-> [c]
-> [u]
-> [DataDefinition]
-> [InstanceModel]
-> [GenDefn]
-> [TheoryModel]
-> [ConceptInstance]
-> [Section]
-> [LabelledContent]
-> [Reference]
-> ChunkDB
cdb [q]
s [t]
t [c]
c [u]
u [DataDefinition]
d [InstanceModel]
ins [GenDefn]
gd [TheoryModel]
tm [ConceptInstance]
ci [Section]
sect [LabelledContent]
lc [Reference]
r = SymbolMap
-> TermMap
-> ConceptMap
-> UnitMap
-> TraceMap
-> TraceMap
-> DatadefnMap
-> InsModelMap
-> GendefMap
-> TheoryModelMap
-> ConceptInstanceMap
-> SectionMap
-> LabelledContentMap
-> ReferenceMap
-> ChunkDB
CDB (forall c. (Quantity c, MayHaveUnit c) => [c] -> SymbolMap
symbolMap [q]
s) (forall c. Idea c => [c] -> TermMap
termMap [t]
t) (forall c. Concept c => [c] -> ConceptMap
conceptMap [c]
c)
  (forall u. IsUnit u => [u] -> UnitMap
unitMap [u]
u) forall k a. Map k a
Map.empty forall k a. Map k a
Map.empty (forall a. HasUID a => [a] -> Map UID (a, Int)
idMap [DataDefinition]
d) (forall a. HasUID a => [a] -> Map UID (a, Int)
idMap [InstanceModel]
ins) (forall a. HasUID a => [a] -> Map UID (a, Int)
idMap [GenDefn]
gd) (forall a. HasUID a => [a] -> Map UID (a, Int)
idMap [TheoryModel]
tm)
  (forall a. HasUID a => [a] -> Map UID (a, Int)
idMap [ConceptInstance]
ci) (forall a. HasUID a => [a] -> Map UID (a, Int)
idMap [Section]
sect) (forall a. HasUID a => [a] -> Map UID (a, Int)
idMap [LabelledContent]
lc) (forall a. HasUID a => [a] -> Map UID (a, Int)
idMap [Reference]
r)

-- | Gets the units of a 'Quantity' as 'UnitDefn's.
collectUnits :: Quantity c => ChunkDB -> [c] -> [UnitDefn]
collectUnits :: forall c. Quantity c => ChunkDB -> [c] -> [UnitDefn]
collectUnits ChunkDB
m = forall a b. (a -> b) -> [a] -> [b]
map (forall u. IsUnit u => u -> UnitDefn
unitWrapper forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b c. (a -> b -> c) -> b -> a -> c
flip UID -> UnitMap -> UnitDefn
unitLookup (ChunkDB
m forall s a. s -> Getting a s a -> a
^. Lens' ChunkDB UnitMap
unitTable))
 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall u. IsUnit u => u -> [UID]
getUnits forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (forall c. HasUID c => ChunkDB -> c -> Maybe UnitDefn
getUnitLup ChunkDB
m)

-- | Trace a 'UID' to related 'UID's.
traceLookup :: UID -> TraceMap -> [UID]
traceLookup :: UID -> TraceMap -> [UID]
traceLookup UID
c = forall a. a -> Maybe a -> a
fromMaybe [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup UID
c

-- | Translates a traceability map into a reference map.
generateRefbyMap :: TraceMap -> RefbyMap
generateRefbyMap :: TraceMap -> TraceMap
generateRefbyMap = forall v k. Ord v => Map k [v] -> Map v [k]
invert

-- | Trace a 'UID' to referenced 'UID's.
refbyLookup :: UID -> RefbyMap -> [UID]
refbyLookup :: UID -> TraceMap -> [UID]
refbyLookup UID
c = forall a. a -> Maybe a -> a
fromMaybe [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup UID
c