import { BrickDisplay, type BrickDisplayImage } from "@common/display/brick";
import { choice, delay, randInt, range } from "@common/utils";

import figuresImage from './assets/figures.png';
import placeSound from './assets/place.ogg';
import fillSound from './assets/fill.ogg';
import wrongSound from './assets/wrong.ogg';

let display: BrickDisplay;
const field: BrickDisplayImage = {
    image: [],
    width: 8,
    height: 8,
};

const figuresSpritesheet = BrickDisplay.convertImage(figuresImage);
const figures = extractFigures(figuresSpritesheet);
let currentFigure: BrickDisplayImage = generateFigure();
let nextFigure: BrickDisplayImage = generateFigure();
let currentFigureX = 4;
let currentFigureY = 14;
let currentFigureBlink: boolean;
let frame: number = 0;
const rowsToClear = new Set<number>();
const colsToClear = new Set<number>();

function extractFigures(spritesheet: BrickDisplayImage): BrickDisplayImage[] {
    const figures: BrickDisplayImage[] = [];

    for (let j = 0; j < spritesheet.height; j += 3) {
        for (let i = 0; i < spritesheet.width; i += 3) {
            const figure = BrickDisplay.extractSprite(spritesheet, i, j, 3, 3);
            figures.push(BrickDisplay.autoCrop(figure));
        }
    }

    return figures;
}

function generateFigure() {
    return BrickDisplay.rotateSprite(choice(figures), randInt(0, 4) * 90 as any);
}

function tryToPlace() {
    if (canPlaceFigure(currentFigure, currentFigureX - 1, currentFigureY - 1)) {
        let pixelsCount = 0;
        for (let y = currentFigureY; y < currentFigureY + currentFigure.height; y++) {
            for (let x = currentFigureX; x < currentFigureX + currentFigure.width; x++) {
                const fieldX = x - 1;
                const fieldY = y - 1;
                const figureX = x - currentFigureX;
                const figureY = y - currentFigureY;
                const px = currentFigure.image[figureY * currentFigure.width + figureX];
                if (px) {
                    pixelsCount++;
                    field.image[fieldY * field.width + fieldX] = true;
                }
            }
        }
        for (let y = currentFigureY; y < currentFigureY + currentFigure.height; y++) {
            let lineFilled = true;
            for (let fieldX = 0; fieldX < field.width; fieldX++) {
                const fieldY = y - 1;
                const px = field.image[fieldY * field.width + fieldX];
                if (!px) {
                    lineFilled = false;
                    break;
                }
            }

            if (lineFilled) {
                rowsToClear.add(y - 1);
            }
        }

        for (let x = currentFigureX; x < currentFigureX + currentFigure.width; x++) {
            let lineFilled = true;
            for (let fieldY = 0; fieldY < field.height; fieldY++) {
                const fieldX = x - 1;
                const px = field.image[fieldY * field.width + fieldX];
                if (!px) {
                    lineFilled = false;
                    break;
                }
            }

            if (lineFilled) {
                colsToClear.add(x - 1);
            }
        }

        if (rowsToClear.size > 0 || colsToClear.size > 0) {
            fillSound.currentTime = 0;
            fillSound.play();
        } else {
            placeSound.currentTime = 0;
            placeSound.play();
        }

        currentFigure = nextFigure;
        nextFigure = generateFigure()
        currentFigureX = 4;
        currentFigureY = 14;
        display.score += pixelsCount;

        if (!canPlaceAnywhere(currentFigure)) {
            display.gameOver = true;
            wrongSound.currentTime = 0;
            wrongSound.play();
        }
    } else {
        wrongSound.currentTime = 0;
        wrongSound.play();
    }
}

function canPlaceFigure(figure: BrickDisplayImage, checkX: number, checkY: number): boolean {
    for (let y = checkY; y < checkY + figure.height; y++) {
        for (let x = checkX; x < checkX + figure.width; x++) {
            if (colsToClear.has(x) || rowsToClear.has(y)) continue;
            const figureX = x - checkX;
            const figureY = y - checkY;
            const figurePx = figure.image[figureY * figure.width + figureX];
            const fieldPx = field.image[y * field.width + x];
            if (figurePx && fieldPx) {
                return false;
            }
        }
    }

    return true;
}

function canPlaceAnywhere(figure: BrickDisplayImage): boolean {
    for (let y = 0; y <= field.height - figure.height; y++) {
        for (let x = 0; x <= field.width - figure.width; x++) {
            if (range(4).some(i => canPlaceFigure(BrickDisplay.rotateSprite(figure, (i * 90) as any), x, y))) {
                return true;
            }
        }
    }

    return false;
}

function reset() {
    currentFigure = generateFigure();
    nextFigure = generateFigure();
    currentFigureX = 4;
    currentFigureY = 14;
    field.image = [];
    display.score = 0;
    display.gameOver = false;
}

function onKeyDown(e: KeyboardEvent) {
    if (rowsToClear.size > 0 || colsToClear.size > 0) {
        return;
    }
    const isUp = e.code === 'ArrowUp' || e.code === 'KeyW';
    const isDown = e.code === 'ArrowDown' || e.code === 'KeyS';
    const isLeft = e.code === 'ArrowLeft' || e.code === 'KeyA';
    const isRight = e.code === 'ArrowRight' || e.code === 'KeyD';
    if (display.gameOver) {
        if (e.code === 'Space') {
            reset();
        }
    } else if (isUp && currentFigureY > 1) {
        currentFigureY--;
    } else if (isDown && currentFigureY < (20 - currentFigure.height)) {
        currentFigureY++;
    } else if (isLeft && currentFigureX > 1) {
        currentFigureX--;
    } else if (isRight && currentFigureX < (9 - currentFigure.width)) {
        currentFigureX++;
    } else if (e.code === 'Space') {
        if (currentFigureY <= (9 - currentFigure.height)) {
            tryToPlace();
        } else {
            currentFigure = BrickDisplay.rotateSprite(currentFigure, 90);
        }
    }
    e.stopPropagation();
    e.preventDefault();
    return false;
}

async function loop() {
    frame++;
    if (frame % 6 === 0) {
        currentFigureBlink = !currentFigureBlink;
    }

    if (rowsToClear.size > 0 || colsToClear.size > 0) {
        display.score += Math.pow(rowsToClear.size + colsToClear.size, 2) * 100;
        for (let i = 0; i < field.width; i++) {
            for (const y of rowsToClear) {
                field.image[y * field.width + i] = false;
            }
            for (const x of colsToClear) {
                field.image[i * field.width + x] = false;
            }
            display.fillRect(1, 1, field.width, field.height, false);
            display.drawImage(field, 1, 1);
            display.update();
            await delay(20);
        }
        rowsToClear.clear();
        colsToClear.clear();
    }

    display.clear();
    display.drawRect(0, 0, 9, 9);

    display.drawImage(field, 1, 1);

    if (currentFigureBlink) {
        display.drawImage(currentFigure, currentFigureX, currentFigureY, false, true);
    }

    display.clear(true);
    display.drawImage(nextFigure, 0, 0, true);

    display.update();
    requestAnimationFrame(loop);
}

export default async function main() {
    display = new BrickDisplay();
    display.init();

    display.helpText = <>
        <div>Try not to</div>
        <div>Run Out Of Space</div>
        <div>Arrows - move</div>
        <div>Space - rotate on bottom</div>
        <div>Space - place on top</div>
    </>;

    document.addEventListener('keydown', onKeyDown);
    requestAnimationFrame(loop);
}