{-# OPTIONS -cpp #-}
{-# LANGUAGE CPP, ForeignFunctionInterface #-}

------------------------------------------------------------------------
-- Program for converting .hsc files to .hs files, by converting the
-- file into a C program which is run to generate the Haskell source.
-- Certain items known only to the C compiler can then be used in
-- the Haskell module; for example #defined constants, byte offsets
-- within structures, etc.
--
-- See the documentation in the Users' Guide for more details.

#if defined(__GLASGOW_HASKELL__) && !defined(BUILD_NHC)
#include "../../includes/ghcconfig.h"
#endif

import Control.Monad            ( liftM, forM_ )
import Data.List                ( isSuffixOf )
import System.Console.GetOpt

#if defined(mingw32_HOST_OS)
import Foreign
import Foreign.C.String
#endif
import System.Directory         ( doesFileExist, findExecutable )
import System.Environment       ( getProgName, getArgs )
import System.Exit              ( ExitCode(..), exitWith )
import System.FilePath          ( normalise, splitFileName, splitExtension )
import System.IO

#ifdef BUILD_NHC
import System.Directory         ( getCurrentDirectory )
#else
import Data.Version             ( showVersion )
import Paths_hsc2hs as Main     ( getDataFileName, version )
#endif

import Common
import CrossCodegen
import DirectCodegen
import Flags
import HSCParser

#ifdef mingw32_HOST_OS
# if defined(i386_HOST_ARCH)
#  define WINDOWS_CCONV stdcall
# elif defined(x86_64_HOST_ARCH)
#  define WINDOWS_CCONV ccall
# else
#  error Unknown mingw32 arch
# endif
#endif

#ifdef BUILD_NHC
getDataFileName s = do here <- getCurrentDirectory
                       return (here++"/"++s)
version = "0.67" -- TODO!!!
showVersion = id
#endif

versionString :: String
versionString = "hsc2hs version " ++ showVersion version ++ "\n"

main :: IO ()
main = do
    prog <- getProgramName
    let header = "Usage: "++prog++" [OPTIONS] INPUT.hsc [...]\n"
        usage = usageInfo header options
    args <- getArgs
    let (fs, files, errs) = getOpt Permute options args
    let mode = foldl (.) id fs emptyMode
    case mode of
        Help     -> bye usage
        Version  -> bye versionString
        UseConfig config ->
            case (files, errs) of
            ((_:_), []) -> processFiles config files usage
            (_,     _ ) -> die (concat errs ++ usage)

getProgramName :: IO String
getProgramName = liftM (`withoutSuffix` "-bin") getProgName
   where str `withoutSuffix` suff
            | suff `isSuffixOf` str = take (length str - length suff) str
            | otherwise             = str

bye :: String -> IO a
bye s = putStr s >> exitWith ExitSuccess

processFiles :: ConfigM Maybe -> [FilePath] -> String -> IO ()
processFiles configM files usage = do
    mb_libdir <- getLibDir

    (template, extraFlags) <- findTemplate usage mb_libdir configM
    compiler <- findCompiler mb_libdir configM
    let linker = case cmLinker configM of
                 Nothing -> compiler
                 Just l -> l
        config = Config {
                     cmTemplate    = Id template,
                     cmCompiler    = Id compiler,
                     cmLinker      = Id linker,
                     cKeepFiles    = cKeepFiles configM,
                     cNoCompile    = cNoCompile configM,
                     cCrossCompile = cCrossCompile configM,
                     cCrossSafe    = cCrossSafe configM,
                     cVerbose      = cVerbose configM,
                     cFlags        = cFlags configM ++ extraFlags
                 }

    let outputter = if cCrossCompile config then outputCross else outputDirect

    forM_ files (\name -> do
        (outName, outDir, outBase) <- case [f | Output f <- cFlags config] of
             [] -> if not (null ext) && last ext == 'c'
                      then return (dir++base++init ext,  dir, base)
                      else
                         if ext == ".hs"
                            then return (dir++base++"_out.hs", dir, base)
                            else return (dir++base++".hs",     dir, base)
                   where
                    (dir,  file) = splitFileName  name
                    (base, ext)  = splitExtension file
             [f] -> let
                 (dir,  file) = splitFileName  f
                 (base, _)    = splitExtension file
                 in return (f, dir, base)
             _ -> onlyOne "output file"
        let file_name = normalise name
        toks <- parseFile file_name
        outputter config outName outDir outBase file_name toks)

findTemplate :: String -> Maybe FilePath -> ConfigM Maybe
             -> IO (FilePath, [Flag])
findTemplate usage mb_libdir config
 = -- If there's no template specified on the commandline, try to locate it
   case cmTemplate config of
   Just t ->
       return (t, [])
   Nothing -> do
     -- If there is no Template flag explicitly specified, try
     -- to find one. We first look near the executable.  This only
     -- works on Win32 or Hugs (getExecDir). If this finds a template
     -- file then it's certainly the one we want, even if hsc2hs isn't
     -- installed where we told Cabal it would be installed.
     --
     -- Next we try the location we told Cabal about.
     --
     -- If neither of the above work, then hopefully we're on Unix and
     -- there's a wrapper script which specifies an explicit template flag.
     mb_templ1 <-
       case mb_libdir of
       Nothing   -> return Nothing
       Just path -> do
       -- Euch, this is horrible. Unfortunately
       -- Paths_hsc2hs isn't too useful for a
       -- relocatable binary, though.
         let
             templ1 = path ++ "/template-hsc.h"
             incl = path ++ "/include/"
         exists1 <- doesFileExist templ1
         if exists1
            then return $ Just (templ1, CompFlag ("-I" ++ incl))
            else return Nothing
     case mb_templ1 of
         Just (templ1, incl) ->
             return (templ1, [incl])
         Nothing -> do
             templ2 <- getDataFileName "template-hsc.h"
             exists2 <- doesFileExist templ2
             if exists2 then return (templ2, [])
                        else die ("No template specified, and template-hsc.h not located.\n\n" ++ usage)

findCompiler :: Maybe FilePath -> ConfigM Maybe -> IO FilePath
findCompiler mb_libdir config
 = case cmCompiler config of
   Just c -> return c
   Nothing ->
       do let search_path = do
                  mb_path <- findExecutable default_compiler
                  case mb_path of
                      Nothing ->
                          die ("Can't find "++default_compiler++"\n")
                      Just path -> return path
              -- if this hsc2hs is part of a GHC installation on
              -- Windows, then we should use the mingw gcc that
              -- comes with GHC (#3929)
              inplaceGccs = case mb_libdir of
                            Nothing -> []
                            Just d  -> [d ++ "/../mingw/bin/gcc.exe"]
              search [] = search_path
              search (x : xs) = do b <- doesFileExist x
                                   if b then return x else search xs
          search inplaceGccs

parseFile :: String -> IO [Token]
parseFile name
  = do h <- openBinaryFile name ReadMode
       -- use binary mode so we pass through UTF-8, see GHC ticket #3837
       -- But then on Windows we end up turning things like
       --     #let alignment t = e^M
       -- into
       --     #define hsc_alignment(t ) printf ( e^M);
       -- which gcc doesn't like, so strip out any ^M characters.
       s <- hGetContents h
       let s' = filter ('\r' /=) s
       case runParser parser name s' of
         Success _ _ _ toks -> return toks
         Failure (SourcePos name' line) msg ->
           die (name'++":"++show line++": "++msg++"\n")

getLibDir :: IO (Maybe String)
getLibDir = fmap (fmap (++ "/lib")) $ getExecDir "/bin/hsc2hs.exe"

-- (getExecDir cmd) returns the directory in which the current
--                  executable, which should be called 'cmd', is running
-- So if the full path is /a/b/c/d/e, and you pass "d/e" as cmd,
-- you'll get "/a/b/c" back as the result
getExecDir :: String -> IO (Maybe String)
getExecDir cmd =
    getExecPath >>= maybe (return Nothing) removeCmdSuffix
    where initN n = reverse . drop n . reverse
          removeCmdSuffix = return . Just . initN (length cmd) . normalise

getExecPath :: IO (Maybe String)
#if defined(mingw32_HOST_OS)
getExecPath = try_size 2048 -- plenty, PATH_MAX is 512 under Win32.
  where
    try_size size = allocaArray (fromIntegral size) $ \buf -> do
        ret <- c_GetModuleFileName nullPtr buf size
        case ret of
          0 -> return Nothing
          _ | ret < size -> fmap Just $ peekCWString buf
            | otherwise  -> try_size (size * 2)

foreign import WINDOWS_CCONV unsafe "windows.h GetModuleFileNameW"
  c_GetModuleFileName :: Ptr () -> CWString -> Word32 -> IO Word32
#else
getExecPath = return Nothing
#endif
