module Camfort.Input where
import qualified Data.ByteString.Char8 as B
import qualified Language.Fortran.Parser as Fortran
import Language.Fortran.PreProcess
import Language.Fortran
import Data.Monoid
import Data.Generics.Uniplate.Operations
import Camfort.Analysis.Annotations
import Language.Haskell.ParseMonad
import qualified Language.Haskell.Syntax as LHS
import System.Directory
import Camfort.Helpers
import Camfort.Output
import Camfort.Traverse
import Data.Data
import Data.List (nub, (\\), elemIndices, intersperse)
class Default t where
    defaultValue :: t
getOption :: forall t opt . (Data opt, Data t, Default opt) => [t] -> opt
getOption [] = defaultValue
getOption (x : xs) =
    case universeBi x :: [opt] of
      []        -> getOption xs
      (opt : _) -> opt
doAnalysis :: (Program A -> Program Annotation)
           -> FileOrDir -> [Filename] -> IO ()
doAnalysis aFun src excludes = do
  if excludes /= [] && excludes /= [""]
  then putStrLn $ "Excluding " ++ (concat $ intersperse "," excludes)
                               ++ " from " ++ src ++ "/"
  else return ()
  ps <- readParseSrcDir src excludes
  let inFiles = map Fortran.fst3 ps
  let outFiles = filter (\f -> not ((take (length $ src ++ "out") f) == (src ++ "out"))) inFiles
  let asts' = map (\(f, _, ps) -> aFun ps) ps
  outputAnalysisFiles src asts' outFiles
doAnalysisSummary :: (Monoid s, Show s)
                  => (Program A -> s) -> FileOrDir -> [Filename] -> IO ()
doAnalysisSummary aFun d excludes = do
  if excludes /= [] && excludes /= [""]
  then putStrLn $ "Excluding " ++ (concat $ intersperse "," excludes)
                               ++ " from " ++ d ++ "/"
  else return ()
  ps <- readParseSrcDir d excludes
  let inFiles = map Fortran.fst3 ps
  putStrLn "Output of the analysis:"
  putStrLn $ show $ Prelude.foldl (\n (f, _, ps) -> n `mappend` (aFun ps)) mempty ps
doAnalysisReport :: ([(Filename, Program A)] -> (String, t1))
                 -> FileOrDir -> [Filename] -> t -> IO ()
doAnalysisReport rFun inSrc excludes outSrc = do
  if excludes /= [] && excludes /= [""]
  then putStrLn $ "Excluding " ++ (concat $ intersperse "," excludes)
                               ++ " from " ++ inSrc ++ "/"
  else return ()
  ps <- readParseSrcDir inSrc excludes
  putStr "\n"
  let (report, ps') = rFun (map (\(f, inp, ast) -> (f, ast)) ps)
  putStrLn report
doAnalysisReport' :: ([(Filename, Program A)] -> (String, t1))
                  -> FileOrDir -> [Filename] -> t -> IO ()
doAnalysisReport' rFun inSrc excludes outSrc = do
  if excludes /= [] && excludes /= [""]
  then putStrLn $ "Excluding " ++ (concat $ intersperse "," excludes)
                               ++ " from " ++ inSrc ++ "/"
  else return ()
  ps <- readParseSrcDir inSrc excludes
  putStr "\n"
  let (report, ps') = rFun (map (\(a, b, c) -> (a, c)) ps)
  putStrLn report
doRefactor :: ([(Filename, Program A)]
           -> (String, [(Filename, Program Annotation)]))
           -> FileOrDir -> [Filename] -> FileOrDir -> IO ()
doRefactor rFun inSrc excludes outSrc = do
  if excludes /= [] && excludes /= [""]
  then putStrLn $ "Excluding " ++ (concat $ intersperse "," excludes)
                               ++ " from " ++ inSrc ++ "/"
  else return ()
  ps <- readParseSrcDir inSrc excludes
  let (report, ps') = rFun (map (\(f, inp, ast) -> (f, ast)) ps)
  
  putStrLn report
  let outFiles = map fst ps'
  let outData = zip3 outFiles (map (B.pack . Fortran.snd3) ps) (map snd ps')
  outputFiles inSrc outSrc outData
readParseSrcDir :: FileOrDir -> [Filename] -> IO [(Filename, String, Program A)]
readParseSrcDir inp excludes = do
    isdir <- isDirectory inp
    files <- if isdir then do
               files <- rGetDirContents inp
               return $ (map (\y -> inp ++ "/" ++ y) files) \\ excludes
             else return [inp]
    mapM readParseSrcFile files
rGetDirContents :: FileOrDir -> IO [String]
rGetDirContents d = do
    ds <- getDirectoryContents d
    ds' <- return $ ds \\ [".", ".."] 
    rec ds'
      where
        rec []     = return $ []
        rec (x:xs) = do xs' <- rec xs
                        g <- doesDirectoryExist (d ++ "/" ++ x)
                        if g then
                           do x' <- rGetDirContents (d ++ "/" ++ x)
                              return $ (map (\y -> x ++ "/" ++ y) x') ++ xs'
                        else if isFortran x
                             then return $ x : xs'
                             else return $ xs'
isFortran x = elem (fileExt x) [".f", ".f90", ".f77", ".cmn", ".inc"]
readParseSrcFile :: Filename -> IO (Filename, String, Program A)
readParseSrcFile f = do
    putStrLn f
    inp <- readFile f
    ast <- parse f
    return $ (f, inp, map (fmap (const unitAnnotation)) ast)
parse  :: Filename -> IO (Program ())
parse f =
    let mode = ParseMode { parseFilename = f }
        selectedParser = case (fileExt f) of
                           ".cmn" -> Fortran.include_parser
                           ".inc" -> Fortran.include_parser
                           _      -> Fortran.parser
    in do inp <- readFile f
          
          
          
          case runParserWithMode mode selectedParser (' ' : pre_process inp) of
             (ParseOk p)       -> return $ p
             (ParseFailed l e) -> error e
fileExt x = let ix = elemIndices '.' x
            in if (length ix == 0) then ""
               else Prelude.drop (Prelude.last ix) x