package main

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

var rules map[string]string

func main() {
	numIters := 18
	rules = make(map[string]string)

	inp := StdinToStrings()
	for i := range inp {
		pts := strings.Split(inp[i], " ")
		rules[pts[0]] = pts[2]
		rules[Flip(pts[0])] = pts[2]
		rules[Rotate(pts[0], 90)] = pts[2]
		rules[Flip(Rotate(pts[0], 90))] = pts[2]
		rules[Rotate(pts[0], 180)] = pts[2]
		rules[Flip(Rotate(pts[0], 180))] = pts[2]
		rules[Rotate(pts[0], 270)] = pts[2]
		rules[Flip(Rotate(pts[0], 270))] = pts[2]
	}

	pattern := ".#./..#/###"
	PrettyPrint(pattern)
	for i := 0; i < numIters; i++ {
		ClearScreen()
		PrettyPrint(pattern)
		time.Sleep(time.Second / 2)
		pattern = Tick(pattern)
	}
	fmt.Println("Bits On", strings.Count(pattern, "#"))
}

func PrettyPrint(inp string) {
	for _, v := range strings.Split(inp, "/") {
		fmt.Println(v)
	}
}

func Tick(pt string) string {
	subPatterns := SplitBlocks(pt)
	var tickSubs []string
	for i := range subPatterns {
		tickSubs = append(tickSubs, ApplyRule(subPatterns[i]))
	}
	return CombineBlocks(tickSubs)
}

func ApplyRule(inp string) string {
	if v, ok := rules[inp]; ok {
		return v
	}
	// This is an error.
	fmt.Println("No rule for", inp)
	os.Exit(1)
	return ""
}

func SplitBlocks(pt string) []string {
	var ret []string
	pts := strings.Split(pt, "/")
	if len(pts[0]) <= 3 {
		return []string{pt}
	}
	// Assume 3x3
	blkSize := 3
	if len(pts[0])%2 == 0 {
		// 2x2 blocks
		blkSize = 2
	}

	for j := 0; j < len(pts); j += blkSize {
		for i := 0; i < len(pts[j]); i += blkSize {
			if blkSize == 2 {
				ret = append(ret, pts[j][i:i+blkSize]+"/"+pts[j+1][i:i+blkSize])
			} else {
				ret = append(ret, pts[j][i:i+blkSize]+"/"+pts[j+1][i:i+blkSize]+"/"+pts[j+2][i:i+blkSize])
			}
		}
	}
	return ret
}

func CombineBlocks(pts []string) string {
	var ret []string
	if len(pts) == 1 {
		return pts[0]
	}
	var subPts [][]string
	for i := range pts {
		subPts = append(subPts, strings.Split(pts[i], "/"))
	}
	subPtSz := len(subPts[0])
	ptSize := int(math.Sqrt(float64(len(subPts) * subPtSz * subPtSz)))
	w, h := (ptSize / subPtSz), (ptSize / subPtSz)
	for i := 0; i < h; i++ {
		st := i * w
		for j := 0; j < subPtSz; j++ {
			var bld string
			for k := 0; k < w; k++ {
				bld += subPts[st+k][j]
			}
			ret = append(ret, bld)
		}
	}

	return strings.Join(ret, "/")
}

func Flip(pt string) string {
	pts := strings.Split(pt, "/")
	var newPts []string
	for i := range pts {
		newPts = append(newPts, RevString(pts[i]))
	}
	return strings.Join(newPts, "/")
}

func Rotate(pt string, deg int) string {
	if deg == 0 {
		return pt
	}
	if deg%90 != 0 {
		// We can't do this
		fmt.Println("Invalid Rotation:", deg)
		os.Exit(1)
	}
	pts := strings.Split(pt, "/")
	var tst string
	if len(pts[0])%2 == 0 {
		// 2x2 pattern
		tst = string(pts[1][0]) + string(pts[0][0]) + "/"
		tst += string(pts[1][1]) + string(pts[0][1])
		return Rotate(tst, deg-90)
	}
	// 3x3 pattern
	tst = string(pts[2][0]) + string(pts[1][0]) + string(pts[0][0]) + "/"
	tst += string(pts[2][1]) + string(pts[1][1]) + string(pts[0][1]) + "/"
	tst += string(pts[2][2]) + string(pts[1][2]) + string(pts[0][2])
	return Rotate(tst, deg-90)
}

func RevString(s string) string {
	runes := []rune(s)
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return string(runes)
}

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

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