package gno

import (
	"fmt"
)

//----------------------------------------
// Word.TokenString()

var wordTokenStrings = map[Word]string{
	// Operations
	ADD:             "+",
	SUB:             "-",
	MUL:             "*",
	QUO:             "/",
	REM:             "%",
	BAND:            "&",
	BOR:             "|",
	XOR:             "^",
	SHL:             "<<",
	SHR:             ">>",
	BAND_NOT:        "&^",
	ADD_ASSIGN:      "+=",
	SUB_ASSIGN:      "-=",
	MUL_ASSIGN:      "*=",
	QUO_ASSIGN:      "/=",
	REM_ASSIGN:      "%=",
	BAND_ASSIGN:     "&=",
	BOR_ASSIGN:      "|=",
	XOR_ASSIGN:      "^=",
	SHL_ASSIGN:      "<<=",
	SHR_ASSIGN:      ">>=",
	BAND_NOT_ASSIGN: "&^=",
	LAND:            "&&",
	LOR:             "||",
	ARROW:           "<-",
	INC:             "++",
	DEC:             "--",
	EQL:             "==",
	LSS:             "<",
	GTR:             ">",
	ASSIGN:          "=",
	NOT:             "!",
	NEQ:             "!=",
	LEQ:             "<=",
	GEQ:             ">=",
	DEFINE:          ":=",

	// Branch operations
	BREAK:       "break",
	CONTINUE:    "continue",
	FALLTHROUGH: "fallthrough",
	GOTO:        "goto",
}

func (w Word) TokenString() string {
	s, ok := wordTokenStrings[w]
	if !ok {
		panic(fmt.Sprintf("no token repr for %s (%d)", w.String(), w))
	}
	return s
}

//----------------------------------------
// Node.String()

func (p ValuePath) String() string {
	switch p.Type {
	case VPUverse:
		return fmt.Sprintf("VPUverse(%d)", p.Index)
	case VPBlock:
		return fmt.Sprintf("VPBlock(%d,%d)", p.Depth, p.Index)
	case VPField:
		return fmt.Sprintf("VPField(%d,%d,%s)", p.Depth, p.Index, p.Name)
	case VPSubrefField:
		return fmt.Sprintf("VPSubrefField(%d,%d,%s)", p.Depth, p.Index, p.Name)
	case VPValMethod:
		return fmt.Sprintf("VPValMethod(%d,%s)", p.Index, p.Name)
	case VPPtrMethod:
		return fmt.Sprintf("VPPtrMethod(%d,%s)", p.Index, p.Name)
	case VPInterface:
		return fmt.Sprintf("VPInterface(%s)", p.Name)
	case VPDerefField:
		return fmt.Sprintf("VPDerefField(%d,%d,%s)", p.Depth, p.Index, p.Name)
	case VPDerefValMethod:
		return fmt.Sprintf("VPDerefValMethod(%d,%s)", p.Index, p.Name)
	case VPDerefPtrMethod:
		return fmt.Sprintf("VPDerefPtrMethod(%d,%s)", p.Index, p.Name)
	case VPDerefInterface:
		return fmt.Sprintf("VPDerefInterface(%s)", p.Name)
	case VPNative:
		return fmt.Sprintf("VPNative(%s)", p.Name)
	default:
		panic("illegal_value_type")
	}
}

func (n NameExpr) String() string {
	return fmt.Sprintf("%s<%s>", n.Name, n.Path.String())
}

func (n BasicLitExpr) String() string {
	return n.Value
}

func (n BinaryExpr) String() string {
	return fmt.Sprintf("%s %s %s",
		n.Left.String(),
		n.Op.TokenString(),
		n.Right.String(),
	)
}

func (n CallExpr) String() string {
	if n.Varg {
		return fmt.Sprintf("%s(%s...)", n.Func, n.Args.String())
	} else {
		return fmt.Sprintf("%s(%s)", n.Func, n.Args.String())
	}
}

func (n IndexExpr) String() string {
	return fmt.Sprintf("%s[%s]", n.X, n.Index)
}

func (n SelectorExpr) String() string {
	return fmt.Sprintf("%s.%s", n.X, n.Sel)
}

func (n SliceExpr) String() string {
	ls, hs, ms := "", "", ""
	if n.Low != nil {
		ls = n.Low.String()
	}
	if n.High != nil {
		hs = n.High.String()
	}
	if n.Max != nil {
		ms = n.Max.String()
	}
	if ms == "" {
		return fmt.Sprintf("%s[%s:%s]", n.X, ls, hs)
	} else {
		return fmt.Sprintf("%s[%s:%s:%s]", n.X, ls, hs, ms)
	}
}

func (n StarExpr) String() string {
	return fmt.Sprintf("*(%s)", n.X)
}

func (n RefExpr) String() string {
	return fmt.Sprintf("&(%s)", n.X)
}

func (n TypeAssertExpr) String() string {
	if n.Type == nil {
		return fmt.Sprintf("%s.(type)", n.X)
	} else {
		return fmt.Sprintf("%s.(%s)", n.X, n.Type)
	}
}

func (n UnaryExpr) String() string {
	return fmt.Sprintf("%s%s", n.Op.TokenString(), n.X)
}

func (n CompositeLitExpr) String() string {
	if n.Type == nil {
		return fmt.Sprintf("<elided>{%s}", n.Elts.String())
	} else {
		return fmt.Sprintf("%s{%s}", n.Type.String(), n.Elts.String())
	}
}

func (n FuncLitExpr) String() string {
	return fmt.Sprintf("func %s{ %s }", n.Type, n.Body.String())
}

func (n KeyValueExpr) String() string {
	if n.Key == nil {
		return fmt.Sprintf("%s", n.Value)
	} else {
		return fmt.Sprintf("%s: %s", n.Key, n.Value)
	}
}

func (n FieldTypeExpr) String() string {
	if n.Tag == nil {
		return fmt.Sprintf("%s %s", n.Name, n.Type)
	} else {
		return fmt.Sprintf("%s %s %s", n.Name, n.Type, n.Tag)
	}
}

func (n ArrayTypeExpr) String() string {
	if n.Len == nil {
		return fmt.Sprintf("[...]%s", n.Elt)
	} else {
		return fmt.Sprintf("[%s]%s", n.Len, n.Elt)
	}
}

func (n SliceTypeExpr) String() string {
	if n.Vrd {
		return fmt.Sprintf("...%s", n.Elt)
	} else {
		return fmt.Sprintf("[]%s", n.Elt)
	}
}

func (n InterfaceTypeExpr) String() string {
	return fmt.Sprintf("interface { %v }", n.Methods)
}

func (n ChanTypeExpr) String() string {
	switch n.Dir {
	case SEND:
		return fmt.Sprintf("<-chan %s", n.Value)
	case RECV:
		return fmt.Sprintf("chan<- %s", n.Value)
	case SEND | RECV:
		return fmt.Sprintf("chan %s", n.Value)
	default:
		panic("unexpected chan dir")
	}
}

func (n FuncTypeExpr) String() string {
	params := ""
	if 0 < len(n.Params) {
		params = n.Params.String()
	}
	results := ""
	if 0 < len(n.Results) {
		results = " " + n.Results.String()
	}
	return fmt.Sprintf("func(%s)%s", params, results)
}

func (n MapTypeExpr) String() string {
	return fmt.Sprintf("map[%s] %s", n.Key, n.Value)
}

func (n StructTypeExpr) String() string {
	return fmt.Sprintf("struct { %v }", n.Fields)
}

func (n MaybeNativeTypeExpr) String() string {
	return fmt.Sprintf("maybenative(%s)", n.Type.String())
}

func (n AssignStmt) String() string {
	return fmt.Sprintf("%v %s %v", n.Lhs, n.Op.TokenString(), n.Rhs)
}

func (n BlockStmt) String() string {
	return fmt.Sprintf("{ %s }", n.Body.String())
}

func (n BranchStmt) String() string {
	if n.Label == "" {
		return n.Op.TokenString()
	} else {
		return fmt.Sprintf("%s %s<%d,%d>",
			n.Op.TokenString(), string(n.Label),
			n.Depth, n.BodyIndex)
	}
}

func (n DeclStmt) String() string {
	return n.Body.String()
}

func (n DeferStmt) String() string {
	return "defer " + n.Call.String()
}

func (n EmptyStmt) String() string {
	return ""
}

func (n ExprStmt) String() string {
	return n.X.String()
}

func (n ForStmt) String() string {
	init, cond, post := "", "", ""
	if n.Init != nil {
		init = n.Init.String()
	}
	if n.Cond != nil {
		cond = n.Cond.String()
	}
	if n.Post != nil {
		post = n.Post.String()
	}
	return fmt.Sprintf("for %s; %s; %s { %s }",
		init, cond, post, n.Body.String())
}

func (n GoStmt) String() string {
	return "go " + n.Call.String()
}

func (n IfStmt) String() string {
	init := ""
	if n.Init != nil {
		init = n.Init.String() + "; "
	}
	cond := n.Cond.String()
	then := n.Then.String()
	els_ := n.Else.String()
	if n.Else.Body == nil {
		return fmt.Sprintf("if %s%s { %s }", init, cond, then)
	} else {
		return fmt.Sprintf("if %s%s { %s } else { %s }",
			init, cond, then, els_)
	}
}

func (n IfCaseStmt) String() string {
	return n.Body.String()
}

func (n IncDecStmt) String() string {
	if n.Op == INC {
		return n.X.String() + "++"
	} else if n.Op == DEC {
		return n.X.String() + "--"
	} else {
		panic("unexpected operator")
	}
}

func (n RangeStmt) String() string {
	if n.Key == nil {
		if n.Value != nil {
			panic("unexpected value in range stmt with no key")
		}
		return fmt.Sprintf("for range %s { %s }",
			n.X.String(), n.Body.String())
	} else if n.Value == nil {
		return fmt.Sprintf("for %s %s range %s { %s }",
			n.Key.String(), n.Op.TokenString(),
			n.X.String(), n.Body.String())
	} else {
		return fmt.Sprintf("for %s, %s %s range %s { %s }",
			n.Key.String(), n.Value.String(), n.Op.TokenString(),
			n.X.String(), n.Body.String())
	}
}

func (n ReturnStmt) String() string {
	if len(n.Results) == 0 {
		return fmt.Sprintf("return")
	} else {
		return fmt.Sprintf("return %v", n.Results)
	}
}

func (n PanicStmt) String() string {
	return fmt.Sprintf("panic(%s)", n.Exception.String())
}

func (n SelectStmt) String() string {
	cases := ""
	for i, s := range n.Cases {
		if i == 0 {
			cases += s.String()
		} else {
			cases += "; " + s.String()
		}
	}
	return fmt.Sprintf("select { %s }", cases)
}

func (n SelectCaseStmt) String() string {
	return fmt.Sprintf("case %v: %s", n.Comm.String(), n.Body.String())
}

func (n SendStmt) String() string {
	return fmt.Sprintf("%s <- %s", n.Chan.String(), n.Value.String())
}

func (n SwitchStmt) String() string {
	init := ""
	if n.Init != nil {
		init = n.Init.String() + "; "
	}
	varName := ""
	if n.VarName != "" {
		varName = string(n.VarName) + ":="
	}
	cases := ""
	for i, s := range n.Clauses {
		if i == 0 {
			cases += s.String()
		} else {
			cases += "; " + s.String()
		}
	}
	return fmt.Sprintf("switch %s%s%s { %s }",
		init, varName, n.X.String(), cases)
}

func (n SwitchClauseStmt) String() string {
	if len(n.Cases) == 0 {
		return fmt.Sprintf("default: %s", n.Body.String())
	} else {
		return fmt.Sprintf("case %v: %s", n.Cases, n.Body.String())
	}
}

func (n FuncDecl) String() string {
	recv := ""
	if n.IsMethod {
		recv = "(" + n.Recv.String() + ") "
	}
	return fmt.Sprintf("func %s%s%s { %s }",
		recv, n.Name, n.Type.String()[4:], n.Body.String())
}

func (n ImportDecl) String() string {
	if n.Name == "" {
		return fmt.Sprintf("import %s", n.PkgPath)
	} else {
		return fmt.Sprintf("import %s %s", n.Name, n.PkgPath)
	}
}

func (n ValueDecl) String() string {
	mod := "var"
	if n.Const {
		mod = "const"
	}
	names := n.NameExprs.String()
	type_ := ""
	if n.Type != nil {
		type_ = " " + n.Type.String()
	}
	value := ""
	if n.Values != nil {
		value = " = " + n.Values.String()
	}
	return fmt.Sprintf("%s %s%s%s", mod, names, type_, value)
}

func (n TypeDecl) String() string {
	if n.IsAlias {
		return fmt.Sprintf("type %s = %s", n.Name, n.Type.String())
	} else {
		return fmt.Sprintf("type %s %s", n.Name, n.Type.String())
	}
}

func (n FileNode) String() string {
	// return fmt.Sprintf("file{ package %s ... }", n.PkgName) // , n.Decls.String())
	return fmt.Sprintf("file{ package %s; %s }", n.PkgName, n.Decls.String())
}

func (n PackageNode) String() string {
	return fmt.Sprintf("package(%s)", n.PkgName)
}

func (n RefNode) String() string {
	return fmt.Sprintf("ref(%s)", n.Location.String())
}

//----------------------------------------
// Node slice strings
// NOTE: interface-generics or?

func (xs Exprs) String() string {
	str := ""
	for i, x := range xs {
		if i == 0 {
			str += x.String()
		} else {
			str += ", " + x.String()
		}
	}
	return str
}

func (nxs NameExprs) String() string {
	str := ""
	for i, nx := range nxs {
		if i == 0 {
			str += nx.String()
		} else {
			str += ", " + nx.String()
		}
	}
	return str
}

func (fts FieldTypeExprs) String() string {
	str := ""
	for i, x := range fts {
		if i == 0 {
			str += x.String()
		} else {
			str += ", " + x.String()
		}
	}
	return str
}

func (kvs KeyValueExprs) String() string {
	str := ""
	for i, x := range kvs {
		if i == 0 {
			str += x.String()
		} else {
			str += ", " + x.String()
		}
	}
	return str
}

func (ss Body) String() string {
	str := ""
	for i, s := range ss {
		if i == 0 {
			str += s.String()
		} else {
			str += "; " + s.String()
		}
	}
	return str
}

func (ds Decls) String() string {
	str := ""
	for i, s := range ds {
		if i == 0 {
			str += s.String()
		} else {
			str += "; " + s.String()
		}
	}
	return str
}

func (cx ConstExpr) String() string {
	return fmt.Sprintf("(const %s)", cx.TypedValue.String())
}

func (ctx constTypeExpr) String() string {
	if ctx.Type == nil { // type switch case
		return fmt.Sprintf("(const-type nil)")
	} else {
		return fmt.Sprintf("(const-type %s)", ctx.Type.String())
	}
}