{-# LANGUAGE UnicodeSyntax, NoMonomorphismRestriction, TypeSynonymInstances, DeriveDataTypeable #-}
module Todos.Parser
    (parsePlain, parseAlternate)
    where

import Prelude hiding (putStrLn,readFile,getContents,print)
import Data.List
import Text.ParserCombinators.Parsec
import Data.Char

import Todos.Unicode
import Todos.Types
import Todos.Dates
import Todos.Config

type TParser a = GenParser Char BaseConfig a

strip   String  String
strip = reverse  p  reverse  p
  where
    p = dropWhile isSpace

pSpace  TParser Char
pSpace = oneOf " \t"

pSpace'  TParser String
pSpace' = do
    pSpace
    return " "

pSpaces  TParser String
pSpaces = many pSpace

pDeps  TParser [String]
pDeps = do
    string "("
    ws  (many1  noneOf ",)\n\r") `sepBy` (char ',')
    string ")"
    return $ map strip ws

pTags  TParser [String]
pTags = do
    ts  between (char '[') (char ']') $ word `sepBy1` pSpace
    pSpaces
    return ts
  where
    word = many1 (noneOf " \t\n\r]")

pItem  DateTime  TParser TodoItem
pItem date = do
    pos  getPosition
    s  pSpaces
    conf  getState
    stat  if skipStatus conf
             then case forcedStatus conf of
                    Just fs  return fs
                    Nothing  return "*"
            else do 
                rs  pWord
                case forcedStatus conf of
                  Just fs  return fs
                  Nothing  return rs
    dates  (try (pSpecDates date) <|> return [])
    tags  (try pTags <|> return [])
    namew  many1 pWord
    pSpaces
    deps  (try pDeps <|> return [])
    pSpaces
    descr  many (noneOf "\n\r")
    pSpaces
    many  oneOf "\n\r"
    return  Item {
        itemLevel = fromIntegral $ length s,
        itemName = unwords namew,
        itemTags = tags,
        depends = deps,
        itemStatus = stat,
        itemDescr = descr,
        startDate = lookup StartDate dates,
        endDate = lookup EndDate dates,
        deadline = lookup Deadline dates,
        fileName = sourceName pos,
        lineNr = sourceLine pos }

pWord  TParser String
pWord = do
    w  many1 (noneOf " \t\n\r")
    (try pSpace') <|> (return w)
    return w

pItems  DateTime  TParser [TodoItem]
pItems date = do
  its  many (pItem date)
  eof
  return its

unwords'   String  [String]  String
unwords' prefix lst =
  let (hd:tl) = map (filter (/='\r')) lst
      addLines = filter (not  (prefix `isPrefixOf`)) tl
  in  case addLines of
        []  hd
        _   hd  "    {" ++ (unwords addLines) ++ "}"

filterN  (Num a, Enum a)  Int  String  [String]  ([a], [String])
filterN n prefix lst = 
  let zipped = zip [0..] lst
      good   = filter (isGood  snd) zipped
      lns    = map fst good
      sub k l = (take l)  (drop k)
      ans = map (unwords' prefix) [sub j n lst | j  lns]
      isGood x = prefix `isPrefixOf` x
      cut = drop (1+length prefix) 
  in (map (+1) lns, map cut ans)

filterJoin  Int  String  String  ([Int], String)
filterJoin n prefix str = 
  let (ns, lns) = filterN n prefix (lines str)
  in  (ns, unlines lns)

-- | Read list of TODO items from plain format 
parsePlain  BaseConfig
            DateTime   -- ^ Current date/time
            SourceName -- ^ Source file name
            String     -- ^ String to parse
            [TodoItem]
parsePlain conf date path text = 
  case runParser (pItems date) conf path text of
      Right items  items
      Left e  error $ show e

-- | Read list of TODO items from alternate format
parseAlternate  BaseConfig 
                Int        -- ^ Number of lines after matching to include to item's description
                String     -- ^ Prefix to match
                DateTime   -- ^ Current date/time
                SourceName -- ^ Source file name
                String     -- ^ String to parse
                [TodoItem]
parseAlternate conf next prefix date path text = 
  let (ns, filtered) = filterJoin next prefix text
      renumber lst = zipWith renumber1 ns lst
      renumber1 n item = item {lineNr=n}
  in case runParser (pItems date) conf path filtered of
       Right items  renumber items
       Left e       error $ show e