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
	Gs   *ast.Scope        // go Scope map
	Cmap map[string]string // lookup C name
}

func NewBScope(up *BScope, gs *ast.Scope) *BScope {
	return &BScope{Up: up, Gs: gs, Cmap: make(map[string]string, 4)}
}

func (p *BScope) Lookup(k string) (bs *BScope, obj *ast.Object, cname string) {
	bs = p
	for bs != nil {
		_, ok := bs.Gs.Objects[k]
		if ok {
			cname, _ = bs.Cmap[k]
			return
		}
		bs = bs.Up
	}
	panic("cannot Lookup: " + k)
	return
}

func (p *BScope) String() string {
	return Fmt("BScope(%#v) ", p.Cmap)
}
// 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{})
	file    *ast.File
	fset    *token.FileSet
	indent  int
	Top     *BScope
	Builtin *BScope
}

func NewBak(file *ast.File, fset *token.FileSet) *Bak {
	z := &Bak{
		file: file,
		fset: fset,
		cw:   Creat("__." + file.Name.Name + ".gobak.cc"),
		hw:   Creat("__." + file.Name.Name + ".gobak.h"),
		iw:   Creat("__." + file.Name.Name + ".gobak.i"),
		jw:   Creat("__." + file.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...)) }
	builtinScope := ast.NewScope(nil)
	builtinScope.Objects = types.Universe.Objects
	z.Builtin = NewBScope(nil, builtinScope)
	z.Top = NewBScope(z.Builtin, z.file.Scope)
	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 {
	return b.MangleObj(i.Obj)
}
func (b *Bak) MangleObj(obj *ast.Object) string {
	for k, v := range types.Universe.Objects {
		if v == obj {
			return k + "_Go"
		}
	}
	for _, v := range b.file.Scope.Objects {
		if v == obj {
			return Fmt("%s__%s", b.file.Name.Name, obj.Name)
		}
	}
	return Fmt("%s_", obj.Name) // Just add _ to avoid C reserved words.
}
func (b *Bak) Init() {
	guard := Skidify(b.file.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.file.Name.Name+".gobak.h")
	b.c(`#include "%s"`, "__."+b.file.Name.Name+".gobak.i")
	b.j(`void main__init() {`)

	b.Top = NewBScope(nil, b.file.Scope)
	for k, v := range b.file.Scope.Objects {
		b.Top.Cmap[k] = b.MangleObj(v)
	}
	println("TOP SCOPE: " + b.Top.String())
}
func (b *Bak) Finish() {
	guard := Skidify(b.file.Name.Name + ".gobak.h")
	b.c(`#include "%s"`, "__."+b.file.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.file.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)

	if p.Type == nil {
		// TODO const
		panic(0)
	}

	// do var
	for i, name := range p.Names {
		// TODO: this BScope thing needs to handle consts, types, imports?...
		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))
	}
}
