-- | This module defines the types used for implementing GEP problems
--   and operations.  A few functions are also provided for convenience
--   here for performing common operations.
--

module GEP.Types (
    -- * Types
    Genome(..),
    Symbol,
    Gene,
    Chromosome,
    Individual,
    SymTable,

    -- * Functions
    tailLength,
    geneLength,
    allsymbols,
    chromToGenes,
    genesToChrom,
    isNonterminal
) where

-- | A symbol in a chromosome
type Symbol     = Char

-- | A gene in a chromosome is a list of symbols
type Gene       = [Symbol]

-- | A chromosome is a list of symbols.  We avoided using a list of genes to
--   maintain the view of a chromosome as nothing more than a flattened,
--   linear sequence of genes.
type Chromosome = [Symbol]

-- | An individual is a chromosome
type Individual = Chromosome

-- | Symbol table used for fitness tests.  We assume that there is exactly
--   one pair per symbol.  If there are symbols missing, fitness testing
--   may fail (the library does not have facilities yet to allow for
--   default values).  If a symbol occurs multiple times in the symbol
--   table, no guarantee is provided for which value will be chosen.
type SymTable a = [(Symbol,a)]

-- | Data type representing a genome.  The genome contains all necessary
--   parameters to interpret a chromosome.  These include the alphabet (split
--   between terminal and nonterminal characters), connective characters for
--   multi-gene chromosomes, the maximum arity of any nonterminal, the length
--   of the head of a gene, and the number of genes per chromosome.
data Genome = Genome {
      terminals     :: [Symbol], -- ^ Set of terminal symbols
      nonterminals  :: [Symbol], -- ^ Set of nonterminal symbols
      geneConnector :: Symbol,   -- ^ Symbol connecting genes in a chromosome
      maxArity      :: Int,      -- ^ Highest arity nonterminal function
      headLength    :: Int,      -- ^ Length of gene head sequence
      numGenes      :: Int       -- ^ Number of genes per chromosome
} deriving Show

-- | Given a genome, provide the list of all symbols possible in a chromosome.
--   This is just nonterminals ++ terminals.
allsymbols :: Genome   -- ^ Genome 
           -> [Symbol] -- ^ List of symbols
allsymbols g = (terminals g)++(nonterminals g)

-- | Return the length of the tail of a gene for a given genome
tailLength :: Genome   -- ^ Genome
           -> Int      -- ^ Number of symbols in a gene tail
tailLength g = ((headLength g) * ((maxArity g)-1))+1

-- | Return length of a gene (tail + head) for a given genome
geneLength :: Genome   -- ^ Genome 
           -> Int      -- ^ Total length of a gene.
geneLength g = (headLength g) + (tailLength g)

-- | Test if a symbol is a nonterminal
isNonterminal :: Symbol  -- ^ Symbol to test 
              -> Genome  -- ^ Genome providing context
              -> Bool    -- ^ True if symbol is a nonterminal, false otherwise
isNonterminal s g =
  let isNT []                 = False
      isNT (x:_)  | (s == x)  = True
      isNT (_:xs) | otherwise = (isNT xs)
  in
    isNT (nonterminals g)

-- | Fracture a chromosome into a set of genes
chromToGenes :: Chromosome  -- ^ Chromosome to split into a set of genes 
             -> Int         -- ^ Length of a single gene
             -> [Gene]      -- ^ Ordered list of genes from chromosome
chromToGenes [] _ = []
chromToGenes c  glen = (take glen c):(chromToGenes (drop glen c) glen)

-- | Assemble a chromosome from a set of genes
genesToChrom :: [Gene]      -- ^ List of genes
             -> Chromosome  -- ^ Chromosome assembled from genes
genesToChrom genes = foldl (++) [] genes