{-# LANGUAGE FlexibleContexts #-}
module Villefort.Database (makeQuery
                          ,getSubjects
                          ,execQuery
                          ,addDaily
                          ,clean
                          ,getDone
                          ,getDb
                          ,addTask) where

import Control.Monad.Reader (MonadIO,MonadReader,liftIO,ask)
import Database.HDBC.Sqlite3 (Connection,connectSqlite3)
import Database.HDBC (SqlValue
                     ,execute
                     ,commit
                     ,disconnect
                     ,toSql
                     ,fromSql
                     ,quickQuery'
                     ,prepare)
import System.Environment (getArgs)
import Paths_Villefort (getDataDir)
import Villefort.Definitions (VConfig(..))
import Data.Convertible.Base (Convertible)

-- | gets list of subjects from local database
getSubjects :: (MonadReader VConfig m, MonadIO m) => m [String]
getSubjects = (\x-> (!! 0) <$> x) <$> makeQuery "select Subject from todo where state = 0 group by Subject"

-- | get paths tests for --custom flag to allow for executing custom builds
path' ::  (MonadReader VConfig m, MonadIO m) => m FilePath
path' = do
  env <- ask
  let s = showDatabase env
  args <- liftIO $ getArgs
  let cont = do
        if length args > 1 then
         if args !! 0 == "--custom" then
           return $ args !! 1
         else liftIO $ getDataDir
         else liftIO $ getDataDir
  if s then (liftIO $ putStrLn =<< getDataDir) >> cont else cont


-- | connects to database checks if custom database path is set
getDb :: (MonadReader VConfig m, MonadIO m) => m Connection
getDb = do
  env <- ask
  let dat = database env
  let isDat = not $ null $ dat
  if isDat then  liftIO $ connectSqlite3 dat else do
    path <- path'
    let fullpath = (path ++ "/data/todo.db")
    liftIO $ connectSqlite3 fullpath

-- | converts from sqlValues to Strings
convRow :: [[SqlValue]] -> [[String]]
convRow dat = Prelude.map (\x -> Prelude.map (\y -> fromSql y :: String ) x) dat

-- | takes sqlQuery and returns results as a string
makeQuery :: (MonadReader VConfig m, MonadIO m) => String -> m [[String]]
makeQuery query = do
  conn <- getDb
  taskRaw <- liftIO $ quickQuery' conn query []
  liftIO $ disconnect conn
  return (convRow taskRaw)

-- | executes a query that changes values in database
execQuery :: (Convertible a SqlValue,
  MonadIO m,
  MonadReader VConfig m) => String -> [a] -> m ()
execQuery query params = do
  conn <- getDb
  stmt <- liftIO $ prepare conn query
  _ <- ($) liftIO $ execute stmt (map toSql params)
  _ <- ($) liftIO $ commit conn
  liftIO $ disconnect conn

-- | gets the task id for the next avaible todo
getNextId :: (MonadReader VConfig m, MonadIO m) => m Integer
getNextId = do
  f <- makeQuery "select id from todo order by id desc"
  let rawid = head $ f
  pure $ (read (rawid !! 0) :: Integer) +1

-- | adds new task sanitizes input to avoid SQL escaping
addTask ::  (MonadReader VConfig m, MonadIO m) => String -> String -> String -> String -> m ()
addTask todoSummary todoTitle date todoSubject = do
  nextSqlId <- getNextId
  execQuery "insert into todo (id,Description,Title,Entered,Due,State,time,Subject) Values (?,?,?,datetime('now', 'localtime'),?,1,0,?)"  [show nextSqlId, (clean todoSummary),(clean todoTitle), date, (clean todoSubject)]

-- | logs new daily entry due on same day
addDaily :: (MonadReader VConfig m, MonadIO m) => [String] -> m ()
addDaily addD= do
  lastRowId <- getNextId
  execQuery "insert into todo (id,Description,Title,Entered,Due,State,time,Subject ) Values (?,?,?,current_date,current_date,1,0,?)" $ [show lastRowId] ++ addD

-- | lists the todo items finished today 
getDone :: (MonadReader VConfig m, MonadIO m) => m [[String]]
getDone = makeQuery  "select Title, time  from todo where substr(Due,1,10) = Date('now','localtime') and time != 0"

-- | rudimentary sanitization
clean :: String -> String
clean = id