module Main where

import Codec.Sarsi (Event (..), Level (..), Location (..), Message (..))
import Control.Concurrent.MVar (MVar, newMVar, readMVar, swapMVar)
import qualified Data.List as List
import Data.Machine (ProcessT, asParts, final, runT, scan, sinkPart_, (<~))
import Data.Text (Text, pack)
import qualified Data.Text as Text
import Data.Text.IO (hPutStrLn)
import Sarsi (Topic (..), getBroker, getTopic)
import qualified Sarsi as Sarsi
import Sarsi.Consumer (consumeOrWait)
import System.IO (Handle, IOMode (WriteMode), hClose, hFlush, openFile)
import System.IO.Machine (sinkIO)

-- TODO Remove the MVar by impl. scanM ((a -> b -> m a) -> a -> ProcessT m (k b) a) in machines

title :: String
title = concat [Sarsi.title, "-vi"]

-- :set efm=%f:%l:%c:%t\ %m
toVi :: Message -> Text
toVi (Message (Location fp col ln) lvl txts) = Text.concat [header, pack " ", body]
  where
    header = Text.concat $ List.intersperse (pack ":") [fp, pack $ show ln, pack $ show col, tpe lvl]
    body = Text.concat $ List.intersperse (pack " ") txts
    tpe Error = pack "e"
    tpe Warning = pack "w"

data Action = Append | Replace deriving (Show)

data LogEvent = LogEvent Action Text deriving (Show)

data Command = Throw LogEvent | Echo String deriving (Show)

convert :: Bool -> Event -> (Bool, [Command])
convert first (Notify msg@(Message loc lvl _)) = (False, [Throw e, Echo $ concat [show loc, " ", show lvl]])
  where
    e = LogEvent mode $ toVi msg
    mode = if first then Replace else Append
convert _ e = (True, [Echo $show e])

converter :: Bool -> ProcessT IO Event (Bool, [Command])
converter first = scan f (first, []) where f (first', _) event = convert first' event

dump :: FilePath -> Maybe Handle -> Command -> IO (Maybe Handle)
dump _ h (Echo txt) = (putStrLn $ concat [title, ": ", txt]) >> return h
dump fp Nothing e = openFile fp WriteMode >>= \h -> dump fp (Just h) e
dump _ (Just h) (Throw (LogEvent Append txt)) = fmap (const $ Just h) $ (hPutStrLn h txt >> hFlush h)
dump fp (Just h) (Throw (LogEvent Replace txt)) = hClose h >> dump fp Nothing (Throw $ LogEvent Append txt)

writer :: FilePath -> MVar (Maybe Handle) -> Command -> IO ()
writer tp var c = do
  h <- readMVar var
  h' <- dump fp h c
  _ <- swapMVar var h'
  return ()
  where
    fp = tp ++ ".vi"

main :: IO ()
main = do
  b <- getBroker
  t <- getTopic b "."
  var <- newMVar Nothing
  consumeOrWait t $ consumer t var
  where
    consumer (Topic _ tp _) var first src = do
      res <- runT $ final <~ sinkPart_ id ((sinkIO $ writer tp var) <~ asParts) <~ converter (maybe True id first) <~ src
      return $ Left $ head res
