package main

import (
	"bytes"
	"fmt"
	"sort"
	"strings"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

func main() {
	inp := h.StdinToStringSlice()
	solve(inp)
}

func solve(inp []string) {
	var segments []seg
	for i := range inp {
		pts := strings.Split(inp[i], "|")
		var patterns [][]byte
		for _, v := range strings.Fields(pts[0]) {
			patterns = append(patterns, []byte(v))
		}
		var output [][]byte
		for _, v := range strings.Fields(pts[1]) {
			output = append(output, []byte(v))
		}
		segments = append(segments, DeduceSeg(patterns, output))
	}

	var c1, c4, c7, c8 int
	var total int
	for i := range segments {
		c1 += segments[i].instances(1)
		c4 += segments[i].instances(4)
		c7 += segments[i].instances(7)
		c8 += segments[i].instances(8)
		total += segments[i].value()
	}

	fmt.Println("# Part 1")
	fmt.Println("1,4,7,8 appear", c1+c4+c7+c8, "times")
	fmt.Println("")
	fmt.Println("# Part 2")
	fmt.Println("Total", total)
}

type seg struct {
	N, NE, SE, S, SW, NW, C                                    byte
	Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine []byte
	Output                                                     []int
}

func DeduceSeg(pInput, oInput [][]byte) seg {
	segments := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g'}
	// 0, 3, 5, 9
	var patterns [][]byte
	for _, p := range pInput {
		sort.Slice(p, func(i int, j int) bool { return p[i] < p[j] })
		patterns = append(patterns, p)
	}
	var output [][]byte
	for _, o := range oInput {
		sort.Slice(o, func(i int, j int) bool { return o[i] < o[j] })
		output = append(output, o)
	}
	var removeFromSegments = func(v ...byte) {
		for k := range v {
			rem := -1
			for b := range segments {
				if segments[b] == v[k] {
					rem = b
				}
			}
			if rem > -1 {
				segments = append(segments[:rem], segments[rem+1:]...)
			}
		}
	}
	var removeFromPatterns = func(v ...[]byte) {
		for k := range v {
			rem := -1
			for i := range patterns {
				if string(patterns[i]) == string(v[k]) {
					rem = i
				}
			}
			if rem > -1 {
				patterns = append(patterns[:rem], patterns[rem+1:]...)
			}
		}
	}
	s := seg{
		N:  '-',
		NE: '-',
		SE: '-',
		S:  '-',
		SW: '-',
		NW: '-',
		C:  '-',
	}
	for i := range patterns {
		switch {
		case len(patterns[i]) == 2:
			s.One = patterns[i]
		case len(patterns[i]) == 4:
			s.Four = patterns[i]
		case len(patterns[i]) == 3:
			s.Seven = patterns[i]
		case len(patterns[i]) == 7:
			s.Eight = patterns[i]
		}
	}
	removeFromPatterns(s.One, s.Four, s.Seven, s.Eight)

	//First, we know the N segment, it's what shows up in 'seven' , but not in 'one'
	for i := range s.Seven {
		if bytes.IndexByte(s.One, s.Seven[i]) == -1 {
			s.N = s.Seven[i]
			removeFromSegments(s.N)
			break
		}
	}

	// Six length numbers:
	// 0, 9, 6
	// We know that 'seven' is inside of 'nine' and 'zero' but not 'six'
	for _, p := range patterns {
		if len(p) == 6 {
			for _, v := range s.Seven {
				if bytes.IndexByte(p, v) == -1 {
					// 'v' also has to be the NE segment
					s.NE = v
					removeFromSegments(s.NE)
					s.Six = p
					break
				}
			}
		}
	}
	removeFromPatterns(s.Six)

	for _, v := range s.Seven {
		if v != s.N && v != s.NE {
			s.SE = v
			removeFromSegments(s.SE)
		}
	}

	// Five length numbers: 2, 3, 5
	// Two is missing the SE segment
	// Five is missing the NE segment
	for _, p := range patterns {
		if len(p) == 5 {
			if bytes.IndexByte(p, s.SE) == -1 {
				s.Two = p
			}
			if bytes.IndexByte(p, s.NE) == -1 {
				s.Five = p
			}
		}
	}
	removeFromPatterns(s.Two, s.Five)

	// The remaining 5 length is Three
	for _, p := range patterns {
		if len(p) == 5 {
			s.Three = p
			break
		}
	}
	removeFromPatterns(s.Three)

	// Nine is the same as Five except for NE
	for _, p := range patterns {
		for _, b := range p {
			if b != s.NE && bytes.IndexByte(s.Five, b) == -1 {
				s.Zero = p
				break
			}
		}
	}
	removeFromPatterns(s.Zero)
	s.Nine = patterns[0]

	// NW is the non-SE that is missing from Two
	for _, b := range segments {
		if b != s.SE && bytes.IndexByte(s.Two, b) == -1 {
			s.NW = b
			removeFromSegments(s.NW)
			break
		}
	}
	// SW is the segment in Six that's not in Five
	for _, b := range segments {
		if bytes.IndexByte(s.Five, b) == -1 && bytes.IndexByte(s.Six, b) != -1 {
			s.SW = b
			removeFromSegments(s.SW)
		}
	}
	// C is the remaining segment in Four that's not in 0
	for _, b := range segments {
		if bytes.IndexByte(s.Zero, b) == -1 && bytes.IndexByte(s.Four, b) != -1 {
			s.C = b
			removeFromSegments(s.C)
		}
	}
	// The last segment is S
	s.S = segments[0]

	// Now fill in the outputs
	for i := range output {
		s.Output = append(s.Output, s.valueOf(output[i]))
	}

	return s
}

func (s seg) instances(v int) int {
	var t int
	for _, o := range s.Output {
		if o == v {
			t++
		}
	}
	return t
}

func (s seg) value() int {
	var retStr string
	for _, o := range s.Output {
		retStr = retStr + h.Itoa(o)
	}
	return h.Atoi(retStr)
}

func (s seg) valueOf(b []byte) int {
	sort.Slice(b, func(i int, j int) bool { return b[i] < b[j] })
	switch string(b) {
	case string(s.Zero):
		return 0
	case string(s.One):
		return 1
	case string(s.Two):
		return 2
	case string(s.Three):
		return 3
	case string(s.Four):
		return 4
	case string(s.Five):
		return 5
	case string(s.Six):
		return 6
	case string(s.Seven):
		return 7
	case string(s.Eight):
		return 8
	case string(s.Nine):
		return 9
	}
	return -1
}

func (s seg) String() string {
	ret := fmt.Sprintf("%s\n", string([]byte{' ', s.N, s.N, s.N, s.N, ' '}))
	ret = fmt.Sprintf("%s%s\n", ret, string([]byte{s.NW, ' ', ' ', ' ', ' ', s.NE}))
	ret = fmt.Sprintf("%s%s\n", ret, string([]byte{s.NW, ' ', ' ', ' ', ' ', s.NE}))
	ret = fmt.Sprintf("%s%s\n", ret, string([]byte{' ', s.C, s.C, s.C, s.C, ' '}))
	ret = fmt.Sprintf("%s%s\n", ret, string([]byte{s.SW, ' ', ' ', ' ', ' ', s.SE}))
	ret = fmt.Sprintf("%s%s\n", ret, string([]byte{s.SW, ' ', ' ', ' ', ' ', s.SE}))
	ret = fmt.Sprintf("%s%s\n", ret, string([]byte{' ', s.S, s.S, s.S, s.S, ' '}))
	return ret
}
