package main

import (
	"fmt"

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

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

func binStrToUint64(inp string) uint64 {
	var res uint64
	for i := range inp {
		if inp[i] == '0' {
			res = res << 1
		} else {
			res = (res << 1) | 1
		}
	}
	return res
}

type Pair struct {
	zero int
	one  int
}

func (p Pair) MostByte(fallback byte) byte {
	if p.zero == p.one {
		return fallback
	} else if p.zero > p.one {
		return '0'
	}
	return '1'
}
func (p Pair) LeastByte(fallback byte) byte {
	if p.zero == p.one {
		return fallback
	} else if p.zero < p.one {
		return '0'
	}
	return '1'
}

func buildReport(inp []string) map[int]Pair {
	totals := make(map[int]Pair)
	for k := range inp {
		for i := range inp[k] {
			var p Pair
			var ok bool
			if p, ok = totals[i]; !ok {
				p = Pair{zero: 0, one: 0}
			}
			if inp[k][i] == '0' {
				p.zero++
			} else {
				p.one++
			}
			totals[i] = p
		}
	}
	return totals
}

func part1(inp []string) {
	totals := buildReport(inp)
	var gamma uint64
	var epsilon uint64
	var gammaStr string
	var epsilonStr string
	for i := 0; i <= len(inp[0]); i++ {
		p, ok := totals[i]
		if ok {
			if p.zero > p.one {
				//gamma = gamma << 1
				//epsilon = (epsilon << 1) | 1
				gammaStr = gammaStr + "0"
				epsilonStr = epsilonStr + "1"
			} else {
				//gamma = (gamma << 1) | 1
				//epsilon = epsilon << 1
				gammaStr = gammaStr + "1"
				epsilonStr = epsilonStr + "0"
			}
		}
	}
	gamma = binStrToUint64(gammaStr)
	epsilon = binStrToUint64(epsilonStr)
	fmt.Println("# Part 1")
	fmt.Println("gamma:", gammaStr, gamma)
	fmt.Println("epsilon:", epsilonStr, epsilon)
	fmt.Println("power consumption:", (gamma * epsilon))
	fmt.Println("")
}

func part2(inp []string) {
	fmt.Println("# Part 2")

	bldOx := make([]string, len(inp))
	copy(bldOx, inp)
	idx := 0
	for len(bldOx) > 1 && idx < len(inp[0]) {
		totals := buildReport(bldOx)
		var next []string
		for i := range bldOx {
			if bldOx[i][idx] == totals[idx].MostByte('1') {
				next = append(next, bldOx[i])
			}
		}
		bldOx = next
		idx++
	}
	oxV := binStrToUint64(bldOx[0])
	fmt.Println("Oxygen Rating:", bldOx, oxV)
	co2 := make([]string, len(inp))
	copy(co2, inp)
	idx = 0
	for len(co2) > 1 && idx < len(inp[0]) {
		totals := buildReport(co2)
		var next []string
		for i := range co2 {
			if co2[i][idx] == totals[idx].LeastByte('0') {
				next = append(next, co2[i])
			}
		}
		co2 = next
		idx++
	}
	co2V := binStrToUint64(co2[0])
	fmt.Println("CO2 Rating:", co2, co2V)
	fmt.Println("Life Support Rating:", (oxV * co2V))
}
