package day24

import (
	"adventofcode2024/utils"
	"fmt"
	"regexp"
	"sort"
	"strings"
)

type Network struct {
	gates []*Gate
	wires []*Wire
}

type State int

const (
	NA State = iota - 1
	ZERO
	ONE
)

type Wire struct {
	name  string
	state State
}

type Operation int

const (
	AND Operation = iota
	OR
	XOR
)

type Gate struct {
	in  [2]*Wire
	out *Wire
	op  Operation
}

func Part1(input string) int {

	network := &Network{}
	network.wires = make([]*Wire, 0)

	data := strings.Split(input, "\n\n")

	network.AddWires(data[0])
	network.AddGates(data[1])
	for {
		if network.OutputKnown() {
			break
		}
		for _, gate := range network.gates {
			//			fmt.Printf("%d: %v\n", i, gate)
			if gate.out.state == NA && gate.in[0].state != NA && gate.in[1].state != NA {
				switch gate.op {
				case AND:
					gate.out.state = And(gate.in[0].state, gate.in[1].state)
				case OR:
					gate.out.state = Or(gate.in[0].state, gate.in[1].state)
				case XOR:
					gate.out.state = Xor(gate.in[0].state, gate.in[1].state)
				}
			}
		}
	}
	return int(network.Output())
}

func Part2(input string) int64 {

	data := strings.Split(input, "\n\n")

	inputs := strings.Split(data[0], "\n")
	num_inputs := len(inputs) / 2
	fmt.Println(num_inputs)
	var x_value int64
	var y_value int64
	var z_value int64
	y_value = 0
	for i := 0; i < num_inputs; i++ {
	//	y_value = 1 << i
		x_value = 1 << i
		network := &Network{}
		network.wires = make([]*Wire, 0)
		network.AddWires2("x", num_inputs, x_value)
		network.AddWires2("y", num_inputs, y_value)
		network.AddGates(data[1])
		//		for i, gate := range network.gates {
		//			fmt.Printf("%d: %v\n", i, gate)
		//		}
		network.SwapOutputs("z07", "rts")
		network.SwapOutputs("z12", "jpj")
		network.SwapOutputs("z26", "kgj")
		network.SwapOutputs("vvw", "chv")

		//		for i, gate := range network.gates {
		//			fmt.Printf("%d: %v\n", i, gate)
		//		}
		for {
			if network.OutputKnown() {
				break
			}
			for _, gate := range network.gates {
				//			fmt.Printf("%d: %v\n", i, gate)
				if gate.out.state == NA && gate.in[0].state != NA && gate.in[1].state != NA {
					switch gate.op {
					case AND:
						gate.out.state = And(gate.in[0].state, gate.in[1].state)
					case OR:
						gate.out.state = Or(gate.in[0].state, gate.in[1].state)
					case XOR:
						gate.out.state = Xor(gate.in[0].state, gate.in[1].state)
					}
				}
			}
		}
		z_value = network.Output()
		if x_value +  y_value  != z_value {
			fmt.Printf("(%d):\t x:%4d\t: y:%4d z:%4d\n", i, x_value, y_value, z_value)
		}
	}
	ans := []string{"z07", "rts", "z12", "jpj", "z26", "kgj", "vvw", "chv"}
	sort.Strings(ans)
	fmt.Println(strings.Join(ans, ","))
	return z_value
}

func (n *Network) AddWires(data string) {
	for _, line := range strings.Split(data, "\n") {
		v := strings.Split(line, ": ")
		n.AddWire(&Wire{name: v[0], state: to_state(v[1])})
	}
}

func (n *Network) AddWire(wire *Wire) {
	n.wires = append(n.wires, wire)
}

func (n *Network) AddWires2(w string, count int, value int64) {
	for x := 0; x < count; x++ {
		name := fmt.Sprintf("%s%02d", w, x)
		v := fmt.Sprintf("%d", (value>>x)&1)
		n.AddWire(&Wire{name: name, state: to_state(v)})
	}

}

func (n *Network) AddGates(data string) {
	// ntg XOR fgs -> mjb
	pattern := `^([a-z0-9]{3}) (AND|OR|XOR) ([a-z0-9]{3}) -> ([a-z[0-9]{3})$`

	re := regexp.MustCompile(pattern)
	for _, line := range strings.Split(data, "\n") {
		matches := re.FindAllStringSubmatch(line, -1)
		for _, match := range matches {
			n.AddGate(match[1], match[2], match[3], match[4], &n.wires)
		}
	}
}

func (n *Network) SwapOutputs(a, b string) {
	_, wire1 := to_wire(a, n.wires)
	_, wire2 := to_wire(b, n.wires)
	for _, gate := range n.gates {
		if gate.out.name == a {
			gate.out = wire2
		} else if gate.out.name == b {
			gate.out = wire1
		}
	}
}

func (n *Network) AddGate(in0, op, in1, out string, wires *[]*Wire) {
	var in [2]*Wire
	var outw *Wire
	*wires, in[0] = to_wire(in0, *wires)
	*wires, in[1] = to_wire(in1, *wires)
	*wires, outw = to_wire(out, *wires)
	n.gates = append(n.gates, &Gate{in: in,
		op:  to_operation(op),
		out: outw})
}

func (n *Network) OutputKnown() bool {
	for _, gate := range n.gates {
		if gate.out.name[0] != 'z' {
			continue
		}
		if gate.out.state == NA {
			return false
		}
	}
	return true
}

func (n *Network) Output() int64 {
	var ret int64
	ret = 0
	for _, gate := range n.gates {
		if gate.out.name[0] != 'z' {
			continue
		}
		addr := utils.MustAtoi(gate.out.name[1:])
		if gate.out.state == ONE {
			ret += 1 << addr
		}
	}
	return ret
}

func (n Gate) String() string {
	return n.in[0].name + "(" + n.in[0].state.String() + ")\t" + n.op.String() + " " + n.in[1].name + "(" + n.in[1].state.String() + ")\t ->\t" + n.out.name + " (" + n.out.state.String() + ")"
}
func to_state(in string) State {
	switch in {
	case "1":
		return ONE
	case "0":
		return ZERO
	}
	return NA
}

func to_operation(in string) Operation {
	var val Operation
	switch in {
	case "AND":
		val = AND
	case "OR":
		val = OR
	case "XOR":
		val = XOR
	}
	return val
}

func to_wire(in string, wires []*Wire) ([]*Wire, *Wire) {
	for _, v := range wires {
		if in == v.name {
			return wires, v
		}
	}
	wire := Wire{name: in, state: NA}
	wires = append(wires, &wire)
	return wires, &wire
}

func And(a, b State) State {
	if a == ONE && b == ONE {
		return ONE
	}
	return ZERO
}

func Or(a, b State) State {
	if a == ONE || b == ONE {
		return ONE
	}
	return ZERO
}

func Xor(a, b State) State {
	if (a == ONE && b == ZERO) || (a == ZERO && b == ONE) {
		return ONE
	}
	return ZERO
}

func (state State) String() string {
	switch state {
	case NA:
		return "NA"
	case ZERO:
		return "0"
	case ONE:
		return "1"
	default:
		return "Invalid"
	}
}

func (op Operation) String() string {
	switch op {
	case OR:
		return "OR"
	case AND:
		return "AND"
	case XOR:
		return "XOR"
	default:
		return "Invalid"
	}
}
