-- |
-- Code to read configuration files.
--
-- Author: mjsottile\@computer.org
--

module GEP.Util.ConfigurationReader (
  readParameters
) where

import GEP.Params
import GEP.Types
import System.IO
import Maybe

--
-- given a list of pairs mapping keys to values, lookup the various
-- parameters and populate the rates, genome, and simparams structures
--
extractParameters :: [(String,String)] -> (Rates,Genome,SimParams)
extractParameters config = (r,g,s)
    where
      s = SimParams { 
	    popSize          = fromJust (lookupInt "populationSize" config),
	    selectionRange   = fromJust (lookupDouble "selectionRange" config),
	    maxFitness       = fromJust (lookupDouble "maxFitness" config),
	    numGenerations   = fromJust (lookupInt "numGenerations" config),
	    maxISLen         = fromJust (lookupInt "maxISLen" config),
	    maxRISLen        = fromJust (lookupInt "maxRISLen" config),
	    rouletteExponent = fromJust (lookupDouble "rouletteExponent" config) 
	  }
      r = Rates { pMutate = fromJust (lookupDouble "rateMutate" config),
	          p1R     = fromJust (lookupDouble "rate1R" config),
	          p2R     = fromJust (lookupDouble "rate2R" config),
	          pGR     = fromJust (lookupDouble "rateGR" config),
	          pIS     = fromJust (lookupDouble "rateIS" config),
	          pRIS    = fromJust (lookupDouble "rateRIS" config),
	          pGT     = fromJust (lookupDouble "rateGT" config) 
	        }
      g = Genome { 
	    terminals     = fromJust (lookupString "genomeTerminals" config),
	    nonterminals  = fromJust (lookupString "genomeNonterminals" config),
	    geneConnector = fromJust (lookupChar "genomeGeneConnector" config),
	    maxArity      = fromJust (lookupInt "genomeMaxArity" config),
	    numGenes      = fromJust (lookupInt "genomeNumGenes" config),
	    headLength    = fromJust (lookupInt "genomeHeadLength" config)
	  }

--
-- function visible to the outside world.  passes in a string representing
-- the filename of the configuration, and passes back the rates,
-- genome, and simparams structures.  Expected to be called from within the
-- IO monad
--
readParameters :: String -> IO (Rates,Genome,SimParams)
readParameters filename = 
	do config <- readConfiguration filename
	   return $ extractParameters config

--
-- lookup helpers: float, int, char, and string versions
--

lookupDouble :: String -> [(String,String)] -> Maybe Double
lookupDouble _ [] = Nothing
lookupDouble k ((key,value):_) | (k==key)  = Just (read value)
lookupDouble k ((_,_):kvs)     | otherwise = lookupDouble k kvs

lookupInt :: String -> [(String,String)] -> Maybe Int
lookupInt _ [] = Nothing
lookupInt k ((key,value):_) | (k==key)  = Just (read value)
lookupInt k ((_,_):kvs)     | otherwise = lookupInt k kvs

lookupString :: String -> [(String,String)] -> Maybe String
lookupString _ [] = Nothing
lookupString k ((key,value):_) | (k==key)  = Just value
lookupString k ((_,_):kvs)     | otherwise = lookupString k kvs

lookupChar :: String -> [(String,String)] -> Maybe Char
lookupChar _ [] = Nothing
lookupChar k ((key,value):_) | (k==key)  = Just (head value)
lookupChar k ((_,_):kvs)     | otherwise = lookupChar k kvs

--
-- given a string, remove whitespace
--
removeWhitespace :: String -> String
removeWhitespace []                   = []
removeWhitespace (x:xs) | (x == ' ')  = removeWhitespace xs
removeWhitespace (x:xs) | (x == '\t') = removeWhitespace xs
removeWhitespace (x:xs) | otherwise   = x:(removeWhitespace xs)

--
-- split a line formatted as "KEY=VALUE", removing whitespace
--
splitLine :: String -> (String,String)
splitLine l = (front,back)
  where
    cleaned = removeWhitespace l
    front   = takeWhile (\i -> not (i == '=')) cleaned
    back    = drop 1 (dropWhile (\i -> not (i == '=')) cleaned)

--
-- read a file handle and return all of the lines in the file
--
fileToLines :: Handle -> IO [String]
fileToLines h = do eof <- hIsEOF h
                   (if eof
                    then return []
                    else do line <- hGetLine h
                            remainder <- fileToLines h
                            return $ (line:remainder))

--
-- given a filename, open the file, read the lines, and then split them
-- into key/value pairs assuming a "KEY=VALUE" format per line
--
readConfiguration :: String -> IO [(String,String)]
readConfiguration filename = do handle <- openFile filename ReadMode
                                fileLines <- fileToLines handle
                                return $ map (\i -> splitLine i) fileLines