module Hadolint.Pragma
  ( ignored,
    globalIgnored,
    parseIgnorePragma,
    parseShell
  )
  where

import Data.Functor.Identity (Identity)
import Data.Text (Text)
import Data.Void (Void)
import Hadolint.Rule (RuleCode (RuleCode))
import Language.Docker.Syntax
import qualified Control.Foldl as Foldl
import qualified Data.IntMap.Strict as Map
import qualified Data.Set as Set
import qualified Text.Megaparsec as Megaparsec
import qualified Text.Megaparsec.Char as Megaparsec


ignored :: Foldl.Fold (InstructionPos Text) (Map.IntMap (Set.Set RuleCode))
ignored = Foldl.Fold parse mempty id
  where
    parse acc InstructionPos {instruction = Comment comment, lineNumber = line} =
      case parseIgnorePragma comment of
        Just ignores@(_ : _) -> Map.insert (line + 1) (Set.fromList . fmap RuleCode $ ignores) acc
        _ -> acc
    parse acc _ = acc

globalIgnored :: Foldl.Fold (InstructionPos Text) (Set.Set RuleCode)
globalIgnored = Foldl.Fold parse mempty id
  where
    parse acc InstructionPos { instruction = Comment comment } =
      case parseGlobalIgnorePragma comment of
        Just ignores@(_ : _) -> Set.union ( Set.fromList . fmap RuleCode $ ignores ) acc
        _ -> acc
    parse acc _ = acc

parseIgnorePragma :: Text -> Maybe [Text]
parseIgnorePragma = Megaparsec.parseMaybe ignoreParser

parseGlobalIgnorePragma :: Text -> Maybe [Text]
parseGlobalIgnorePragma = Megaparsec.parseMaybe globalIgnoreParser

ignoreParser :: Megaparsec.Parsec Void Text [Text]
ignoreParser = hadolintPragma >> ignore

globalIgnoreParser :: Megaparsec.Parsec Void Text [Text]
globalIgnoreParser = hadolintPragma >> global >> ignore

hadolintPragma :: Megaparsec.Parsec Void Text Text
hadolintPragma = spaces >> string "hadolint" >> spaces1

global :: Megaparsec.Parsec Void Text Text
global = string "global" >> spaces1

ignore :: Megaparsec.Parsec Void Text [Text]
ignore = string "ignore" >> spaces >> string "=" >> spaces >> ruleList

ruleList :: Megaparsec.Parsec Void Text [Text]
ruleList = Megaparsec.sepBy1 ruleName ( spaces >> string "," >> spaces )

ruleName :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)
ruleName =
  Megaparsec.takeWhile1P Nothing (\c -> c `elem` Set.fromList "DLSC0123456789")
  <* inlineComment

parseShell :: Text -> Maybe Text
parseShell = Megaparsec.parseMaybe shellParser

shellParser :: Megaparsec.Parsec Void Text Text
shellParser = hadolintPragma >> string "shell" >> spaces >> string "=" >> spaces >> shellName

shellName :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)
shellName =
  Megaparsec.takeWhile1P Nothing (\c -> c `notElem` Set.fromList "\n\t ")
  <* inlineComment

inlineComment :: Megaparsec.Parsec Void Text (Maybe Text)
inlineComment =
  spaces >> Megaparsec.optional ( string "#" >> Megaparsec.takeWhileP Nothing (/= '\n') )

string :: Megaparsec.Tokens Text
  -> Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)
string = Megaparsec.string

spaces :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)
spaces = Megaparsec.takeWhileP Nothing space

spaces1 :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)
spaces1 = Megaparsec.takeWhile1P Nothing space

space :: Char -> Bool
space c = c == ' ' || c == '\t'
