package main

import (
	_ "fmt"
)

func (smith *Smith) initStatements() {
	smith.statements = []func(){
		smith.stmtOas,
		smith.stmtAs,
		smith.stmtInc,
		smith.stmtIf,
		smith.stmtFor,
		smith.stmtSend,
		smith.stmtRecv,
		smith.stmtSelect,
		smith.stmtSwitchExpr,
		smith.stmtSwitchType,
		smith.stmtTypeDecl,
		smith.stmtVarDecl,
		smith.stmtCall,
		smith.stmtReturn,
		smith.stmtBreak,
		smith.stmtContinue,
		smith.stmtGoto,
		smith.stmtSink,
	}
}

func (smith *Smith) genStatement() {
	if smith.stmtCount >= NStatements {
		return
	}
	smith.exprCount = 0
	smith.stmtCount++
	smith.statements[smith.rnd(len(smith.statements))]()
}

func (smith *Smith) stmtOas() {
	list := smith.atypeList(TraitAny)
	str, vars := smith.fmtOasVarList(list)
	smith.line("%v := %v", str, smith.fmtRvalueList(list))
	for _, v := range vars {
		smith.defineVar(v.id, v.typ)
	}
}

func (smith *Smith) stmtReturn() {
	smith.line("return %v", smith.fmtRvalueList(smith.curFunc.rets))
}

func (smith *Smith) stmtAs() {
	types := smith.atypeList(TraitAny)
	smith.line("%v = %v", smith.fmtLvalueList(types), smith.fmtRvalueList(types))
}

func (smith *Smith) stmtInc() {
	smith.line("%v %v", smith.lvalueOrMapIndex(smith.atype(ClassNumeric)), smith.choice("--", "++"))
}

func (smith *Smith) stmtIf() {
	smith.enterBlock(true)
	smith.enterBlock(true)
	if smith.rndBool() {
		smith.line("if %v {", smith.rvalue(smith.atype(ClassBoolean)))
	} else {
		smith.line("if %v; %v {", smith.stmtSimple(true, nil), smith.rvalue(smith.atype(ClassBoolean)))
	}
	smith.genBlock()
	if smith.rndBool() {
		smith.line("} else {")
		smith.genBlock()
	}
	smith.leaveBlock()
	smith.line("}")
	smith.leaveBlock()
}

func (smith *Smith) stmtFor() {
	smith.enterBlock(true)
	smith.enterBlock(true)
	smith.curBlock.isBreakable = true
	smith.curBlock.isContinuable = true
	var vars []*Var
	switch smith.choice("simple", "complex", "range") {
	case "simple":
		smith.line("for %v {", smith.rvalue(smith.atype(ClassBoolean)))
	case "complex":
		smith.line("for %v; %v; %v {", smith.stmtSimple(true, nil), smith.rvalue(smith.atype(ClassBoolean)), smith.stmtSimple(false, nil))
	case "range":
		switch smith.choice("slice", "string", "channel", "map") {
		case "slice":
			t := smith.atype(TraitAny)
			s := smith.rvalue(smith.sliceOf(t))
			switch smith.choice("one", "two", "oneDecl", "twoDecl") {
			case "one":
				smith.line("for %v = range %v {", smith.lvalueOrBlank(smith.intType), s)
			case "two":
				smith.line("for %v, %v = range %v {", smith.lvalueOrBlank(smith.intType), smith.lvalueOrBlank(t), s)
			case "oneDecl":
				id := smith.newId("Var")
				smith.line("for %v := range %v {", id, s)
				vars = append(vars, &Var{id: id, typ: smith.intType})
			case "twoDecl":
				types := []*Type{smith.intType, t}
				str := ""
				str, vars = smith.fmtOasVarList(types)
				smith.line("for %v := range %v {", str, s)
			default:
				panic("bad")
			}
		case "string":
			s := smith.rvalue(smith.stringType)
			switch smith.choice("one", "two", "oneDecl", "twoDecl") {
			case "one":
				smith.line("for %v = range %v {", smith.lvalueOrBlank(smith.intType), s)
			case "two":
				smith.line("for %v, %v = range %v {", smith.lvalueOrBlank(smith.intType), smith.lvalueOrBlank(smith.runeType), s)
			case "oneDecl":
				id := smith.newId("Var")
				smith.line("for %v := range %v {", id, s)
				vars = append(vars, &Var{id: id, typ: smith.intType})
			case "twoDecl":
				types := []*Type{smith.intType, smith.runeType}
				str := ""
				str, vars = smith.fmtOasVarList(types)
				smith.line("for %v := range %v {", str, s)
			default:
				panic("bad")
			}
		case "channel":
			cht := smith.atype(ClassChan)
			ch := smith.rvalue(cht)
			switch smith.choice("one", "oneDecl") {
			case "one":
				smith.line("for %v = range %v {", smith.lvalueOrBlank(cht.ktyp), ch)
			case "oneDecl":
				id := smith.newId("Var")
				smith.line("for %v := range %v {", id, ch)
				vars = append(vars, &Var{id: id, typ: cht.ktyp})
			default:
				panic("bad")
			}
		case "map":
			t := smith.atype(ClassMap)
			m := smith.rvalue(t)
			switch smith.choice("one", "two", "oneDecl", "twoDecl") {
			case "one":
				smith.line("for %v = range %v {", smith.lvalueOrBlank(t.ktyp), m)
			case "two":
				smith.line("for %v, %v = range %v {", smith.lvalueOrBlank(t.ktyp), smith.lvalueOrBlank(t.vtyp), m)
			case "oneDecl":
				id := smith.newId("Var")
				smith.line("for %v := range %v {", id, m)
				vars = append(vars, &Var{id: id, typ: t.ktyp})
			case "twoDecl":
				types := []*Type{t.ktyp, t.vtyp}
				str := ""
				str, vars = smith.fmtOasVarList(types)
				smith.line("for %v := range %v {", str, m)
			default:
				panic("bad")
			}
		default:
			panic("bad")
		}
	default:
		panic("bad")
	}
	smith.enterBlock(true)
	if len(vars) > 0 {
		smith.line("")
		for _, v := range vars {
			smith.defineVar(v.id, v.typ)
		}
	}
	smith.genBlock()
	smith.leaveBlock()
	smith.leaveBlock()
	smith.line("}")
	smith.leaveBlock()
}

func (smith *Smith) stmtSimple(oas bool, newVars *[]*Var) string {
	// We emit a fake statement in "oas", so make sure that nothing can be inserted in between.
	if smith.curBlock.extendable {
		panic("bad")
	}
	// "send" crashes gccgo with random errors too frequently.
	// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61273
	switch smith.choice("empty", "inc", "assign", "oas", "send", "expr") {
	case "empty":
		return ""
	case "inc":
		return F("%v %v", smith.lvalueOrMapIndex(smith.atype(ClassNumeric)), smith.choice("--", "++"))
	case "assign":
		list := smith.atypeList(TraitAny)
		return F("%v = %v", smith.fmtLvalueList(list), smith.fmtRvalueList(list))
	case "oas":
		if !oas {
			return ""
		}
		list := smith.atypeList(TraitAny)
		str, vars := smith.fmtOasVarList(list)
		if newVars != nil {
			*newVars = vars
		}
		res := F("%v := %v", str, smith.fmtRvalueList(list))
		smith.line("")
		for _, v := range vars {
			smith.defineVar(v.id, v.typ)
		}
		return res
	case "send":
		t := smith.atype(TraitSendable)
		return F("%v <- %v", smith.rvalue(t), smith.rvalue(t.ktyp))
	case "expr":
		return ""
	default:
		panic("bad")
	}
}

func (smith *Smith) stmtSend() {
	t := smith.atype(TraitSendable)
	smith.line("%v <- %v", smith.rvalue(t), smith.rvalue(t.ktyp))
}

func (smith *Smith) stmtRecv() {
	t := smith.atype(TraitReceivable)
	ch := smith.rvalue(t)
	switch smith.choice("normal", "decl") {
	case "normal":
		smith.line("%v, %v = <-%v", smith.lvalueOrBlank(t.ktyp), smith.lvalueOrBlank(smith.boolType), ch)
	case "decl":
		vv := smith.newId("Var")
		ok := smith.newId("Var")
		smith.line("%v, %v := <-%v", vv, ok, ch)
		smith.defineVar(vv, t.ktyp)
		smith.defineVar(ok, smith.boolType)
	default:
		panic("bad")
	}
}

func (smith *Smith) stmtTypeDecl() {
	id := smith.newId("Type")
	t := smith.atype(TraitAny)
	smith.line("type %v %v", id, t.id)

	newTyp := new(Type)
	*newTyp = *t
	newTyp.id = id
	newTyp.namedUserType = true
	if t.class == ClassStruct {
		newTyp.literal = func() string {
			// replace struct name with new type id
			l := t.literal()
			l = l[len(t.id)+1:]
			return "(" + id + l
		}
		newTyp.complexLiteral = func() string {
			// replace struct name with new type id
			l := t.complexLiteral()
			l = l[len(t.id)+1:]
			return "(" + id + l
		}
	} else {
		newTyp.literal = func() string {
			return F("%v(%v)", id, t.literal())
		}
		if t.complexLiteral != nil {
			newTyp.complexLiteral = func() string {
				return F("%v(%v)", id, t.complexLiteral())
			}
		}
	}
	smith.defineType(newTyp)
}

func (smith *Smith) stmtVarDecl() {
	id := smith.newId("Var")
	t := smith.atype(TraitAny)
	smith.line("var %v %v = %v", id, t.id, smith.rvalue(t))
	smith.defineVar(id, t)
}

func (smith *Smith) stmtSelect() {
	smith.enterBlock(true)
	smith.line("select {")
	for smith.rnd(5) != 0 {
		smith.enterBlock(true)
		elem := smith.atype(TraitAny)
		cht := smith.chanOf(elem)
		ch := smith.rvalue(cht)
		if smith.rndBool() {
			smith.line("case %v <- %v:", ch, smith.rvalue(elem))
		} else {
			switch smith.choice("one", "two", "oneDecl", "twoDecl") {
			case "one":
				smith.line("case %v = <-%v:", smith.lvalueOrBlank(elem), ch)
			case "two":
				smith.line("case %v, %v = <-%v:", smith.lvalueOrBlank(elem), smith.lvalueOrBlank(smith.boolType), ch)
			case "oneDecl":
				vv := smith.newId("Var")
				smith.line("case %v := <-%v:", vv, ch)
				smith.defineVar(vv, elem)
			case "twoDecl":
				vv := smith.newId("Var")
				ok := smith.newId("Var")
				smith.line("case %v, %v := <-%v:", vv, ok, ch)
				smith.defineVar(vv, elem)
				smith.defineVar(ok, smith.boolType)
			default:
				panic("bad")
			}
		}
		smith.genBlock()
		smith.leaveBlock()
	}
	if smith.rndBool() {
		smith.enterBlock(true)
		smith.line("default:")
		smith.genBlock()
		smith.leaveBlock()
	}
	smith.line("}")
	smith.leaveBlock()
}

func (smith *Smith) stmtSwitchExpr() {
	var t *Type
	cond := ""
	if smith.rndBool() {
		t = smith.atype(TraitComparable)
		cond = smith.rvalue(t)
	} else {
		t = smith.boolType
	}
	smith.enterBlock(true)
	smith.enterBlock(true)
	smith.curBlock.isBreakable = true
	var vars []*Var
	if smith.rndBool() {
		smith.line("switch %v {", cond)
	} else {
		smith.line("switch %v; %v {", smith.stmtSimple(true, &vars), cond)
	}
	// TODO: we generate at most one case, because if we generate more,
	// we can generate two cases with equal constants.
	fallth := false
	if smith.rndBool() {
		smith.enterBlock(true)
		smith.line("case %v:", smith.rvalue(t))
		smith.genBlock()
		smith.leaveBlock()
		if smith.rndBool() {
			fallth = true
			smith.line("fallthrough")
		}
	}
	if fallth || len(vars) > 0 || smith.rndBool() {
		smith.enterBlock(true)
		smith.line("default:")
		smith.genBlock()
		for _, v := range vars {
			smith.line("_ = %v", v.id)
			v.used = true
		}
		smith.leaveBlock()
	}
	smith.leaveBlock()
	smith.line("}")
	smith.leaveBlock()
}

func (smith *Smith) stmtSwitchType() {
	cond := smith.lvalue(smith.atype(TraitAny))
	smith.enterBlock(true)
	smith.curBlock.isBreakable = true
	smith.line("switch COND := (interface{})(%v); COND.(type) {", cond)
	if smith.rndBool() {
		smith.enterBlock(true)
		smith.line("case %v:", smith.atype(TraitAny).id)
		smith.genBlock()
		smith.leaveBlock()
	}
	if smith.rndBool() {
		smith.enterBlock(true)
		smith.line("default:")
		smith.genBlock()
		smith.leaveBlock()
	}
	smith.line("}")
	smith.leaveBlock()
}

func (smith *Smith) stmtCall() {
	if smith.rndBool() {
		smith.stmtCallBuiltin()
	}
	t := smith.atype(ClassFunction)
	prefix := smith.choice("", "go", "defer")
	smith.line("%v %v(%v)", prefix, smith.rvalue(t), smith.fmtRvalueList(t.styp))
}

func (smith *Smith) stmtCallBuiltin() {
	prefix := smith.choice("", "go", "defer")
	switch fn := smith.choice("close", "copy", "delete", "panic", "print", "println", "recover"); fn {
	case "close":
		smith.line("%v %v(%v)", prefix, fn, smith.rvalue(smith.atype(ClassChan)))
	case "copy":
		smith.line("%v %v", prefix, smith.exprCopySlice())
	case "delete":
		t := smith.atype(ClassMap)
		smith.line("%v %v(%v, %v)", prefix, fn, smith.rvalue(t), smith.rvalue(t.ktyp))
	case "panic":
		smith.line("%v %v(%v)", prefix, fn, smith.rvalue(smith.atype(TraitAny)))
	case "print":
		fallthrough
	case "println":
		list := smith.atypeList(TraitPrintable)
		smith.line("%v %v(%v)", prefix, fn, smith.fmtRvalueList(list))
	case "recover":
		smith.line("%v %v()", prefix, fn)
	default:
		panic("bad")
	}
}

func (smith *Smith) stmtBreak() {
	if !smith.curBlock.isBreakable {
		return
	}
	smith.line("break")
}

func (smith *Smith) stmtContinue() {
	if !smith.curBlock.isContinuable {
		return
	}
	smith.line("continue")
}

func (smith *Smith) stmtGoto() {
	// TODO: suppport goto down
	id := smith.materializeGotoLabel()
	smith.line("goto %v", id)
}

func (smith *Smith) stmtSink() {
	// Makes var escape.
	smith.line("SINK = %v", smith.exprVar(smith.atype(TraitAny)))
}
