module Parser (runParser) 
where

  import Text.ParserCombinators.Parsec hiding (runParser)
  import Text.ParserCombinators.Parsec.Char
  import Text.Parsec.Token
  import Text.Parsec.Language (emptyDef)

  import Data.List (intersperse, concat)
  import Data.Maybe
  
  import Types

  -- Para ejecutar el parser
  runParser = parse parseComm ""

  -- Analizador de tokens
  lis :: TokenParser u
  lis = makeTokenParser (emptyDef { commentStart     = "/*" ,
                                    commentEnd       = "*/" ,
                                    commentLine      = "//" ,
                                    opLetter         = char '=',
                                    reservedOpNames  = ["=", ":", "&&", "||", "<<", ">>", ";", "[", "]", "{", "}"],
                                    reservedNames    = ["def", 
                                                        -- Posibles parametros
                                                        "limit","rating","search","genre", "sort_by", "order",
                                                        -- Generos
                                                        "action", "adventure", "animation", "comedy", "crime",
                                                        "documentary", "drama", "horror", "romance", "sci-fi",
                                                        "thriller", "western",
                                                        -- Orden (order)
                                                        "asc", "desc",
                                                        -- Ordenar según... (sort_by)
                                                        "title", "year", "rating"
                                                       ]
                                  }
                        )

  --------------------------------------
  -- Parser de comandos del lenguaje (def, <<)
  
  parseComm = chainl1 (try parseDef <|> try parseAdd <|> try parseRem) parseSeq
  
  parseDef :: Parser Comm
  parseDef = do reserved lis "def"
                name <- identifier lis
                reservedOp lis "="
                list <- parseList
                return (Def name list)
 
  parseRem :: Parser Comm
  parseRem = do name <- identifier lis
                symbol lis ">>"
                list <- parseList
                return (Rem name list)
 
  parseAdd :: Parser Comm 
  parseAdd = do name <- identifier lis
                symbol lis "<<" 
                list <- parseList 
                return (Add name list)

  parseSeq :: Parser (Comm -> Comm -> Comm)
  parseSeq = do symbol lis ";"
                return Seq  

  --------------------------------------
  -- Parser de listas
  
  parseList :: Parser FList
  parseList = chainl1 (try parseNew <|> try parseVar) parseAnd 

  parseAnd :: Parser (FList -> FList -> FList)
  parseAnd = do symbol lis "&&"
                return And

  parseNew = do symbol lis "{"
                params <- commaSep lis parseParam   -- primero lo parseo como una lista de AuxParams
                symbol lis "}"
                case toRecord params initRecord of  -- acá me fijo si los datos son consistentes
                  Just p -> return (New p)
                  Nothing -> unexpected "parameters: Cada parámetro puede ser ingresado una única vez"
  parseVar = do name <- identifier lis
                return (Var name)
 

  -- Funciones auxiliares -------------
  -- El objetivo es convertir la lista de parámetros ingresadas a un record
  -- para detectar si hay alguna inconsistencia en los datos. 
  -- Por ejemplo, def list = {limit = 1, limit = 10} no debería ser una expresión
  -- correcta.
  initRecord :: Params
  initRecord = (P Nothing Nothing Nothing Nothing Nothing Nothing)

  toRecord :: [AuxParams] -> Params -> Maybe Params
  toRecord ((Rating r):xs) p = if (isNothing (rate p)) then toRecord xs (p {rate  = Just r}) else Nothing
  toRecord ((Limit l):xs)  p = if (isNothing (limit p)) then toRecord xs (p {limit = Just l}) else Nothing
  toRecord ((Genre g):xs) p = if (isNothing (genre p)) then toRecord xs (p {genre  = Just g}) else Nothing
  toRecord ((Query q):xs) p = if (isNothing (query p)) then toRecord xs (p {query = Just q}) else Nothing
  toRecord ((Sort s):xs) p = if (isNothing (sorted p)) then toRecord xs (p {sorted = Just s}) else Nothing
  toRecord ((Order o):xs) p = if (isNothing (order p)) then toRecord xs (p {order = Just o}) else Nothing
  toRecord [] p = Just p

  --------------------------------------
  -- Parser para parametros (AuxParams)
  parseParam :: Parser AuxParams
  parseParam = try parseLength <|>
               try parseRating <|>
               try parseGenre  <|>
               try parseSearch <|>
               try parseOrder  <|>
               try parseSort

  parseLength :: Parser AuxParams
  parseLength = do reserved lis "limit"
                   reservedOp lis ":"
                   v <- natural lis
                   return (Limit v)

  parseRating :: Parser AuxParams
  parseRating = do reserved lis "rating"
                   reservedOp lis ":"
                   v <- natural lis
                   return (Rating v)

  parseGenre :: Parser AuxParams
  parseGenre = do reserved lis "genre"
                  reservedOp lis ":"
                  g <- isAllowedGenre
                  return (Genre g)
  
  parseSearch :: Parser AuxParams
  parseSearch = do reserved lis "search"
                   reservedOp lis ":"
                   v <- sepBy1 (identifier lis) spaces
                   return (Query (concat (intersperse " " v)))

  
  parseSort :: Parser AuxParams
  parseSort = do reserved lis "sort_by"
                 reservedOp lis ":"
                 s <- isAllowedSort
                 return (Sort s)

  parseOrder :: Parser AuxParams
  parseOrder = do reserved lis "order"
                  reservedOp lis ":"
                  ( try (do reserved lis "asc"
                            return (Order "asc")) <|>
                    try (do reserved lis "desc"
                            return (Order "desc")) )

  --------------------------------------
  -- Parser de formas de ordenamiento
  isAllowedSort :: Parser String
  isAllowedSort  = (do reserved lis "title"
                       return "title")  <|>
                   (do reserved lis "rating"
                       return "rating") <|> 
                   (do reserved lis "year"
                       return "year")

  --------------------------------------
  -- Parser de generos
  isAllowedGenre :: Parser String 
  isAllowedGenre = try isAction <|>
                   try isAdventure <|>
                   try isAnimation <|>
                   try isComedy <|>
                   try isCrime <|>
                   try isDocumentary <|>
                   try isDrama <|>
                   try isHorror <|>
                   try isRomance <|>
                   try isSciFi <|>
                   try isThriller <|>
                   try isWestern

  isAction      = do reserved lis "action"
                     return "action"

  isAdventure   = do reserved lis "adventure"
                     return "adventure"

  isAnimation   = do reserved lis "animation"
                     return "animation"

  isComedy      = do reserved lis "comedy"
                     return "comedy"

  isCrime       = do reserved lis "crime"
                     return "crime"
  
  isDocumentary = do reserved lis "documentary"
                     return "documentary"
  
  isDrama       = do reserved lis "drama"
                     return "drama"
  
  isHorror      = do reserved lis "horror"
                     return "horror"
  
  isRomance     = do reserved lis "romance"
                     return "romance"
  
  isSciFi       = do reserved lis "sci-fi"
                     return "sci-fi"
  
  isThriller    = do reserved lis "thriller"
                     return "thriller"
  
  isWestern     = do reserved lis "western"
                     return "western"
    
