package circuitcompiler

import (
	"errors"
	"fmt"
	"math/big"
	"regexp"
	"strconv"
	"strings"
)

var variableIndicationSign = "@"

// Circuit is the data structure of the compiled circuit
type Circuit struct {
	Inputs []string
	Name   string
	root   *gate
	//after reducing
	//constraintMap map[string]*Constraint
	gateMap map[string]*gate
}

type gate struct {
	index      int
	left       *gate
	right      *gate
	funcInputs []*gate
	value      *Constraint //is a pointer a good thing here??
	leftIns    []factor    //leftIns and RightIns after addition gates have been reduced. only multiplication gates remain
	rightIns   []factor
}

func (g gate) String() string {
	return fmt.Sprintf("Gate %v : %v  with left %v right %v", g.index, g.value, g.leftIns, g.rightIns)
}

//type variable struct {
//	val string
//}

// Constraint is the data structure of a flat code operation
type Constraint struct {
	// v1 op v2 = out
	Op  Token
	V1  string
	V2  string
	Out string
	//fV1  *variable
	//fV2  *variable
	//fOut *variable
	//Literal string
	//TODO once i've implemented a new parser/lexer we do this differently
	Inputs []string // in func declaration case
	//fInputs []*variable
	negate bool
	invert bool
}

func (c Constraint) String() string {
	if c.negate || c.invert {
		return fmt.Sprintf("|%v = %v %v %v|  negated: %v, inverted %v", c.Out, c.V1, c.Op, c.V2, c.negate, c.invert)
	}
	return fmt.Sprintf("|%v = %v %v %v|", c.Out, c.V1, c.Op, c.V2)
}

func newCircuit(name string) *Circuit {
	return &Circuit{Name: name, gateMap: make(map[string]*gate)}
}

func (p *Program) addFunction(constraint *Constraint) (c *Circuit) {
	name := constraint.Out

	b, name2, _ := isFunction(name)
	if !b {
		panic(fmt.Sprintf("not a function: %v", constraint))
	}
	name = name2

	if _, ex := p.functions[name]; ex {
		panic("function already declared")
	}

	c = newCircuit(name)

	p.functions[name] = c

	renamedInputs := make([]string, len(constraint.Inputs))
	//I need the inputs to be defined as input constraints for each function for later renaming conventions
	//if constraint.Literal == "main" {
	for i, in := range constraint.Inputs {
		newConstr := &Constraint{
			Op:  IN,
			Out: in,
		}
		//if name == "main" {
		//	p.addGlobalInput(*newConstr)
		//}
		c.addConstraint(newConstr)
		renamedInputs[i] = newConstr.Out
	}
	//}

	c.Inputs = renamedInputs
	return

}

func (circ *Circuit) addConstraint(constraint *Constraint) {
	if _, ex := circ.gateMap[constraint.Out]; ex {
		panic("already used FlatConstraint")
	}
	gateToAdd := &gate{value: constraint}

	if constraint.Op == DIVIDE {
		constraint.Op = MULTIPLY
		constraint.invert = true
	}
	if constraint.Op == MINUS {
		constraint.Op = PLUS
		constraint.negate = true
	}

	//todo this is dangerous.. if someone would use out as variable name, things would be fucked
	if constraint.Out == "out" {
		constraint.Out = circ.Name //composeNewFunction(circ.Name, circ.Inputs)
		circ.root = gateToAdd
	} else {
		constraint.Out = circ.renamer(constraint.Out)
	}

	constraint.V1 = circ.renamer(constraint.V1)
	constraint.V2 = circ.renamer(constraint.V2)

	circ.gateMap[constraint.Out] = gateToAdd
}

func (circ *Circuit) currentOutputName() string {

	return composeNewFunction(circ.Name, circ.currentOutputs())
}

func (circ *Circuit) currentOutputs() []string {

	renamedInputs := make([]string, len(circ.Inputs))
	for i, in := range circ.Inputs {
		if _, ex := circ.gateMap[in]; !ex {
			panic("not existing input")
		}
		renamedInputs[i] = circ.gateMap[in].value.Out
	}

	return renamedInputs

}

func (circ *Circuit) renamer(constraint string) string {

	if constraint == "" {
		return ""
	}

	if b, _ := isValue(constraint); b {
		circ.gateMap[constraint] = &gate{value: &Constraint{Op: CONST, Out: constraint}}
		return constraint
	}

	if b, name, inputs := isFunction(constraint); b {
		renamedInputs := make([]string, len(inputs))
		for i, in := range inputs {
			renamedInputs[i] = circ.renamer(in)
		}
		nn := composeNewFunction(name, renamedInputs)
		circ.gateMap[nn] = &gate{value: &Constraint{Op: FUNC, Out: nn, Inputs: renamedInputs}}
		return nn
	}

	return circ.Name + variableIndicationSign + constraint

}

//func (circ *Circuit) renameInputs(inputs []string) {
//	if len(inputs) != len(circ.Inputs) {
//		panic("given inputs != circuit.Inputs")
//	}
//	mapping := make(map[string]string)
//	for i := 0; i < len(inputs); i++ {
//		if _, ex := circ.gateMap[inputs[i]]; ex {
//
//			//this is a tricky part. So we replace former inputs with the new ones, thereby
//			//it might be, that the new input name has already been used for some output inside the function
//			//currently I dont know an elegant way how to handle this renaming issue
//			if circ.gateMap[inputs[i]].value.Op != IN {
//				panic(fmt.Sprintf("renaming collsion with %s", inputs[i]))
//			}
//
//		}
//		mapping[circ.Inputs[i]] = inputs[i]
//	}
//	//fmt.Println(mapping)
//	//circ.Inputs = inputs
//	permute := func(in string) string {
//		if out, ex := mapping[in]; ex {
//			return out
//		}
//		return in
//	}
//
//	permuteListe := func(in []string) []string {
//		for i := 0; i < len(in); i++ {
//			in[i] = permute(in[i])
//		}
//		return in
//	}
//
//	for _, constraint := range circ.gateMap {
//
//		if constraint.value.Op == IN {
//			constraint.value.Out = permute(constraint.value.Out)
//			continue
//		}
//
//		if b, n, in := isFunction(constraint.value.Out); b {
//			constraint.value.Out = composeNewFunction(n, permuteListe(in))
//			constraint.value.Inputs = permuteListe(in)
//		}
//		if b, n, in := isFunction(constraint.value.V1); b {
//			constraint.value.V1 = composeNewFunction(n, permuteListe(in))
//			constraint.value.Inputs = permuteListe(in)
//		}
//		if b, n, in := isFunction(constraint.value.V2); b {
//			constraint.value.V2 = composeNewFunction(n, permuteListe(in))
//			constraint.value.Inputs = permuteListe(in)
//		}
//
//		constraint.value.V1 = permute(constraint.value.V1)
//		constraint.value.V2 = permute(constraint.value.V2)
//
//	}
//	return
//}

func getContextFromVariable(in string) string {
	//if strings.Contains(in, variableIndicationSign) {
	//	return strings.Split(in, variableIndicationSign)[0]
	//}
	//return ""
	return strings.Split(in, variableIndicationSign)[0]
}

func composeNewFunction(fname string, inputs []string) string {
	builder := strings.Builder{}
	builder.WriteString(fname)
	builder.WriteRune('(')
	for i := 0; i < len(inputs); i++ {
		builder.WriteString(inputs[i])
		if i < len(inputs)-1 {
			builder.WriteRune(',')
		}
	}
	builder.WriteRune(')')
	return builder.String()
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func TreeDepth(g *gate) int {
	return printDepth(g, 0)
}

func printDepth(g *gate, d int) int {
	d = d + 1
	if g.left != nil && g.right != nil {
		return max(printDepth(g.left, d), printDepth(g.right, d))
	} else if g.left != nil {
		return printDepth(g.left, d)
	} else if g.right != nil {
		return printDepth(g.right, d)
	}
	return d
}
func CountMultiplicationGates(g *gate) int {
	if g == nil {
		return 0
	}
	if len(g.rightIns) > 0 || len(g.leftIns) > 0 {
		return 1 + CountMultiplicationGates(g.left) + CountMultiplicationGates(g.right)
	} else {
		return CountMultiplicationGates(g.left) + CountMultiplicationGates(g.right)
	}
	return 0
}

//TODO avoid printing multiple times in case of loops
func PrintTree(g *gate) {
	printTree(g, 0)
}
func printTree(g *gate, d int) {
	d += 1

	if g.leftIns == nil || g.rightIns == nil {
		fmt.Printf("Depth: %v - %s \t \t \t \t \n", d, g.value)
	} else {
		fmt.Printf("Depth: %v - %s \t \t \t \t with  l %v  and r %v\n", d, g.value, g.leftIns, g.rightIns)
	}
	if g.funcInputs != nil {
		for _, v := range g.funcInputs {
			printTree(v, d)
		}
	}

	if g.left != nil {
		printTree(g.left, d)
	}
	if g.right != nil {
		printTree(g.right, d)
	}
}

func Xor(a, b bool) bool {
	return (a && !b) || (!a && b)
}

func (g *gate) ExtractValues(in []int) (er error) {
	if b, v1 := isValue(g.value.V1); b {
		if b2, v2 := isValue(g.value.V2); b2 {
			in = append(in, v1, v2)
			return nil
		}
	}
	return errors.New(fmt.Sprintf("Gate \"%s\" has no int values", g.value))
}

func (g *gate) OperationType() Token {
	return g.value.Op
}

//returns index of e if its in arr
//return -1 if e not in arr
func indexInArray(arr []string, e string) int {
	for i, a := range arr {
		if a == e {
			return i
		}
	}
	panic("lul")
	return -1
}
func isValue(a string) (bool, int) {
	v, err := strconv.Atoi(a)
	if err != nil {
		return false, 0
	}
	return true, v
}
func isFunction(a string) (tf bool, name string, inputs []string) {

	if !strings.ContainsRune(a, '(') && !strings.ContainsRune(a, ')') {
		return false, "", nil
	}
	name = strings.Split(a, "(")[0]

	// read string inside ( )
	rgx := regexp.MustCompile(`\((.*?)\)`)
	insideParenthesis := rgx.FindStringSubmatch(a)
	varsString := strings.Replace(insideParenthesis[1], " ", "", -1)
	inputs = strings.Split(varsString, ",")

	return true, name, inputs
}

type Inputs struct {
	Private []*big.Int
	Publics []*big.Int
}
