package guards

import (
	"sort"
	"strconv"
	"strings"
	"time"
)

type Guard struct {
	ID         int
	Sleep      [60]int
	SleepTotal int
}

type SleepEvent struct {
	start, end int
}

type Shift struct {
	start       time.Time
	guard       int
	sleepEvents []*SleepEvent
}

// ParseInput takes the input data and creates a series of "Shift" objects,
// which are just a structured form of the data.
func ParseInput(rawData []string) []*Shift {
	sort.Strings(rawData)

	shifts := []*Shift{}
	var currentShift *Shift
	var currentSleepEvent *SleepEvent

	for _, line := range rawData {
		data := strings.Split(line, "]")
		timestamp, _ := time.Parse("2006-01-02 15:04", data[0][1:])
		event := data[1][1:]

		// handle beginning of shift
		if strings.HasPrefix(event, "Guard #") {
			// save old shift first
			if currentShift != nil {
				shifts = append(shifts, currentShift)
			}

			// now extract the guard ID and create a new Shift object
			guardID, _ := strconv.Atoi(strings.Split(event, " ")[1][1:])
			currentShift = &Shift{
				guard: guardID,
				start: timestamp,
			}
		}

		if strings.HasPrefix(event, "falls") {
			currentSleepEvent = &SleepEvent{
				start: timestamp.Minute(),
			}
		}

		if strings.HasPrefix(event, "wakes") {
			currentSleepEvent.end = timestamp.Minute()
			currentShift.sleepEvents = append(currentShift.sleepEvents, currentSleepEvent)
		}
	}

	// save last shift
	shifts = append(shifts, currentShift)

	return shifts
}

// BuildGuards takes the shift data created by ParseInput and builds a map of
// 'guards', with a per-minute counter (and handy sum) of their sleep habits.
func BuildGuards(data []*Shift) map[int]*Guard {
	guards := make(map[int]*Guard)

	for _, shift := range data {
		// fetch the guard for this shift, initialize if necessary
		var guard *Guard
		// declaring 'ok' here seems to be the only way to keep 'guard' from becoming
		// a local variable inside the conditional. This seems like a flaw.
		var ok bool
		if guard, ok = guards[shift.guard]; !ok {
			guard = &Guard{
				ID: shift.guard,
			}
			guards[guard.ID] = guard
		}

		for _, event := range shift.sleepEvents {
			guard.SleepTotal += event.end - event.start
			for i := event.start; i < event.end; i++ {
				guard.Sleep[i]++
			}
		}
	}

	return guards
}