package qrcode

import (
	"errors"
	"fmt"
)

var (
	// ErrorOutRangeOfW x out of range of Width
	ErrorOutRangeOfW = errors.New("out of range of width")

	// ErrorOutRangeOfH y out of range of Height
	ErrorOutRangeOfH = errors.New("out of range of height")
)

// newMatrix generate a matrix with map[][]qrbool
func newMatrix(width, height int) *Matrix {
	mat := make([][]qrvalue, width)
	for w := 0; w < width; w++ {
		mat[w] = make([]qrvalue, height)
	}

	m := &Matrix{
		mat:    mat,
		width:  width,
		height: height,
	}

	m.init()
	return m
}

// Matrix is a matrix data type
// width:3 height: 4 for [3][4]int
type Matrix struct {
	mat    [][]qrvalue
	width  int
	height int
}

// do some init work
func (m *Matrix) init() {
	for w := 0; w < m.width; w++ {
		for h := 0; h < m.height; h++ {
			m.mat[w][h] = QRValue_INIT_V0
		}
	}
}

// print to stdout
func (m *Matrix) print() {
	m.iter(IterDirection_ROW, func(x, y int, s qrvalue) {
		fmt.Printf("%s ", s)
		if (x + 1) == m.width {
			fmt.Println()
		}
	})
}

// Copy matrix into a new Matrix
func (m *Matrix) Copy() *Matrix {
	mat2 := make([][]qrvalue, m.width)
	for w := 0; w < m.width; w++ {
		mat2[w] = make([]qrvalue, m.height)
		copy(mat2[w], m.mat[w])
	}

	m2 := &Matrix{
		width:  m.width,
		height: m.height,
		mat:    mat2,
	}

	return m2
}

// Width ... width
func (m *Matrix) Width() int {
	return m.width
}

// Height ... height
func (m *Matrix) Height() int {
	return m.height
}

// set [w][h] as true
func (m *Matrix) set(w, h int, c qrvalue) error {
	if w >= m.width || w < 0 {
		return ErrorOutRangeOfW
	}
	if h >= m.height || h < 0 {
		return ErrorOutRangeOfH
	}
	m.mat[w][h] = c
	return nil
}

// at state qrvalue from matrix with position {x, y}
func (m *Matrix) at(w, h int) (qrvalue, error) {
	if w >= m.width || w < 0 {
		return QRValue_INIT_V0, ErrorOutRangeOfW
	}
	if h >= m.height || h < 0 {
		return QRValue_INIT_V0, ErrorOutRangeOfH
	}
	return m.mat[w][h], nil
}

// iterDirection scan matrix direction
type iterDirection uint8

const (
	// IterDirection_ROW for row first
	IterDirection_ROW iterDirection = iota + 1

	// IterDirection_COLUMN for column first
	IterDirection_COLUMN
)

// Iterate the Matrix with loop direction IterDirection_ROW major or IterDirection_COLUMN major.
// IterDirection_COLUMN is recommended.
func (m *Matrix) Iterate(direction iterDirection, fn func(x, y int, s QRValue)) {
	m.iter(direction, fn)
}

func (m *Matrix) iter(dir iterDirection, visitFn func(x int, y int, v qrvalue)) {
	// row direction first
	if dir == IterDirection_ROW {
		for h := 0; h < m.height; h++ {
			for w := 0; w < m.width; w++ {
				visitFn(w, h, m.mat[w][h])
			}
		}
		return
	}

	// column direction first
	for w := 0; w < m.width; w++ {
		for h := 0; h < m.height; h++ {
			visitFn(w, h, m.mat[w][h])
		}
	}
}

// Row return a row of matrix, cur should be y dimension.
func (m *Matrix) Row(cur int) []qrvalue {
	if cur >= m.height || cur < 0 {
		return nil
	}

	col := make([]qrvalue, m.height)
	for w := 0; w < m.width; w++ {
		col[w] = m.mat[w][cur]
	}
	return col
}

// Col return a slice of column, cur should be x dimension.
func (m *Matrix) Col(cur int) []qrvalue {
	if cur >= m.width || cur < 0 {
		return nil
	}

	return m.mat[cur]
}
