-- This file is part of nagios-perfdata.
--
-- Copyright 2014 Anchor Systems Pty Ltd and others.
--
-- The code in this file, and the program it is a part of, is made
-- available to you by its authors as open source software: you can
-- redistribute it and/or modify it under the terms of the BSD license.

{-# LANGUAGE OverloadedStrings #-}

module Data.Nagios.Perfdata.GearmanResult(
    perfdataFromGearmanResult,
) where

import           Data.Nagios.Perfdata.Error
import           Data.Nagios.Perfdata.Metric

import           Control.Applicative
import           Data.Attoparsec.ByteString.Char8
import qualified Data.ByteString                  as S
import qualified Data.ByteString.Char8            as C
import           Data.Int
import qualified Data.Map                         as M
import           Prelude                          hiding (takeWhile)

type CheckResultField = (String,String)

checkResultSep :: Parser String
checkResultSep = many1 (char '\n')

checkResultFieldName :: Parser String
checkResultFieldName = manyTill anyChar (char '=')

checkResultFieldValue :: Parser String
checkResultFieldValue = manyTill anyChar checkResultSep

checkResultField :: Parser CheckResultField
checkResultField = (,) `fmap` checkResultFieldName <*> checkResultFieldValue

checkResult :: Parser [CheckResultField]
checkResult = skipMany (char '"') *> many1 checkResultField <* skipMany (char '"')

type CheckResultMap = M.Map String String

mapResultItems :: [CheckResultField] -> CheckResultMap
mapResultItems = foldl (flip (uncurry M.insert)) M.empty

extractResultItems :: Result [CheckResultField] -> Either ParserError CheckResultMap
extractResultItems (Done _ is) = Right $ mapResultItems is
extractResultItems (Fail _ ctxs err) = Left $ fmtParseError ctxs err
extractResultItems (Partial f) = extractResultItems (f "")

checkTimestamp :: CheckResultMap -> Either ParserError Int64
checkTimestamp m =
    case M.lookup "finish_time" m of
        Nothing -> Left "finish_time not found"
        Just t  -> do
            x <- parseDouble (C.pack t)
            return $ floor  $ x * nanosecondFactor
  where
    nanosecondFactor = 1000000000

parseDouble :: C.ByteString -> Either ParserError Double
parseDouble s = complete (parse double s)
  where
    complete (Done _ i) = Right i
    complete (Fail _ ctxs err) = Left $ fmtParseError ctxs err
    complete (Partial f) = complete (f "")

parsePluginOutput :: S.ByteString -> Either ParserError String
parsePluginOutput s = complete (parse metricSection s)
  where
    complete (Done _ i) = Right i
    complete (Fail _ ctxs err) = Left $ fmtParseError ctxs err
    complete (Partial f) = complete (f "")
    metricSection = manyTill anyChar (char '|') *> many1 anyChar

checkMetrics :: CheckResultMap -> Either ParserError MetricList
checkMetrics m =
    case M.lookup "output" m of
        Nothing -> Left "check output not found"
        Just s -> do
            metricPart <- parsePluginOutput (C.pack s)
            parseMetricString (C.pack metricPart)

checkHostname :: CheckResultMap -> Either ParserError String
checkHostname m =
    case M.lookup "host_name" m of
        Nothing -> Left "host_name not found"
        Just h -> Right h

checkServiceState :: CheckResultMap -> Either ParserError ReturnState
checkServiceState m =
    case M.lookup "return_code" m of
        Nothing -> Left "return_code not found"
        Just d  -> case C.readInteger (C.pack d) of
            Nothing -> Left "could not parse return code as an integer"
            Just (r,_) -> case parseReturnCode r of
                Nothing -> Left "invalid return code"
                Just rc  -> Right rc

checkType :: CheckResultMap -> Either ParserError HostOrService
checkType m =
    case M.lookup "service_description" m of
        Nothing -> Right Host
        Just sd -> do
            sd' <- Right (C.pack sd)
            state <-  checkServiceState m
            return $ Service $ ServicePerfdata sd' state

extractCheckItems :: S.ByteString -> Either ParserError CheckResultMap
extractCheckItems = extractResultItems . parse checkResult

-- |Takes the output of a Nagios check formatted according to [0] and
-- reported by mod_gearman[1],  and attempts to parse it into a Perfdata
-- object. This should be used, for example, for consuming perfdata from
-- mod_gearman check_result queues.
--
-- [0]: https://nagios-plugins.org/doc/guidelines.html
-- [1]: https://labs.consol.de/nagios/mod-gearman/
perfdataFromGearmanResult :: S.ByteString -> Either ParserError Perfdata
perfdataFromGearmanResult s = do
    m <- extractCheckItems s
    typ <- checkType m
    name <- checkHostname m
    t <- checkTimestamp m
    state <- Right Nothing
    ms <- checkMetrics m
    return $ Perfdata typ t name state ms
