package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
)

func main() {
	if err := myMain(); err != nil {
		log.Println(err)
	}
}

func myMain() error {
	// parse input.txt
	data, err := parseInput()
	if err != nil {
		return err
	}

	first, err := solveFirst(data)
	if err != nil {
		return err
	}
	log.Println("Part1:", first)

	second, err := solveSecond(data)
	if err != nil {
		return err
	}
	log.Println("Part2:", second)

	return nil
}

func solveFirst(data []string) (int64, error) {
	g, e, err := getGE(data)
	if err != nil {
		return 0, err
	}
	gint, err := strconv.ParseInt(g, 2, 64)
	if err != nil {
		return 0, err
	}
	eint, err := strconv.ParseInt(e, 2, 64)
	if err != nil {
		return 0, err
	}

	return gint * eint, nil
}

func getGE(data []string) (string, string, error) {
	gamma := strings.Builder{}
	epsilon := strings.Builder{}
	var zero, one int
	gammaLen := len(data[0])
	for j := 0; j < gammaLen; j++ {
		for _, line := range data {
			switch line[j] {
			case '0':
				zero++
			case '1':
				one++
			default:
				return "", "", fmt.Errorf("invalid binary")
			}
		}
		if one > zero {
			gamma.WriteByte('1')
			epsilon.WriteByte('0')
		} else {
			gamma.WriteByte('0')
			epsilon.WriteByte('1')
		}
		zero, one = 0, 0
	}

	return gamma.String(), epsilon.String(), nil
}

func solveSecond(data []string) (int64, error) {
	ogr, err := strconv.ParseInt(OGR(data), 2, 64)
	if err != nil {
		return 0, err
	}
	cgr, err := strconv.ParseInt(CGR(data), 2, 64)
	if err != nil {
		return 0, err
	}
	return ogr * cgr, nil
}

func getCommon(data []string, col int) (int, int) {
	zero, one := 0, 0
	for _, line := range data {
		switch line[col] {
		case '0':
			zero++
		case '1':
			one++
		}
	}

	return zero, one
}

func bitCriteria(zero, one int, t string) byte {
	switch t {
	case "oxigen":
		if zero > one {
			return '0'
		} else {
			return '1'
		}
	case "CO2":
		if zero > one {
			return '1'
		} else {
			return '0'
		}
	default:
		panic("unreachable")
	}
}

func OGR(data []string) string {
	for k := 0; k < len(data[0]); k++ {
		next := getOxCO2(data, k, "oxigen")
		if len(next) == 1 {
			return next[0]
		}
		data = next
	}
	return "OGR SZOPÓ"
}

func CGR(data []string) string {
	for k := 0; k < len(data[0]); k++ {
		next := getOxCO2(data, k, "CO2")
		if len(next) == 1 {
			return next[0]
		}
		data = next
	}
	return "CGR SZOPÓ"
}

func getOxCO2(data []string, k int, kind string) []string {
	var nextData []string
	zero, one := getCommon(data, k)
	for _, line := range data {
		if line[k] == bitCriteria(zero, one, kind) {
			nextData = append(nextData, line)
		}
	}

	return nextData
}

func parseInput() ([]string, error) {
	fd, err := os.Open("input.txt")
	if err != nil {
		return nil, err
	}
	defer fd.Close()

	buf := bufio.NewScanner(fd)
	var ret []string
	for buf.Scan() {
		ret = append(ret, buf.Text())
	}

	return ret, nil
}