package main

import (
	"AoC2020/helper"
	"fmt"
	"math"
	"os"
	"regexp"
	"strconv"
	"strings"
)

var lineBreak = "\r\n"

type tile struct {
	neighbourID [4]int
	edges [4]string
	picture []string
}

func main() {
	args := os.Args[1:]
	input, err := helper.GetFile(args[0])
	if err != nil {
		fmt.Println(err)
	}
	tileInput := strings.Split(input, lineBreak+lineBreak)
	tiles := getTiles(tileInput)
	var tilesWithNeighbours = make(map[int]tile)
	for id,tile := range tiles {
		tilesWithNeighbours[id] = getNeighboursForTile(id,tile,tiles)
	}
	var corners = make(map[int]tile)
	for key, tile := range tilesWithNeighbours {
		pictureEdges := 0
		for _, val := range tile.neighbourID{
			if val == 0 {
				pictureEdges++
			}
		}
		if pictureEdges == 2 {
			corners[key] = tile
		}
	}
	product := 1
	for key,_ := range corners {
		product *= key
	}
	fmt.Println(product)
	fmt.Println(tilesWithNeighbours[1951])
	var picture = make(map[[2]int]int)
	picture = getPictureStep(1951,tilesWithNeighbours,[]int{},picture)
}

func getPicture(corners map[int]tile, allTiles map[int]tile) map[[2]int]int {
	var picture = make(map[[2]int]int)
	//len := math.Sqrt(float64(len(allTiles)))
	currentTileId := 1951
	currentTile := corners[currentTileId]
	picture[[2]int{0,0}] = currentTileId
	printPicture(currentTile.picture)
	return picture
}

func getPictureStep(currentTile int,allTiles map[int]tile,visitedTiles []int, picture map[[2]int]int) map[[2]int]int{
	   	lenght := int(math.Sqrt(float64(len(allTiles))))
		position := [2]int{0,lenght}
		for allTiles[currentTile].neighbourID[1] != 0 || allTiles[currentTile].neighbourID[2] != 0{
			allTiles[currentTile] = rotate(allTiles[currentTile])
			fmt.Println(allTiles[currentTile])
		}
		fmt.Println(allTiles[currentTile])
		for len(visitedTiles) < 2{
			picture[position] = currentTile
			visitedTiles = append(visitedTiles,currentTile)
			nextPosition, _ := getNextPosition(position,lenght,picture)
			//nextTileId, _ := getNextTile(allTiles[currentTile].edges[edge],allTiles,currentTile)
			position = nextPosition
		}
		return picture
}

func getNextPosition(position [2]int, length int, picture map[[2]int]int) ([2]int, int) {
	if (position[1]+1) < length && picture[[2]int{position[0],position[1]+1}] == 0 {
		return [2]int{position[0],position[1]+1}, 0
	}
	if (position[0]-1) > 0 && picture[[2]int{position[0]-1,position[1]}] == 0 {
		return [2]int{position[0]-1,position[1]}, 3
	}
	if (position[1]-1) > 0 && picture[[2]int{position[0],position[1]-1}] == 0 {
		return [2]int{position[0],position[1]-1}, 2
	}
	if (position[0]+1) < 0 && picture[[2]int{position[0]+1,position[1]}] == 0 {
		return [2]int{position[0]+1,position[1]}, 1
	}
	return position, -1
}

func getNextTile(edge string, allTiles map[int]tile, currentTile int) (int, tile){
	var newTile tile
	for id, tile := range allTiles{
		if id != currentTile {
			if stringContains(edge, tile.edges[:]) {
				return id, tile
			}
			newTile = flip(tile, 0)
			if stringContains(edge, newTile.edges[:]) {
				return id, tile
			}
			newTile = flip(tile, 1)
			if stringContains(edge, newTile.edges[:]) {
				return id, tile
			}
		}
	}
	return 0,tile{}
}

func contains(elem int, array []int) bool{
	for _, val := range array{
		if val == elem{
			return true
		}
	}
	return false
}

func stringContains(elem string, array []string) bool{
	for _, val := range array{
		if val == elem{
			return true
		}
	}
	return false
}

func getSomeTile(m map[int]tile) int {
	for k := range m {
		return k
	}
	return 0
}

func printPicture(input[]string){
	for _, row := range input {
		fmt.Println(row)
	}
	fmt.Println()
}

func rotate(tile tile) tile{
	var newPicture [10][10]rune
	for i:= 0; i < len(tile.picture); i++ {
		for j:= 0; j < len(tile.picture); j++ {
			newPicture[i][j] = rune(tile.picture[len(tile.picture)-j-1][i]);
		}
	}
	var newNeighbourId [4]int
	newNeighbourId[0] = tile.neighbourID[3]
	for i,val := range tile.neighbourID[:2]{
		newNeighbourId[i+1] = val
	}
	tile.neighbourID = newNeighbourId
	for i,row := range newPicture {
		tile.picture[i] = string(row[:])
	}
	tile = setEdges(tile)
	return tile
}

//0 horizontal
//1 vertical
func flip(tile tile, direction int) tile{
	var newPicture []string
	if direction == 0{
		for i := len(tile.picture)-1; i >= 0; i--{
			newPicture = append(newPicture,tile.picture[i])
		}
	}
	if direction == 1{
		for _,val := range tile.picture {
			newPicture = append(newPicture,reverse(val))
		}
	}
	newEdges := getAllPossibleEdgesForFlippedTile(tile,direction)
	for i:= 0; i < len(tile.edges); i++{
		tile.edges[i] = newEdges[i]
	}
	tile.picture = newPicture
	return tile
}

func getTiles(input []string) map[int]tile{
	var tiles = make(map[int]tile)
	for _, val := range input {
		id, tile := getTile(val)
		tiles[id] = tile
	}
	return tiles
}

func getTile(input string) (int, tile) {
	tilesRow := strings.Split(input, lineBreak)
	var edges [4]string
	edges[0] = tilesRow[1]
	edges[2] = tilesRow[len(tilesRow)-1]
	for _, val := range tilesRow[1:] {
		edges[3] = edges[3]+val[0:1]
		edges[1] = edges[1]+val[len(val)-1:]
	}
	r, _ := regexp.Compile("[0-9]{4}")
	number, _ := strconv.Atoi(r.FindString(tilesRow[0]))
	tile := tile{[4]int{},edges, tilesRow[1:]}
	return number, setEdges(tile)
}

func setEdges(tile tile)tile{
	var newEdges [4]string
	newEdges[0] = tile.picture[0]
	newEdges[2] = tile.picture[len(tile.picture)-1]
	for _, val := range tile.picture {
		newEdges[3] = newEdges[3]+val[0:1]
		newEdges[1] = newEdges[1]+val[len(val)-1:]
	}
	tile.edges = newEdges
	return tile
}

func getAllPossibleEdgesForTile(tile tile, direction int) []string{
	switch direction {
	case -1:
		var edges [4]string
		for i, edge := range tile.edges {
			edges[i] = edge
		}
		return edges[:]
	case 0:
		return getAllPossibleEdgesForFlippedTile(tile,0)
	case 1:
		return getAllPossibleEdgesForFlippedTile(tile,1)
	}
	return []string{}
}

func getAllPossibleEdgesForFlippedTile(tile tile, direction int) []string{
	var edges [4]string
	for i:= 0; i < len(tile.edges); i ++ {
		if i % 2 == direction {
			edges[i] = reverse(tile.edges[i])
		}else{
			edges[i] = reverse(tile.edges[i])
		}
	}
	return edges[:]
}

func getNeighboursForTile(choosenTile int, tile tile, allTiles map[int]tile) tile{
	var idToPossibleEdges = make(map[int][]string)
	for id,tile := range allTiles {
		idToPossibleEdges[id] = getAllPossibleEdgesForTile(tile,-1)
		idToPossibleEdges[id] = append(idToPossibleEdges[id],getAllPossibleEdgesForTile(tile,0)...)
		idToPossibleEdges[id] = append(idToPossibleEdges[id],getAllPossibleEdgesForTile(tile,1)...)
	}
	for i, edge := range tile.edges {
		tile.neighbourID[i] = getFittingTileForEdge(choosenTile,edge,idToPossibleEdges)
	}
	return tile
}

func getFittingTileForEdge(choosenTile int, edge string, idToPossibleEdges map[int][]string) int{
	for id,edges := range idToPossibleEdges {
		if choosenTile != id && hasMatchingEdge(edges,edge){
			return id
		}
	}
	return 0
}

func hasMatchingEdge(edges []string, edge string) bool {
	for _, val := range edges {
		if val == edge{
			return true
		}
	}
	return false
}

func reverse(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)
}


