module Language.Dockerfile.Parser where
import           Control.Monad                 (void)
import           Data.ByteString.Char8         (pack)
import           Data.String
import           Text.Parsec                   hiding (label)
import           Text.Parsec.String            (Parser)
import qualified Text.Parsec.Token             as Token
import           Language.Dockerfile.Lexer
import           Language.Dockerfile.Normalize
import           Language.Dockerfile.Syntax
comment :: Parser Instruction
comment = do
  void $ char '#'
  text <- untilEol
  return $ Comment text
taggedImage :: Parser BaseImage
taggedImage = do
  name <- untilOccurrence ":\n"
  void $ oneOf ":"
  tag <- untilEol
  return $ TaggedImage name tag
digestedImage :: Parser BaseImage
digestedImage = do
  name <- untilOccurrence "@\n"
  void $ oneOf "@"
  digest <- untilEol
  return $ DigestedImage name (pack digest)
untaggedImage :: Parser BaseImage
untaggedImage = do
  name <- many (noneOf "\n")
  return $ UntaggedImage name
baseImage :: Parser BaseImage
baseImage = try taggedImage
    <|> try digestedImage
    <|> try untaggedImage
from :: Parser Instruction
from = do
  reserved "FROM"
  image <- baseImage
  return $ From image
cmd :: Parser Instruction
cmd = do
  reserved "CMD"
  args <- arguments
  return $ Cmd args
copy :: Parser Instruction
copy = do
  reserved "COPY"
  src <- many (noneOf " ")
  Token.whiteSpace lexer
  dst <- many (noneOf "\n")
  return $ Copy src dst
stopsignal :: Parser Instruction
stopsignal = do
  reserved "STOPSIGNAL"
  args <- many (noneOf "\n")
  return $ Stopsignal args
quotedValue:: Parser String
quotedValue = do
    void $ char '"'
    literal <- untilOccurrence "\""
    void $ char '"'
    return literal
rawValue :: Parser String
rawValue = many1 (noneOf [' ','=','\n'])
singleValue :: Parser String
singleValue = try quotedValue <|> try rawValue
pair :: Parser (String, String)
pair = do
  key <- rawValue
  void $ oneOf "= "
  value <- singleValue
  return (key, value)
pairs :: Parser Pairs
pairs = do
    first <- pair
    next <- remainingPairs
    return (first:next)
remainingPairs :: Parser Pairs
remainingPairs =
    try (char ' ' >> pairs)
    <|> try (return [])
label :: Parser Instruction
label = do
  reserved "LABEL"
  p <- pairs
  return $ Label p
arg :: Parser Instruction
arg = do
  reserved "ARG"
  p <- untilEol
  return $ Arg p
env :: Parser Instruction
env = do
  reserved "ENV"
  p <- pairs
  return $ Env p
user :: Parser Instruction
user = do
  reserved "USER"
  username <- untilEol
  return $ User username
add :: Parser Instruction
add = do
  reserved "ADD"
  src <- untilOccurrence " "
  Token.whiteSpace lexer
  dst <- untilOccurrence "\n"
  return $ Add src dst
expose :: Parser Instruction
expose = do
  reserved "EXPOSE"
  sports <- untilEol
  let port = fromString sports
  return $ Expose port
run :: Parser Instruction
run = do
  reserved "RUN"
  c <- arguments
  return $ Run c
untilEol :: Parser String
untilEol = many (noneOf "\n")
untilOccurrence :: String -> Parser String
untilOccurrence t = many $ noneOf t
workdir :: Parser Instruction
workdir = do
  reserved "WORKDIR"
  directory <- many (noneOf "\n")
  return $ Workdir directory
volume :: Parser Instruction
volume = do
  reserved "VOLUME"
  directory <- many (noneOf "\n")
  return $ Volume directory
maintainer :: Parser Instruction
maintainer = do
  reserved "MAINTAINER"
  name <- untilEol
  return $ Maintainer name
argumentsExec :: Parser Arguments
argumentsExec = brackets $ commaSep stringLiteral
argumentsShell :: Parser Arguments
argumentsShell = do
    args <- untilEol
    return $ words args
arguments :: Parser Arguments
arguments = try argumentsExec <|> try argumentsShell
entrypoint :: Parser Instruction
entrypoint = do
  reserved "ENTRYPOINT"
  args <- arguments
  return $ Entrypoint args
onbuild :: Parser Instruction
onbuild = do
  reserved "ONBUILD"
  i <- parseInstruction
  return $ OnBuild i
eolInstruction :: Parser Instruction
eolInstruction = do
  eol
  return EOL
parseInstruction :: Parser Instruction
parseInstruction
    = try onbuild
    <|> try from
    <|> try copy
    <|> try run
    <|> try workdir
    <|> try entrypoint
    <|> try volume
    <|> try expose
    <|> try env
    <|> try arg
    <|> try user
    <|> try label
    <|> try stopsignal
    <|> try cmd
    <|> try maintainer
    <|> try add
    <|> try comment
    <|> try eolInstruction
contents :: Parser a -> Parser a
contents p = do
    Token.whiteSpace lexer
    r <- p
    eof
    return r
eol :: Parser ()
eol = void $ char '\n' <|> (char '\r' >> option '\n' (char '\n'))
dockerfile :: Parser Dockerfile
dockerfile = many $ do
    
    
    
    pos <- getPosition
    i <- parseInstruction
    optional eol
    
    return $ InstructionPos i (sourceName pos) (sourceLine pos)
parseString :: String -> Either ParseError Dockerfile
parseString s = parse (contents dockerfile) "<string>" $ normalizeEscapedLines s
parseFile :: String -> IO (Either ParseError Dockerfile)
parseFile file = do
    program <- readFile file
    return $ parse (contents dockerfile) file $ normalizeEscapedLines program