// Copyright 2014 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package blocks

import (
	"bytes"
	"image"
	_ "image/png"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/colorm"
	rblocks "github.com/hajimehoshi/ebiten/v2/examples/resources/images/blocks"
)

var imageBlocks *ebiten.Image

func init() {
	img, _, err := image.Decode(bytes.NewReader(rblocks.Blocks_png))
	if err != nil {
		panic(err)
	}
	imageBlocks = ebiten.NewImageFromImage(img)

}

type Angle int

const (
	Angle0 Angle = iota
	Angle90
	Angle180
	Angle270
)

func (a Angle) RotateRight() Angle {
	if a == Angle270 {
		return Angle0
	}
	return a + 1
}

func (a Angle) RotateLeft() Angle {
	if a == Angle0 {
		return Angle270
	}
	return a - 1
}

type BlockType int

const (
	BlockTypeNone BlockType = iota
	BlockType1
	BlockType2
	BlockType3
	BlockType4
	BlockType5
	BlockType6
	BlockType7
	BlockTypeMax = BlockType7
)

type Piece struct {
	blockType BlockType
	blocks    [][]bool
}

func transpose(bs [][]bool) [][]bool {
	blocks := make([][]bool, len(bs))
	for j, row := range bs {
		blocks[j] = make([]bool, len(row))
	}
	// Transpose the argument matrix.
	for i, col := range bs {
		for j, v := range col {
			blocks[j][i] = v
		}
	}
	return blocks
}

// Pieces is the set of all the possible pieces.
var Pieces map[BlockType]*Piece

func init() {
	const (
		f = false
		t = true
	)
	Pieces = map[BlockType]*Piece{
		BlockType1: {
			blockType: BlockType1,
			blocks: transpose([][]bool{
				{f, f, f, f},
				{t, t, t, t},
				{f, f, f, f},
				{f, f, f, f},
			}),
		},
		BlockType2: {
			blockType: BlockType2,
			blocks: transpose([][]bool{
				{t, f, f},
				{t, t, t},
				{f, f, f},
			}),
		},
		BlockType3: {
			blockType: BlockType3,
			blocks: transpose([][]bool{
				{f, t, f},
				{t, t, t},
				{f, f, f},
			}),
		},
		BlockType4: {
			blockType: BlockType4,
			blocks: transpose([][]bool{
				{f, f, t},
				{t, t, t},
				{f, f, f},
			}),
		},
		BlockType5: {
			blockType: BlockType5,
			blocks: transpose([][]bool{
				{t, t, f},
				{f, t, t},
				{f, f, f},
			}),
		},
		BlockType6: {
			blockType: BlockType6,
			blocks: transpose([][]bool{
				{f, t, t},
				{t, t, f},
				{f, f, f},
			}),
		},
		BlockType7: {
			blockType: BlockType7,
			blocks: transpose([][]bool{
				{t, t},
				{t, t},
			}),
		},
	}
}

const (
	blockWidth       = 10
	blockHeight      = 10
	fieldBlockCountX = 10
	fieldBlockCountY = 20
)

func drawBlock(r *ebiten.Image, block BlockType, x, y int, clr colorm.ColorM) {
	if block == BlockTypeNone {
		return
	}

	op := &colorm.DrawImageOptions{}
	op.GeoM.Translate(float64(x), float64(y))

	srcX := (int(block) - 1) * blockWidth
	colorm.DrawImage(r, imageBlocks.SubImage(image.Rect(srcX, 0, srcX+blockWidth, blockHeight)).(*ebiten.Image), clr, op)
}

func (p *Piece) InitialPosition() (int, int) {
	size := len(p.blocks)
	x := (fieldBlockCountX - size) / 2
	y := 0
Loop:
	for j := 0; j < size; j++ {
		for i := 0; i < size; i++ {
			if p.blocks[i][j] {
				break Loop
			}
		}
		y--
	}
	return x, y
}

// isBlocked returns a boolean value indicating whether
// there is a block at the position (x, y) of the piece with the given angle.
func (p *Piece) isBlocked(i, j int, angle Angle) bool {
	size := len(p.blocks)
	i2, j2 := i, j
	switch angle {
	case Angle0:
	case Angle90:
		i2 = j
		j2 = size - 1 - i
	case Angle180:
		i2 = size - 1 - i
		j2 = size - 1 - j
	case Angle270:
		i2 = size - 1 - j
		j2 = i
	}
	return p.blocks[i2][j2]
}

// collides returns a boolean value indicating whether
// the piece at (x, y) with the given angle would collide with the field's blocks.
func (p *Piece) collides(field *Field, x, y int, angle Angle) bool {
	size := len(p.blocks)
	for i := 0; i < size; i++ {
		for j := 0; j < size; j++ {
			if field.IsBlocked(x+i, y+j) && p.isBlocked(i, j, angle) {
				return true
			}
		}
	}
	return false
}

func (p *Piece) AbsorbInto(field *Field, x, y int, angle Angle) {
	size := len(p.blocks)
	for i := 0; i < size; i++ {
		for j := 0; j < size; j++ {
			if p.isBlocked(i, j, angle) {
				field.setBlock(x+i, y+j, p.blockType)
			}
		}
	}
}

func (p *Piece) DrawAtCenter(r *ebiten.Image, x, y, width, height int, angle Angle) {
	x += (width - len(p.blocks[0])*blockWidth) / 2
	y += (height - len(p.blocks)*blockHeight) / 2
	p.Draw(r, x, y, angle)
}

func (p *Piece) Draw(r *ebiten.Image, x, y int, angle Angle) {
	for i := range p.blocks {
		for j := range p.blocks[i] {
			if p.isBlocked(i, j, angle) {
				drawBlock(r, p.blockType, i*blockWidth+x, j*blockHeight+y, colorm.ColorM{})
			}
		}
	}

}
