{-# Language PatternGuards, CPP #-}

module HsImport.Main
   ( hsimport
   , hsimportWithArgs
   ) where

import Control.Monad (when)
import System.Exit (exitFailure, exitSuccess)
import System.IO (hPutStrLn, stderr)
import Data.Maybe (isJust, mapMaybe)
import Data.List (foldl', partition)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import qualified Config.Dyre as Dyre
import qualified Language.Haskell.Exts as HS
import HsImport.ImportChange
import HsImport.HsImportSpec
import HsImport.ImportPos (ImportPos(..))
import qualified HsImport.Args as Args
import HsImport.Config
import HsImport.Utils
import HsImport.Types

#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
#endif


hsimport :: Config -> IO ()
hsimport = Dyre.wrapMain $ Dyre.defaultParams
   { Dyre.projectName = "hsimport"
   , Dyre.realMain    = realMain
   , Dyre.showError   = \config err -> config { configError = Just err }
   }
   where
      realMain :: Config -> IO ()
      realMain config = do
         case configError config of
            Just error -> hPutStrLn stderr ("hsimport: " ++ error) >> exitFailure
            _          -> return ()

         args     <- Args.hsImportArgs
         maybeErr <- hsimportWithArgs config args
         case maybeErr of
            Just err -> hPutStrLn stderr ("hsimport: " ++ err) >> exitFailure
            _        -> exitSuccess


hsimportWithArgs :: Config -> Args.HsImportArgs -> IO (Maybe ErrorMessage)
hsimportWithArgs config args = do
   maybeSpec <- hsImportSpec args
   case maybeSpec of
      Left  error -> return $ Just error
      Right spec  -> hsimportWithSpec config spec


hsimportWithSpec :: Config -> HsImportSpec -> IO (Maybe ErrorMessage)
hsimportWithSpec Config { prettyPrint = prettyPrint, findImportPos = findImportPos } spec = do
   let impChanges = importChanges (moduleImport spec) (symbolImport spec) (parsedSrcFile spec)
   case partition hasImportError impChanges of
       ([], changes) -> do
          srcLines <- lines . T.unpack <$> TIO.readFile (sourceFile spec)
          let srcLines' = applyChanges srcLines changes
          when (srcLines' /= srcLines || isJust (saveToFile spec)) $
             TIO.writeFile (outputFile spec) (T.pack $ unlines srcLines')
          return Nothing

       (errors, _) ->
          return (Just (unlines $ mapMaybe toErrorMessage errors))

   where
      applyChanges = foldl' applyChange

      applyChange srcLines (ReplaceImportAt srcSpan importDecl) =
         let numTakes = max 0 (HS.srcSpanStartLine srcSpan - 1)
             numDrops = HS.srcSpanEndLine srcSpan
             in take numTakes srcLines ++ [prettyPrint importDecl] ++ drop numDrops srcLines

      applyChange srcLines (AddImportAfter srcLine importDecl) =
         let numTakes = srcLine
             numDrops = numTakes
             in take numTakes srcLines ++ [prettyPrint importDecl] ++ drop numDrops srcLines

      applyChange srcLines (AddImportAtEnd importDecl) =
         srcLines ++ [prettyPrint importDecl]

      applyChange srcLines (FindImportPos importDecl) =
         case findImportPos importDecl allImportDecls of
             Just (After impDecl)  -> applyChange srcLines (AddImportAfter (lastSrcLine . HS.ann $ impDecl)
                                                                           importDecl)
             Just (Before impDecl) -> applyChange srcLines (AddImportAfter (max 0 ((firstSrcLine . HS.ann $ impDecl) - 1))
                                                                           importDecl)
             _                     -> applyChange srcLines (AddImportAfter (lastSrcLine . HS.ann . last $ allImportDecls)
                                                                           importDecl)

      applyChange srcLines NoImportChange = srcLines

      applyChange _ (ImportError _) = error "hsimportWithSpec: unexpected 'ImportError'"

      outputFile spec
         | Just file <- saveToFile spec = file
         | otherwise                    = sourceFile spec

      allImportDecls = importDecls $ parsedSrcFile spec