module Parse.Module (header, headerAndImports, getModuleName) where

import Control.Applicative ((<$>), (<*>))
import Text.Parsec hiding (newline, spaces)

import Parse.Helpers
import qualified AST.Module as Module
import qualified AST.Variable as Var


getModuleName :: String -> Maybe String
getModuleName source =
  case iParse getModuleName source of
    Right name -> Just name
    Left _     -> Nothing
  where
    getModuleName =
      do  optional freshLine
          (names, _) <- header
          return (Module.nameToString names)


headerAndImports :: IParser Module.HeaderAndImports
headerAndImports =
  do  optional freshLine
      (names, exports) <-
          option (["Main"], Var.openListing) (header `followedBy` freshLine)
      imports' <- imports
      return $ Module.HeaderAndImports names exports imports'


header :: IParser ([String], Var.Listing Var.Value)
header =
  do  try (reserved "module")
      whitespace
      names <- dotSep1 capVar <?> "name of module"
      whitespace
      exports <- option Var.openListing (listing value)
      whitespace <?> "reserved word 'where'"
      reserved "where"
      return (names, exports)


imports :: IParser [(Module.Name, Module.ImportMethod)]
imports =
  many (import' `followedBy` freshLine)


import' :: IParser (Module.Name, Module.ImportMethod)
import' =
  do  try (reserved "import")
      whitespace
      names <- dotSep1 capVar
      (,) names <$> method (Module.nameToString names)
  where
    method :: String -> IParser Module.ImportMethod
    method defaultAlias =
      Module.ImportMethod
          <$> option (Just defaultAlias) (Just <$> as')
          <*> option Var.closedListing exposing

    as' :: IParser String
    as' =
      do  try (whitespace >> reserved "as")
          whitespace
          capVar <?> "alias for module"

    exposing :: IParser (Var.Listing Var.Value)
    exposing =
      do  try (whitespace >> reserved "exposing")
          whitespace
          listing value


listing :: IParser a -> IParser (Var.Listing a)
listing item =
  do  try (whitespace >> char '(')
      whitespace
      listing <-
          choice
            [ const Var.openListing <$> string ".."
            , Var.Listing <$> commaSep1 item <*> return False
            ] <?> "listing of values (x,y,z)"
      whitespace
      char ')'
      return listing


value :: IParser Var.Value
value =
    val <|> tipe
  where
    val =
      Var.Value <$> (lowVar <|> parens symOp)

    tipe =
      do  name <- capVar
          maybeCtors <- optionMaybe (listing capVar)
          case maybeCtors of
            Nothing -> return (Var.Alias name)
            Just ctors -> return (Var.Union name ctors)