package main

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

func main() {
	width := 50
	height := 6
	if len(os.Args) >= 2 {
		width = atoi(os.Args[1])
	}
	if len(os.Args) >= 3 {
		height = atoi(os.Args[2])
	}
	input := stdinToStringSlice()

	d := CreateDisplay(width, height)
	for idx, ins := range input {
		fmt.Println(idx, ins)
		d.ClearScreen()
		d.ProcInstruction(ins)
		d.PrintScreen()
		time.Sleep(time.Millisecond * 25)
	}
	fmt.Println("Voltage Used: ", d.GetVoltage())
}

type Display struct {
	Width  int
	Height int
	Screen [][]bool
}

func CreateDisplay(w, h int) *Display {
	d := Display{Width: w, Height: h}
	for i := 0; i < h; i++ {
		var row []bool
		for j := 0; j < w; j++ {
			row = append(row, false)
		}
		d.Screen = append(d.Screen, row)
	}
	return &d
}

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

func (d *Display) PrintScreen() {
	fmt.Print("\u250C")
	for i := 0; i < d.Width; i++ {
		fmt.Print("\u2500")
	}
	fmt.Println("\u2510")
	for y := range d.Screen {
		fmt.Print("\u2502")
		for x := range d.Screen[y] {
			if d.Screen[y][x] {
				fmt.Print("\u2588")
			} else {
				fmt.Print(" ")
			}
		}
		fmt.Println("\u2502")
	}
	fmt.Print("\u2514")
	for i := 0; i < d.Width; i++ {
		fmt.Print("\u2500")
	}
	fmt.Println("\u2518")
}

func (d *Display) SetWidth(w int) {
	d.Width = w
}

func (d *Display) SetHeight(h int) {
	d.Height = h
}

func (d *Display) ProcInstruction(ins string) {
	pts := strings.Fields(ins)
	switch pts[0] {
	case "rect":
		if len(pts) < 2 {
			return
		}
		dim := strings.Split(pts[1], "x")
		bldX := atoi(dim[0])
		bldY := atoi(dim[1])
		d.CreateRect(bldX, bldY)
	case "rotate":
		if len(pts) < 5 {
			return
		}
		args := strings.Split(pts[2], "=")
		switch pts[1] {
		case "row":
			if args[0] != "y" {
				return
			}
			d.RotateRow(atoi(args[1]), atoi(pts[4]))
		case "column":
			if args[0] != "x" {
				return
			}
			d.RotateCol(atoi(args[1]), atoi(pts[4]))
		}
	}
}

func (d *Display) CreateRect(x, y int) {
	if y >= d.Height && x >= d.Width {
		fmt.Println("  Error creating rect", x, y)
		return
	}
	for i := 0; i < y; i++ {
		for j := 0; j < x; j++ {
			d.Screen[i][j] = true
		}
	}
}

func (d *Display) RotateRow(y, dist int) {
	orig, err := d.GetRow(y)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
		return
	}
	for i := range orig {
		d.Screen[y][(i+dist)%d.Width] = orig[i]
	}
}

func (d *Display) RotateCol(x, dist int) {
	orig, err := d.GetCol(x)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
		return
	}

	for i := range orig {
		d.Screen[(i+dist)%d.Height][x] = orig[i]
	}
}

func (d *Display) GetRow(y int) ([]bool, error) {
	var ret []bool
	if d.Height <= y {
		return ret, errors.New("Invalid Row Requested")
	}
	for i := 0; i < d.Width; i++ {
		ret = append(ret, d.Screen[y][i])
	}
	return ret, nil
}

func (d *Display) GetCol(x int) ([]bool, error) {
	var ret []bool
	if d.Width <= x {
		return ret, errors.New("Invalid Column Requested")
	}
	for i := 0; i < d.Height; i++ {
		ret = append(ret, d.Screen[i][x])
	}
	return ret, nil
}

func (d *Display) GetVoltage() int {
	var ret int
	for i := range d.Screen {
		for j := range d.Screen[i] {
			if d.Screen[i][j] {
				ret++
			}
		}
	}
	return ret
}

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
}
