package main

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

// Op constants.
const (
	OpMask string = "mask"
	OpMem         = "mem"
)

// Op ...
type Op struct {
	Type string
	Mask string
	Addr int
	Val  int
}

// PortComputer ...
type PortComputer struct {
	Mask string
	Mem  map[int]int
}

// Set ...
func (p *PortComputer) Set(addr int, val int) {
	bit := 35
	for _, r := range p.Mask {
		if r != 'X' {
			if r == '1' {
				val |= 1 << bit
			} else {
				val &= ^(1 << bit)
			}
		}
		bit--
	}
	p.Mem[addr] = val
}

// SetV2 ...
func (p *PortComputer) SetV2(addr int, val int) {
	bit := 35
	for _, r := range p.Mask {
		if r == '1' {
			addr |= 1 << bit
		} else if r == 'X' {
			addr &= ^(1 << bit)
		}
		bit--
	}
	p.Mem[addr] = val

	floating := strings.Count(p.Mask, "X")
	iterations := int(math.Pow(2, float64(floating)))
	for i := 0; i < iterations; i++ {
		a := addr
		bit := 0
		for j := 35; j >= 0; j-- {
			if p.Mask[j] == 'X' {
				if i&(1< 0 {
					a |= 1 << (35 - j)
				}
				bit++
			}
		}
		p.Mem[a] = val
	}
}

// Task1 ...
func Task1(input []Op) int {
	p := PortComputer{
		Mem: make(map[int]int),
	}

	for _, op := range input {
		switch op.Type {
		case OpMask:
			p.Mask = op.Mask
		case OpMem:
			p.Set(op.Addr, op.Val)
		}
	}

	var result int
	for _, v := range p.Mem {
		result += int(v)
	}
	return result
}

// Task2 ...
func Task2(input []Op) int {
	p := PortComputer{
		Mem: make(map[int]int),
	}

	for _, op := range input {
		switch op.Type {
		case OpMask:
			p.Mask = op.Mask
		case OpMem:
			p.SetV2(op.Addr, op.Val)
		}
	}

	var result int
	for _, v := range p.Mem {
		result += int(v)
	}
	return result
}

func main() {
	file, _ := os.Open("input.txt")
	scanner := bufio.NewScanner(file)

	var input []Op
	for scanner.Scan() {
		parts := strings.Split(scanner.Text(), "=")
		if strings.HasPrefix(parts[0], "mask") {
			input = append(input, Op{
				Type: OpMask,
				Mask: parts[1][1:],
			})
		} else {
			var addr, val int
			fmt.Sscanf(parts[0], "mem[%d]", &addr)
			val, _ = strconv.Atoi(parts[1][1:])
			input = append(input, Op{
				Type: OpMem,
				Addr: addr,
				Val:  val,
			})
		}
	}
	file.Close()

	result := Task1(input)
	fmt.Printf("Task 1: %d\n", result)

	result = Task2(input)
	fmt.Printf("Task 2: %d\n", result)
}