{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

module Highlight.Hrep.Run where

import Prelude ()
import Prelude.Compat

import Control.Exception (IOException)
import Data.ByteString (ByteString)
import Data.Maybe (maybeToList)
import Data.Monoid ((<>))
import Pipes (Producer)
import Text.RE.PCRE (RE, (*=~), anyMatches)
import Text.RE.Replace (replaceAll)

import Highlight.Common.Color
       (colorForFileNumber, colorReset, colorVividRedBold,
        colorVividWhiteBold)
import Highlight.Common.Error (handleErr)
import Highlight.Common.Options (CommonOptions(..))
import Highlight.Hrep.Monad
       (FilenameHandlingFromFiles(..), HrepM, Output,
        compileHighlightRegexWithErr, createInputData, getInputFilenamesM,
        getRecursiveM, handleInputData, runHrepM, runOutputProducer)
import Highlight.Pipes (stdinLines)

run :: CommonOptions -> IO ()
run opts = do
  eitherRes <- runHrepM opts prog
  either handleErr return eitherRes

prog :: HrepM ()
prog = do
  outputProducer <- hrepOutputProducer stdinLines
  runOutputProducer outputProducer

hrepOutputProducer
  :: Producer ByteString HrepM ()
  -> HrepM (Producer Output HrepM ())
hrepOutputProducer stdinProducer = do
  regex <- compileHighlightRegexWithErr
  inputFilenames <- getInputFilenamesM
  recursive <- getRecursiveM
  inputData <- createInputData recursive inputFilenames stdinProducer
  let outputProducer =
        handleInputData
          (handleStdinInput regex)
          (handleFileInput regex)
          handleError
          inputData
  return outputProducer

handleStdinInput
  :: Monad m
  => RE -> ByteString -> m [ByteString]
handleStdinInput regex input =
  return $ formatNormalLine regex input

formatNormalLine :: RE -> ByteString -> [ByteString]
formatNormalLine regex =
  maybeToList . highlightMatchInRed regex

handleFileInput
  :: Monad m
  => RE
  -> FilenameHandlingFromFiles
  -> ByteString
  -> Int
  -> ByteString
  -> m [ByteString]
handleFileInput regex NoFilename _ _ input =
  return $ formatNormalLine regex input
handleFileInput regex PrintFilename filePath fileNumber input =
  return $ formatLineWithFilename regex fileNumber filePath input

formatLineWithFilename
  :: RE -> Int -> ByteString -> ByteString -> [ByteString]
formatLineWithFilename regex fileNumber filePath input =
  case highlightMatchInRed regex input of
    Nothing -> []
    Just line ->
      [ colorForFileNumber fileNumber
      , filePath
      , colorVividWhiteBold
      ,  ": "
      , colorReset
      , line
      ]

handleError
  :: Monad m
  => ByteString
  -> IOException
  -> Maybe IOException
  -> m [ByteString]
handleError filePath _ (Just _) =
  return ["Error when trying to read file or directory \"", filePath, "\""]
handleError filePath _ Nothing =
  return ["Error when trying to read file \"", filePath, "\""]

highlightMatchInRed :: RE -> ByteString -> Maybe ByteString
highlightMatchInRed regex input =
  let matches = input *=~ regex
      didMatch = anyMatches matches
  in if didMatch
       then Just $ replaceAll replaceInRedByteString matches
       else Nothing

replaceInRedByteString :: ByteString
replaceInRedByteString = colorVividRedBold <> "$0" <> colorReset