module Utils.Katt.SourceHandler
(parseFilter, findFiles, determineLanguage, findMainClass, languageKattisName, languageContentType)
where
import           Control.Applicative ((<$>))
import           Control.Arrow ((***))
import           Control.Monad (filterM, mapAndUnzipM, void)
import qualified Data.ByteString.Char8 as B
import           Data.List ((\\), isSuffixOf)
import qualified Data.Set as Set
import           System.Directory (doesDirectoryExist, getDirectoryContents)
import           System.FilePath (takeBaseName, takeExtension)
import           Text.Parsec
import           Text.Parsec.ByteString
import           Utils.Katt.Utils
supported :: KattisLanguage -> Set.Set FilePath
supported LangCplusplus = Set.fromList [".cc", ".cpp", ".hpp", ".h"]
supported LangC         = Set.fromList [".c", ".h"]
supported LangJava      = Set.fromList [".java"]
supported LangHaskell   = Set.fromList [".hs"]
parseFilter :: [String] -> Maybe ([FilePath], [FilePath])
parseFilter input = (filter' *** filter') <$> mapAndUnzipM go input
  where
  go ('+' : file) = Just (file, "")
  go ('-' : file) = Just ("", file)
  go _ = Nothing
  filter' = filter (not . null)
findFiles :: IO [FilePath]
findFiles = explore "" "."
  where
  explore prefix dir = do
    contents <- (\\ [".", ".."]) <$> getDirectoryContents dir
    let withPrefix = map (prefix++) contents
    dirs <- filterM doesDirectoryExist withPrefix
    let sourceFiles = filter isValidSourceFile (withPrefix \\ dirs)
    nextDepth <- mapM exploreDir dirs
    return $ sourceFiles ++ concat nextDepth
  isValidSourceFile file = any (`isSuffixOf` file)
    (Set.toList . Set.unions $ map supported [LangCplusplus, LangC, LangJava, LangHaskell])
  exploreDir dir = explore (dir ++ "/") dir
determineLanguage :: [FilePath] -> Maybe KattisLanguage
determineLanguage files
  | is LangC = Just LangC
  | is LangCplusplus = Just LangCplusplus
  | is LangJava = Just LangJava
  | is LangHaskell = Just LangHaskell
  | otherwise = Nothing
  where
  fileSet = Set.fromList $ map takeExtension files
  is lang = fileSet `Set.isSubsetOf` supported lang
findMainClass :: ([FilePath], KattisLanguage) -> IO (Maybe FilePath)
findMainClass ([], _)            = return Nothing
findMainClass (_, LangCplusplus) = return $ Just ""
findMainClass (_, LangC)         = return $ Just ""
findMainClass (_, LangHaskell)   = return $ Just ""
findMainClass (files, LangJava)  = survey <$> filterM containsMain files
  where
  containsMain file = do
    parseResult <- parseFromFile mainParser file
    case parseResult of
      Right _ -> return True
      Left _ -> return False
  mainParser = manyTill
    (lineComment <|> blockComment <|> stringData <|> void anyChar)
    mainFunc
  blockComment = void $ string "/*" >> manyTill anyChar (try $ string "*/")
  lineComment = void . try $ string "//" >> manyTill anyChar newline
  stringData = void $ char '"' >> manyTill anyChar (char '"')
  mainFunc = try $ mapM_ keyWord ["public", "static", "void", "main"]
  keyWord str = void $ string str >> spaces
  survey [singleton] = Just $ takeBaseName singleton
  survey _ = Nothing
languageContentType :: KattisLanguage -> B.ByteString
languageContentType LangCplusplus = "text/x-c++src"
languageContentType LangJava = "text/x-c++src"
languageContentType LangC = "text/x-c++src"
languageContentType LangHaskell = "text/x-c++src"
languageKattisName :: KattisLanguage -> B.ByteString
languageKattisName LangCplusplus = "C++"
languageKattisName LangJava = "Java"
languageKattisName LangC = "C"
languageKattisName LangHaskell = "Haskell"