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

module Todos.Types where

import Prelude hiding (putStr, putStrLn,readFile,getContents,print)
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 qualified Data.Map as M
import Text.ParserCombinators.Parsec
import Numeric

import Todos.Unicode

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

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

data DateTime =
  DateTime {
    year  Int,
    month  Int,
    day  Int,
    hour  Int,
    minute  Int,
    second  Int }
  deriving (Eq,Ord,Data,Typeable)

-- | 12 months names.
months  [String]
months = ["january",
          "february",
          "march",
          "april",
          "may",
          "june",
          "july",
          "august",
          "september",
          "october",
          "november",
          "december"]

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

-- | Show name of given month
showMonth   Int  String
showMonth i = capitalize $ months !! (i-1)

instance Show DateTime where
  show (DateTime y m d h mins s) = 
    show d  " "  showMonth m  " "  show y  ", " 
      show h  ":"  show mins  ":"  show s

-- | Only time, without date
data Time = 
  Time {
    tHour  Int,
    tMinute  Int,
    tSecond  Int }
  deriving (Eq,Ord,Show,Data,Typeable)

-- | 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