module Sifflet.Foreign.ToScheme
    (SExpr(..)
    , Atom(..)
    , Indent
    , Exporter
    , SchemeOptions(..)
    , defaultSchemeOptions
    , exprToSExpr
    , functionNameToSchemeName
    , valueToSExpr
    , exprToSchemeRepr
    , exprToSchemePretty
    , exprToScheme
    , inl
    , sepLines2
    , functionsToSExprs
    , functionsToPrettyScheme
    , defToSExpr
    , exportScheme
    )
where
import Paths_sifflet_lib 
import Data.Number.Sifflet
import Sifflet.Foreign.Exporter
import Sifflet.Language.Expr
import Sifflet.Text.Repr
import Sifflet.Text.Pretty
import Sifflet.Util
data SExpr = SAtom Atom | SList [SExpr]
           deriving (Eq, Show)
data Atom = SFloat Double
          | SInt Integer
          | SSymbol String
          | SString String
          | SChar Char
          | SBool Bool
          | SFunction Function
          deriving (Eq, Show)
type Indent = Int
data SchemeOptions = 
    SchemeOptions { defineWithLambda :: Bool 
                    
                    
                  }
    deriving (Eq, Show)
defaultSchemeOptions :: SchemeOptions
defaultSchemeOptions = SchemeOptions {defineWithLambda = False}
flattish :: [SExpr] -> Bool
flattish sexprs =
    case sexprs of
      [] -> True
      x:xs -> atom x && flattish xs
atom :: SExpr -> Bool
atom sexpr =
    case sexpr of
      SAtom _ -> True
      SList [] -> True
      _ -> False
exprToSExpr :: Expr -> SExpr
exprToSExpr expr =
    case expr of
      EUndefined -> 
          SAtom (SSymbol "*sifflet-undefined*")
      ESymbol (Symbol str) -> 
          SAtom (SSymbol (functionNameToSchemeName str))
      EBool b -> valueToSExpr (VBool b)
      EChar c -> valueToSExpr (VChar c)
      ENumber n -> valueToSExpr (VNumber n)
      EString s -> valueToSExpr (VString s)
      EIf cond action altAction ->
          SList [SAtom (SSymbol "if"), exprToSExpr cond,
                 exprToSExpr action, exprToSExpr altAction]
      EList exprs -> 
          
          
          
          SList (SAtom (SSymbol "list") : (map exprToSExpr exprs))
      ECall fsym args -> 
          SList (exprToSExpr (ESymbol fsym) :  map exprToSExpr args) 
      _ -> errcats ["exprToSExpr: extended expr:", show expr]
functionNameToSchemeName :: String -> String
functionNameToSchemeName name =
    case name of
      "mod" -> "modulo"
      "add1" -> "sifflet-add1"
      "sub1" -> "sifflet-sub1"
      "==" -> "equal?"
      "/=" -> "sifflet-not-equal?"
      "null" -> "null?"
      "head" -> "car"
      "tail" -> "cdr"
      ":" -> "cons"
      _ -> name
valueToSExpr :: Value -> SExpr
valueToSExpr value =
    case value of
      VList vs -> 
          SList [SAtom (SSymbol "quote"), SList (map valueToSExpr vs)]
      _ ->
          SAtom (case value of
                   VBool b -> SBool b
                   VChar c -> SChar c
                   VNumber (Exact i) -> SInt i
                   VNumber (Inexact x) -> SFloat x
                   VString s -> SString s
                   VFun f -> SFunction f
                   VList _ -> 
                       error ("valueToSExpr: Impossible!  " ++
                              "We can't have VList here!")
                )
exprToSchemeRepr :: Expr -> String
exprToSchemeRepr = repr . exprToSExpr
exprToSchemePretty :: Expr -> String
exprToSchemePretty = pretty . exprToSExpr
exprToScheme :: Expr -> String
exprToScheme = exprToSchemePretty
instance Repr SExpr where
    repr sexpr =
        case sexpr of
          SAtom satom ->
              case satom of
                SFloat x -> show x
                SInt i -> show i
                SSymbol name -> name 
                SString str -> show str 
                SChar char -> show char
                SBool False -> "#f"
                SBool True -> "#t"
                
                
                
                SFunction (Function mname _ _ _) ->
                    case mname of
                      Nothing -> 
                          error "SExpr/repr: cannot repr unnamed function"
                      Just name -> 
                          functionNameToSchemeName name
          SList exprs ->
              "(" ++ unwords (map repr exprs) ++ ")"
instance Pretty SExpr where
    pretty = prettyLoop 0
prettyLoop :: Indent -> SExpr -> String
prettyLoop ind sexpr =
    case sexpr of
      SAtom _ -> repr sexpr
      SList xs ->
          if flattish xs
          then repr sexpr
          else
              case xs of
                [] -> error "prettyLoop: non-flattish xs cannot be []."
                [SAtom (SSymbol "if"), _, _, _] ->
                    displayList2 ind (ind + 4) xs
                [SAtom (SSymbol "define"), _, _] ->
                    displayList2 ind (ind + 4) xs
                [SAtom (SSymbol "lambda"), _, _] ->
                    displayList2 ind (ind + 4) xs
                SAtom (SSymbol name) : args ->
                    
                    
                    
                    case args of
                      [] ->
                          
                          displayList1 ind (ind + length name + 2) xs
                      _ ->
                          
                          
                          displayList2 ind (ind + length name + 2) xs
                _ -> displayList1 ind (ind + 1) xs
inl :: Int -> String
inl ind = "\n" ++ replicate ind ' '
displayList1 :: Indent -> Indent -> [SExpr] -> String
displayList1 ind ind' xs =
    case xs of
      [] -> error "displayList1: empty list"
      x:xs' -> "(" ++ 
               prettyLoop ind x ++ 
               displayTail ind' xs'
displayList2 :: Indent -> Indent -> [SExpr] -> String
displayList2 ind ind' xs =
    case xs of
      x0:x1:xs' -> "(" ++ prettyLoop ind x0 ++
                   " " ++ prettyLoop ind' x1 ++
                   displayTail ind' xs'
      _ -> error "displayList2: list is too short"
displayTail :: Indent -> [SExpr] -> String
displayTail ind xs =
    case xs of
       [] -> ")"
       
       x:[] -> inl ind ++ prettyLoop ind x ++ ")"
       x:xs' -> inl ind ++ prettyLoop ind x ++ displayTail ind xs'
functionsToSExprs :: SchemeOptions -> Functions -> [SExpr]
functionsToSExprs options (Functions fs) =
    map (defToSExpr options . functionToDef) fs
functionsToPrettyScheme :: SchemeOptions -> Functions -> String
functionsToPrettyScheme options = 
    sepLines2 . map pretty . functionsToSExprs options
defToSExpr :: SchemeOptions -> FunctionDefTuple -> SExpr
defToSExpr options (name, args, _atypes, _rtype, body) =
    let asym = SAtom . SSymbol
        sdefine = asym "define"
        sname = asym name
        sbody = exprToSExpr body
    in case args of
         [] -> SList [sdefine, sname, sbody]
         _:_ ->
             let argAtoms = map asym args
             in if defineWithLambda options
                then let slambda = asym "lambda"
                         sargs = SList argAtoms
                         slambdaArgsBody = SList [slambda, sargs, sbody]
                     in SList [sdefine, sname, slambdaArgsBody]
                else let snameArgs = SList (sname : argAtoms)
                     in SList [sdefine, snameArgs, sbody]
exportScheme :: SchemeOptions -> Exporter
exportScheme options functions path = do
  {
    let header = ";;; File: " ++ path ++
                 "\n;;; Generated by the Sifflet->Scheme exporter."
  ; lib <- schemeLibSiffletPath >>= readFile
  ; writeFile path 
              (sepLines2 [header,
                              functionsToPrettyScheme options functions,
                              lib])
  }
schemeLibSiffletPath :: IO FilePath
schemeLibSiffletPath = getDataFileName "sifflet.scm"