package sidecheck

import (
	"strings"
)

// Constants for the different board piece types.
const (
	BOARD int = iota
	PIECE
	SELECTED
)

type coords struct {
	x, y int
}

func coordsAround(x, y int) []coords {
	return []coords{
		{x - 1, y - 1},
		{x - 1, y},
		{x - 1, y + 1},
		{x, y - 1},
		{x, y + 1},
		{x + 1, y - 1},
		{x + 1, y},
		{x + 1, y + 1},
	}
}

// Board defines a square or rectangular grid to represent a chess-style board.
type Board struct {
	rows, cols int
	board      [][]int
}

// NewBoard creates an empty Board with the given size.
func NewBoard(rows, cols int) *Board {
	board := Board{
		rows, cols,
		make([][]int, rows),
	}
	for i := 0; i < rows; i++ {
		board.board[i] = make([]int, cols)
	}
	return &board
}

// StringToBoard creates a Board from a string.
func StringToBoard(b string) *Board {
	rows := strings.Split(b, "\n")
	rowMax := len(rows)
	if rowMax == 0 {
		panic("Board needs to have at least 1 row")
	}
	colMax := len(rows[0])
	if colMax == 0 {
		panic("Board needs to have at least 1 column")
	}

	board := *NewBoard(rowMax, colMax)
	for i := 0; i < rowMax; i++ {
		if len(rows[i]) != colMax {
			panic("Column mismatch")
		}
		for j := 0; j < colMax; j++ {
			switch rows[i][j] {
			case '.':
				board.board[i][j] = BOARD
			case 'o':
				board.board[i][j] = PIECE
			default:
				panic("Unknown board char encountered")
			}
		}
	}
	return &board
}

// Piecep returns true if there's a piece at x, y.
func (b Board) Piecep(x, y int) bool {
	if x < 0 || y < 0 || x >= b.rows || y >= b.cols {
		return false
	}
	return b.board[x][y] == PIECE
}

// String converts Board to a string.
func (b Board) String() string {
	var result string
	spotChar := []string{".", "o", "+"}
	for _, row := range b.board {
		for _, spot := range row {
			result += spotChar[spot]
		}
		result += "\n"
	}
	return result
}

// NScheck checks if any pieces allow for a path across.
func (b Board) NScheck() bool {
	for j := 0; j < b.cols; j++ {
		if b.Piecep(0, j) {
			if found := b.nscheck(newPiece(0, j)); found {
				return true
			}
		}
	}
	return false
}

func (b Board) nscheck(p *piece) bool {
	if p.x == (b.rows - 1) {
		return true
	}
	cs := coordsAround(p.x, p.y)
	for _, c := range cs {
		if c.y > 0 && b.Piecep(c.x, c.y) && !p.seen(c.x, c.y) {
			next := newPiece(c.x, c.y)
			next.parent = p
			if found := b.nscheck(next); found {
				return true
			}
		}
	}
	return false
}

// WEcheck checks if any pieces allow for a path across.
func (b Board) WEcheck() bool {
	for i := 0; i < b.rows; i++ {
		if b.Piecep(i, 0) {
			if found := b.wecheck(newPiece(i, 0)); found {
				return true
			}
		}
	}
	return false
}

func (b Board) wecheck(p *piece) bool {
	if p.y == (b.cols - 1) {
		return true
	}
	cs := coordsAround(p.x, p.y)
	for _, c := range cs {
		if c.x > 0 && b.Piecep(c.x, c.y) && !p.seen(c.x, c.y) {
			next := newPiece(c.x, c.y)
			next.parent = p
			if found := b.wecheck(next); found {
				return true
			}
		}
	}
	return false
}

// piece tracks a connected line of pieces through a board.
type piece struct {
	x, y   int
	parent *piece
}

// newPiece creates a new node.
func newPiece(x, y int) *piece {
	p := &piece{
		x:      x,
		y:      y,
		parent: nil,
	}
	return p
}

// seen returns is a coord has been seen.
func (p piece) seen(x, y int) bool {
	pp := p.parent
	for pp != nil {
		if pp.x == x && pp.y == y {
			return true
		}
		pp = pp.parent
	}
	return false
}