package main

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

var allRules []Rule

func main() {
	inp := StdinToStrings()
	for i := range inp {
		allRules = append(allRules, Rule(inp[i]))
	}
	//pattern := Pattern(".#./..#/###")
	pattern := Pattern("../##")
	fmt.Println(pattern)
	if !pattern.IsValid() {
		fmt.Println("Invalid Pattern Given. Must be square.")
	}
	ClearScreen()
	pattern.PrettyPrint()
	time.Sleep(time.Second)
	for iters := 0; iters < 5; iters++ {
		ClearScreen()
		pattern = Tick(pattern)
		pattern.PrettyPrint()
		time.Sleep(time.Second)
	}
	fmt.Println("On:", pattern.GetOnCount())
}

// Stitch takes a slice of patterns and turns them into one large (square) pattern
func Stitch(inp []Pattern) Pattern {
	ppr := int(math.Sqrt(float64(len(inp))))
	newSize := ppr * inp[0].Size()
	var currRow int
	rows := make(map[int]string)
	for i := 0; i < len(inp); i++ {
		currRow = (i / ppr) * 2
		for j := 0; j < len(inp[0]); j++ {
			if len(rows[currRow+j]) == newSize {
				rows[currRow+j] += "/"
			}
			rows[currRow+j] += inp[i].GetRow(j)
		}
	}
	var lines []string
	for i := 0; i < newSize; i++ {
		lines = append(lines, rows[i])
	}

	return Pattern(strings.Join(lines, "/"))
}

// Run the pattern, or all of it's subpatterns
// through the rules and return the new pattern
func Tick(inp Pattern) Pattern {
	if inp.SubpatternCount() > 1 {
		var subs []Pattern
		// Tick each subpattern
		for i := 0; i < inp.SubpatternCount(); i++ {
			if s := inp.GetSubpattern(i); s.IsValid() {
				subs = append(subs, Tick(s))
			} else {
				log.Fatal("Error ticking pattern")
			}
		}
		return Stitch(subs)
	}

	var foundMatch bool
	for i := range allRules {
		if allRules[i].Matches(inp) {
			inp = allRules[i].Output()
			foundMatch = true
		}
	}
	if !foundMatch {
		fmt.Println("~ ERROR TICKING ~")
		inp.PrettyPrint()
		fmt.Println("~ ERROR TICKING ~")
		os.Exit(1)
	}
	return inp
}

/**
 * A pattern is a string with some special methods
 */
type Pattern string

// Print a pattern prettily. (in a square)
func (p Pattern) PrettyPrint() {
	pts := strings.Split(string(p), "/")
	for i := range pts {
		fmt.Println(pts[i])
	}
}

func (p Pattern) GetOnCount() int {
	return strings.Count(string(p), "#")
}

// Returns if a pattern is valid.
// A pattern is valid if it has more than 0 rows
// and every row is the same length as the number
// of columns
func (p Pattern) IsValid() bool {
	pts := strings.Split(string(p), "/")
	if len(pts) == 0 {
		return false
	}
	for i := range pts {
		if len(pts[i]) != len(pts) {
			return false
		}
	}
	return true
}

// Returns the "size" of the pattern
// That is, how many rows/columns it has
func (p Pattern) Size() int {
	return len(strings.Split(string(p), "/"))
}

// Flip returns a new pattern that has been flipped horizontally
func (p Pattern) Flip() Pattern {
	// We can only flip the smallest patterns (2x2 or 3x3)
	if p.SubpatternCount() != 1 {
		return p
	}
	pts := strings.Split(string(p), "/")
	for i := range pts {
		pts[i] = RevString(pts[i])
	}
	return Pattern(strings.Join(pts, "/"))
}

// Rotate returns a new pattern that has been rotated deg degrees
// Only right-angles
func (p Pattern) Rotate(deg int) Pattern {
	// We can only rotate the smallest patterns (2x2 or 3x3)
	if p.SubpatternCount() != 1 {
		return p
	}

	if deg < 0 {
		deg += 360
	} else if deg == 0 {
		return p
	}

	pts := strings.Split(string(p), "/")
	ret := make([]string, len(pts))
	use := p
	switch deg {
	case 90:
		if p.Size()%3 == 0 {
			ret[0] = RevString(use.GetCol(2))
			ret[1] = RevString(use.GetCol(1))
			ret[2] = RevString(use.GetCol(0))
		} else {
			ret[0] = RevString(use.GetCol(1))
			ret[1] = RevString(use.GetCol(0))
		}
	case 180:
		if p.Size()%3 == 0 {
			ret[0] = RevString(use.GetRow(2))
			ret[1] = RevString(use.GetRow(1))
			ret[2] = RevString(use.GetRow(0))
		} else {
			ret[0] = RevString(use.GetRow(1))
			ret[1] = RevString(use.GetRow(0))
		}
	case 270:
		if p.Size()%3 == 0 {
			ret[0] = use.GetCol(2)
			ret[1] = use.GetCol(1)
			ret[2] = use.GetCol(0)
		} else {
			ret[0] = use.GetCol(1)
			ret[1] = use.GetCol(0)
		}

	}
	use = Pattern(strings.Join(ret, "/"))
	return Pattern(strings.Join(ret, "/"))
}

// GetRow returns a row as a string
func (p Pattern) GetRow(row int) string {
	pts := strings.Split(string(p), "/")
	if row >= len(pts) {
		return ""
	}
	return pts[row]
}

// GetCol returns a column as a string
func (p Pattern) GetCol(col int) string {
	var ret string
	pts := strings.Split(string(p), "/")
	if col >= len(pts[0]) {
		return ""
	}
	for i := 0; i < len(pts); i++ {
		ret = ret + string(pts[i][col])
	}
	return ret
}

// Counts the number of subpatterns in the pattern
func (p Pattern) SubpatternCount() int {
	if p.Size()%2 == 0 {
		return (p.Size() / 2) * (p.Size() / 2)
	} else {
		return (p.Size() / 3) * (p.Size() / 3)
	}
}

// Gets a specific subpattern out of the pattern
func (p Pattern) GetSubpattern(i int) Pattern {
	if i > p.SubpatternCount() {
		return Pattern("")
	}
	subSize := 3 // Assume 3x3 subpatterns
	if p.Size()%2 == 0 {
		// Subpatterns are actually 2x2
		subSize = 2
	}
	ppr := p.Size() / subSize
	col := i % (p.Size() / subSize)
	row := i / ppr
	ptString := ""
	for j := 0; j < subSize; j++ {
		ptString += p.GetRow((row * 2) + j)[(col*subSize):(col*subSize)+subSize] + "/"
	}
	return Pattern(ptString[:len(ptString)-1])
}

/**
 * Rule is an interface that can take a pattern and return if it matches,
 * report on it's size, or return the resulting pattern from applying itself
 * to a pattern
 */
type RuleFace interface {
	Matches(inp Pattern) bool
	InputSize() int
	Apply(inp Pattern) Pattern
}

/**
 * Rule implements the RuleFace interface
 * for 2x2 => 3x3 transitions
 * or 3x3 => 4x4 transitions
 */
type Rule string

func (r Rule) Input() Pattern {
	pts := strings.Split(string(r), " ")
	return Pattern(pts[0])
}

func (r Rule) Output() Pattern {
	pts := strings.Split(string(r), " ")
	return Pattern(pts[2])
}

func (r Rule) Matches(inp Pattern) bool {
	if inp.Size() != r.InputSize() {
		return false
	}
	// Try it rotated 90,180,270
	for i := 0; i < 360; i += 90 {
		if r.Equals(inp.Rotate(i)) || r.Equals(inp.Flip().Rotate(i)) {
			return true
		}
	}
	return false
}

func (r Rule) InputSize() int {
	return len(strings.Split(string(r.Input()), "/"))
}

// Equals is a direct string comparison
func (r Rule) Equals(inp Pattern) bool {
	return string(r.Input()) == string(inp)
}

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
}
