/*:
 * @target MZ
 * @author Aerosys
 * @plugindesc [Tier 3] [Version 1.1.0] [MV & MZ]
 * @base MK_SandboxEngine
 * 
 * 
 * @command openBuildMenu
 * @text Open Build Menu
 * 
 * @arg category
 * @text Category (optional)
 * @desc when not given, all learned Blueprints are displayed
 * 
 * 
 * @param buildSelectWindow
 * @text Build Select Window
 * 
 * @param buildSelectWindowColumns
 * @parent buildSelectWindow
 * @text Number of Columns
 * @type number
 * @default 5
 * 
 * @param buildSelectWindowRows
 * @parent buildSelectWindow
 * @text Number of Rows
 * @type number
 * @default 1
 * 
 * @param a
 * @text _
 * 
 * @param materialCostsWindow
 * @text Material Costs Window
 * 
 * @param showMaterialCostsWindow
 * @parent materialCostsWindow
 * @text show?
 * @type boolean
 * @default true
 * @desc When you have no build costs in your game at all, you may want to hide this window entirely
 * 
 * @param materialCostsWindowRows
 * @parent materialCostsWindow
 * @text Number of Rows
 * @type number
 * @default 3
 * 
 * @param goldIcon
 * @parent materialCostsWindow
 * @text Gold Icon
 * @type icon
 * @default 314
 * 
 * @param staminaIcon
 * @parent materialCostsWindow
 * @text Stamina Icon
 * @type icon
 * @default 164
 * 
 * @param staminaText
 * @parent materialCostsWindow
 * @text Stamina Text
 * @default Stamina
 * 
 * @param leftSideText
 * @parent materialCostsWindow
 * @text Text (left side)
 * @default \I[%1] %2:
 * @desc Use %1 for Item Icon, %2 for Item Name
 * 
 * @param rightSideText
 * @parent materialCostsWindow
 * @text Text (right side)
 * @default \C[%1]%2 / %3
 * @desc Use %1 for Text Color, %2 for required quantity, %3 for quantity in Inventory
 * 
 * @param textWhenNoCosts
 * @parent materialCostsWindow
 * @text Text (No costs)
 * @default \C[3]Unlimited
 * @desc You may use Text Codes
 * 
 * @param textColors
 * @parent materialCostsWindow
 * @text Text Colors
 * 
 * @param textColorWhenOk
 * @parent textColors
 * @text When items in inventory
 * @type color
 * @default 3
 * 
 * @param textColorWhenNotOk
 * @parent textColors
 * @text When item is missing
 * @type color
 * @default 10
 * 
 * 
 * @param b
 * @text _
 * 
 * @param descriptionWindow
 * @text Blueprint Description Window
 * 
 * @param showDescriptionWindow
 * @parent descriptionWindow
 * @text show?
 * @type boolean
 * @default true
 * 
 * @param blueprintDescriptionWindowDrawEval
 * @parent descriptionWindow
 * @text JS: Draw
 * @type note
 * @default "const text = arguments[0];\nconst nLines = text.split('\\n').length;\nconst textHeight = nLines.clamp(1, 3) * this.lineHeight();\nconst y1 = this.contentsHeight() / 2 - textHeight / 2;\n\n(text).split('\\n').forEach((line, i) => {\n    this.drawTextEx(\n        line,\n        0,\n        i * this.lineHeight() + y1,\n        this.contentsWidth(),\n    )\n});"
 * 
 * 
 * @param c
 * @text _
 * 
 * @param commonEvents
 * @text Common Events
 * @type struct<CommonEvents>
 * @desc All Common Events are optional
 */

/*~struct~CommonEvents:
 *
 * @param onOpenMenuCommonEventId
 * @text Open Build Menu
 * @type common_event
 * 
 * @param onCloseMenuCommonEventId
 * @text Close Build Menu
 * @type common_event
 */


(function() {


const PLUGIN_NAME = 'MK_BuildMenu';

const reject = (reason) => {
    const message = (
        'An Error has occurred in the Plugin %1: %2 ' +
        'If the problem persists, contact the Plugin Creator.'
    ).format(PLUGIN_NAME, reason);
    throw Error(message);
}

if (!PluginManager._parameters[PLUGIN_NAME.toLowerCase()]) {
    reject((
        "Please check that this plugin's filename is \"%1.js\". " +
        "Subdirectories (e.g.: js/plugins/xy/thisPlugin.js) are not allowed."
    ).format(PLUGIN_NAME));
}

const structure = (serialized, parameterName) => {
    if (!serialized) {
        reject((
            "The Plugin Parameter \"%1\" is missing. " +
            "Please check it in the Plugin Manager. It may help to re-install this Plugin (i.e.: remove, re-add)."
        ).format(parameterName));
    }
    try {
        return JSON.parse(serialized);
    
    } catch (e) {
        reject((
            "The Plugin Parameter \"%1\" is corrupted. " +
            "Please check it in the Plugin Manager. It may help to re-install this Plugin (i.e.: remove, re-add)."
        ).format(parameterName));
    }
}

const customFunction = (body, parameterName) => {
    if (!body) {
        reject((
            'The Plugin Parameter "%1" is missing. ' +
            'Please check it in the Plugin Manager. It may help to re-install this Plugin (i.e.: remove, re-add).'
        ).format(parameterName));
    }
    try {
        return new Function(JSON.parse(body));
    
    } catch (e) {
        reject((
            'The Plugin Parameter "%1" contains an error and could not be interpreted. ' +
            'Please check it in the Plugin Manager. It may also help to re-install this Plugin (i.e.: remove, re-add). ' +
            'Cause: %2'
        ).format(parameterName, e));
    }
}

const requirePlugin = (plugin, pluginName) => {
    if (!plugin) reject('The required Plugin "%1" is missing. Please add it to the Plugin Manager.'.format(pluginName));
}
requirePlugin(MK.Sandbox, 'MK_SandboxEngine');


const params                            = PluginManager.parameters(PLUGIN_NAME);
const columns                           = Number(params.buildSelectWindowColumns) || 5;
const nRows                             = Number(params.buildSelectWindowRows) || 1;
const textColorWhenOk                   = Number(params.textColorWhenOk);
const textColorWhenNotOk                = Number(params.textColorWhenNotOk);
const showMaterialCostsWindow           = 'true' == params.showMaterialCostsWindow;
const materialCostsWindowRows           = Number(params.materialCostsWindowRows) || 3;
const showDescriptionWindow             = 'true' == params.showDescriptionWindow;
const blueprintDescriptionWindowDrawEval = customFunction(
                                                params.blueprintDescriptionWindowDrawEval,
                                                'Description Window Draw',
);
const commonEvents                      = params.commonEvents && structure(params.commonEvents, 'Common Events') || { };
const onOpenMenuCommonEventId           = Number(commonEvents.onOpenMenuCommonEventId);
const onCloseMenuCommonEventId          = Number(commonEvents.onCloseMenuCommonEventId);
const downscaleFactors                  = [1, 2, 3, 4];

MK.BuildMenu                    = { };
MK.BuildMenu.goldIcon           = Number(params.goldIcon);
MK.BuildMenu.staminaIcon        = Number(params.staminaIcon);
MK.BuildMenu.staminaText        = params.staminaText;
MK.BuildMenu.leftSideText       = params.leftSideText;
MK.BuildMenu.rightSideText      = params.rightSideText;
MK.BuildMenu.textWhenNoCosts    = params.textWhenNoCosts;


const getIndex = (x, y, z, width, height) => (z * height + y) * width + x;
const getTile = (dataMap, x, y, z) => dataMap.data[getIndex(x, y, z, dataMap.width, dataMap.height)];

const imageCache = { };


function getDownscaledImage(folder, name, factor, f) {
    const key = '%1%2_%3'.format(folder, name, factor);
    
    if (imageCache[key]) {
        f(imageCache[key]);
    } else {
        const bitmap = ImageManager.loadBitmap(folder, name);
        bitmap.addLoadListener(() => {
            const downscaledBitmap = downscale(bitmap, factor);
            imageCache[key] = downscaledBitmap;

            f(downscaledBitmap);
        });
    }
}

function downscale(bitmap, factor) {
    if (factor == 1) return bitmap;
    
    const source = bitmap._canvas || bitmap._image;
    const toReturn = new Bitmap(bitmap.width / factor, bitmap.height / factor);

    toReturn.clearRect(0, 0, toReturn.width, toReturn.height);
    toReturn.context.globalCompositeOperation = 'source-over';

    toReturn.context.drawImage(
        source,
        0,
        0,
        bitmap.width,
        bitmap.height,
        0,
        0,
        bitmap.width / factor,
        bitmap.height / factor,
    );
    return toReturn;
}


// =====================================================================================
// Window Build Select
// =====================================================================================

function Window_BuildSelect() {
    this.initialize(...arguments);
}

Window_BuildSelect.prototype = Object.create(Window_Selectable.prototype);
Window_BuildSelect.prototype.constructor = Window_BuildSelect;

Window_BuildSelect.prototype.initialize = function(/*rectangle*/) {
    Window_Selectable.prototype.initialize.apply(this, arguments);

    this.createCancelButton();

    const tileset = $dataTilesets[$gameMap.tilesetId()];

    if (tileset) {
        tileset.tilesetNames.forEach((name, index) => {
            downscaleFactors.forEach(factor => {
                this['tilesetBitmaps' + factor] = this['tilesetBitmaps' + factor] || [ ];

                if (name) {
                    getDownscaledImage(
                        'img/tilesets/',
                        name,
                        factor,
                        (bitmap) => {
                            this['tilesetBitmaps' + factor][index] = bitmap;
                            this.refresh();
                        },
                    );
                } else {
                    this['tilesetBitmaps' + factor][index] = null;
                }
            });
        });
    }
}

Window_BuildSelect.prototype.setCategory = function(category) {
    if (this._category != category) {
        this._category = category;
        this.refresh();
    }
}

Window_BuildSelect.prototype.createCancelButton = function() {
    if (ConfigManager.touchUI) {
        this.updatePadding();
        
        this._cancelButton = new Sprite_Button('cancel');
        this._cancelButton.x = this.width - this._cancelButton.width;
        this._cancelButton.y = 0 - this._cancelButton.height - 2;

        this.addChild(this._cancelButton);
    }
}

Window_BuildSelect.prototype.refresh = function() {
    this._data = $gameParty.learnedBlueprints()
        .filter(blueprint => this.includes(blueprint))
        .map(blueprint => this.mapBlueprint(blueprint));
    
    Window_Selectable.prototype.refresh.call(this);
}

Window_BuildSelect.prototype.includes = function(blueprint) {
    return (
        blueprint &&
        (!this._category || this._category == blueprint.category)
    );
}

Window_BuildSelect.prototype.mapBlueprint = function(blueprint) {
    const result    = { };
    const regionId  = MK.Sandbox.getRegionIdsFromBlueprint(blueprint)[0];
    const assetInfo = MK.MapManipulation.getAssetInfo(blueprint.sourceMapName, regionId);

    for (let attr in assetInfo) { result[attr] = assetInfo[attr] };
    for (let attr in blueprint) { result[attr] = blueprint[attr] };
    
    return result;
}

Window_BuildSelect.prototype.maxCols = function() {
    return columns;
}

Window_BuildSelect.prototype.maxItems = function() {
    return this._data ? this._data.length : 1;
}

Window_BuildSelect.prototype.itemHeight = function() {
    return this.itemWidth();
}

Window_BuildSelect.prototype.itemAt = function(index) {
    return this._data && this._data[index];
}

Window_BuildSelect.prototype.item = function() {
    return this.itemAt(this.index());
}

Window_BuildSelect.prototype.drawItem = function(index) {
    const asset = this.itemAt(index);
    const rectangle = this.itemRect(index);

    asset && this.performDraw(rectangle, asset);
}

Window_BuildSelect.prototype.performDraw = function(rectangle, asset) {
    const downscaleFactor = this.determineDownscaleFactor(rectangle, asset);
    const w = asset.width * $gameMap.tileWidth() / downscaleFactor;
    const h = asset.height * $gameMap.tileHeight() / downscaleFactor;
    
    const rectangle2 = new Rectangle(
        rectangle.width / 2 - w / 2 + rectangle.x,
        rectangle.height / 2 - h / 2 + rectangle.y,
        w,
        h,
    );
    
    for (let x = 0; x < asset.width; x++) {
        for (let y = 0; y < asset.height; y++) {
            for (let z = 0; z < 4; z++) {
                this.drawSpot(rectangle2, asset, x, y, z, downscaleFactor);
                this.drawEvent(rectangle2, asset, x, y, downscaleFactor);
            }
        }
    }
}

Window_BuildSelect.prototype.determineDownscaleFactor = function(rectangle, asset) {
    return downscaleFactors.find(f => 
        asset.width * $gameMap.tileWidth() / f < rectangle.width &&
        asset.height * $gameMap.tileHeight() / f < rectangle.height
    ) || Math.max(...downscaleFactors);
}

Window_BuildSelect.prototype.drawSpot = function(rectangle, asset, x, y, z, downscaleFactor) {
    const sourceMap = MK.MapManipulation.getSourceMap(asset.sourceMapName);
    const tileId = getTile(sourceMap, asset.x + x, asset.y + y, z);

    if (Tilemap.isTileA5(tileId)) {
        const tilesetNumber = 4;
        this.drawNormalTile(rectangle, tilesetNumber, tileId, x, y, downscaleFactor);
    }
    else if (Tilemap.isAutotile(tileId)) {
        this.drawAutotile(rectangle, tileId, x, y, downscaleFactor);
    }
    else if (tileId) {
        const tilesetNumber = Math.floor(tileId / 256) + 5;
        this.drawNormalTile(rectangle, tilesetNumber, tileId, x, y, downscaleFactor);
    }
}

Window_BuildSelect.prototype.drawAutotile = function(rectangle, tileId, x, y, downscaleFactor) {
    const w = $gameMap.tileWidth();
    const h = $gameMap.tileHeight();
    const w1 = w / 2 / downscaleFactor;
    const h1 = h / 2 / downscaleFactor;
    const kind = Tilemap.getAutotileKind(tileId);
    const shape = Tilemap.getAutotileShape(tileId);
    const tx = kind % 8;
    const ty = Math.floor(kind / 8);
    let autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
    let tilesetNumber, bx, by;

    if (Tilemap.isTileA1(tileId)) {
        tilesetNumber = 0;

        if (kind == 0) {
            bx = 0;
            by = 0;
        }
        else if (kind == 1) {
            bx = 0;
            by = 3;
        }
        else if (kind == 2) {
            bx = 6;
            by = 0;
        }
        else if (kind == 3) {
            bx = 6;
            by = 3;
        }
        else {
            bx = Math.floor(tx / 4) * 8;
            by = ty * 6 + (Math.floor(tx / 2) % 2) * 3;

            if (kind % 2 != 0) {
                bx += 6;
                autotileTable = Tilemap.WATERFALL_AUTOTILE_TABLE;
            }
        }
    }
    if (Tilemap.isTileA2(tileId)) {
        tilesetNumber = 1;
        bx = tx * 2;
        by = (ty - 2) * 3;
    }
    if (Tilemap.isTileA3(tileId)) {
        tilesetNumber = 2;
        bx = tx * 2;
        by = (ty - 6) * 2;
        autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
    }
    if (Tilemap.isTileA4(tileId)) {
        tilesetNumber = 3;
        bx = tx * 2;
        by = Math.floor((ty - 10) * 2.5 + (ty % 2 === 1 ? 0.5 : 0));
        if (ty % 2 === 1) {
            autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
        }
    }
    
    for (let i = 0; i < 4; i++) {
        const qsx = autotileTable[shape][i][0];
        const qsy = autotileTable[shape][i][1];
        const sx  = (bx * 2 + qsx) * w1;
        const sy  = (by * 2 + qsy) * h1;
        const dx  = (i % 2) * w1 + (w * x) / downscaleFactor + rectangle.x;
        const dy  = Math.floor(i / 2) * h1 + (h * y) / downscaleFactor + rectangle.y;
        
        this.blt(
            tilesetNumber,
            sx,
            sy,
            w1,
            h1,
            dx,
            dy,
            downscaleFactor,
        );
    }
}

Window_BuildSelect.prototype.drawNormalTile = function(rectangle, tilesetNumber, tileId, x, y, downscaleFactor) {
    const w = $gameMap.tileWidth() / downscaleFactor;
    const h = $gameMap.tileHeight() / downscaleFactor;
    const sx = ((Math.floor(tileId / 128) % 2) * 8 + (tileId % 8)) * w;
    const sy = (Math.floor((tileId % 256) / 8) % 16) * h;
    const dx = w * x + rectangle.x;
    const dy = h * y + rectangle.y;

    this.blt(
        tilesetNumber,
        sx,
        sy,
        w,
        h,
        dx,
        dy,
        downscaleFactor,
    );
}

Window_BuildSelect.prototype.drawEvent = function(rectangle, asset, x, y, downscaleFactor) {
    const sourceMap = MK.MapManipulation.getSourceMap(asset.sourceMapName);
    const event = sourceMap.events.find(event =>
        event &&
        event.x == x + asset.x &&
        event.y == y + asset.y
    );

    if (event) {
        const page              = event.pages[0];
        const characterIndex    = page.image.characterIndex;
        const characterName     = page.image.characterName;
        const direction         = page.image.direction;
        const pattern           = page.image.pattern;
        const tileId            = page.image.tileId;

        if (characterName) {
            getDownscaledImage('img/characters/', characterName, downscaleFactor,
                (bitmap) => {
                    const isBigCharacter    = ImageManager.isBigCharacter(characterName);
                    const isObjectCharacter = ImageManager.isObjectCharacter(characterName);
                    const pw                = bitmap.width / (isBigCharacter ? 3 : 12);
                    const ph                = bitmap.height / (isBigCharacter ? 4 : 8);

                    const characterBlockX   = isBigCharacter
                                                ? 0
                                                : Math.floor(characterIndex % 4) * 3;
                    const characterBlockY   = isBigCharacter
                                                ? 0
                                                : Math.floor(characterIndex / 4) * 4;
                    const characterPatternY = (direction - 2) / 2;
                    const sx                = (characterBlockX + pattern) * pw;
                    const sy                = (characterBlockY + characterPatternY) * ph;

                    const dx = ((x + 0.5) * $gameMap.tileWidth() - pw / 2) + rectangle.x;
                    const dy = ((y + 1) * $gameMap.tileHeight() - ph - (isObjectCharacter ? 0 : 6)) + rectangle.y;

                    this.contents.blt(
                        bitmap,
                        sx,
                        sy,
                        pw,
                        ph,
                        dx,
                        dy,
                    );
                }
            )
        }
        if (tileId) {
            const pw = $gameMap.tileWidth() / downscaleFactor;
            const ph = $gameMap.tileHeight() / downscaleFactor;
            const sx = ((Math.floor(tileId / 128) % 2) * 8 + (tileId % 8)) * pw;
            const sy = (Math.floor((tileId % 256) / 8) % 16) * ph;
            const setNumber = Math.floor(tileId / 256) + 5;
            const bitmap = this['tilesetBitmaps' + downscaleFactor][setNumber];

            if (bitmap) {
                this.contents.blt(
                    bitmap,
                    sx,
                    sy,
                    pw,
                    ph,
                    x * pw + rectangle.x,
                    y * ph + rectangle.y,
                );
            }
        }
    }
}

Window_BuildSelect.prototype.blt = function(tilesetNumber, sx, sy, w, h, dx, dy, factor) {
    const container = this['tilesetBitmaps' + factor];
    const tilesetBitmap = container && container[tilesetNumber];

    if (tilesetBitmap) {
        this.contents.blt(
            tilesetBitmap,
            sx,
            sy,
            w,
            h,
            dx,
            dy,
        );
    } else {
        this.contents.clearRect(dx, dy, w, h);
    }
}

Window_BuildSelect.prototype.update = function() {
    Window_Selectable.prototype.update.call(this);

    if (this.index() !== this._lastIndex) {
        this._lastIndex = this.index();
        this._materialsWindow && this._materialsWindow.setBlueprint(this.item());
        this._descriptionWindow && this._descriptionWindow.setText(this.item() && this.item().description);
    }
}

Window_BuildSelect.prototype.isCurrentItemEnabled = function() {
    return this.isEnabled(this.item());
}

Window_BuildSelect.prototype.isEnabled = function(blueprint) {
    return blueprint && MK.Sandbox.canBuild(blueprint.blueprintName);
}


// =====================================================================================
// Window Materials
// =====================================================================================

function Window_RequiredMaterials() {
    this.initialize(...arguments);
}

Window_RequiredMaterials.prototype = Object.create(Window_Base.prototype);
Window_RequiredMaterials.prototype.constructor = Window_RequiredMaterials;

Window_RequiredMaterials.prototype.setBlueprint = function(blueprint) {
    if (this.blueprint != blueprint) {
        this.blueprint = blueprint;

        this.refresh();
    }
}

Window_RequiredMaterials.prototype.refresh = function() {
    this.contents && this.contents.clear();
    this.contentsBack && this.contentsBack.clear();
    
    this.draw();
}

Window_RequiredMaterials.prototype.draw = function() {
    if (!this.blueprint) return;

    if (this.isUnlimited()) {
        this.drawUnlimited();
    } else {
        this.drawWithCosts();
    }
}

Window_RequiredMaterials.prototype.drawUnlimited = function() {
    const text = MK.BuildMenu.textWhenNoCosts || 'Unlimited';

    this.drawTextEx(
        text,
        this.contentsWidth() / 2 - this.getTextWidth(text) / 2,
        this.contentsHeight() / 2 - this.lineHeight() / 2,
    );
}

Window_RequiredMaterials.prototype.drawWithCosts = function() {
    const n = this.blueprint.requiredItems.length
                + (this.blueprint.gold ? 1 : 0)
                + (this.blueprint.staminaCost ? 1 : 0);
    
    const height    = n * this.lineHeight();
    const y1        = this.contentsHeight() / 2 - height / 2;
    
    this.blueprint.requiredItems.forEach((entry, index) => {
        const item = $dataItems[entry.itemId];
        if (!item) return;

        const requiredQuantity = Math.round(entry.quantity * $gameSystem.buildCostModifier());
        const availableQuantity = $gameParty.numItems(item);

        this.drawLine(
            index * this.lineHeight() + y1,
            item.iconIndex,
            item.name,
            requiredQuantity,
            availableQuantity,
        );
    });

    if (this.blueprint.gold) {
        const index = this.blueprint.requiredItems.length;
        const requiredQuantity = Math.round(this.blueprint.gold * $gameSystem.buildCostModifier());
        const availableQuantity = $gameParty.gold();

        this.drawLine(
            index * this.lineHeight() + y1,
            MK.BuildMenu.goldIcon,
            TextManager.currencyUnit,
            requiredQuantity,
            availableQuantity,
        );
    }

    if (this.blueprint.staminaCost) {
        const index = this.blueprint.requiredItems.length
                        + (this.blueprint.gold ? 1 : 0);
        const requiredQuantity = Math.round(this.blueprint.staminaCost * $gameSystem.buildCostModifier());
        const availableQuantity = MK.Sandbox.currentStamina();

        this.drawLine(
            index * this.lineHeight() + y1,
            MK.BuildMenu.staminaIcon,
            MK.BuildMenu.staminaText || 'Stamina',
            requiredQuantity,
            availableQuantity,
        );
    }
}

Window_RequiredMaterials.prototype.drawLine = function(y, iconIndex, itemName, requiredQuantity, availableQuantity) {
    const text1 = MK.BuildMenu.leftSideText.format(iconIndex, itemName);
    const text2 = MK.BuildMenu.rightSideText.format(
        this.getTextColor(requiredQuantity <= availableQuantity),
        requiredQuantity,
        availableQuantity,
    );

    this.drawTextEx(text1, 5, y);
    this.drawTextEx(text2, this.contentsWidth() - this.getTextWidth(text2) - 10, y);
}

Window_RequiredMaterials.prototype.isUnlimited = function() {
    return !this.blueprint || (
        this.blueprint.requiredItems.every(entry =>
            Math.round(entry.quantity * $gameSystem.buildCostModifier()) <= 0
        ) &&
        Math.round(this.blueprint.gold * $gameSystem.buildCostModifier()) <= 0 &&
        Math.round(this.blueprint.staminaCost * $gameSystem.buildCostModifier()) <= 0
    );
}

Window_RequiredMaterials.prototype.getTextWidth = function(text) {
    return 'MZ' == Utils.RPGMAKER_NAME
        ? this.textSizeEx(text).width
        : this.drawTextEx(text, 0, this.contents.height);
}

Window_RequiredMaterials.prototype.getTextColor = function(ok) {
    return ok ? textColorWhenOk : textColorWhenNotOk;
}

const alias_GameParty_gainItem = Game_Party.prototype.gainItem;
Game_Party.prototype.gainItem = function() {
    alias_GameParty_gainItem.apply(this, arguments);

    if (SceneManager._scene._requiredMaterialsWindow)
        SceneManager._scene._requiredMaterialsWindow.refresh();
}

const alias_GameParty_gainGold = Game_Party.prototype.gainGold;
Game_Party.prototype.gainGold = function() {
    alias_GameParty_gainGold.apply(this, arguments);

    if (SceneManager._scene._requiredMaterialsWindow)
        SceneManager._scene._requiredMaterialsWindow.refresh();
}

const alias_GameVariables_setValue = Game_Variables.prototype.setValue;
Game_Variables.prototype.setValue = function(variableId, value) {
    alias_GameVariables_setValue.apply(this, arguments);

    if (SceneManager._scene._requiredMaterialsWindow)
        SceneManager._scene._requiredMaterialsWindow.refresh();
}


// =====================================================================================
// Window Description
// =====================================================================================

function Window_BlueprintDescription() {
    this.initialize(...arguments);
}

Window_BlueprintDescription.prototype = Object.create(Window_Base.prototype);
Window_BlueprintDescription.prototype.constructor = Window_BlueprintDescription;

Window_BlueprintDescription.prototype.initialize = function(/* arguments */) {
    Window_Base.prototype.initialize.apply(this, arguments);

    this.refresh();
}

Window_BlueprintDescription.prototype.setText = function(text) {
    if (this._text != text) {
        this._text = text;
        this.refresh();
    }
}

Window_BlueprintDescription.prototype.refresh = function() {
    this.contents && this.contents.clear();
    this.contentsBack && this.contentsBack.clear();

    blueprintDescriptionWindowDrawEval.call(this, this._text || '');
}


// =====================================================================================
// MK
// =====================================================================================

MK.Sandbox.openBuildMenu = function(category) {
    if (!MK.Sandbox.canOpenBuildMenu()) return;
    
    const window = SceneManager._scene._buildSelectWindow;
    window.setCategory(category);
    window.refresh();
    window.show();
    window.activate();
    if (!window.item()) window.select(0);

    const materialsWindow = SceneManager._scene._requiredMaterialsWindow;
    showMaterialCostsWindow && materialsWindow.refresh();
    showMaterialCostsWindow && materialsWindow.show();

    const descriptionWindow = SceneManager._scene._descriptionWindow;
    showDescriptionWindow && descriptionWindow.refresh();
    showDescriptionWindow && descriptionWindow.show();
    
    $gameTemp.selectingFromBuildMenu = true;

    onOpenMenuCommonEventId && $gameTemp.reserveCommonEvent(onOpenMenuCommonEventId);
}

MK.Sandbox.canOpenBuildMenu = function() {
    return (
        SceneManager._scene instanceof Scene_Map &&
        !MK.Sandbox.isBuildMode() &&
        !MK.Sandbox.isRestoreMode() &&
        !SceneManager._scene._buildSelectWindow.isOpenAndActive()
    );
}


// =====================================================================================
// Scene Map
// =====================================================================================

const alias_SceneMap_createDisplayObjects = Scene_Map.prototype.createDisplayObjects;
Scene_Map.prototype.createDisplayObjects = function() {
    alias_SceneMap_createDisplayObjects.call(this);

    this.createBuildSelectWindow();
    this.createRequiredMaterialsWindow();
    this.createBlueprintDescriptionWindow();

    this._buildSelectWindow._materialsWindow = this._requiredMaterialsWindow;
    this._buildSelectWindow._descriptionWindow = this._descriptionWindow;
}

const alias_SceneMap_callMenu = Scene_Map.prototype.callMenu;
Scene_Map.prototype.callMenu = function() {
    
    if ($gameTemp.selectingFromBuildMenu) {
        this.closeBuildWindow();
        this.menuCalling = false;
    } else {
        alias_SceneMap_callMenu.call(this);
    }
}

Scene_Map.prototype.createBuildSelectWindow = function() {
    const rectangle = this.buildSelectWindowRectangle();

    if ('MZ' == Utils.RPGMAKER_NAME) {
        this._buildSelectWindow = new Window_BuildSelect(rectangle);
    } else {
        this._buildSelectWindow = new Window_BuildSelect(
            rectangle.x,
            rectangle.y,
            rectangle.width,
            rectangle.height,
        );
    }
    this._buildSelectWindow.refresh();

    if ($gameTemp.selectingFromBuildMenu) {
        this._buildSelectWindow.activate();
    } else {
        this._buildSelectWindow.hide();
    }
    this._buildSelectWindow.setHandler('ok', this.onBlueprintOkay.bind(this));
    this._buildSelectWindow.setHandler('cancel', this.closeBuildWindow.bind(this));
    this.addChild(this._buildSelectWindow);
}

Scene_Map.prototype.createRequiredMaterialsWindow = function() {
    const rectangle = this.requiredMaterialsWindowRectangle();

    if ('MZ' == Utils.RPGMAKER_NAME) {
        this._requiredMaterialsWindow = new Window_RequiredMaterials(rectangle);
    } else {
        this._requiredMaterialsWindow = new Window_RequiredMaterials(
            rectangle.x,
            rectangle.y,
            rectangle.width,
            rectangle.height,
        );
    }
    if (!$gameTemp.selectingFromBuildMenu) {
        this._requiredMaterialsWindow.hide();
    }
    this.addChild(this._requiredMaterialsWindow);
}

Scene_Map.prototype.createBlueprintDescriptionWindow = function() {
    const rectangle = this.blueprintDescriptionWindowRectangle();

    if ('MZ' == Utils.RPGMAKER_NAME) {
        this._descriptionWindow = new Window_BlueprintDescription(rectangle);
    } else {
        this._descriptionWindow = new Window_BlueprintDescription(
            rectangle.x,
            rectangle.y,
            rectangle.width,
            rectangle.height,
        );
    }
    if (!$gameTemp.selectingFromBuildMenu) {
        this._descriptionWindow.hide();
    }
    this.addChild(this._descriptionWindow);
}

Scene_Map.prototype.onBlueprintOkay = function() {
    this._buildSelectWindow.hide();
    this._descriptionWindow.hide();

    const blueprint = this._buildSelectWindow.item();
    blueprint && MK.Sandbox.startBuildMode(blueprint.blueprintName);
    
    $gameTemp.selectingFromBuildMenu = false;
    $gameTemp.selectedFromBuildMenu = true;
}

Scene_Map.prototype.closeBuildWindow = function() {
    this._buildSelectWindow.deactivate();
    this._buildSelectWindow.hide();
    this._requiredMaterialsWindow.hide();
    this._descriptionWindow.hide();

    $gameTemp.selectingFromBuildMenu = false;
    onCloseMenuCommonEventId && $gameTemp.reserveCommonEvent(onCloseMenuCommonEventId);
}

// Rectangles

Scene_Map.prototype.buildSelectWindowRectangle = function() {
    return new Rectangle(
        this.buildSelectWindowLeft(),
        this.buildSelectWindowTop(),
        this.buildSelectWindowWidth(),
        this.buildSelectWindowHeight(),
    );
}

Scene_Map.prototype.buildSelectWindowLeft = function() {
    return 10;
}

Scene_Map.prototype.buildSelectWindowTop = function() {
    return this.requiredMaterialsWindowTop() - this.buildSelectWindowHeight();
}

Scene_Map.prototype.buildSelectWindowWidth = function() {
    return Graphics.boxWidth - 2 * this.buildSelectWindowLeft();
}

Scene_Map.prototype.buildSelectWindowHeight = function() {
    const width = this.buildSelectWindowWidth();
    const padding = 'MZ' == Utils.RPGMAKER_NAME
        ? $gameSystem.windowPadding()
        : Window_BuildSelect.prototype.standardPadding();
    
    return nRows * Math.ceil(width / columns) + 2 * (padding - 2);
}

Scene_Map.prototype.requiredMaterialsWindowRectangle = function() {
    return new Rectangle(
        this.requiredMaterialsWindowLeft(),
        this.requiredMaterialsWindowTop(),
        this.requiredMaterialsWindowWidth(),
        this.requiredMaterialsWindowHeight(),
    );
}

Scene_Map.prototype.requiredMaterialsWindowLeft = function() {
    return this.buildSelectWindowLeft();
}

Scene_Map.prototype.requiredMaterialsWindowTop = function() {
    return Graphics.boxHeight - this.requiredMaterialsWindowHeight() - this.buildSelectWindowLeft();
}

Scene_Map.prototype.requiredMaterialsWindowWidth = function() {
    return this.buildSelectWindowWidth() * 0.4;
}

Scene_Map.prototype.requiredMaterialsWindowHeight = function() {
    return 'MZ' == Utils.RPGMAKER_NAME
        ? this.calcWindowHeight(materialCostsWindowRows)
        : new Window_Base(0, 0, 0, 0).fittingHeight(materialCostsWindowRows);
}

Scene_Map.prototype.blueprintDescriptionWindowRectangle = function() {
    return new Rectangle(
        this.requiredMaterialsWindowLeft() + this.requiredMaterialsWindowWidth(),
        this.requiredMaterialsWindowTop(),
        this.buildSelectWindowWidth() - this.requiredMaterialsWindowWidth(),
        this.requiredMaterialsWindowHeight(),
    );
}

const alias_GamePlayer_canMove = Game_Player.prototype.canMove;
Game_Player.prototype.canMove = function() {
    return (
        alias_GamePlayer_canMove.call(this) &&
        !$gameTemp.selectingFromBuildMenu
    );
}

const alias_Sandbox_quitBuildMode = MK.Sandbox.quitBuildMode;
MK.Sandbox.quitBuildMode = function() {
    alias_Sandbox_quitBuildMode.call(this);

    if ($gameTemp.selectedFromBuildMenu) {
        const category = SceneManager._scene._buildSelectWindow._category
        MK.Sandbox.openBuildMenu(category);
        $gameTemp.selectedFromBuildMenu = false;
    }
}

const alias_Sandbox_canEnableBuildMode = MK.Sandbox.canEnableBuildMode;
MK.Sandbox.canEnableBuildMode = function() {
    return (
        alias_Sandbox_canEnableBuildMode.call(this) &&
        !SceneManager._scene._buildSelectWindow.isOpenAndActive()
    );
}

const alias_Sandbox_canEnableRestoreMode = MK.Sandbox.canEnableRestoreMode;
MK.Sandbox.canEnableRestoreMode = function() {
    return (
        alias_Sandbox_canEnableRestoreMode.call(this) &&
        !SceneManager._scene._buildSelectWindow.isOpenAndActive()
    );
}

const alias_GameMap_isEventRunning = Game_Map.prototype.isEventRunning;
Game_Map.prototype.isEventRunning = function() {
    return (
        alias_GameMap_isEventRunning.call(this) ||
        (
            SceneManager._scene._buildSelectWindow &&
            SceneManager._scene._buildSelectWindow.isOpenAndActive()
        )
    );
}


if (PluginManager.registerCommand) {
    
    PluginManager.registerCommand(PLUGIN_NAME, 'openBuildMenu', args => {
        MK.Sandbox.openBuildMenu(args.category);
    });
}


})();
