import { Color, Glyphs, isCorner, isHorizontal, isVertical, TextRegion } from "@common/display/text";
import { gameLoop } from "@common/game";
import type { Point } from "@common/geometry";
import Input from "@common/input";
import { BSP } from "@common/level/bsp";
import { bresenhamCircleGen } from "@common/navigation/bresenham";
import { SeededRandom } from "@common/random";
import { Factions } from "@common/rpg/components/faction";
import { getPosition, move, Position } from "@common/rpg/components/position";
import { createViewport } from "@common/rpg/components/render/viewport";
import { Sprite } from "@common/rpg/components/sprite";
import { Serialization } from "@common/rpg/core/serialization";
import { World } from "@common/rpg/core/world";
import { TextDisplaySystem } from "@common/rpg/systems/render/text";
import { Resources } from "@common/rpg/utils/resources";
import { resolveVariables } from "@common/rpg/utils/variables";

const WALL = '#';
const PLAYER = '@';

function createMap(world: World, random: SeededRandom) {
    const MAP_SIZE = 100;
    const mapData = new Array(MAP_SIZE * MAP_SIZE).fill(WALL);

    BSP.generateLevel(MAP_SIZE, MAP_SIZE, (x, y) => { mapData[x + y * MAP_SIZE] = '.'; }, {
        minWidth: 10,
        minHeight: 8,
        depth: 10,
        random,
    });
    const mapDataString: string[] = [];
    const maskDataString: string[] = [];
    for (let i = 0; i < MAP_SIZE; i++) {
        mapDataString.push(mapData.slice(i * MAP_SIZE, (i + 1) * MAP_SIZE).join(''));
    }
    for (let i = 0; i < MAP_SIZE * 3; i++) {
        maskDataString.push(Array.from({ length: MAP_SIZE * 3 }, () => ' ').join(''));
    }

    const map = world.createEntity('map');
    map.add(new Sprite(Resources.add('map', new TextRegion(mapDataString))));
    map.add(new Position(0, 0, -2));

    const mask = world.createEntity('mask');
    mask.add(new Sprite(Resources.add('mask', new TextRegion(maskDataString))));
    mask.add(new Position(-MAP_SIZE, -MAP_SIZE, 100));

    return { map, mask };
}

function createPlayer(world: World, x: number, y: number) {
    const player = world.createEntity('player');
    player.add(new Position(x, y, 10));
    player.add(new Sprite(Resources.add('player', new TextRegion(PLAYER, Color.YELLOW))));
    Factions.join(player, 'players');

    return player;
}

function createEnemy(world: World, x: number, y: number, sprite: string, ...factions: string[]) {
    const enemy = world.createEntity('enemy_*');
    enemy.add(new Position(x, y, 9));
    enemy.add(new Sprite(sprite));
    factions.forEach(faction => Factions.join(enemy, faction));

    return enemy;
}

function handleMovement() {
    let dx = 0;
    let dy = 0;

    const isReleasedOrRepeated = (...keys: Input.KeyCode[]) =>
        keys.some(key => Input.isReleased(key) || Input.isRepeated(key));

    const left = isReleasedOrRepeated(Input.KeyCode.LEFT, Input.KeyCode.H);
    const right = isReleasedOrRepeated(Input.KeyCode.RIGHT, Input.KeyCode.L);
    const up = isReleasedOrRepeated(Input.KeyCode.UP, Input.KeyCode.K);
    const down = isReleasedOrRepeated(Input.KeyCode.DOWN, Input.KeyCode.J);

    const upLeft = isReleasedOrRepeated(Input.KeyCode.Y);
    const upRight = isReleasedOrRepeated(Input.KeyCode.U);
    const downLeft = isReleasedOrRepeated(Input.KeyCode.B);
    const downRight = isReleasedOrRepeated(Input.KeyCode.N);

    if (left) dx -= 1;
    if (right) dx += 1;
    if (up) dy -= 1;
    if (down) dy += 1;

    if (upLeft) {
        dx -= 1;
        dy -= 1;
    }
    if (upRight) {
        dx += 1;
        dy -= 1;
    }
    if (downLeft) {
        dx -= 1;
        dy += 1;
    }
    if (downRight) {
        dx += 1;
        dy += 1;
    }

    return { dx, dy };
}

export default gameLoop(() => {
    Input.setRepeatInterval(50);
    const world = new World();
    const display = world.addSystem(new TextDisplaySystem(100, 25)).display;

    const random = new SeededRandom();
    const { map, mask } = createMap(world, random);

    const mapData = Resources.get(TextRegion, map.get(Sprite)!.image)!;
    const emptyCells: Point[] = [];
    for (let x = 0; x < mapData.width; x++) {
        for (let y = 0; y < mapData.height; y++) {
            if (mapData.get(x, y)[0] === '.') {
                emptyCells.push({ x, y });
            }
        }
    }

    const maskData = Resources.get(TextRegion, mask.get(Sprite)!.image)!;
    const startCell = random.choice(emptyCells);

    const enemyCell = random.choice(emptyCells.filter(c => c !== startCell));

    const viewport = createViewport(world, {
        width: display.width,
        height: display.height,
        worldX: startCell.x - (display.width >> 1),
        worldY: startCell.y - (display.height >> 1),
        screenX: 0,
        screenY: 0,
    });
    const player = createPlayer(world, startCell.x, startCell.y);

    Resources.add('goblin', new TextRegion('g', Color.DARK_GREEN));
    const goblins = Factions.getFaction(world, 'goblins');
    const monsters = Factions.getFaction(world, 'monsters');

    Factions.setReputationOf(goblins.entity, 'players', -100);
    Factions.setReputationOf(monsters.entity, 'players', -100);

    Factions.setReputationOf(goblins.entity, 'monsters', 100);
    Factions.setReputationOf(monsters.entity, 'goblins', 100);

    const enemy = createEnemy(world, enemyCell.x, enemyCell.y, 'goblin', 'goblins', 'monsters');
    const state = {
        display,
        world,
        map, mapData,
        mask, maskData, maskDirty: true,
        random,
        viewport,
        player,
        enemy,
        isWall: (x: number, y: number): boolean => {
            const [ch] = state.mapData.get(x, y);
            return ch === WALL || isVertical(ch) || isHorizontal(ch) || isCorner(ch);
        },
        updateMask: () => {
            if (!state.maskDirty) return;

            const { mapData, maskData, player, isWall } = state;

            const playerPos = getPosition(player)!;
            for (let x = 0; x < mapData.width; x++) {
                for (let y = 0; y < mapData.height; y++) {
                    const [ch, fg] = maskData.get(x + mapData.width, y + mapData.height);
                    if (ch !== ' ' && fg !== Color.GRAY) {
                        maskData.set(x + mapData.width, y + mapData.height, [mapData.get(x, y)[0], Color.GRAY]);
                    }
                }
            }
            for (const { x, y } of bresenhamCircleGen(playerPos.x, playerPos.y, 10, { fill: 'shadow', breaker: isWall })) {
                maskData.set(x + mapData.width, y + mapData.height, '\0');
            }
            state.maskDirty = false;
        }
    };

    Factions.setReputationIn(player, 'monsters', 5);
    console.log(Factions.getReputation(enemy, player));

    return state;
}, (dt, state) => {
    const {
        world,
        viewport,
        player,
        updateMask,
        isWall,
    } = state;

    const hasInput = Input.hasReleased() || Input.hasRepeated();
    const playerPos = getPosition(player)!;

    if (hasInput) {
        if (Input.isHeld(Input.KeyCode.SHIFT)) {
        } else {
            let { dx, dy } = handleMovement();

            if (isWall(playerPos.x + dx, playerPos.y)) {
                dx = 0;
            }
            if (isWall(playerPos.x, playerPos.y + dy)) {
                dy = 0;
            }
            if (isWall(playerPos.x + dx, playerPos.y + dy)) {
                dx = 0
                dy = 0;
            }

            if (dx || dy) {
                move(player, dx, dy);
                move(viewport, dx, dy);

                playerPos.x + dx;
                playerPos.y + dy;
            }
        }

        // TODO environment step

        state.maskDirty = true;
    }

    updateMask();
    world.update(dt);
});
