package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/fatih/color"
)

// This is a purely mathematical solution to this problem
// We don't care about what the elements are.
// Loop on each floor and consider:
// 1: We have less than 3 items on the floor
//		So move them all up; 1 total move
// 2: We have more than 3 items on the floor
//		So move two up and one back down; 2 total moves

// Setting visual to true will output what's happening
var visual bool

func main() {
	input := stdinToStringSlice()
	var numMoves int
	if !visual {
		var lvlItmCnts []int
		for i := range input {
			lvlItmCnts = append(lvlItmCnts, strings.Count(input[i], "generator")+strings.Count(input[i], "microchip"))
		}
		for i := 0; i < 3; i++ {
			for lvlItmCnts[i] > 0 {
				if lvlItmCnts[i] < 3 {
					lvlItmCnts[i+1] = lvlItmCnts[i+1] + lvlItmCnts[i]
					lvlItmCnts[i] = 0
					numMoves++
				} else {
					lvlItmCnts[i] = lvlItmCnts[i] - 1
					lvlItmCnts[i+1] = lvlItmCnts[i+1] + 1
					numMoves = numMoves + 2
				}
			}
		}
	} else {
		// Visual mode spits a lot more stuff out, so it has
		// to track a lot more things...
		numMoves = VisualMode(input)
	}
	fmt.Println("Number of moves:", numMoves)
}

func VisualMode(input []string) int {
	// This code requires oxford commas in the input :)
	var elevatorFloor int
	var floorInv [][]string
	var componentRegistry []string
	for i := range input {
		listIdx := strings.Index(input[i], "a ")
		if listIdx == -1 {
			floorInv = append(floorInv, []string{})
			continue
		}
		input[i] = input[i][listIdx:]
		floorInv = append(floorInv, strings.Split(input[i], ", "))
	}
	for i := range floorInv {
		elevatorFloor = i
		for j := range floorInv[i] {
			tp := "G"
			if strings.Contains(floorInv[i][j], "microchip") {
				tp = "M"
			}
			floorInv[i][j] = strings.TrimPrefix(floorInv[i][j], "a ")
			floorInv[i][j] = strings.TrimPrefix(floorInv[i][j], "and a ")
			ele := strings.ToUpper(floorInv[i][j][:2])
			floorInv[i][j] = ele + ":" + tp
			componentRegistry = append(componentRegistry, floorInv[i][j])
		}
	}
	var numMoves int
	var pt1, pt2 string
	for i := 0; i < len(floorInv)-1; i++ {
		for len(floorInv[i]) > 1 {
			if len(floorInv[i]) > 1 {
				// If there are exactly two items on the floor, move them up
				if len(floorInv[i]) == 2 {
					pt1, pt2, floorInv[i] = floorInv[i][0], floorInv[i][1], floorInv[i][2:]
					floorInv[i+1] = append(floorInv[i+1], pt1, pt2)
					numMoves++
				} else if len(floorInv[i]) > 2 {
					// If more than two, move one up, but add 2 to the move
					// To simulate, moving two up then one back down
					pt1, floorInv[i] = floorInv[i][0], floorInv[i][1:]
					floorInv[i+1] = append(floorInv[i+1], pt1)
					numMoves = numMoves + 2
				}
			} else {
				// Only one left on floor, move it up
				pt1, floorInv[i] = floorInv[i][0], []string{}
				floorInv[i+1] = append(floorInv[i+1], pt1, pt2)
				numMoves++
			}
			if visual {
				ClearScreen()
				PrintScreen(floorInv, elevatorFloor, componentRegistry)
				time.Sleep(time.Millisecond * 250)
			}
		}
	}
	return numMoves
}

func ClearScreen() {
	fmt.Print("\033[H\033[2J")
}

func PrintScreen(floorInv [][]string, elevatorFloor int, componentRegistry []string) {
	c := color.New(color.FgCyan)
	w := color.New(color.FgWhite)
	for i := len(floorInv); i > 0; i-- {
		c.Printf("F%d ", i)
		if elevatorFloor == i-1 {
			c.Print("E ")
		} else {
			w.Print(". ")
		}
		for cr := range componentRegistry {
			var fnd bool
			for j := range floorInv[i-1] {
				if floorInv[i-1][j] == componentRegistry[cr] {
					fnd = true
					fmt.Print(componentRegistry[cr] + " ")
					break
				}
			}
			if !fnd {
				fmt.Print(".    ")
			}
		}
		fmt.Println()
	}
}

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

func itoa(i int) string {
	return strconv.Itoa(i)
}
