-- | Sifflet to abstract syntax tree for Python.
-- Use Python module's pretty to pretty-print the result.

module Sifflet.Foreign.ToPython
    (
     PythonOptions(..)
    , defaultPythonOptions
    , exprToPExpr
    , valueToPExpr
    , nameToPython
    , fixIdentifierChars
    , functionToPyDef
    , defToPy
    , functionsToPyModule
    , functionsToPrettyPy
    , exportPython
    )

where

import Char (isAlpha, isDigit, ord)
import Control.Monad (unless)

import Sifflet.Foreign.Exporter
import Sifflet.Foreign.Python
import Sifflet.Language.Expr
import Sifflet.Text.Pretty

import System.Directory (copyFile, doesFileExist)
import System.FilePath (replaceFileName)

import Paths_sifflet_lib        -- Cabal-generated paths module

-- | Options for Python export.
-- Should probably include choices like Python2 or Python3;
-- if statement or if expression.  Right now, just a placeholder.

data PythonOptions = PythonOptions
                   deriving (Eq, Show)


defaultPythonOptions :: PythonOptions
defaultPythonOptions = PythonOptions

exprToPExpr :: Expr -> PExpr
exprToPExpr expr =
    case expr of
      EUndefined -> var "undefined"
      ESymbol (Symbol str) -> var str -- ???
      ELit value -> valueToPExpr value
      EIf cond action altAction ->
          -- 2 choices here: Python if statement (condS)
          -- or Python if expression (condE)
          condE (exprToPExpr cond) 
                (exprToPExpr action)
                (exprToPExpr altAction)
      EList exprs -> 
          call "li" (map exprToPExpr exprs)
      ECall (Symbol fname) args -> 
          -- Python distinguishes between functions and operators
          case nameToPython fname of
            Left operator ->
                case args of
                  [left, right] -> 
                      POperate operator
                               (exprToPExpr left)
                               (exprToPExpr right)
                  _ -> error "exprToPExpr: operation does not have 2 operands"
            Right pname ->
                call pname (map exprToPExpr args)


valueToPExpr :: Value -> PExpr
valueToPExpr value =
    case value of
      VList vs -> call "li" (map valueToPExpr vs)
      VBool b -> bool b
      VChar c -> char c
      VInt i -> pInt i
      VFloat x -> pFloat x
      VStr s -> string s
      VFun f -> var "undefined" -- fix!  Is it fixable? ***
          -- Scheme: SFunction f

-- | Convert Sifflet name (of a function) to Python name
nameToPython :: String -> Either POperator String
nameToPython name =
    case name of 
      "+" -> Left opPlus
      "-" -> Left opMinus
      "*" -> Left opTimes
      "div" -> Left opIDiv
      "mod" -> Left opMod
      "/" -> Left opFDiv -- invalid for integers in Python 2!
      "==" -> Left opEq
      "/=" -> Left opNe
      ">" -> Left opGt
      ">=" -> Left opGe
      "<" -> Left opLt
      "<=" -> Left opLe
      "add1" -> Right "add1"
      "sub1" -> Right "sub1"
      "zero?" -> Right "eqZero"
      "positive?" -> Right "gtZero"
      "negative?" -> Right "ltZero"
      "null" -> Right "null"
      "head" -> Right "head"
      "tail" -> Right "tail"
      ":" -> Right "cons"
      _ -> Right (fixIdentifierChars name)

-- | Remove characters that are not valid in a Python identifier,
-- and in some cases, insert other characters to show what's missing

fixIdentifierChars :: String -> String
fixIdentifierChars =
    let fix s =
            case s of
              [] -> []
              c:cs ->
                  if isAlpha c || isDigit c || c == '_'
                  then c : fix cs
                  else case c of
                         '?' -> "_QUESTION_" ++ fix cs
                         -- other cases can be inserted here
                         _ -> "_CHR" ++ show (ord c) ++ "_" ++ fix cs


    in fix

functionToPyDef :: Function -> PStatement
functionToPyDef = defToPy . functionToDef

defToPy :: FunctionDefTuple -> PStatement
defToPy (fname, paramNames, _, _, body) =
    fun (fixIdentifierChars fname) paramNames (exprToPExpr body)

functionsToPyModule :: Functions -> PModule
functionsToPyModule (Functions fs) = PModule (map functionToPyDef fs)

functionsToPrettyPy :: Functions -> String
functionsToPrettyPy = pretty . functionsToPyModule

exportPython :: PythonOptions -> Exporter
exportPython _options funcs path = 
    let header = "# File: " ++ path ++
                 "\n# Generated by the Sifflet->Python exporter.\n\n" ++
                 "from sifflet import *\n\n"
        libDest = replaceFileName path "sifflet.py"
    in do
      {
        libDestExists <- doesFileExist libDest
       ; unless libDestExists 
               (do
                 -- copy sifflet.py to the same directory as path
                 {
                   libSrc <- pythonLibSiffletPath 
                 ; copyFile libSrc libDest
                 }
               )
      ; writeFile path (header ++ (functionsToPrettyPy funcs))
      }

pythonLibSiffletPath :: IO FilePath
pythonLibSiffletPath = getDataFileName "sifflet.py"