{-
Copyright 2013, 2014 Marcelo Garlet Millani
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
-}
{-|
Module      : DescriLo
Description : 
Copyright   : (c) Marcelo Garlet Millani
License     : GPL-3
Maintainer  : marcelogmillani@gmail.com
Stability   : experimental

Loads a file or 'String' into a list of 'Description's. 'Description's with the same name are allowed.

Example file:

>[item A] # the first item
>  property 1 = value 1 # indentation is ignored
>  property 2 = value 2
>[item B]
>  something=else # whitespaces after and before each value / property are ignored


-}
module Data.DescriLo(loadDescriptionFile, loadDescriptionString, checkAttribute, Description(Description, name, values)) where

import System.IO

data Description =
  Description {
    name :: String,
    values :: [(String,String)]
    }

instance Show Description where
  show Description {name=n, values=vs} =
    "[" ++ n ++ "]\n" ++ foldl (++) "" (map (\(x,y) -> "\t" ++ x ++ " = " ++ y ++ "\n") vs)

data Element a = Variable a a | Definition a | Nil deriving Show

trimL (' ':r) = trimL r
trimL ('\t':r) = trimL r
trimL str = str

trimR [] = []
trimR (' ':[]) = []
trimR ('\t':[]) = []
trimR (h:r) =
  let trimmed = trimR r in
  case trimmed of
    [] -> [h]
    ' ':[] -> [h]
    '\t':[] -> [h]
    a -> h:a

trim s = trimR $ trimL s

-- | loads all descriptions from a file
--
-- usage: loadDescriptionFile filename defaultName
--
-- loads the given file, using 'defaultName' as the default description name in case the document doesn't start with the definition of one

loadDescriptionFile fname defName = do
  fl <- readFile fname
  return $ loadDescriptionString fl defName

-- | loads all descriptions from a string
--
-- usage: loadDescriptionString string defaultName
--
-- behaves just like loadDescriptionFile, but receives a 'String' instead
loadDescriptionString string defaultName =
  let lns = lines string in
  loadDescriptions lns Description{name = defaultName, values = []}

-- returns a list of descriptions
loadDescriptions lns cat = loadDescriptions' lns cat False
loadDescriptions' [] cat hasVars = if hasVars then [cat] else []
loadDescriptions' (h:rest) cat hasVars =
  let ln = loadLine h in
  case ln of
    Variable left right ->
      let loaded = loadDescriptions' rest cat True in
      case loaded of
        [] -> [cat{values = [(left,right)]}]
        (rcat:rrest) -> rcat{values = (left,right):values rcat}:rrest
    Definition newName -> (if hasVars then (cat :) else id) $ loadDescriptions' rest Description{name = newName, values = []} False
    Nil -> loadDescriptions' rest cat hasVars

-- returns an Element
loadLine ln =
  case ln of
    ' ':rest -> loadLine rest
    '\t':rest -> loadLine rest
    '#':_ -> Nil
    '\n':_ -> Nil
    '[':rest -> Definition $ loadDescription rest
    _ -> loadVariable ln

loadDescription ln =
  case ln of
    [] -> ""
    ']':[] -> ""
    h:rest -> h:loadDescription rest

-- if ln is l = r, returns Variable l r
-- trims l and r, so that there are no whitespaces before or after
loadVariable ln =
  let (left,right) = loadVariableLeft ln in
  case (trim left, trim right) of
    ([],_) -> Nil
    (_,[]) -> Nil
    (a,b) -> Variable a b

loadVariableLeft [] = ([],[])
loadVariableLeft (h:rest) =
  let (left,right) = loadVariableLeft rest in
  case h of
    '=' -> ([], rest)
    _ -> (h:left,loadVariableRight right)

-- removes comments to the right of the value
loadVariableRight [] = []
loadVariableRight ('#':r) = []
loadVariableRight (h:r) = h:loadVariableRight r

-- | checks if the specified attribute satisfies the given function, when it exists
-- if the given attribute does not exist, returns False
checkAttribute lval compareF Description{values = (h:r)} = checkAttribute' lval compareF (h:r)
checkAttribute' _ _ [] = False
checkAttribute' lval compareF ((l,r):rest) =
  if l == lval then compareF r else checkAttribute' lval compareF rest