{-# LANGUAGE UnicodeSyntax, DeriveDataTypeable, TypeSynonymInstances, FlexibleInstances, NoMonomorphismRestriction #-}

module Todos.Types where

import Prelude hiding (putStr, putStrLn,readFile,getContents,print)
import Prelude.Unicode
import Data.Hash

import Data.Function 
import Data.Generics hiding (GT)
import Data.Char (toUpper)
import Data.Maybe
import Data.Tree
import Data.List
import Data.Dates
import qualified Data.Map as M
import Text.Parsec
import Numeric

type 𝔹 = Bool

-- | Kind of date
data DateType = StartDate
              | EndDate
              | Deadline
  deriving (Eq)

instance Show DateType where
  show StartDate = "start"
  show EndDate = "end"
  show Deadline = "deadline"

-- | capitalize first letter of the string
capitalize  String  String
capitalize [] = []
capitalize (x:xs) = (toUpper x):xs

-- | TODO item itself.
data TodoItem = Item {
    itemLevel  ,               -- ^ Indentation level (from source file)
    itemPrefix  String,         -- ^ A prefix before item in source file (or empty string)
    itemName  String,           -- ^ Name (title) of the item
    itemTags  [String],         -- ^ Tags of the item
    depends  [String],          -- ^ Names (titles) of item's depends
    itemStatus  String,         -- ^ Status of the item
    itemDescr  String,          -- ^ Description of the item
    startDate  Maybe DateTime,  -- ^ Date when TODO is planned to start
    endDate  Maybe DateTime,    -- ^ Date when TODO is planned to end
    deadline  Maybe DateTime,   -- ^ Deadline for this TODO
    fileName  FilePath,         -- ^ Path to the source file
    lineNr  Line,               -- ^ Line in the source file, where this item was defined
    itemNumber                 -- ^ Raw number of item
  }
  deriving (Eq,Data,Typeable)

instance Hashable TodoItem where
    hash item = foldl1 combine $ map ($ item) [hash  itemName, hash  itemDescr,
                                               hash  itemTags, hash  itemStatus]

-- | Make an ID for any hashable item. 16 hexadecimal digits.
makeId :: (Hashable a)  a  String
makeId item =
  let s = showHex (asWord64 $ hash item) ""
      l = length s 
  in  if l < 16 
        then replicate (16-l) '0' ++ s
        else s

-- | Tree of TODO items.
type Todo = Tree TodoItem

type TodoMap = M.Map String Todo

data Limit = Unlimited
           | Limit {unLimit  }
  deriving (Eq,Show)

instance Ord Limit where
    compare Unlimited Unlimited = EQ
    compare Unlimited _ = GT
    compare _ Unlimited = LT
    compare (Limit x) (Limit y) = compare x y

-- | Command line flag
data CmdLineFlag = QF {queryFlag  QueryFlag}
                 | MF {modeFlag  ModeFlag}
                 | OF {outFlag  OutFlag}
                 | LF {limFlag  LimitFlag}
                 | HelpF
    deriving (Eq,Show)

-- | Flags to specify query
data QueryFlag = Tag String
               | Name {unName  String}
               | IdIs String
               | Status String
               | Description String
               | StartDateIs DateTime
               | EndDateIs DateTime
               | DeadlineIs DateTime
               | AndCons
               | OrCons
               | NotCons
               | NoFilter
     deriving (Eq,Ord,Show)        

data LimitFlag = Prune {unPrune  }
               | Start {unMin  }
    deriving (Eq,Show)

-- | Flags to specify parsing mode
data ModeFlag = Execute {unExecute  String}
              | Prefix {unPrefix  String}
              | Format {getFormat  String}
              | DoNotReadStatus
              | SetStatus {newStatus  String}
              | SetTopStatus {newTopStatus  String}
              | GroupByFile
              | GroupByTag
              | GroupByStatus
    deriving (Eq,Ord,Show)

-- | Flags to control output
data OutFlag = OnlyFirst 
             | Colors
             | Highlight
             | Ids
             | DotExport
             | IndentWith {getIndentString  String}
             | Sort {getSorting  SortingType}
    deriving (Eq,Ord,Show)

-- | Type of sorting
data SortingType = DoNotSort
                 | ByTitle
                 | ByStatus
                 | ByTags 
                 | ByStartDate
                 | ByEndDate
                 | ByDeadline
    deriving (Eq,Ord,Show)

readSort  String  SortingType
readSort "no" = DoNotSort
readSort "title" = ByTitle
readSort "status" = ByStatus
readSort "tags" = ByTags
readSort "start-date" = ByStartDate
readSort "end-date" = ByEndDate
readSort "deadline" = ByDeadline
readSort s = error $ "Unknown sorting type: "++s

instance (Ord a)  Ord (Tree a) where
  compare = compare `on` rootLabel

-- TODO: - rename Options → QueryOptions or similar
-- | Result of parsing command line
data Options = O [QueryFlag] [ModeFlag] [OutFlag] [LimitFlag]
             | Help

-- | What to do with selected items
data TodoCommand =
    JustShow              -- ^ Just output items to console
  | ShowAsDot             -- ^ Output graph in DOT format
  | SystemCommand String  -- ^ Execute this system command for each item
  deriving (Eq, Show)

-- | Data type to store complex queries
data Composed = Pred QueryFlag            -- ^ Simple query
              | And Composed Composed     -- ^ Logical AND
              | Or Composed Composed      -- ^ Logical OR
              | Not Composed              -- ^ Logical NOT
              | Empty                     -- ^ Empty query
              | HelpC                     -- ^ User requests help
    deriving (Eq,Show)

is   (Functor f)  t  f a  f (t, a)
t `is`  x = (\a  (t,a)) `fmap` x

showDate   (DateType, DateTime)  String
showDate (t,d) = show t  ": "  show d

showDates   [Maybe (DateType, DateTime)]  String
showDates = intercalate "; "  map showDate  catMaybes

instance Show TodoItem where
    show item = s  " "  dates  tags  name  (if null descr then "" else "    "descr)
      where
        name = itemName item
        ts = itemTags item
        s = itemStatus item
        descr = itemDescr item
        dates | null dates' = ""
              | otherwise = "("  dates'  ") "
        dates' = showDates [StartDate `is` startDate item, EndDate `is` endDate item, Deadline `is` deadline item]
        tags = if null ts
                 then ""
                 else "["  (unwords ts)  "] "

instance Ord TodoItem where
  compare item1 item2 = 
      let c1 = (compare `on` itemLevel) item1 item2
          c2 = (compare `on` itemStatus) item1 item2
          c3 = (compare `on` itemName) item1 item2
      in  if c1 == EQ
            then if c2 == EQ 
                   then c3
                   else c2
            else c1