package day23

import (
	"fmt"
	"regexp"
	"sort"
	"strings"
)

type Lan struct {
	nodes []*Node
}

type Node struct {
	key      string
	adjacent []*Node
	loops    [][]*Node
}

func Part1(input string) int {
	total := 0

	lan := &Lan{}

	pattern := `^([a-z]{2})-([a-z]{2})$`
	re := regexp.MustCompile(pattern)
	lines := strings.Split(input, "\n")
	for _, line := range lines {
		matches := re.FindAllStringSubmatch(line, -1)
		for _, match := range matches {
			lan.AddNode(match[1])
			lan.AddNode(match[2])
			lan.AddConnection(match[1], match[2])
			lan.AddConnection(match[2], match[1])
		}
	}
	for _, node := range lan.nodes {
		if node.key[0] == 't' {
			connections := node.adjacent
			for _, connection := range connections {
				c := lan.getNode(connection.key)
				for _, n1 := range c.adjacent {
					for _, n2 := range connections {
						if n1.key == n2.key {
							already_found := false
							for _, loop := range node.loops {
								if contains(loop, c.key) && contains(loop, n2.key) {
									already_found = true
									break
								}
							}

							for _, loop := range c.loops {
								if contains(loop, node.key) && contains(loop, n2.key) {
									already_found = true
									break
								}
							}

							for _, loop := range n2.loops {
								if contains(loop, c.key) && contains(loop, node.key) {
									already_found = true
									break
								}
							}

							if !already_found {
								already_found = true
								total++
								loop := make([]*Node, 0)
								loop = append(loop, node)
								loop = append(loop, c)
								loop = append(loop, n2)
								node.loops = append(node.loops, loop)
								fmt.Printf("%v,%v,%v\n", node.key, c.key, n2.key)
							}
						}
					}
				}
			}
		}
	}
	lan.Print()
	t := 0
	for _, node := range lan.nodes {
		if node.key[0] == 't' {
			t += len(node.loops)
			fmt.Printf("Node: %v\n", node.key)
			for _, loop := range node.loops {
				for _, n := range loop {
					fmt.Printf(" %v ", n.key)
				}
				fmt.Println()
			}
		}
	}
	fmt.Printf("Total: %d\n", t)
	return total
}

func Part2(input string) int {
	total := 0

	lan := &Lan{}

	pattern := `^([a-z]{2})-([a-z]{2})$`
	re := regexp.MustCompile(pattern)
	lines := strings.Split(input, "\n")
	for _, line := range lines {
		matches := re.FindAllStringSubmatch(line, -1)
		for _, match := range matches {
			lan.AddNode(match[1])
			lan.AddNode(match[2])
			lan.AddConnection(match[1], match[2])
			lan.AddConnection(match[2], match[1])
		}
	}
	for _, node := range lan.nodes {
		connections := node.adjacent
		for _, connection := range connections {
			c := lan.getNode(connection.key)
			for _, n1 := range c.adjacent {
				for _, n2 := range connections {
					if n1.key == n2.key {
						already_found := false
						for _, loop := range node.loops {
							if contains(loop, c.key) && contains(loop, n2.key) {
								already_found = true
								break
							}
						}

						for _, loop := range c.loops {
							if contains(loop, node.key) && contains(loop, n2.key) {
								already_found = true
								break
							}
						}

						for _, loop := range n2.loops {
							if contains(loop, c.key) && contains(loop, node.key) {
								already_found = true
								break
							}
						}

						if !already_found {
							already_found = true
							total++
							loop := make([]*Node, 0)
							loop = append(loop, node)
							loop = append(loop, c)
							loop = append(loop, n2)
							node.loops = append(node.loops, loop)
							fmt.Printf("%v,%v,%v\n", node.key, c.key, n2.key)
						}
					}
				}
			}
		}
	}
	lan.Print()
	maxLoop := 0
	var maxNode *Node
	for _, node := range lan.nodes {
		if len(node.loops) > maxLoop {
			maxLoop = len(node.loops)
			maxNode = node
		}
	}
	fmt.Printf("Max: %d\n", len(maxNode.loops))
	nodeList := make([]*Node, 0)
	keyList := make([]string, 0)
	for _, loop := range maxNode.loops {
		for _, l := range loop {
			if !contains(nodeList, l.key) {
				nodeList = append(nodeList, l)
				keyList = append(keyList, l.key)
			}
		}
	}
	sort.Strings(keyList)
	fmt.Println(strings.Join(keyList, ","))
	return len(nodeList)
}

func (g *Lan) AddNode(key string) {
	if !contains(g.nodes, key) {
		g.nodes = append(g.nodes, &Node{key: key})
	}
}

func (g *Lan) AddConnection(from, to string) {
	fromNode := g.getNode(from)
	toNode := g.getNode(to)

	if fromNode == nil || toNode == nil {
		err := fmt.Errorf("Error: Invalid edge (%v --> %v)", from, to)
		fmt.Println(err.Error())
	} else if contains(fromNode.adjacent, to) {
		err := fmt.Errorf("Error: Existing edge (%v --> %v)", from, to)
		fmt.Println(err.Error())
	} else {
		fromNode.adjacent = append(fromNode.adjacent, toNode)
	}
}

func (g *Lan) getNode(key string) *Node {
	for i, v := range g.nodes {
		if v.key == key {
			return g.nodes[i]
		}
	}
	return nil
}

func (g *Lan) Print() {
	for _, v := range g.nodes {
		fmt.Printf("\nNode %v : ", v.key)
		for _, v := range v.adjacent {
			fmt.Printf(" %v ", v.key)
		}
		fmt.Println()
		for _, loop := range v.loops {
			for _, v := range loop {
				if v != nil {
					fmt.Printf(" %v ", v.key)
				}
			}
			fmt.Println()
		}
	}
}

func contains(list []*Node, key string) bool {
	for _, v := range list {
		if key == v.key {
			return true
		}
	}
	return false
}
