package main

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

var guards map[int]*Guard

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

func buildGuardMap(inp []string) {
	sort.Strings(inp)
	guards = make(map[int]*Guard)
	var currGuard int
	var sleepStart time.Time
	for _, v := range inp {
		t, _ := time.Parse("2006-01-02 15:04", v[1:17])
		actionParts := strings.Split(v[19:], " ")
		switch actionParts[0][0] {
		case 'G':
			currGuard = Atoi(actionParts[1][1:])
		case 'f':
			sleepStart = t
		case 'w':
			if _, ok := guards[currGuard]; !ok {
				guards[currGuard] = &Guard{
					id:             currGuard,
					totalSleepTime: 0,
					sleepLog:       make([]Nap, 0, 5),
				}
			}
			guards[currGuard].TakeNap(sleepStart, t)
		}
	}
}

func part1() {
	var sleepiest int
	var sleepiestTime int
	for k, v := range guards {
		if v.TotalNapTime() > sleepiestTime {
			sleepiest = k
			sleepiestTime = v.TotalNapTime()
		}
	}
	min, _ := guards[sleepiest].GetSleepiestMinute()
	fmt.Println("= Part 1 =")
	fmt.Println("Sleepiest Guard:", sleepiest, "(", sleepiestTime, ") Sleepiest Minute:", min)
	fmt.Println("= Answer =")
	fmt.Println(sleepiest * min)
}

func part2() {
	var sleepiestTime int
	type tuple struct {
		v1, v2 int
	}
	sleepiestMinutes := make(map[int]tuple)
	for _, v := range guards {
		v1, v2 := v.GetSleepiestMinute()
		sleepiestMinutes[v.id] = tuple{v1, v2}
		if v.TotalNapTime() > sleepiestTime {
			sleepiestTime = v.TotalNapTime()
		}
	}
	var highestCount, highestCountGuard, highestCountMinute int
	for k, v := range sleepiestMinutes {
		if v.v2 > highestCount {
			highestCount = v.v2
			highestCountGuard = k
			highestCountMinute = v.v1
		}
	}
	fmt.Println("= Part 2 =")
	fmt.Printf("Guard: %d\nMinute: %d\nCount: %d\n", highestCountGuard, highestCountMinute, highestCount)
	fmt.Println("= Answer =")
	fmt.Println(highestCountGuard * highestCountMinute)
}

type Guard struct {
	id             int
	totalSleepTime int
	sleepLog       []Nap
}

func (g *Guard) TakeNap(start, end time.Time) {
	g.sleepLog = append(
		g.sleepLog,
		Nap{start: start, end: end})
}

func (g *Guard) TotalNapTime() int {
	var ret int
	for _, v := range g.sleepLog {
		ret += int(v.end.Sub(v.start).Minutes())
	}
	return ret
}

func (g *Guard) GetSleepiestMinute() (int, int) {
	minuteMap := make(map[int]int)
	for _, v := range g.sleepLog {
		wrkMin := v.start.Minute()
		for wrkMin < v.end.Minute() {
			minuteMap[wrkMin]++
			wrkMin++
		}
	}
	var sleepiestMinute, sleepiestCount int
	for k, v := range minuteMap {
		if v > sleepiestCount {
			sleepiestMinute = k
			sleepiestCount = v
		}
	}
	return sleepiestMinute, minuteMap[sleepiestMinute]
}

type Nap struct {
	start time.Time
	end   time.Time
}

func StdinToStringSlice() []string {
	var input []string
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		input = append(input, scanner.Text())
	}
	return input
}

func Atoi(i string) int {
	var ret int
	var err error
	if ret, err = strconv.Atoi(i); err != nil {
		log.Fatal("Invalid Atoi")
	}
	return ret
}
