-- | Exports Sifflet to Haskell
-- Requires haskell-src package.

module Sifflet.Foreign.ToHaskell
    (
      HaskellOptions(..)
    , defaultHaskellOptions
    , exportHaskell
    , functionsToHsModule
    , functionToHsDecl
    , exprToHsExpr
    )
where

import Char (toUpper)
import qualified Data.Map as M
import System.FilePath (dropExtension, takeFileName)

import Sifflet.Foreign.Exporter
import Sifflet.Foreign.Haskell
import Sifflet.Language.Expr
import Sifflet.Util


-- Main types and functions

-- | User configurable options for export to Haskell.
-- Currently these options are unused.
-- The line width options should probably go somewhere else,
-- maybe as PrettyOptions.
data HaskellOptions = 
    HaskellOptions {optionsSoftMaxLineWidth :: Int
                   , optionsHardMaxLineWidth :: Int
                   }
                    deriving (Eq, Show)

-- | The default options for export to Haskell.
defaultHaskellOptions :: HaskellOptions
defaultHaskellOptions = HaskellOptions {optionsSoftMaxLineWidth = 72,
                                        optionsHardMaxLineWidth = 80}

-- | Export functions with specified options to a file
exportHaskell :: HaskellOptions -> Exporter
exportHaskell _options functions path =
    let header = "-- File: " ++ path ++
                 "\n-- Generated by the Sifflet->Haskell exporter.\n\n"
    in writeFile path (header ++ 
                       hsPretty (functionsToHsModule 
                                (pathToModuleName path)
                                functions))


pathToModuleName :: FilePath -> String
pathToModuleName path =
    case dropExtension (takeFileName path) of
      [] -> "Test"
      c : cs -> toUpper c : cs

-- ------------------------------------------------------------------------

-- | Converting Sifflet to Haskell syntax tree

-- | Create a module from a module name and Functions.
functionsToHsModule :: String -> Functions -> Module
functionsToHsModule modname (Functions fs) =
    Module {moduleName = modname
           , moduleExports = Nothing
           , moduleImports = ImportDecl ["Data.Number.Sifflet"]
           , moduleDecls = map functionToHsDecl fs
           }

-- | Create a declaration from a Function.
-- Needs work: infer and declare the type of the function.
-- Minimally parenthesized.
functionToHsDecl :: Function -> Decl
functionToHsDecl (Function mname _atypes _rtype impl) =
    case (mname, impl) of
      (Nothing, _) -> error "functionToHsDecl: function has no name"
      (_, Primitive _) -> error "functionToHsDecl: function is primitive"
      (Just fname, Compound args body) ->
          Decl {declIdent = fname
               , declType = Nothing -- to be improved later
               , declParams = args
               , declExpr = (simplifyExpr haskellRules)
                            (exprToHsExpr body)}

haskellRules :: [Expr -> Expr]
haskellRules = commonRulesForSimplifyingExprs ++ 
               [ruleIfRight, ruleRightToLeft]

-- | Converts a Sifflet Expr to a fully parenthesized Haskell Expr
exprToHsExpr :: Expr -> Expr
exprToHsExpr expr =
    case expr of
      EUndefined -> ESymbol (Symbol "undefined")
      ESymbol _ -> expr
      EBool _ -> expr
      EChar _ -> expr
      ENumber _ -> expr
      EString _ -> expr

      EIf c a b -> EIf (exprToHsExpr c) (exprToHsExpr a) (exprToHsExpr b)
      EList es -> EList (map exprToHsExpr es)
      ECall (Symbol fname) args -> 
          case nameToHaskell fname of
            Left op ->      -- operator
                case args of
                  [left, right] -> 
                      EOp op (EGroup (exprToHsExpr left))
                             (EGroup (exprToHsExpr right))
                  _ -> error 
                       "exprToHsExpr: operation does not have 2 operands"
            Right funcName ->   -- function
                   ECall (Symbol funcName) (map (EGroup . exprToHsExpr) args)
      _ -> errcats ["exprToHsExpr: extended expr:", show expr]

-- | Map Sifflet names to Haskell names.
-- Returns a Left Operator for Haskell operators,
-- which always have the same name as their corresponding Sifflet 
-- functions, or a Right String for Haskell function and variable names.
nameToHaskell :: String -> Either Operator String
nameToHaskell name =
    case M.lookup name operatorTable of
      Just op -> Left op
      Nothing ->
        -- Most names would have the same names in Haskell,
        -- but there are a few special cases.
        Right (case name of
                 "zero?" -> "eqZero"
                 "positive?" -> "gtZero"
                 "negative?" -> "ltZero"
                 "add1" -> "succ"
                 "sub1" -> "pred"
                 _ -> name)