package main

import "bufio"
import "flag"
import "fmt"
import "go/ast"
import "go/parser"
import "go/token"
import "go/types"
import "os"
import "regexp"

func Creat(filename string) *bufio.Writer {
  println("Creat: " + filename)
  z, err := os.Create(filename)
  if err != nil {panic(err)}
  return bufio.NewWriter(z)
}

var NonIdent *regexp.Regexp = regexp.MustCompile(`[^A-Za-z0-9_]`)
func Skidify(s string) string {return NonIdent.ReplaceAllString(s, "_")}
func Fmt(s string, aa... interface{}) string {return fmt.Sprintf(s, aa...)}

type BScope struct {
	Up	*BScope
	Cname	map[string]string
	Ctype	map[string]string
}
func NewBScope(up *BScope) *BScope {
  return &BScope{Up:up, Cname:make(map[string]string), Ctype:make(map[string]string)}
}
func (p *BScope) findCNameAndType(k string) (string, string, bool) {
  for {
    cvar, ok1 := p.Cname[k]
    ctype, ok2 := p.Ctype[k]
    if ok1 && ok2 { return cvar, ctype, true }
    if ok1 || ok2 { panic(Fmt("ok %v ok2 %v", ok1, ok2)) }
    p = p.Up
    if p == nil { return "", "", false }
  }
  panic(nil);
}

// Bak is the Gobak compiler for one file.
type Bak struct {
	cw	*bufio.Writer
	hw	*bufio.Writer
	iw	*bufio.Writer
	jw	*bufio.Writer
	c	func(f string, aa... interface{})
	h	func(f string, aa... interface{})
	i	func(f string, aa... interface{})
	j	func(f string, aa... interface{})
	top	*ast.File
	fset	*token.FileSet
	indent	int
	Scope	*BScope
}
func NewBak(top *ast.File, fset *token.FileSet) *Bak {
  z := &Bak{
      top: top,
      fset: fset,
      cw: Creat("__."+top.Name.Name+".gobak.cc"),
      hw: Creat("__."+top.Name.Name+".gobak.h"),
      iw: Creat("__."+top.Name.Name+".gobak.i"),
      jw: Creat("__."+top.Name.Name+".gobak.j"),
  }
  z.c = func(f string, aa... interface{}) {z.WriteSmartCw(f, aa...)}
  z.h = func(f string, aa... interface{}) {z.hw.WriteString(fmt.Sprintf(f+"\n", aa...))}
  z.i = func(f string, aa... interface{}) {z.iw.WriteString(fmt.Sprintf(f+"\n", aa...))}
  z.j = func(f string, aa... interface{}) {z.jw.WriteString(fmt.Sprintf(f+"\n", aa...))}
  return z
}
func (b *Bak) WriteIndentedCw(s string) {
  for i := 0; i < b.indent; i++ {
    s = "    " + s
  }
  b.cw.WriteString(s)
}

func (b *Bak) WriteSmartCw(f string, aa... interface{}) {
  s := Fmt(f+"\n", aa...)

  if len(s)>2 && s[0]=='/' && s[1]=='/' {
    // It's a comment
  } else if len(s)>1 && s[len(s)-2]=='{' && s[0] == '}' {
    b.indent --
    b.WriteIndentedCw(s)
    b.indent ++
  } else if len(s)>1 && s[len(s)-2]=='{' {
    b.WriteIndentedCw(s)
    b.indent ++
  } else if len(s)>1 && s[0] == '}' {
    b.indent --
    b.WriteIndentedCw(s)
  } else {
    b.WriteIndentedCw(s)
  }  
}
func (b *Bak) Mangle(i *ast.Ident) string {
  for k, v := range types.Universe.Objects {
    if v == i.Obj { return k + "_Go" }
  }
  for _, v := range b.top.Scope.Objects {
    if v == i.Obj {
      return Fmt("%s__%s", b.top.Name.Name, i.Name)
    }
  }
  return Fmt("%s_", i.Name)  // Just add _ to avoid C reserved words.
}
func (b *Bak) Init() {
  guard := Skidify(b.top.Name.Name+".gobak.h")
  b.h(`#include <stdio.h>`)
  b.h(`#include "gobak_rt.h"`)
  b.h(`#ifndef %s`, guard)
  b.h(`#define %s`, guard)
  b.c(`#include "%s"`, "__."+b.top.Name.Name+".gobak.h")
  b.c(`#include "%s"`, "__."+b.top.Name.Name+".gobak.i")
  b.j(`void main__init() {`);

  b.Scope = NewBScope(nil)
  for k, v := range b.top.Scope.Objects {
    b.Scope.Cname[k] = b.Mangle(&ast.Ident{Obj:v})
    b.Scope.Ctype[k] = b.Mangle(&ast.Ident{Obj:v}) // TODO
  }
}
func (b *Bak) Finish() {
  guard := Skidify(b.top.Name.Name+".gobak.h")
  b.c(`#include "%s"`, "__."+b.top.Name.Name+".gobak.j")
  b.h(`#endif  // %s`, guard)
  b.j(`}  // end main__init()`);
  b.cw.Flush();
  b.hw.Flush();
  b.iw.Flush();
  b.jw.Flush();
}
func (b *Bak) Run() {
  b.Init()
  decls := b.top.Decls
  for i, decl := range decls {
    Say("Decl", i, decl)
    switch d := decl.(type) {
      case *ast.FuncDecl: b.doFuncDecl(d)
        
      case *ast.GenDecl: b.doGenDecl(d)

      default:
        Say("TODO", decl)
    }

  }
}
func (b *Bak) doGenDecl(decl *ast.GenDecl) {
  Say("TODO: doGenDecl");
  for i, x := range decl.Specs {
    Say(i, x)
    switch xx := x.(type) {
      case *ast.ValueSpec:
        b.doValueSpec(xx)
      default:
        Say("TODO", x)
        panic(x)
    }
  }
}
func (b *Bak) doValueSpec(p *ast.ValueSpec) {
  Say("doValueSpec: Type", p.Type)
  for i, x := range p.Names {
    Say("doValueSpec: Names !", i, x)
  }
  for i, x := range p.Values {
    Say("doValueSpec: Values !", i, x)
  }
  Say(p)
  for i, name := range p.Names {
    cname := b.Mangle(name)
    ctype := b.Mangle(p.Type.(*ast.Ident))
    b.h("extern %s %s;", ctype, cname);
    b.i("%s %s;", ctype, cname);
    b.j("%s = %s;", cname, b.doExpr(p.Values[i]));
  }
}
func (b *Bak) doFuncDecl(d *ast.FuncDecl) {
  Say("doFuncDecl", d)
  Say("Doc", d.Doc)
  name := d.Name.Name
  ttype := d.Type
  body := d.Body
  Say("name", name)
  Say("ttype", ttype)
  Say("body", body)
  params := d.Type.Params
  results := d.Type.Results
  stmts := d.Body.List
  Say("params", params)
  Say("results", results)
  Say("stmts", stmts)

  ret_t := "void"
  if results != nil {
    // The *last* Go result is the C function return value.
    nr := len(results.List)
    // There may be more than on ename, but we only do this for the *last* one.
    ret_t = b.Mangle(results.List[nr-1].Type.(*ast.Ident))
  }

  Say(params)

  p_list := b.MangledFuncParams(params, results)
  b.h("%s %s(%s);", ret_t, b.Mangle(d.Name), p_list)
  b.c("")
  b.c("")
  b.c("%s %s(%s) {", ret_t, b.Mangle(d.Name), p_list)
  for _, x := range stmts {
    b.doStmt(x)
    b.c("")
  }
  b.c("}  //  end %s()", b.Mangle(d.Name))
}

var nextGen int = 1000
func Gen(prefix string) string { 
  nextGen ++
  return Fmt("%s_%d", prefix, nextGen)
}

func (b *Bak) doStmt(stmt ast.Stmt) {
  b.c("//==stmt// %#v", stmt)
  if stmt == nil {return}
  switch x := stmt.(type) {
    case *ast.ExprStmt:
      b.c(b.doExpr(x.X) + ";")
    case *ast.ReturnStmt:
      z := ""
      for i, y := range x.Results {
	if i < len(x.Results) - 1 {
	  // if not the last one, set the C out argument.
	  z += Fmt(" *_r_%d = ", i+1)
	}
        z += Fmt(" (%s),", b.doExpr(y))
      }
      z = Chomp(z, ',')
      b.c(`return %s;`, z)
    case *ast.BlockStmt:
      for _, y := range x.List {
        b.doStmt(y);
      }
    case *ast.IfStmt:
      b.doStmt(x.Init);
      b.c("if (%s) {", b.doExpr(x.Cond))
      b.doStmt(x.Body);
      b.c("} else {")
      b.doStmt(x.Else);
      b.c("}  // end if")
    case *ast.ForStmt:
      b.doStmt(x.Init);
      b.c("while (%s) {", b.doExpr(x.Cond))
      b.doStmt(x.Body);
      b.c("post:");
      b.doStmt(x.Post);
      b.c("}  // end while")
    case *ast.IncDecStmt:
      b.c("%s %s;", b.doExpr(x.X), x.Tok);

    case *ast.AssignStmt:
      for k_, v_ := range x.Lhs {
        b.c("//LHS[%d]: %#v", k_, v_)
      }
      for k_, v_ := range x.Rhs {
        b.c("//RHS[%d]: %#v", k_, v_)
      }

      if x.Tok == 47 {  // ":="
        for _, l := range x.Lhs {
          b.c("int %s = 0;", b.Mangle(l.(*ast.Ident)))
        }
      }

      // Temp place to hold multi-return results.
      temp := make([]string, len(x.Lhs))
      if len(x.Lhs) > 1 {
        for i, lhs := range x.Lhs {
	  temp[i] = Gen("lhs");
	  // Declare vars and init them.
	  b.c("%s %s = 0;", CTypeOfIdent(lhs.(*ast.Ident)), temp[i])
	}

	if len(x.Rhs) == len(x.Lhs) {
	  // Pairwise assignments.
          for i, r := range x.Rhs {
            rhs := b.doExpr(r)
	    b.c("%s = (%s);", temp[i], rhs)
	  }
          for i, l := range x.Lhs {
            lhs := b.doExpr(l)
	    b.c("%s = (%s);", lhs, temp[i])
	  }
	} else if len(x.Rhs) == 1 {
	  // Many lhs, One rhs:  must be function call on rhs.
	  switch rhx := x.Rhs[0].(type) {
	    case *ast.CallExpr:
	      b.c("%s = %s;", temp[len(temp)-1], b.doCallExpr(rhx, temp))
	    default:
	      Say(rhx);
	      panic(rhx);
	  }
          for i, l := range x.Lhs {
            lhs := b.doExpr(l)
	    b.c("%s = (%s);", lhs, temp[i])
	  }

	} else {
	  panic(Fmt("len(lhs)=%d len(rhs)=%d", len(x.Lhs), len(x.Rhs)))
	}
      } else if len(x.Rhs) == 1 {
        lhs := b.doExpr(x.Lhs[0])
        rhs := b.doExpr(x.Rhs[0])
        b.c("(%s) = (%s);", lhs, rhs)
      } else {
        panic(Fmt("len(lhs)=%d len(rhs)=%d", len(x.Lhs), len(x.Rhs)))
      }

      
/*
      for i, _ := range x.Lhs {
        lhs := b.doExpr(x.Lhs[i])
        rhs := b.doExpr(x.Rhs[i])
	if x.Tok == 47 {  // ":="
	  switch lhx := x.Lhs[i].(type) {
	    case *ast.Ident:
	      switch rhx := x.Rhs[i].(type) {
	        case *ast.BasicLit:
		  switch rhx.Kind {
		    case token.INT:
	              b.c("int_Go %s = 0;", lhs)
		  }
	      }
	  }
	}
        b.c("(%s) = (%s);", lhs, rhs)
      }
*/
    default:
      b.c("STATEMENT ???<%#v>??? ", stmt)
  }
}
func CTypeOfIdent(id *ast.Ident) string {
  switch kind := id.Obj.Kind ; kind {
    default:
      Say(id.Obj)
      return "int"
  }
  return "int"
}

func (b *Bak) doCallExpr(x *ast.CallExpr, temp []string) string {
      b.c("//==::::: %#v", x.Fun)
      b.c("//==::::: %#v", x.Args)
      funStr := b.doExpr(x.Fun)
      argsStr := ""
      for _, y := range x.Args {
        argsStr += b.doExpr(y) + ","
      }
      for i, t := range temp {
        if i < len(temp)-1 {
          argsStr += Fmt(" &%s,", t)
	}
      }
      argsStr = Chomp(argsStr, ',')
      b.c("//==::::: %#v", funStr)
      return Fmt(`(%s)(%s)`, funStr, argsStr)
}


func (b *Bak) doExpr(expr ast.Expr) string {
  b.c("//==expr: %#v", expr)
  switch x := expr.(type) {
    case *ast.CallExpr:
      return b.doCallExpr(x, nil)
    case *ast.Ident:
      return b.Mangle(x)
    case *ast.BasicLit:
      return x.Value
    case *ast.BinaryExpr:
      xx := b.doExpr(x.X)
      yy := b.doExpr(x.Y)
      return Fmt(`(%s)%s(%s)`, xx, x.Op, yy)
  }
  return Fmt(" ???<%#v>??? ", expr)
}

func (b *Bak) MangledFuncParams(params, results *ast.FieldList) string {
  z := ""
  if params != nil {
	  for _, p := range params.List {
	    names := p.Names
	    if names == nil {
	      z += b.MangleTypeOfField(p) + " "
	      z += "_" + ","  // TODO -- does anonymous happen?
	    } else {
	      for _, name := range names {
		z += b.MangleTypeOfField(p) + " "
		z += b.Mangle(name) + ","
	      }
	    }
	  }
  }
  if results != nil {
          n := 1
	  for i, p := range results.List {
	    names := p.Names
	    if names == nil {
	      if i == len(results.List) - 1 {
		break  // Don't do the last one, it's the C return value.
	      }
              z += Fmt("%s* _r_%d,", b.MangleTypeOfField(p), n)
	      n ++
	    } else {
	      for j, name := range names {
		if i == len(results.List) - 1 && j == len(names) - 1 {
		  break  // Don't do the last one, it's the C return value.
		}
		// TODO: support setting return values by name.
                z += Fmt("%s* /*%s*/ _r_%d,", b.MangleTypeOfField(p), name, n)
	        n ++
	      }
	    }
	  }
  }
  return Chomp(z, ',')
}
func Chomp(s string, c byte) string {
  slen := len(s)
  if slen > 1 && s[slen-1] == c { s = s[:slen-1] } // chomp final c
  return s
}

func (b *Bak) MangleTypeOfField(f *ast.Field) string {
  z := "void"
  if f != nil {
    z = b.Mangle(f.Type.(*ast.Ident))
  }
  return z
}
func main() {
        flag.Parse()
        fset := token.NewFileSet()
        files := make(map[string]*ast.File)

        for _, filename := range flag.Args() {

                tree, err := parser.ParseFile(fset, filename, nil, 0)
                if err != nil {
                        panic(err)
                }
                //
                   println("## Universe");
                   for k, v := range types.Universe.Objects {
                     println("k=", k, "  v=", v)
                   }
                   println("## Unsafe");
                   for k, v := range types.Unsafe.Objects {
                     println("k=", k, "  v=", v)
                   }
                //
                println("#####################################")
                importer := types.GcImporter
                univ := types.Universe

                files[filename] = tree
                pkg, err := ast.NewPackage(fset, files, importer, univ)
                if err != nil {
                        panic(err)
                }
                _ = pkg

                //n, err := ast.Fprint(os.Stdout, fset, tree, nil)
                //println("n=", n)
        }

        for name, tree := range files {
                println("#####################################")
                println("#######################")
                println("#######################   ", name)
                println("#######################")
                n, err := ast.Fprint(os.Stdout, fset, tree, nil)
                if err != nil {
                        panic(err)
                }
                println("n=", n)
                println("")
		b := NewBak(tree, fset)
		defer b.Finish()
		b.Run()
        }
}
func Say(a... interface{}) {
  for _, x := range a {
    print(fmt.Sprintf(" %#v =====", x))
  }
  println()
}
  
