const Phaser = require('phaser');

const Item = require('./elements/item');

const {
    TEXT,
    POSITIONS: { ITEM_LIST },
} = require('./constants');

const X = ITEM_LIST.x();
const Y = ITEM_LIST.y();
const WIDTH = ITEM_LIST.width();
const HEIGHT = ITEM_LIST.height();
const ITEM_WIDTH = ITEM_LIST.itemWidth();
const ITEM_HEIGHT = ITEM_LIST.itemHeight();

const BOX_X = X + ITEM_WIDTH * 0.5;
const BOX_Y = Y + ITEM_HEIGHT + ITEM_HEIGHT * 2;
const BOX_ROWS = 6;
const BOX_COLUMNS = 3;

const INV_X = X + ITEM_WIDTH * 0.5;
const INV_Y = Y + ITEM_HEIGHT * 12;
const INV_ROWS = 3;
const INV_COLUMNS = 3;

const COMB_X = X + ITEM_WIDTH * 0.5;
const COMB_Y = Y + ITEM_HEIGHT * 19;
const COMB_ROWS = 1;
const COMB_COLUMNS = 3;

const drawVbox = (graphics) => {
    const boxDrawX = BOX_X - ITEM_WIDTH * 0.05;
    const boxDrawY = BOX_Y - ITEM_HEIGHT * 0.05;
    graphics.strokeRect(boxDrawX, boxDrawY, ITEM_WIDTH * 1.1 * BOX_COLUMNS, ITEM_HEIGHT * 1.1 * BOX_ROWS);

    for (let i = 0; i < (BOX_COLUMNS - 1); i += 1) {
        const x = boxDrawX + (i + 1) * ITEM_WIDTH * 1.1;
        graphics.lineBetween(x, boxDrawY, x, boxDrawY + ITEM_HEIGHT * 1.1 * BOX_ROWS);
    }
    for (let i = 0; i < (BOX_ROWS - 1); i += 1) {
        const y = boxDrawY + (i + 1) * ITEM_HEIGHT * 1.1;
        graphics.lineBetween(boxDrawX, y, boxDrawX + ITEM_WIDTH * 1.1 * BOX_COLUMNS, y);
    }
};

const drawInventory = (graphics) => {
    const invDrawX = INV_X - ITEM_WIDTH * 0.05;
    const invDrawY = INV_Y - ITEM_HEIGHT * 0.05;
    graphics.strokeRect(invDrawX, invDrawY, ITEM_WIDTH * 1.1 * INV_COLUMNS, ITEM_HEIGHT * 1.1 * INV_ROWS);

    for (let i = 0; i < (INV_COLUMNS - 1); i += 1) {
        const x = invDrawX + (i + 1) * ITEM_WIDTH * 1.1;
        graphics.lineBetween(x, invDrawY, x, invDrawY + ITEM_HEIGHT * 1.1 * INV_ROWS);
    }
    for (let i = 0; i < (INV_ROWS - 1); i += 1) {
        const y = invDrawY + (i + 1) * ITEM_HEIGHT * 1.1;
        graphics.lineBetween(invDrawX, y, invDrawX + ITEM_WIDTH * 1.1 * INV_COLUMNS, y);
    }
};

const drawCombiner = (graphics) => {
    const combDrawX = COMB_X - ITEM_WIDTH * 0.05;
    const combDrawY = COMB_Y - ITEM_HEIGHT * 0.05;
    graphics.strokeRect(combDrawX, combDrawY, ITEM_WIDTH * 1.1 * COMB_COLUMNS, ITEM_HEIGHT * 1.1 * COMB_ROWS);

    for (let i = 0; i < (COMB_COLUMNS - 1); i += 1) {
        const x = combDrawX + (i + 1) * ITEM_WIDTH * 1.1;
        graphics.lineBetween(x, combDrawY, x, combDrawY + ITEM_HEIGHT * 1.1 * COMB_ROWS);
    }
    for (let i = 0; i <= (COMB_ROWS - 1); i += 1) {
        const y = combDrawY + (i + 1) * ITEM_HEIGHT * 1.1;
        graphics.lineBetween(combDrawX, y, combDrawX + ITEM_WIDTH * 1.1 * COMB_COLUMNS, y);
    }
};

class CombinerHitBox extends Phaser.GameObjects.Rectangle {
    constructor(scene, x, y, i) {
        super(scene, x, y, ITEM_WIDTH, ITEM_HEIGHT, 0x000000);
        this.setOrigin(0);
        this.x = x;
        this.y = y;
        this.slot = i;
        this.item = false;
        this.itemSelect = () => this.setFillStyle(0x222222);
        this.itemDeselect = () => this.setFillStyle(0x000000);
    }

    allocate(item) {
        if (this.item) this.deallocate();
        item.setPosition(this.x + item.width / 2, this.y + item.height / 2);
        this.item = item;
    }

    deallocate() {
        if (this.item) this.item.setPosition(this.item.origX, this.item.origY);
        this.item = false;
    }
}

class DeleteHitBox extends Phaser.GameObjects.Rectangle {
    constructor(scene, x, y) {
        super(scene, x, y, ITEM_WIDTH * 3.4, ITEM_HEIGHT * 1.25, 0x444444);
        this.setOrigin(0);
        this.itemSelect = () => this.setFillStyle(0xff0000);
        this.itemDeselect = () => this.setFillStyle(0x444444);
    }
}


const itemCheckHitbox = (scene, pointer) => {
    const { list } = scene.scene.get('MenuCrypList').children;
    const hitboxes = list.filter(c => c.cryp)
        .concat(scene.children.list.filter(c => c instanceof CombinerHitBox || c instanceof DeleteHitBox));

    let found;
    for (let i = 0; i < hitboxes.length; i += 1) {
        if (Phaser.Geom.Rectangle.ContainsPoint(hitboxes[i].getBounds(), pointer.position)) {
            found = hitboxes[i];
        } else {
            hitboxes[i].itemDeselect();
        }
    }

    return found;
};

class ItemList extends Phaser.Scene {
    constructor() {
        super({ key: 'ItemList', active: true });
    }

    updateData(parent, key, data) {
        if (key === 'player' || key === 'scores') {
            this.registry.events.off('changedata', this.updateData, this);
            this.registry.events.off('setdata', this.updateData, this);
            this.scene.restart();
        }
    }

    create() {
        const player = this.registry.get('player');
        const scores = this.registry.get('scores') || [];
        if (!player) return false;
        const { vbox } = player;
        this.registry.events.on('changedata', this.updateData, this);
        this.registry.events.on('setdata', this.updateData, this);
        if (!vbox.bound) return false;
        if (!this.combinerItems || vbox.bound.length < this.registry.get('boundLength')) {
            this.combinerItems = [-1, -1, -1];
        }
        this.registry.set('boundLength', vbox.bound.length);
        const ws = this.registry.get('ws');

        // Static Elements
        const graphics = this.add.graphics();
        graphics.lineStyle(5, 0x808080, 1.0);
        drawCombiner(graphics);
        drawInventory(graphics);
        drawVbox(graphics);

        this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 0.5, `vBox - ${vbox.bits}b`, TEXT.HEADER);
        this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 11, 'inventory', TEXT.HEADER);
        this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 18, 'iCombinator', TEXT.HEADER);
        this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 23, `Wins: ${player.score.wins}`, TEXT.HEADER);
        this.add.text(X + ITEM_WIDTH * 0.5, Y + ITEM_HEIGHT * 24, `Losses: ${player.score.losses}`, TEXT.HEADER);

        const discard = this.add
            .rectangle(X + ITEM_WIDTH * 0.4, Y + ITEM_HEIGHT * 1.5, ITEM_WIDTH * 3.4, ITEM_HEIGHT * 1.25, 0x444444)
            .setInteractive()
            .setOrigin(0)
            .on('pointerdown', () => this.registry.get('ws').sendVboxDiscard(vbox.instance));
        this.add.text(discard.getCenter().x, discard.getCenter().y, 'discard - 5b', TEXT.HEADER)
            .setOrigin(0.5, 0.5);

        const combine = this.add
            .rectangle(X + ITEM_WIDTH * 0.4, Y + ITEM_HEIGHT * 20.25, ITEM_WIDTH * 3.4, ITEM_HEIGHT * 1.25, 0x444444)
            .setInteractive()
            .setOrigin(0)
            .on('pointerdown', () => {
                ws.sendVboxCombine(vbox.instance, this.combinerItems);
                this.combinerItems = [-1, -1, -1];
                this.children.list.filter(obj => obj instanceof CombinerHitBox).forEach(cBox => cBox.deallocate());
            });
        this.add.text(combine.getCenter().x, combine.getCenter().y, 'combine', TEXT.HEADER)
            .setOrigin(0.5, 0.5);

        for (let i = 0; i < 3; i += 1) {
            const ITEM_X = ITEM_WIDTH * 1.1 * (i % COMB_COLUMNS) + COMB_X;
            const ITEM_Y = ITEM_HEIGHT * 1.1 * Math.floor(i / COMB_COLUMNS) + COMB_Y;
            this.add.existing(new CombinerHitBox(this, ITEM_X, ITEM_Y, i));
        }
        const del = this.add.existing(new DeleteHitBox(this, X + ITEM_WIDTH * 0.4, Y + ITEM_HEIGHT * 15.5));
        this.add.text(del.getCenter().x, del.getCenter().y, 'drop', TEXT.HEADER)
            .setOrigin(0.5, 0.5);

        // Generate Items
        vbox.bound.forEach((item, i) => {
            const ITEM_X = ITEM_WIDTH * 1.1 * (i % INV_COLUMNS) + INV_X + ITEM_WIDTH * 0.5;
            const ITEM_Y = ITEM_HEIGHT * 1.1 * Math.floor(i / INV_COLUMNS) + INV_Y + ITEM_HEIGHT * 0.5;
            const clickFn = () => this.registry.set('itemInfo', { item });
            const itemBox = new Item(this, item, i, ITEM_X, ITEM_Y, ITEM_WIDTH, ITEM_HEIGHT);
            itemBox.on('pointerdown', clickFn);

            this.input.setDraggable(itemBox);
            this.add.existing(itemBox);
        });

        vbox.free.forEach((type, i) => {
            type.forEach((item, j) => {
                const ITEM_X = ITEM_WIDTH * 1.1 * i + BOX_X + ITEM_WIDTH * 0.5;
                const ITEM_Y = ITEM_HEIGHT * 1.1 * j + BOX_Y + ITEM_HEIGHT * 0.5;
                const clickFn = () => {
                    this.registry.set('itemInfo', { item });
                    ws.sendVboxAccept(vbox.instance, i, j);
                };
                const itemBox = new Item(this, item, i, ITEM_X, ITEM_Y, ITEM_WIDTH, ITEM_HEIGHT);
                itemBox.on('pointerdown', clickFn);
                this.add.existing(itemBox);
            });
        });

        // Restore previous combiner item slots
        this.combinerItems.forEach((index, i) => {
            if (index === -1) return false;
            const item = this.children.list.filter(obj => obj instanceof Item).find(it => it.index === index);
            const hitBox = this.children.list.filter(obj => obj instanceof CombinerHitBox).find(hb => hb.slot === i);
            if (item) hitBox.allocate(item);
            return true;
        });

        // allocation functions
        const allocate = (item, hitBox) => {
            hitBox.allocate(item);
            this.combinerItems[hitBox.slot] = item.index;
            this.registry.set('combinerItems', this.combinerItems);
        };

        const deallocate = (item) => {
            const clearIndex = this.combinerItems.indexOf(item.index);
            if (clearIndex !== -1) {
                this.children.list.filter(obj => obj instanceof CombinerHitBox)
                    .forEach((cBox) => {
                        if (cBox.item === item) cBox.deallocate();
                    });
                this.combinerItems[clearIndex] = -1;
                this.registry.set('combinerItems', this.combinerItems);
            }
            item.setPosition(item.origX, item.origY);
        };

        const findUnallocated = () => {
            for (let i = 0; i <= 2; i += 1) {
                if (this.combinerItems[i] === -1) {
                    return this.children.list.filter(obj => obj instanceof CombinerHitBox).find(hb => hb.slot === i);
                }
            } return false;
        };

        // this.add.text(ITEM_WIDTH * 11, ITEM_HEIGHT * 1.1, 'Scoreboard', TEXT.HEADER);
        // scores.forEach(([name, score], i) => {
        //     const SCORE_X = ITEM_WIDTH * 11;
        //     const SCORE_Y = ITEM_HEIGHT * 1.1 * (i + 2);
        //     this.add.text(SCORE_X, SCORE_Y, `${score.wins} - ${score.losses} | ${name}`, TEXT.NORMAL);
        // });

        // Add Handlers
        this.input.on('dragstart', (pointer, item) => {
            if (!(item instanceof Item)) return false;
            return true;
        });

        this.input.on('drag', (pointer, item, dragX, dragY) => {
            if (!(item instanceof Item)) return false;
            item.setPosition(dragX, dragY);
            const hitBox = itemCheckHitbox(this, pointer);
            if (hitBox) hitBox.itemSelect();
            return true;
        });

        this.input.on('dragend', (pointer, item) => {
            if (!(item instanceof Item)) return false;
            // Check first for hitbox interaction
            const hitBox = itemCheckHitbox(this, pointer);
            if (hitBox) {
                // hitbox can only be the combinerhitbox, deletehitbox or cryp avatar
                hitBox.itemDeselect();
                if (hitBox instanceof CombinerHitBox) {
                    if (hitBox.item === item) deallocate(item);
                    else allocate(item, hitBox);
                } else if (hitBox instanceof DeleteHitBox) {
                    ws.sendVboxReclaim(vbox.instance, item.index);
                } else {
                    ws.sendVboxApply(vbox.instance, hitBox.cryp.id, item.index);
                    deallocate(item);
                } return true;
            }
            // If not interacting with hitbox and didn't move much try to allocate the item
            if (Math.hypot(item.x - item.origX, item.y - item.origY) < Math.hypot(item.width, item.height)) {
                // Check theres a free combiner slot
                const cBox = findUnallocated();
                if (cBox) {
                    allocate(item, cBox);
                    return true;
                }
            }
            // If the item hasn't been allocated above reset to natural location
            // Check if item needs to be deallocated
            // Scene will restart if there is vbox change
            deallocate(item);
            return true;
        });
        return this;
    }

    cleanUp() {
        this.registry.events.off('changedata', this.updateData, this);
        this.registry.events.off('setdata', this.updateData, this);
        this.scene.remove();
    }
}

module.exports = ItemList;
