{-
  Query
-}

module Query where

-- DBlimited imports
import Schema
import qualified Utils as U

-- Package imports
import Data.Char
import System.IO
import qualified Text.ParserCombinators.Parsec as P
import qualified Data.Map as M

data Clause = All |
              And Clause Clause |
              Or Clause Clause |
              Equals {getTarget :: String, getTest :: String}
              deriving (Show)

type Row = [String]

data Query = InvalidQuery |
             Query {getSelect :: [Column], getFrom :: Table, getWhere :: Clause}
             deriving (Show)

-- build a query
getQuery :: Schema -> String -> IO Query
getQuery schema queryString = case (P.parse (parseQuery schema) "" queryString) of
                                Left err       -> return InvalidQuery
                                Right (Just q) -> return q

parseQuery :: Schema -> P.Parser (Maybe Query)
parseQuery schema = do
  parseChars "SELECT "
  cols <- U.csv -- re-used from Schema
  parseChars " FROM "
  maybeTable <- (parseTable schema)
  P.char ';'
  return $ case maybeTable of
             Nothing      -> Nothing
             (Just table) -> Just (Query cols table All)
  P.<?> "SELECT a,b,c FROM tablename;"

parseTable :: Schema -> P.Parser (Maybe Table)
parseTable schema = do
  tablename <- U.object
  return (M.lookup tablename schema)

parseChars :: [Char] -> P.Parser String
parseChars [] = return ""
parseChars (c:cs) = do
  pc <- P.char (toUpper c) P.<|> P.char (toLower c)
  pcs <- parseChars cs
  return (toLower pc : pcs)
  P.<?> (c:cs)

-- use the schema to look up and load the table
loadTable :: Table -> IO [Row]
loadTable table = do
  handle <- openFile (getPath table) ReadMode
  raw <- hGetContents handle
  return . (map.(U.split).getDlmt$table) . lines $ raw

-- given a initial list of columns and a subset, return the corresponding subset from a given row
evalSelect :: [Column] -> [Column] -> (Row -> Row)
evalSelect sourceCols pickedCols = 
    (\inputrow -> [ inputrow !! i | i <- indices ])
    where 
      indices = U.selector sourceCols pickedCols

-- given an initial list of columns and a test, determine if a given line passes the test
evalWhere :: [Column] -> Clause -> (Row -> Bool)
evalWhere _cols All              = (\r -> True)
evalWhere cols (And c1 c2)       = (\r -> (evalWhere cols c1 r && evalWhere cols c2 r))
evalWhere cols (Or  c1 c2)       = (\r -> (evalWhere cols c1 r || evalWhere cols c2 r))
evalWhere cols (Equals target f) = undefined

-- given a query and some rows, return the resulting rows
evalQuery :: Query -> IO ()
evalQuery InvalidQuery              = putStrLn "Error in query"
evalQuery (Query cols table clause) = do
  inputRows <- loadTable table
  putStrLn . show . 
               (map $ evalSelect (getCols table) cols) . 
               (filter $ evalWhere (getCols table) clause) $ 
               inputRows