-- Copyright 2017 Fernando Rincon Martin
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}

{-|
Parser for ping output. Parser for the output of Win32 ping utility.
-}
module Data.Attoparsec.Ping.Win32
  ( pingParser
  , module Data.Attoparsec.Ping
  ) where

import Control.Applicative ((<|>))
import Data.Attoparsec.Ping
import Data.Attoparsec.Text
import Data.IP
import Data.Text (Text)
import qualified Data.Text as T
import Data.Word (Word32)
import Development.Placeholders

pingParser :: Parser PingResult
pingParser = parseHostNotFound <|> parseSucceed <|> fail "Not recognized input"

parseHostNotFound :: Parser PingResult
parseHostNotFound = do
  string "Ping request could not find host "
  hostName <- manyTill anyChar (string ". ")
  string "Please check the name and try again."
  return $ PingError $ HostNotFound (T.pack hostName)

parseSucceed :: Parser PingResult
parseSucceed = do
  endOfLine
  string "Pinging "
  (maybeHostName, hostAddress) <- parseHostNameAndAddress
  bytesOfData <- decimal
  string " bytes of data:"
  endOfLine
  lineResults <- parseLineResults
  return
    PingSucceed
    { hostAddress = hostAddress
    , hostNameResolved = maybeHostName
    , bytesOfData = bytesOfData
    , lineResults = lineResults
    }

parseHostNameAndAddress :: Parser (Maybe Text, IPv4)
parseHostNameAndAddress = do
  hostNameAndOrAddress <- T.pack <$> manyTill anyChar (string " with ")
  case T.words hostNameAndOrAddress of
    [address] -> return (Nothing, parseIp4 address)
    [hostName, addressWithBrackets] ->
      return (Just hostName, parseIp4WithBrackets addressWithBrackets)
  where
    parseIp4 address = read (T.unpack address)
    parseIp4WithBrackets addressWithBrackets =
      parseIp4 (T.drop 1 (T.dropEnd 1 addressWithBrackets))

parseLineResults :: Parser [LineResult]
parseLineResults = manyTill parseLineResult endOfLine

parseLineResult :: Parser LineResult
parseLineResult =
  parseRequestTimedOut <|> parseGeneralFailure <|> parseReplyFrom

parseRequestTimedOut :: Parser LineResult
parseRequestTimedOut = do
  string "Request timed out."
  endOfLine
  return RequestTimedOut

parseGeneralFailure :: Parser LineResult
parseGeneralFailure = do
  string "General failure."
  endOfLine
  return GeneralFailure

parseReplyFrom :: Parser LineResult
parseReplyFrom = do
  string "Reply from "
  address <- read <$> manyTill anyChar (char ':')
  replayResult <- parseReplyResult
  return $ ReplyFrom address replayResult

parseReplyResult = parseDestinationHostUnreachable <|> parseResponseReceived

parseDestinationHostUnreachable = do
  string " Destination host unreachable."
  endOfLine
  return DestinationHostUnreachable

parseResponseReceived = do
  string " bytes="
  bytes <- decimal
  string " time"
  timeInMs <- (string "<1" *> return 0) <|> (char '=' *> decimal)
  string "ms TTL="
  ttl <- decimal
  endOfLine
  return ResponseReceived {bytes = bytes, timeInMs = timeInMs, ttl = ttl}