"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("../util");
const BaseForLines_1 = require("./BaseForLines");
const colors_1 = require("./colors");
// BitSet does not export a default so import does not work in webpack-built file
const BitSet2 = require('bitset'); // tslint:disable-line:no-var-requires
class GameSprite extends BaseForLines_1.BaseForLines {
    constructor(source, name, optionalLegendChar) {
        super(source);
        this.name = name;
        this._optionalLegendChar = optionalLegendChar;
        this.trickleCells = new Set();
        this.trickleTiles = new Set();
        this.trickleTilesWithModifier = new Set();
        this.allSpritesBitSetIndex = -1; // will be changed once we have all the sprites
    }
    isOr() {
        return false;
    }
    equals(t) {
        return this === t; // sprites MUST be exact
    }
    getName() {
        return this.name;
    }
    isBackground() {
        return this.name.toLowerCase() === 'background';
    }
    _getDescendantTiles() {
        return [];
    }
    getSprites() {
        // to match the signature of LegendTile
        return [this];
    }
    getSpritesForRuleAction() {
        return this.getSprites();
    }
    hasCollisionLayer() {
        return !!this.collisionLayer;
    }
    hasSingleCollisionLayer() {
        // always true. This is only ever false for OR tiles
        return this.hasCollisionLayer();
    }
    setCollisionLayer(collisionLayer) {
        this.collisionLayer = collisionLayer;
    }
    setCollisionLayerAndIndex(collisionLayer, bitSetIndex) {
        this.collisionLayer = collisionLayer;
        this.collisionLayerIndex = bitSetIndex;
        this.bitSet = new BitSet2();
        this.bitSet.set(bitSetIndex);
    }
    getBitSet() {
        return this.bitSet;
    }
    getBitSetIndex() {
        return this.collisionLayerIndex;
    }
    getCollisionLayer() {
        if (!this.collisionLayer) {
            throw new Error(`ERROR: This sprite was not in a Collision Layer\n${this.toString()}`);
        }
        return this.collisionLayer;
    }
    isInvalid() {
        if (!this.collisionLayer) {
            return 'This object does not have an entry in the COLLISIONLAYERS section.';
        }
        return null;
    }
    clearCaches() {
        this.trickleCells.clear();
    }
    hasCell(cell) {
        return this.trickleCells.has(cell);
    }
    matchesCell(cell) {
        return cell.getSpritesAsSet().has(this);
    }
    getSpritesThatMatch(cell) {
        if (cell.getSpritesAsSet().has(this)) {
            return new Set([this]);
        }
        else {
            return new Set();
        }
    }
    subscribeToCellChanges(t) {
        this.trickleTilesWithModifier.add(t);
    }
    subscribeToCellChangesTile(tile) {
        this.trickleTiles.add(tile);
    }
    addCell(cell, wantsToMove) {
        this.addCells(this, [cell], wantsToMove);
    }
    removeCell(cell) {
        this.removeCells(this, [cell]);
    }
    updateCell(cell, wantsToMove) {
        if (process.env.NODE_ENV === 'development') {
            // check that the cell is already in the sprite cell set
            if (!this.has(cell)) {
                throw new Error(`BUG: Expected cell to already be in the sprite set`);
            }
        }
        // propagate up
        for (const t of this.trickleTiles) {
            t.updateCells(this, [cell], wantsToMove);
        }
        for (const t of this.trickleTilesWithModifier) {
            t.updateCells(this, [cell], wantsToMove);
        }
    }
    addCells(sprite, cells, wantsToMove) {
        for (const cell of cells) {
            if (this.trickleCells.has(cell)) {
                throw new Error(`BUG: should not be trying to add a cell that has already been matched (right?)`);
            }
            this.trickleCells.add(cell);
        }
        // propagate up
        for (const t of this.trickleTiles) {
            t.addCells(this, cells, wantsToMove);
        }
        for (const t of this.trickleTilesWithModifier) {
            t.addCells(this, this, cells, wantsToMove);
        }
    }
    updateCells(sprite, cells, wantsToMove) {
        // propagate up
        for (const t of this.trickleTiles) {
            t.updateCells(this, cells, wantsToMove);
        }
        for (const t of this.trickleTilesWithModifier) {
            t.updateCells(this, cells, wantsToMove);
        }
    }
    removeCells(sprite, cells) {
        for (const cell of cells) {
            this.trickleCells.delete(cell);
        }
        // propagate up
        for (const t of this.trickleTiles) {
            t.removeCells(this, cells);
        }
        for (const t of this.trickleTilesWithModifier) {
            t.removeCells(this, this, cells);
        }
    }
    has(cell) {
        return this.trickleCells.has(cell);
    }
    hasNegationTileWithModifier() {
        for (const t of this.trickleTilesWithModifier) {
            if (t.isNo()) {
                return true;
            }
        }
        for (const tile of this.trickleTiles) {
            if (tile.hasNegationTileWithModifier()) {
                return true;
            }
        }
        return false;
    }
    getCellsThatMatch() {
        return this.trickleCells;
    }
}
exports.GameSprite = GameSprite;
class GameSpriteSingleColor extends GameSprite {
    constructor(source, name, optionalLegendChar, colors) {
        super(source, name, optionalLegendChar);
        this.color = colors[0]; // Ignore if the user added multiple colors (like `transparent yellow`)
    }
    hasPixels() {
        return false;
    }
    getPixels(spriteHeight, spriteWidth) {
        // When there are no pixels then it means "color the whole thing in the same color"
        const rows = [];
        for (let row = 0; row < spriteHeight; row++) {
            rows.push([]);
            for (let col = 0; col < spriteWidth; col++) {
                rows[row].push(this.color);
            }
        }
        return rows;
    }
}
exports.GameSpriteSingleColor = GameSpriteSingleColor;
class GameSpritePixels extends GameSprite {
    constructor(source, name, optionalLegendChar, colors, pixels) {
        super(source, name, optionalLegendChar);
        this.colors = colors;
        this.pixels = pixels.map((row) => {
            return row.map((col) => {
                if (col === '.') {
                    return new colors_1.TransparentColor(this.__source);
                }
                else {
                    return this.colors[col];
                }
            });
        }); // Pixel colors are 0-indexed.
    }
    isInvalid() {
        if (super.isInvalid()) {
            return super.isInvalid();
        }
        let isInvalid = null;
        const colorLen = this.colors.length;
        const rowLen = this.pixels[0].length;
        this.pixels.forEach((row) => {
            if (row.length !== rowLen) {
                isInvalid = `Row lengths do not match. Expected ${rowLen} but got ${row.length}. Row: ${row}`;
            }
            // Check that only '.' or a digit that is less than the number of colors is present
            row.forEach((pixel) => {
                if (pixel !== '.') {
                    if (pixel >= colorLen) {
                        isInvalid = `Pixel number is too high (${pixel}). There are only ${colorLen} colors defined`;
                    }
                }
            });
        });
        return isInvalid;
    }
    getSprites() {
        // to match the signature of LegendTile
        return [this];
    }
    hasPixels() {
        return true;
    }
    getPixels(spriteHeight, spriteWidth) {
        // Make a copy because others may edit it
        return this.pixels.map((row) => {
            return row.map((col) => col);
        });
    }
}
exports.GameSpritePixels = GameSpritePixels;
class GameLegendTile extends BaseForLines_1.BaseForLines {
    constructor(source, spriteNameOrLevelChar, tiles) {
        super(source);
        this.spriteNameOrLevelChar = spriteNameOrLevelChar;
        this.tiles = tiles;
        this.trickleCells = new Set();
        this.trickleTilesWithModifier = new Set();
    }
    equals(t) {
        if (this.isOr() !== t.isOr()) {
            return false;
        }
        const difference = util_1.setDifference(new Set(this.getSprites()), t.getSprites());
        return difference.size === 0;
    }
    isOr() {
        return false;
    }
    isInvalid() {
        if (!this.hasCollisionLayer()) {
            return 'Missing collision layer';
        }
        return null;
    }
    getName() {
        return this.spriteNameOrLevelChar;
    }
    getSpritesForRuleAction() {
        return this.getSprites();
    }
    _getDescendantTiles() {
        // recursively pull all the tiles out
        return this.tiles.concat(util_1._flatten(this.tiles.map((tile) => tile._getDescendantTiles())));
    }
    getSprites() {
        // Use a cache because all the collision layers have not been loaded in time
        if (!this.spritesCache) {
            // 2 levels of indirection should be safe
            // Sort by collisionLayer so that the most-important sprite is first
            this.spritesCache = util_1._flatten(this.tiles.map((tile) => {
                return tile.getSprites();
            })).sort((a, b) => {
                return a.getCollisionLayer().id - b.getCollisionLayer().id;
            }).reverse();
        }
        return this.spritesCache;
    }
    hasCollisionLayer() {
        return !!this.collisionLayer;
    }
    setCollisionLayer(collisionLayer) {
        this.collisionLayer = collisionLayer;
    }
    getCollisionLayer() {
        // OR tiles and AND tiles don't necessarily have a collisionLayer set so pull it from the sprite (this might not work)
        if (this.collisionLayer) {
            return this.collisionLayer;
        }
        // check that all sprites are in the same collisionlayer... if not, thene our understanding is flawed
        const firstCollisionLayer = this.getSprites()[0].getCollisionLayer();
        for (const sprite of this.getSprites()) {
            if (sprite.getCollisionLayer() !== firstCollisionLayer) {
                throw new Error(`ooh, sprites in a tile have different collision layers... that's a problem\n${this.toString()}`);
            }
        }
        return firstCollisionLayer;
    }
    getCellsThatMatch() {
        const matches = new Set();
        for (const sprite of this.getSprites()) {
            for (const cell of sprite.getCellsThatMatch()) {
                matches.add(cell);
            }
        }
        return matches;
    }
    subscribeToCellChanges(t) {
        this.trickleTilesWithModifier.add(t);
        // subscribe this to be notified of all Sprite changes of Cells
        for (const sprite of this.getSprites()) {
            sprite.subscribeToCellChangesTile(this);
        }
    }
    hasNegationTileWithModifier() {
        for (const t of this.trickleTilesWithModifier) {
            if (t.isNo()) {
                return true;
            }
        }
        return false;
    }
    addCells(sprite, cells, wantsToMove) {
        for (const cell of cells) {
            if (!this.trickleCells.has(cell)) {
                if (this.matchesCell(cell)) {
                    this.trickleCells.add(cell);
                    for (const t of this.trickleTilesWithModifier) {
                        t.addCells(this, sprite, [cell], wantsToMove);
                    }
                }
            }
        }
    }
    updateCells(sprite, cells, wantsToMove) {
        // verify that all the cells are in trickleCells
        if (process.env.NODE_ENV === 'development') {
            for (const cell of cells) {
                if (!this.trickleCells.has(cell)) {
                    throw new Error(`Cell was not already added before`);
                }
            }
        }
        for (const t of this.trickleTilesWithModifier) {
            t.updateCells(sprite, cells, wantsToMove);
        }
    }
    removeCells(sprite, cells) {
        for (const cell of cells) {
            if (this.matchesCell(cell)) {
                if (!this.trickleCells.has(cell)) {
                    this.addCells(sprite, [cell], null);
                }
                else {
                    // We need to propagate this is an OR tile
                    // because removing one of the OR tiles
                    // may (or may not) cause this cell to
                    // no longer match
                    this.updateCells(sprite, [cell], null);
                }
            }
            else {
                this.trickleCells.delete(cell);
                for (const t of this.trickleTilesWithModifier) {
                    t.removeCells(this, sprite, [cell]);
                }
            }
        }
    }
    hasCell(cell) {
        return this.trickleCells.has(cell);
    }
}
exports.GameLegendTile = GameLegendTile;
class GameLegendTileSimple extends GameLegendTile {
    constructor(source, spriteNameOrLevelChar, tile) {
        super(source, spriteNameOrLevelChar, [tile]);
    }
    matchesCell(cell) {
        // Update code coverage (Maybe only count the number of times it was true?)
        if (process.env.NODE_ENV === 'development') {
            this.__incrementCoverage();
        }
        // Check that the cell contains all of the tiles (ANDED)
        // Since this is a Simple Tile it should only contain 1 tile so anding is the right way to go.
        for (const tile of this.tiles) {
            if (!tile.matchesCell(cell)) {
                return false;
            }
        }
        return true;
    }
    getSpritesThatMatch(cell) {
        return util_1.setIntersection(new Set(this.getSprites()), cell.getSpritesAsSet());
    }
    hasSingleCollisionLayer() {
        return !!this.collisionLayer;
    }
}
exports.GameLegendTileSimple = GameLegendTileSimple;
class GameLegendTileAnd extends GameLegendTile {
    matchesCell(cell) {
        // Update code coverage (Maybe only count the number of times it was true?)
        if (process.env.NODE_ENV === 'development') {
            this.__incrementCoverage();
        }
        // Check that the cell contains all of the tiles (AND)
        for (const tile of this.tiles) {
            if (!tile.matchesCell(cell)) {
                return false;
            }
        }
        return true;
    }
    getSpritesThatMatch(cell) {
        // return setIntersection(new Set(this.getSprites()), cell.getSpritesAsSet())
        throw new Error(`BUG: This method should only be called for OR tiles`);
    }
    hasSingleCollisionLayer() {
        return !!this.collisionLayer;
    }
}
exports.GameLegendTileAnd = GameLegendTileAnd;
class GameLegendTileOr extends GameLegendTile {
    isOr() {
        return true;
    }
    matchesCell(cell) {
        // Update code coverage (Maybe only count the number of times it was true?)
        if (process.env.NODE_ENV === 'development') {
            this.__incrementCoverage();
        }
        // Check that the cell contains any of the tiles (OR)
        for (const tile of this.tiles) {
            if (tile.matchesCell(cell)) {
                return true;
            }
        }
        return false;
    }
    getSpritesThatMatch(cell) {
        return util_1.setIntersection(new Set(this.getSprites()), cell.getSpritesAsSet());
    }
    hasSingleCollisionLayer() {
        const sprites = this.getSprites();
        for (const sprite of sprites) {
            if (sprite.getCollisionLayer() !== sprites[0].getCollisionLayer()) {
                return false;
            }
        }
        return true;
    }
}
exports.GameLegendTileOr = GameLegendTileOr;
//# sourceMappingURL=tile.js.map