{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}

module TelemetryParser where

import TelemetryTypes

--import qualified Text.Parsec.Text as PT
import Text.Parsec
import Text.Parsec.Char
import Data.Time
import Data.Time.LocalTime
import Data.Text
import Data.List
import Control.Monad.Reader
import Control.Lens
import Control.Lens.Fold
import Data.Maybe


tpsNotifierGetter 1 = _1
tpsNotifierGetter 2 = _2
tpsNotifierGetter _ = error "Getter can be only 1 or 2"

getDate :: (TelemetryMonad m) => String -> m Day
getDate = parseTimeM True defaultTimeLocale "%-d.%-m.%-y"

getTime :: (TelemetryMonad m) => Char -> String -> m TimeOfDay
getTime sep = parseTimeM True defaultTimeLocale (Data.List.intercalate [sep] ["%H","%M","%S,%q"])

fileParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m TelemetryLog
fileParser = undefined

rowParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m (Maybe TelemetryRecord) -> ParsecT TelemetrySource TelemetryState m (Maybe TelemetryRecord, Maybe TelemetryRecord)
rowParser rp = do
    modifyState $ tpsCurrentColumn .~ 1
    char '|' <?> "symbol \'|\' - start of row"
    leftRecord <- rp <?> "left record"
    spaces
    char '|' <?> "record separator or end of records - symbol \'|\'"
    rightRecord <- rp <?> "right record"
    spaces
    optional (char '|' <?> "end of records - symbol \'|\'")
    return (leftRecord, rightRecord)

blockParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m TelemetryBlock
blockParser = do
    day <- headParser <?> "head of block"
    manyTill anyChar endOfLine
    spaces
    parserState <- getState
    modifyState $ set (tpsNotifiers._2) (parserState^.tpsNotifiers._1)
    dummyRecords <- lookAhead $ many1 (try (rowParser dummyRecordParser)) -- <* (manyTill anyChar endOfLine)) -- >> notFollowedBy eof >> notFollowedBy (try headParser)))
    let rightNotifier = getLastNotifier dummyRecords
    when (isJust rightNotifier) (modifyState $ set (tpsNotifiers._2) rightNotifier)
    records <- many1 (try (rowParser recordParser)) -- <* (manyTill anyChar endOfLine)) -- >> notFollowedBy eof >> notFollowedBy (try headParser)))
    return $ TelemetryBlock day records
        where
            getLastNotifier dr = let drFiltered = Data.List.dropWhile (isNothing . fst) $ Data.List.reverse dr in
                case drFiltered of
                    [] -> Nothing
                    _ -> let (TelemetryRecord rightNotifier _ _) = fromJust . fst . Data.List.head $ drFiltered in
                            Just rightNotifier

timeParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m TimeOfDay
timeParser = do
    h <- Text.Parsec.count 2 digit <?> "hours"
    sep <- anyChar
    m <- Text.Parsec.count 2 digit <?> "minutes"
    char sep
    s <- Text.Parsec.count 2 digit <?> "seconds"
    char ','
    ms <- Text.Parsec.count 3 digit <?> "milliseconds"
    getTime sep $ Data.List.intercalate [sep] [h, m, s ++ ',':ms ++ "000000000"]

dummyRecordParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m (Maybe TelemetryRecord)
dummyRecordParser = do
    spaces
    record <- optionMaybe (try rParser)
    skipMany $ Text.Parsec.Char.noneOf "|\r\n"
    modifyState $ tpsCurrentColumn %~ (+1)
    return record
        where
            rParser = do
                parserState <- getState
                parsedNotifier <- try notifierParser <|> do
                    let notifier = parserState^.tpsNotifiers.tpsNotifierGetter (parserState^.tpsCurrentColumn)
                    case notifier of
                        Just goodNotifier -> return goodNotifier
                        Nothing -> unexpected "Empty notifier"
                return $ TelemetryRecord parsedNotifier TelemetryStop (TimeOfDay 0 0 0)

recordParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m (Maybe TelemetryRecord)
recordParser = do
    spaces
    record <- optionMaybe (try rParser)
    when (isNothing record) (skipMany $ Text.Parsec.Char.noneOf "|\r\n")
    modifyState $ tpsCurrentColumn %~ (+1)
    return record
        where
            rParser = do
                parserState <- getState
                parsedNotifier <- try notifierParser <|> do
                    let notifier = parserState^.tpsNotifiers.tpsNotifierGetter (parserState^.tpsCurrentColumn)
                    case notifier of
                        Just goodNotifier -> return goodNotifier
                        Nothing -> unexpected "Empty notifier"
                spaces
                cmd <- commandParser
                spaces
                time <- timeParser
                return $ TelemetryRecord parsedNotifier cmd time

commandParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m TelemetryCommand
commandParser = do
    parserState <- getState
    let notifier = parserState^.tpsNotifiers.tpsNotifierGetter (parserState^.tpsCurrentColumn)
    case notifier of
        Nothing -> unexpected "Empty notifier"
        Just goodNotifier -> do
            let (startCmd, stopCmd) = goodNotifier^.notifierCommands
            try (helperParser TelemetryStart startCmd) <|> helperParser TelemetryStop stopCmd
        where 
            helperParser :: (TelemetryMonad m) => TelemetryCommand -> Text -> ParsecT TelemetrySource TelemetryState m TelemetryCommand
            helperParser cmd cmdName = do
                string (unpack cmdName) <?> ("Command" ++ unpack cmdName)
                return cmd

dayParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m Day
dayParser = do
    d <- many1 digit <?> "day"
    char '.'
    m <- many1 digit <?> "month"
    char '.'
    y <- many1 digit <?> "year"
    getDate $ Data.List.intercalate "." [d, m, y]

dayOnlyParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m Day
dayOnlyParser = modifyState (const emptyTPS) >> dayParser

notifierParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m Notifier
notifierParser = do
    config <- ask
    choice $ config^.tcNotifiers.to (fmap nameParser)
        where
            nameParser :: (TelemetryMonad m) => Notifier -> ParsecT TelemetrySource TelemetryState m Notifier
            nameParser notifier = do
                parserState <- getState
                try . string . unpack $ notifier^.notifierName
                modifyState $ (tpsNotifiers.tpsNotifierGetter (parserState^.tpsCurrentColumn)) .~ Just notifier
                return notifier

notifierThenDayParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m Day
notifierThenDayParser = notifierParser >> spaces >> many (char '-' >> spaces) >> spaces >> dayParser

dayThenNotifierParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m Day
dayThenNotifierParser = do
    day <- dayParser
    spaces >> many (char '-' >> spaces) >> spaces
    notifierParser
    return day

notifierInDayParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m Day
notifierInDayParser = do
    d <- many1 digit <?> "day"
    char '.'
    m1 <- digit <?> "1st digit of month"
    spaces >> notifierParser >> spaces
    m2 <-digit <?> "1nd digit of month"
    char '.'
    y <- many1 digit <?> "year"
    getDate $ Data.List.intercalate "." [d, [m1,m2], y]

headParser :: (TelemetryMonad m) => ParsecT TelemetrySource TelemetryState m Day
headParser = do
    spaces >> many (char '-' >> spaces) >> spaces
    choice [notifierThenDayParser, try notifierInDayParser , try dayThenNotifierParser, dayOnlyParser]
