// DarkPlasma_Promise_Party 2.1.1
// Copyright (c) 2020 DarkPlasma

/**
 * 2021/08/29 2.1.1 2分割の場合、下段キャラのステータスが表示されない不具合を修正
 * 2021/03/26 2.1.0 2パーティ分割に対応
 * 2020/11/09 2.0.3 パラメータが正常に読み込めない不具合を修正
 * 2020/09/29 2.0.2 プラグインコマンドに説明を追加
 * 2020/09/12 2.0.1 不要コードの削除
 * 2020/09/08 2.0.0 パラメータ名を変更
 * 2020/08/30 1.0.1 分割画面を開いた時、最初から先頭キャラを選択する
 * 2020/08/25 1.0.0 MZ版
 */

/*:ja
 * @plugindesc Promise用パーティ分割
 * @author DarkPlasma
 * @license No License
 *
 * @target MZ
 *
 * @param characterWidth
 * @text キャラグラの横サイズ
 * @type number
 * @default 48
 *
 * @param characterHeight
 * @text キャラグラの縦サイズ
 * @type number
 * @default 48
 *
 * @param characterDirectionToLeft
 * @text キャラグラ左向き
 * @type boolean
 * @default true
 *
 * @param fixMembers
 * @text 固定メンバー設定
 * @type struct<FixMembers>[]
 * @default []
 *
 * @command DevideParty
 * @text パーティ分割シーンを開く
 * @desc パーティ編成を2～3つに分けます。全パーティがフルメンバーになるか、控えが0人になる必要があります。
 * @arg partyCount
 * @text パーティ数
 * @desc 分割するパーティ数。2か3のみ指定可能です。
 * @type number
 * @min 2
 * @max 3
 *
 * @command FixMembers
 * @text 固定メンバー設定する
 * @desc 次のパーティ分割における固定メンバーを設定します。
 * @arg id
 * @text 固定メンバー設定ID
 * @desc プラグインパラメータで設定した固定メンバー設定IDを指定します。
 * @type number
 *
 * @command UnfixMembers
 * @text 固定メンバー設定解除する
 *
 * @command ChangeToNextParty
 * @text 次のパーティに交代する
 * @desc パーティ分割していない場合、何も起きません。
 *
 * @command ChangeToPreviousParty
 * @text 前のパーティに交代する
 * @desc パーティ分割していない場合、何も起きません。
 *
 * @command JoinAllMember
 * @text 全パーティ合流する
 * @desc パーティ分割していない場合、何も起きません。
 *
 * @help
 * version: 2.1.1
 * Promise用パーティ分割プラグイン
 *
 * パーティ編成を2～3つに分けます。
 * 全パーティがフルメンバーになるか、
 * 控えメンバーが0人になるまで編成を終えることはできません。
 *
 * プラグインコマンド
 *
 * パーティ分割シーンを開く
 *  パーティ分割シーンを開始します。
 *  既にパーティ分割されている場合、開始できません。
 *
 * 固定メンバー設定する
 *   次のパーティ分割シーンにおける固定メンバーを指定します。
 *   固定メンバー設定は一度パーティを分割し、合流すると解除されます。
 *
 * 固定メンバー設定解除する
 *   固定メンバー設定を解除します。
 *
 * 次のパーティに交代する
 *  次のパーティに交代します。
 *  パーティ分割されていない場合、何も起きません。
 *
 * 前のパーティに交代する
 *  前のパーティに交代します。
 *  パーティ分割されていない場合、何も起きません。
 *
 * 全パーティ合流する
 *  全パーティを合流します。
 *  パーティ分割されていない場合、何も起きません。
 */
/*~struct~FixMembers:
 * @param fixMembersId
 * @desc プラグインコマンドFixMembersで指定するためのID
 * @text 固定メンバー設定ID
 * @type number
 * @default 0
 *
 * @param party1
 * @text パーティ1
 * @type struct<FixParty>
 *
 * @param party2
 * @text パーティ2
 * @type struct<FixParty>
 *
 * @param party3
 * @text パーティ3
 * @type struct<FixParty>
 */
/*~struct~FixParty:
 * @param member1
 * @text 1枠目のメンバー
 * @type struct<FixMember>
 *
 * @param member2
 * @text 2枠目のメンバー
 * @type struct<FixMember>
 *
 * @param member3
 * @text 3枠目のメンバー
 * @type struct<FixMember>
 *
 * @param member4
 * @text 4枠目のメンバー
 * @type struct<FixMember>
 */
/*~struct~FixMember:
 * @param changable
 * @desc この枠を変更可能にするかどうか
 * @text 変更可能
 * @type boolean
 * @default true
 *
 * @param actorId
 * @text この枠の初期設定アクター
 * @type actor
 * @default 0
 */
(() => {
  'use strict';

  const pluginName = document.currentScript.src.replace(/^.*\/(.*).js$/, function () {
    return arguments[1];
  });

  const pluginParameters = PluginManager.parameters(pluginName);

  const settings = {
    characterWidth: Number(pluginParameters.characterWidth || 48),
    characterHeight: Number(pluginParameters.characterHeight || 48),
    characterDirectionToLeft: String(pluginParameters.characterDirectionToLeft || true) === 'true',
    fixMembers: JSON.parse(pluginParameters.fixMembers || '[]').map((e) => {
      return ((parameter) => {
        const parsed = JSON.parse(parameter);
        return {
          fixMembersId: Number(parsed.fixMembersId || 0),
          party1: ((parameter) => {
            const parsed = JSON.parse(parameter);
            return {
              member1: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member1 || '{}'),
              member2: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member2 || '{}'),
              member3: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member3 || '{}'),
              member4: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member4 || '{}'),
            };
          })(parsed.party1 || '{}'),
          party2: ((parameter) => {
            const parsed = JSON.parse(parameter);
            return {
              member1: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member1 || '{}'),
              member2: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member2 || '{}'),
              member3: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member3 || '{}'),
              member4: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member4 || '{}'),
            };
          })(parsed.party2 || '{}'),
          party3: ((parameter) => {
            const parsed = JSON.parse(parameter);
            return {
              member1: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member1 || '{}'),
              member2: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member2 || '{}'),
              member3: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member3 || '{}'),
              member4: ((parameter) => {
                const parsed = JSON.parse(parameter);
                return {
                  changable: String(parsed.changable || true) === 'true',
                  actorId: Number(parsed.actorId || 0),
                };
              })(parsed.member4 || '{}'),
            };
          })(parsed.party3 || '{}'),
        };
      })(e || '{}');
    }),
  };

  class FixMembersSetting {
    /**
     * @param {number} id 固定メンバー設定ID
     * @param {FixPartySetting[]} parties 固定パーティ設定
     */
    constructor(id, parties) {
      this._id = id;
      this._parties = parties;
    }

    /**
     * @param {object} object プラグインパラメータパース後のオブジェクト
     * @return {FixMembersSetting}
     */
    static fromObject(object) {
      return new FixMembersSetting(
        object.fixMembersId,
        [object.party1, object.party2, object.party3].map((party) => {
          return party ? FixPartySetting.fromObject(party) : FixPartySetting.empty();
        })
      );
    }

    get id() {
      return this._id;
    }

    get parties() {
      return this._parties;
    }
  }

  class FixPartySetting {
    /**
     * @param {FixMemberSetting[]} members 固定メンバー設定
     */
    constructor(members) {
      this._members = members;
    }

    /**
     * @param {object} object オブジェクト
     * @return {FixPartySetting}
     */
    static fromObject(object) {
      return new FixPartySetting(
        [object.member1, object.member2, object.member3, object.member4].map((member) => {
          return member ? FixMemberSetting.fromObject(member) : FixMemberSetting.empty();
        })
      );
    }

    /**
     * @return {FixPartySetting}
     */
    static empty() {
      return new FixPartySetting(Array.from(Array(MAX_PARTY_MEMBER).keys()).map((_) => FixMemberSetting.empty()));
    }

    get members() {
      return this._members;
    }
  }

  class FixMemberSetting {
    /**
     * @param {boolean} changable 変更可能か
     * @param {number} actorId アクターID
     */
    constructor(changable, actorId) {
      this._isChangable = changable;
      this._actorId = actorId;
    }

    /**
     * @param {object} object オブジェクト
     * @return {FixMemberSetting}
     */
    static fromObject(object) {
      return new FixMemberSetting(object.changable, object.actorId);
    }

    /**
     * @return {FixMemberSetting}
     */
    static empty() {
      return new FixMemberSetting(true, 0);
    }

    get isChangable() {
      return this._isChangable;
    }

    get actorId() {
      return this._actorId;
    }
  }

  const DEFAULT_PARTY_COUNT = 3;

  /**
   * 分割パーティの数
   * @type {number}
   */
  let PARTY_COUNT = 3;

  /**
   * 1パーティの最大人数
   * @type {number}
   */
  const MAX_PARTY_MEMBER = 4;

  /**
   * デフォルトのキャラグラのサイズ
   * @type {number}
   */
  const DEFAULT_CHARACTER_SIZE = 48;

  /**
   * 待機ウィンドウの上下パディング合計
   * @type {number}
   */
  const WAITING_WINDOW_PADDING = 36;

  /**
   * 待機メンバーウィンドウの横に並べる最大人数
   * @type {number}
   */
  const MAX_WAITING_MEMBER_ROWS = 13;

  const fixMembers = settings.fixMembers.map((fixMember) => FixMembersSetting.fromObject(fixMember));

  /**
   * パーティ分割シーン
   */
  class Scene_DevideParty extends Scene_Base {
    constructor() {
      super();
      this.initialize.apply(this, arguments);
    }

    initialize() {
      super.initialize();
      this._waitingMembers = $gameParty.members();
      this._devidedParties = new Game_DevidedParties();
      // 固定メンバー設定
      if ($gameParty.fixMembersId() !== null) {
        /**
         * @type {FixMembersSetting}
         */
        const fixMembersSetting = fixMembers.find(
          (fixMembersSetting) => fixMembersSetting.id === $gameParty.fixMembersId()
        );
        fixMembersSetting.parties.forEach((party, partyIndex) => {
          party.members.forEach((member, memberIndex) => {
            const actor = this._waitingMembers.find((waitingMember) => waitingMember.actorId() === member.actorId);
            if (actor) {
              this._devidedParties.getParty(partyIndex).fixMember(actor, memberIndex, member.isChangable);
              this._waitingMembers = this._waitingMembers.filter(
                (waitingMember) => waitingMember.actorId() !== member.actorId
              );
            } else if (!member.isChangable) {
              this._devidedParties.getParty(partyIndex).fixMember(null, memberIndex, false);
            }
          });
        });
      }
    }

    create() {
      super.create();
      this.createBackground();
      this.createWindowLayer();
      this.createDevidePartyWindows();
    }

    start() {
      super.start();
      this._selectWindow.activate();
    }

    createBackground() {
      this._backgroundSprite = new Sprite();
      this._backgroundSprite.bitmap = SceneManager.backgroundBitmap();
      this.addChild(this._backgroundSprite);
    }

    createDevidePartyWindows() {
      this.createHelpWindow();
      this.createStatusWindow();
      this.createWaitingMemberWindow();
      this.createDevidedPartyWindows();
      this.createSelectWindow();
      this.createMessageWindowLayer();
    }

    createHelpWindow() {
      this._helpWindow = new Window_Help(this.helpWindowRect());
      this._helpWindow.setText(this.helpWindowText());
      this.addWindow(this._helpWindow);
    }

    /**
     * @return {Rectangle}
     */
    helpWindowRect() {
      return new Rectangle(0, 0, Graphics.boxWidth, this.calcWindowHeight(1, false));
    }

    /**
     * @return {string}
     */
    helpWindowText() {
      return `파티를 ${PARTY_COUNT}개로 나누어 주세요.`;
    }

    /**
     * @return {number}
     */
    helpAreaTop() {
      if (this.isBottomHelpMode()) {
        return this.mainAreaBottom();
      } else if (this.isBottomButtonMode()) {
        return 0;
      } else {
        return this.buttonAreaBottom();
      }
    }

    /**
     * @return {number}
     */
    helpAreaBottom() {
      return this.helpAreaTop() + this.helpAreaHeight();
    }

    /**
     * @return {number}
     */
    helpAreaHeight() {
      return this.calcWindowHeight(2, false);
    }

    /**
     * @return {number}
     */
    mainAreaTop() {
      if (!this.isBottomHelpMode()) {
        return this.helpAreaBottom();
      } else if (this.isBottomButtonMode()) {
        return 0;
      } else {
        return this.buttonAreaBottom();
      }
    }

    /**
     * @return {number}
     */
    mainAreaBottom() {
      return this.mainAreaTop() + this.mainAreaHeight();
    }

    /**
     * @return {number}
     */
    mainAreaHeight() {
      return Graphics.boxHeight - this.buttonAreaHeight() - this.helpAreaHeight();
    }

    createStatusWindow() {
      this._statusWindow = new Window_DevidePartyStatus(this.statusWindowRect());
      this.addWindow(this._statusWindow);
    }

    /**
     * @return {Rectangle}
     */
    statusWindowRect() {
      const ww = Graphics.boxWidth - this.mainCommandWidth();
      const wh = this.calcWindowHeight(4, false);
      const wx = this.isRightInputMode() ? 0 : Graphics.boxWidth - ww;
      const wy = this._helpWindow.height;
      return new Rectangle(wx, wy, ww, wh);
    }

    createWaitingMemberWindow() {
      this._waitingWindow = new Window_WaitingMembers(this.waitingMembersWindowRect());
      this._waitingWindow.setWaitingMembers(this._waitingMembers);
      this.addWindow(this._waitingWindow);
    }

    /**
     * @return {Rectangle}
     */
    waitingMembersWindowRect() {
      const x = 0;
      const y = this._helpWindow.height + this._statusWindow.height;
      const width = Graphics.boxWidth;
      /**
       * デフォルトサイズより大きい場合は1行にする
       */
      const height =
        settings.characterHeight > DEFAULT_CHARACTER_SIZE
          ? settings.characterHeight + WAITING_WINDOW_PADDING
          : (Graphics.boxHeight - y) / 2;
      return new Rectangle(x, y, width, height);
    }

    createDevidedPartyWindows() {
      this._devidedWindows = Array.from(Array(PARTY_COUNT).keys()).map(
        (_, index) => new Window_DevidedParty(this.devidedPartyWindowRect(index))
      );
      this._devidedWindows.forEach((devidedWindow, index) => {
        devidedWindow.setDevidedParty(this._devidedParties.getParty(index));
        this.addWindow(devidedWindow);
      });
    }

    /**
     * @param {number} index インデックス
     * @return {Rectangle}
     */
    devidedPartyWindowRect(index) {
      const y = this._waitingWindow.y + this._waitingWindow.height;
      const width = Graphics.boxWidth / PARTY_COUNT;
      const height = Graphics.boxHeight - y;
      return new Rectangle((Graphics.boxWidth / PARTY_COUNT) * index, y, width, height);
    }

    createSelectWindow() {
      this._selectWindow = new Window_SelectDevidePartyCharacter(this.selectDevidePartyCharacterWindowRect());
      this._selectWindow.setStatusWindow(this._statusWindow);
      this._selectWindow.setWaitingWindow(this._waitingWindow);
      this._selectWindow.setDevidedPartyWindows(this._devidedWindows);
      this._selectWindow.setWaitingMembers(this._waitingMembers);
      this._selectWindow.setDevidedParties(this._devidedParties);
      this._selectWindow.setHandler('ok', this.onPickOk.bind(this));
      this._selectWindow.setHandler('cancel', this.onPickCancel.bind(this));
      this._selectWindow.select(0);
      this.addChild(this._selectWindow);
    }

    /**
     * @return {Rectangle}
     */
    selectDevidePartyCharacterWindowRect() {
      return new Rectangle(0, this._waitingWindow.y, Graphics.boxWidth, Graphics.boxHeight - this._waitingWindow.y);
    }

    createMessageWindowLayer() {
      this._messageWindowLayer = new WindowLayer();
      this._messageWindowLayer.x = (Graphics.width - Graphics.boxWidth) / 2;
      this._messageWindowLayer.y = (Graphics.height - Graphics.boxHeight) / 2;
      this.addChild(this._messageWindowLayer);
      this._messageWindow = new Window_Message(this.messageWindowRect());
      this._messageWindowLayer.addChild(this._messageWindow);
      this._nameBoxWindow = new Window_NameBox();
      this._nameBoxWindow.setMessageWindow(this._messageWindow);
      this._messageWindowLayer.addChild(this._nameBoxWindow);
      this._messageWindow.setNameBoxWindow(this._nameBoxWindow);
      this._choiceListWindow = new Window_ChoiceList();
      this._choiceListWindow.setMessageWindow(this._messageWindow);
      this._messageWindowLayer.addChild(this._choiceListWindow);
      this._messageWindow.setChoiceListWindow(this._choiceListWindow);
      /**
       * 不要なサブウィンドウを削っているため、動かないメソッドを強引に動かす
       */
      this._messageWindow.isAnySubWindowActive = () => this._choiceListWindow.active;
      this._messageWindow.terminateMessage = () => {
        this._messageWindow.close();
        $gameMessage.clear();
      };
    }

    /**
     * @return {Rectangle}
     */
    messageWindowRect() {
      const ww = Graphics.boxWidth;
      const wh = this.calcWindowHeight(4, false) + 8;
      const wx = (Graphics.boxWidth - ww) / 2;
      const wy = 0;
      return new Rectangle(wx, wy, ww, wh);
    }

    onPickOk() {
      const index = this._selectWindow.index();
      const pendingIndex = this._selectWindow.pendingIndex();
      if (pendingIndex >= 0) {
        const actor1 = this.actorFromSelectIndex(index);
        const actor2 = this.actorFromSelectIndex(pendingIndex);
        this.setActor(actor1, pendingIndex);
        this.setActor(actor2, index);
        this._selectWindow.setPendingIndex(-1);
        this._waitingWindow.refresh();
        this._devidedWindows.forEach((devidedWindow) => devidedWindow.refresh());
      } else {
        this._selectWindow.setPendingIndex(index);
      }
      this._selectWindow.activate();
    }

    onPickCancel() {
      if (this._selectWindow.pendingIndex() >= 0) {
        this._selectWindow.setPendingIndex(-1);
      } else {
        // シーン終了判定と後処理
        if (this.canExit()) {
          this._selectWindow.deactivate();
          $gameMessage.setPositionType(0);
          $gameMessage.add('이 파티 편성으로 하시겠습니까?');
          $gameMessage.setChoices(['네', '아니요'], 0, 1);
          $gameMessage.setChoiceCallback((choice) => {
            if (choice === 0) {
              this.commitDevidedPartiesAndExit();
            } else {
              this._selectWindow.activate();
            }
          });
        } else {
          $gameMessage.add('편성이 완료되지 않았습니다!');
        }
      }
    }

    commitDevidedPartiesAndExit() {
      /**
       * 編成からあぶれた待機メンバーを保持
       */
      this._waitingMembers.filter((actor) => !!actor).forEach((actor) => this._devidedParties.addWaitingMember(actor));
      $gameParty.devidePartyInto(this._devidedParties);
      $gamePlayer.refresh();
      this.popScene();
    }

    /**
     * シーンを終了できるかどうか
     * 全パーティフルで埋まっている or
     * パーティに編成していないキャラクターがいない かつ 空のパーティがない
     * @return {boolean}
     */
    canExit() {
      return (
        this._devidedParties.isAllPartyFullMember() ||
        (!this._waitingMembers.some((member) => !!member) && this._devidedParties.noEmptyParty())
      );
    }

    /**
     * アクターの位置をセットする
     * @param {Game_Actor} actor アクター
     * @param {number} index 選択ウィンドウのインデックス
     */
    setActor(actor, index) {
      if (index < this.maxWaitingMemberCount()) {
        this._waitingMembers[index] = actor;
      } else {
        const partyIndex = this.devidedPartyIndex(index);
        const memberIndex = this.devidedPartyMemberIndex(index);
        this._devidedParties.getParty(partyIndex).addMember(actor, memberIndex);
      }
    }

    /**
     * 選択ウィンドウのインデックスから、実際に選択されているアクターを取得する
     * @param {number} index 選択ウィンドウのインデックス
     * @return {Game_Actor}
     */
    actorFromSelectIndex(index) {
      if (index < this.maxWaitingMemberCount()) {
        return this._waitingMembers[index];
      }
      const partyIndex = this.devidedPartyIndex(index);
      const memberIndex = this.devidedPartyMemberIndex(index);
      return this._devidedParties.getParty(partyIndex).actor(memberIndex);
    }

    /**
     * 分割パーティのスロットを選択していた場合、選択しているパーティのインデックスを返す
     * 選択していない場合、-1を返す
     * @param {number} index 選択ウィンドウのインデックス
     * @return {number}
     */
    devidedPartyIndex(index) {
      if (index < this.maxWaitingMemberCount()) {
        return -1;
      }
      return Math.floor(((index - this.maxWaitingMemberCount()) % (PARTY_COUNT * 2)) / 2);
    }

    /**
     * 分割パーティのスロットを選択している場合、選択しているスロットのインデックスを返す
     * 選択していない場合、-1を返す
     * @param {number} index 選択ウィンドウのインデックス
     * @return {number}
     */
    devidedPartyMemberIndex(index) {
      if (index < this.maxWaitingMemberCount()) {
        return -1;
      }
      const devidedModeIndex = index - this.maxWaitingMemberCount();
      return (
        devidedModeIndex -
        this.devidedPartyIndex(index) * 2 -
        (PARTY_COUNT - 1) * 2 * Math.floor(devidedModeIndex / (PARTY_COUNT * 2))
      );
    }

    /**
     * 待機メンバーの最大人数
     * キャラグラの縦サイズ依存で変わる（ひとまず横サイズは48固定で考える）
     * @return {number}
     */
    maxWaitingMemberCount() {
      return settings.characterHeight > DEFAULT_CHARACTER_SIZE ? MAX_WAITING_MEMBER_ROWS : MAX_WAITING_MEMBER_ROWS * 2;
    }

    update() {
      super.update();
      if (!$gameMessage.isBusy()) {
        this._selectWindow.activate();
      }
    }
  }

  class Window_DevidePartyStatus extends Window_SkillStatus {
    loadFaceImages() {
      super.loadFaceImages();
      /**
       * 先頭の顔グラビットマップ
       */
      this._topFaceBitmap = ImageManager.loadFace($gameParty.members()[0].faceName());
      this._topFaceIsVisible = false;
    }

    numVisibleRows() {
      return 4;
    }

    windowHeight() {
      return this.fittingHeight(this.numVisibleRows());
    }

    update() {
      super.update();
      /**
       * 先頭のみ顔グラ読み込みが間に合わないケースがあるため、
       * 準備完了を待って一度だけ再描画処理を走らせる
       */
      if (!this._topFaceIsVisible && this._topFaceBitmap && this._topFaceBitmap.isReady()) {
        this.refresh();
        this._topFaceIsVisible = true;
      }
    }
  }

  class Window_SelectDevidePartyCharacter extends Window_Selectable {
    initialize(rect) {
      super.initialize(rect);
      this.setBackgroundType(2);
      this._pendingIndex = -1;
      this._pendingCursorSprite = new Sprite();
      this._pendingCursorRect = new Rectangle();
      this.addChild(this._pendingCursorSprite);
    }

    /**
     * @param {Window_DevidePartyStatus} statusWindow ステータスウィンドウ
     */
    setStatusWindow(statusWindow) {
      this._statusWindow = statusWindow;
    }

    /**
     * @param {Window_DevidedParty[]} devidedPartyWindows 分割パーティウィンドウ
     */
    setDevidedPartyWindows(devidedPartyWindows) {
      this._devidedWindows = devidedPartyWindows;
    }

    /**
     * @param {Window_WaitingMembers} waitingWindow 待機メンバーウィンドウ
     */
    setWaitingWindow(waitingWindow) {
      this._waitingWindow = waitingWindow;
    }

    /**
     * @return {Window_DevidePartyStatus}
     */
    statusWindow() {
      return this._statusWindow ? this._statusWindow : null;
    }

    /**
     * @return {Window_DevidedParty[]}
     */
    devidedPartyWindows() {
      return this._devidedWindows ? this._devidedWindows : [];
    }

    /**
     * @return {Window_WaitingMembers}
     */
    waitingWindow() {
      return this._waitingWindow ? this._waitingWindow : null;
    }

    /**
     * @param {Game_Actor[]} waitingMembers 待機メンバーリスト
     */
    setWaitingMembers(waitingMembers) {
      this._waitingMembers = waitingMembers;
    }

    /**
     * @param {Game_DevidedParties} devidedParties 分割後のパーティリスト
     */
    setDevidedParties(devidedParties) {
      this._devidedParties = devidedParties;
    }

    maxCols() {
      return MAX_WAITING_MEMBER_ROWS;
    }

    /**
     * 待機メンバー最大人数 + 分割パーティ4x3人
     * @return {number}
     */
    maxItems() {
      return this.maxWaitingMemberCount() + MAX_PARTY_MEMBER * PARTY_COUNT;
    }

    /**
     * デフォルトよりも背の高いキャラグラを使用しているか
     * @return {boolean}
     */
    useTallCharacter() {
      return settings.characterHeight > DEFAULT_CHARACTER_SIZE;
    }

    maxWaitingMemberCount() {
      return this.useTallCharacter() ? MAX_WAITING_MEMBER_ROWS : MAX_WAITING_MEMBER_ROWS * 2;
    }

    itemWidth() {
      return settings.characterWidth;
    }

    lineHeight() {
      return settings.characterHeight;
    }

    maxRows() {
      return 4;
    }

    spacing() {
      return 12;
    }

    /**
     * 待機メンバーを選択している
     * @return {boolean}
     */
    isSelectWaitingMember() {
      return this.index() < this.maxWaitingMemberCount();
    }

    /**
     * 待機メンバーの一番下の行を選択している
     * @return {boolean}
     */
    isSelectLowerLineWaitingMember() {
      return this.isSelectWaitingMember() && (this.useTallCharacter() || this.index() >= MAX_WAITING_MEMBER_ROWS);
    }

    /**
     * 分割パーティの上の行を選択している
     * @return {boolean}
     */
    isSelectUpperLineDevidedParty() {
      return (
        this.index() >= this.maxWaitingMemberCount() && this.index() < this.maxWaitingMemberCount() + PARTY_COUNT * 2
      );
    }

    /**
     * 選択中要素のX座標
     * @return {number}
     */
    selectingXPosition() {
      if (this.isSelectWaitingMember()) {
        return (this.index() % this.maxCols()) * (this.itemWidth() + this.spacing()) - this._scrollX;
      }
      const devidedModeIndex = this.index() - this.maxWaitingMemberCount();
      /**
       * 若干気持ち悪いが、一番左だけ特別扱いする
       */
      if (devidedModeIndex === 0) {
        return 0;
      }
      const windowIndex = Math.floor((devidedModeIndex % (PARTY_COUNT * 2)) / 2);
      const targetWindow = this.devidedPartyWindows()[windowIndex];
      const baseX = targetWindow.width / 2 - settings.characterWidth * 2;
      const diffX = settings.characterWidth * 2;
      return targetWindow.x + baseX + Math.floor(devidedModeIndex % 2) * diffX;
    }

    cursorDown(wrap) {
      if (this.isSelectLowerLineWaitingMember()) {
        const nextIndex =
          this.maxWaitingMemberCount() +
          Math.min(
            Math.round(this.selectingXPosition() / (Graphics.boxWidth / (PARTY_COUNT * 2))),
            PARTY_COUNT * 2 - 1
          );
        this.select(nextIndex);
      } else if (this.isSelectWaitingMember()) {
        super.cursorDown(wrap);
      } else {
        const index = this.index();
        if (index + PARTY_COUNT * 2 < this.maxItems()) {
          this.select(index + PARTY_COUNT * 2);
        }
      }
    }

    cursorUp(wrap) {
      if (this.isSelectUpperLineDevidedParty()) {
        const nextIndex = this.useTallCharacter()
          ? Math.round(this.selectingXPosition() / (Graphics.boxWidth / MAX_WAITING_MEMBER_ROWS))
          : MAX_WAITING_MEMBER_ROWS +
            Math.round(this.selectingXPosition() / (Graphics.boxWidth / MAX_WAITING_MEMBER_ROWS));
        this.select(nextIndex);
      } else if (this.isSelectWaitingMember()) {
        super.cursorUp(wrap);
      } else {
        this.select(this.index() - PARTY_COUNT * 2);
      }
    }

    /**
     * @param {number} index インデックス
     */
    select(index) {
      super.select(index);
      if (this.statusWindow()) {
        if (
          this.isSelectWaitingMember() &&
          this._waitingMembers &&
          this._waitingMembers.length > index &&
          this._waitingMembers[index]
        ) {
          this.statusWindow().setActor(this._waitingMembers[index]);
        } else if (!this.isSelectWaitingMember()) {
          const devidedModeIndex = index - this.maxWaitingMemberCount();
          const partyIndex = Math.floor((devidedModeIndex % (PARTY_COUNT * 2)) / 2);
          const memberIndex =
            devidedModeIndex -
            partyIndex * 2 -
            (PARTY_COUNT * 2 - 2) * Math.floor(devidedModeIndex / (PARTY_COUNT * 2));
          this.statusWindow().setActor(this._devidedParties.getParty(partyIndex).actor(memberIndex));
        } else {
          this.statusWindow().setActor(null);
        }
      }
    }

    /**
     * @return {number}
     */
    pendingIndex() {
      return this._pendingIndex;
    }

    /**
     * @param {number} pendingIndex 選択インデックス
     */
    setPendingIndex(pendingIndex) {
      this._pendingIndex = pendingIndex;
      this.drawPendingItemBackground();
    }

    /**
     * @return {boolean}
     */
    isCurrentItemEnabled() {
      return this.isEnabled(this.index());
    }

    /**
     * @param {number} index
     * @return {boolean}
     */
    isEnabled(index) {
      if (index >= this.maxWaitingMemberCount()) {
        const devidedModeIndex = index - this.maxWaitingMemberCount();
        const partyIndex = Math.floor((devidedModeIndex % (PARTY_COUNT * 2)) / 2);
        const memberIndex =
          devidedModeIndex - partyIndex * 2 - (PARTY_COUNT * 2 - 2) * Math.floor(devidedModeIndex / (PARTY_COUNT * 2));
        /**
         * 固定メンバーの場合は選択不可
         */
        return !this._devidedParties.getParty(partyIndex).isFixedMember(memberIndex);
      }
      return true;
    }

    /**
     * @param {number} index インデックス
     */
    itemRect(index) {
      if (index >= this.maxWaitingMemberCount()) {
        const devidedModeIndex = index - this.maxWaitingMemberCount();
        /**
         * パーティウィンドウのindex
         */
        const windowIndex = Math.floor((devidedModeIndex % (PARTY_COUNT * 2)) / 2);
        const targetWindow = this.devidedPartyWindows()[windowIndex];
        const waitingWindow = this.waitingWindow();
        /**
         * 雑な数字で怖いが暫定で
         */
        const baseX = targetWindow.width / 2 - settings.characterWidth * 2;
        const diffX = settings.characterWidth * 2;
        const baseY = this.useTallCharacter() ? settings.characterHeight / 6 : settings.characterHeight / 3;
        const diffY = this.useTallCharacter() ? (settings.characterHeight / 3) * 4 : (settings.characterHeight / 2) * 3;
        return new Rectangle(
          this.colSpacing() / 2 + targetWindow.x + baseX + Math.floor(devidedModeIndex % 2) * diffX,
          waitingWindow.height + baseY + Math.floor(devidedModeIndex / (PARTY_COUNT * 2)) * diffY,
          this.itemWidth(),
          this.itemHeight()
        );
      } else {
        return new Rectangle(
          this.colSpacing() / 2 + (index % this.maxCols()) * (this.itemWidth() + this.spacing()) - this.scrollBaseX(),
          Math.floor(index / this.maxCols()) * (this.itemHeight() + this.rowSpacing()) - this.scrollBaseY(),
          this.itemWidth(),
          this.itemHeight()
        );
      }
    }

    drawPendingItemBackground() {
      if (this._pendingIndex >= 0) {
        const rect = this.itemRect(this._pendingIndex);
        const color = ColorManager.pendingColor();
        this.changePaintOpacity(false);
        this.contents.fillRect(rect.x, rect.y, rect.width, rect.height, color);
        this.changePaintOpacity(true);
      } else {
        this.contents.clear();
      }
    }
  }

  class Window_DrawActorCharacter extends Window_StatusBase {
    drawActorCharacterLeft(actor, x, y) {
      const bitmap = ImageManager.loadCharacter(actor.characterName());
      const big = ImageManager.isBigCharacter(actor.characterName());
      const pw = bitmap.width / (big ? 3 : 12);
      const ph = bitmap.height / (big ? 4 : 8);
      const n = actor.characterIndex();
      const sx = ((n % 4) * 3 + 1) * pw;
      const sy = (Math.floor(n / 4) * 4 + 1) * ph;
      this.contents.blt(bitmap, sx, sy, pw, ph, x - pw / 2, y - ph);
    }
  }

  class Window_WaitingMembers extends Window_DrawActorCharacter {
    /**
     * @param {Game_Actor[]} waitingMembers 待機メンバーリスト
     */
    setWaitingMembers(waitingMembers) {
      this._waitingMembers = waitingMembers;
    }

    /**
     * @return {Game_Actor[]}
     */
    waitingMembers() {
      return this._waitingMembers ? this._waitingMembers : [];
    }

    maxCols() {
      return MAX_WAITING_MEMBER_ROWS;
    }

    spacing() {
      return 12;
    }

    update() {
      this.waitingMembers().forEach((actor, index) => {
        if (actor) {
          const x = settings.characterWidth / 2 + (index % this.maxCols()) * (settings.characterWidth + this.spacing());
          const y =
            settings.characterHeight + Math.floor(index / this.maxCols()) * (settings.characterHeight + this.spacing());
          if (settings.characterDirectionToLeft) {
            this.drawActorCharacterLeft(actor, x, y);
          } else {
            this.drawActorCharacter(actor, x, y);
          }
        }
      });
    }

    refresh() {
      this.contents.clear();
      this.update();
    }
  }

  class Window_DevidedParty extends Window_DrawActorCharacter {
    initialize(rect) {
      super.initialize(rect);
      this._fixArrowSprites = [];
    }

    /**
     * @param {Game_DevidedParty} devidedParty 分割パーティ
     */
    setDevidedParty(devidedParty) {
      this._devidedParty = devidedParty;
    }

    /**
     * @return {Game_DevidedPartyMember[]}
     */
    devidedPartyMembers() {
      return this._devidedParty ? this._devidedParty.allMembers() : [];
    }

    /**
     * @return {number}
     */
    maxCols() {
      return 2;
    }

    /**
     * @return {boolean}
     */
    useTallCharacter() {
      return settings.characterHeight > DEFAULT_CHARACTER_SIZE;
    }

    update() {
      super.update();
      this.devidedPartyMembers().forEach((member, index) => {
        if (member.actor) {
          /**
           * 最適解はキャラグラサイズ依存な気はするが暫定で
           */
          const baseX = this.width / 2 - (settings.characterWidth / 2) * 3;
          const diffX = settings.characterWidth * 2;
          const baseY = this.useTallCharacter()
            ? (settings.characterHeight / 6) * 7
            : (settings.characterHeight / 3) * 4;
          const diffY = this.useTallCharacter()
            ? (settings.characterHeight / 3) * 4
            : (settings.characterHeight / 2) * 3;
          const x = baseX + (index % this.maxCols()) * diffX;
          const y = baseY + Math.floor(index / this.maxCols()) * diffY;
          if (settings.characterDirectionToLeft) {
            this.drawActorCharacterLeft(member.actor, x, y);
          } else {
            this.drawActorCharacter(member.actor, x, y);
          }
          if (!member.isChangable) {
            const actorId = member.actor.actorId();
            let sprite = this._fixArrowSprites.find((sprite) => sprite.actorId === actorId);
            if (!sprite) {
              sprite = new Sprite_FixMemberArrow(actorId, this._windowskin);
              this._fixArrowSprites.push(sprite);
              sprite.move(
                x + settings.characterWidth / 2 - sprite.width / 2,
                y - settings.characterHeight + sprite.height / 2
              );
              this.addChild(sprite);
            }
          }
        }
      });
    }

    refresh() {
      this.contents.clear();
      this.update();
    }
  }

  class Sprite_FixMemberArrow extends Sprite {
    initialize(actorId, windowskin) {
      super.initialize();
      this._actorId = actorId;
      this.bitmap = windowskin;
      this.anchor.x = 0.5;
      this.anchor.y = 0.5;
      this.setFrame(132, 60, 24, 12);
    }

    refresh() {
      this.anchor.x = 0.5;
      this.anchor.y = 0.5;
      this.setFrame(132, 60, 24, 12);
      this.move(136, 244);
    }

    get actorId() {
      return this._actorId;
    }
  }

  class Game_DevidedParties {
    constructor() {
      this._parties = Array.from(Array(PARTY_COUNT).keys()).map((_) => new Game_DevidedParty());
      this._waitingMembers = [];
    }

    /**
     * @param {number} index パーティインデックス
     * @return {Game_DevidedParty}
     */
    getParty(index) {
      return this._parties[index];
    }

    /**
     * 全てのパーティ枠が埋まっているかどうか
     * @return {boolean}
     */
    isAllPartyFullMember() {
      return !this._parties.some((party) => !party.isFullMember());
    }

    /**
     * 空のパーティが存在しない
     * @return {boolean}
     */
    noEmptyParty() {
      return !this._parties.some((party) => party.isEmpty());
    }

    /**
     * @param {Game_Actor} actor 待機メンバー
     */
    addWaitingMember(actor) {
      this._waitingMembers.push(actor);
    }
  }

  window[Game_DevidedParties.name] = Game_DevidedParties;

  class Game_DevidedParty {
    constructor() {
      this._members = Array.from(Array($gameParty.maxBattleMembers()).keys()).map((_) =>
        Game_DevidedPartyMember.empty()
      );
    }

    /**
     * パーティにメンバーを追加する
     * @param {Game_Actor} actor アクター
     * @param {number} index インデックス
     */
    addMember(actor, index) {
      if (this._members.length <= index || this.isFixedMember(index)) {
        return;
      }
      this._members[index] = new Game_DevidedPartyMember(actor, true);
    }

    /**
     * パーティのメンバーを固定する
     * @param {Game_Actor} actor アクター
     * @param {number} index インデックス
     * @param {boolean} changable 交代可能か
     */
    fixMember(actor, index, changable) {
      this._members[index] = new Game_DevidedPartyMember(actor, changable);
    }

    /**
     * パーティからメンバーを除外する
     * @param {number} index インデックス
     */
    removeActor(index) {
      if (this._members.length <= index || this.isFixedMember(index)) {
        return;
      }
      this._members[index] = Game_DevidedPartyMember.empty();
    }

    /**
     * 指定したインデックスのメンバーのアクターを取得する
     * @param {number} index インデックス
     * @return {Game_Actor}
     */
    actor(index) {
      return this._members[index].actor;
    }

    /**
     * アクターIDからメンバーを取得する
     * @param {number} actorId アクターID
     * @return {Game_DevidedPartyMember}
     */
    findMemberIndexByActorId(actorId) {
      return this._members.findIndex((member) => member.actor && member.actor.actorId() === actorId);
    }

    /**
     * 指定したインデックスのメンバーを入れ替える
     * @param {number} actorId1 インデックス1
     * @param {number} actorId2 インデックス2
     */
    swapOrder(actorId1, actorId2) {
      const member1Index = this.findMemberIndexByActorId(actorId1);
      const member2Index = this.findMemberIndexByActorId(actorId2);
      const member1 = this._members[member1Index];
      this._members[member1Index] = this._members[member2Index];
      this._members[member2Index] = member1;
      $gamePlayer.refresh();
    }

    /**
     * 固定メンバーかどうか
     * @param {number} index インデックス
     * @return {boolean}
     */
    isFixedMember(index) {
      return !this._members[index].isChangable;
    }

    allMembers() {
      return this._members;
    }

    /**
     * @return {Game_Actor[]}
     */
    allActors() {
      return this._members.map((member) => member.actor);
    }

    /**
     * パーティの枠が全部埋まっているかどうか
     * @return {boolean}
     */
    isFullMember() {
      return !this._members.some((member) => !member.actor && member.isChangable);
    }

    /**
     * パーティが空であるかどうか
     * @return {boolean}
     */
    isEmpty() {
      return !this._members.some((member) => !!member.actor);
    }
  }

  window[Game_DevidedParty.name] = Game_DevidedParty;

  class Game_DevidedPartyMember {
    /**
     * @param {Game_Actor} actor アクター
     * @param {boolean} changable 交代可能か
     */
    constructor(actor, changable) {
      this._actorId = actor ? actor.actorId() : -1;
      this._isChangable = changable;
    }

    /**
     * @return {Game_DevidedPartyMember}
     */
    static empty() {
      return new Game_DevidedPartyMember(null, true);
    }

    /**
     * @return {Game_Actor|null}
     */
    get actor() {
      return this._actorId < 0 ? null : $gameActors.actor(this._actorId);
    }

    /**
     * @return {boolean}
     */
    get isChangable() {
      return this._isChangable;
    }
  }

  window[Game_DevidedPartyMember.name] = Game_DevidedPartyMember;

  const _Game_Party_initialize = Game_Party.prototype.initialize;
  Game_Party.prototype.initialize = function () {
    _Game_Party_initialize.call(this);
    this._devidedParties = null;
    this._devidedPartyIndex = 0;
    this._fixMembersId = null;
  };

  /**
   * @param {Game_DevidedParties} devidedParties 分割パーティ情報
   */
  Game_Party.prototype.devidePartyInto = function (devidedParties) {
    this._devidedParties = devidedParties;
    this._devidedPartyIndex = 0;
    this._fixMembersId = null;
  };

  Game_Party.prototype.isDevided = function () {
    return !!this._devidedParties;
  };

  Game_Party.prototype.changeToNextParty = function () {
    if (!this.isDevided()) {
      return;
    }
    this._devidedPartyIndex++;
    if (this._devidedPartyIndex >= PARTY_COUNT) {
      this._devidedPartyIndex = 0;
    }
    $gamePlayer.refresh();
  };

  Game_Party.prototype.changeToPreviousParty = function () {
    if (!this.isDevided()) {
      return;
    }
    this._devidedPartyIndex--;
    if (this._devidedPartyIndex < 0) {
      this._devidedPartyIndex = PARTY_COUNT - 1;
    }
    $gamePlayer.refresh();
  };

  Game_Party.prototype.joinAllDevidedParties = function () {
    this._devidedParties = null;
    $gamePlayer.refresh();
  };

  Game_Party.prototype.fixMembersId = function () {
    return Number.isFinite(this._fixMembersId) ? this._fixMembersId : null;
  };

  Game_Party.prototype.setFixMembers = function (fixMembersId) {
    this._fixMembersId = fixMembersId;
  };

  Game_Party.prototype.unfixMembers = function () {
    this._fixMembersId = null;
  };

  const _Game_Party_allMembers = Game_Party.prototype.allMembers;
  Game_Party.prototype.allMembers = function () {
    return this.isDevided()
      ? this._devidedParties
          .getParty(this._devidedPartyIndex)
          .allActors()
          .filter((actor) => !!actor)
      : _Game_Party_allMembers.call(this);
  };

  const _Game_Party_swapOrder = Game_Party.prototype.swapOrder;
  Game_Party.prototype.swapOrder = function (index1, index2) {
    if (this.isDevided()) {
      const allMembers = this.allMembers();
      this._devidedParties
        .getParty(this._devidedPartyIndex)
        .swapOrder(allMembers[index1].actorId(), allMembers[index2].actorId());
    } else {
      _Game_Party_swapOrder.call(this, index1, index2);
    }
  };

  PluginManager.registerCommand(pluginName, 'DevideParty', (args) => {
    if (!$gameParty.isDevided()) {
      PARTY_COUNT = Number(args.partyCount || DEFAULT_PARTY_COUNT);
      SceneManager.push(Scene_DevideParty);
    }
  });

  PluginManager.registerCommand(pluginName, 'FixMembers', (args) => {
    const fixNumbersId = Number(args.id);
    $gameParty.setFixMembers(Number(fixNumbersId));
  });

  PluginManager.registerCommand(pluginName, 'UnfixMembers', (_) => {
    $gameParty.unfixMembers();
  });

  PluginManager.registerCommand(pluginName, 'ChangeToNextParty', (_) => {
    $gameParty.changeToNextParty();
  });

  PluginManager.registerCommand(pluginName, 'ChangeToPreviousParty', (_) => {
    $gameParty.changeToPreviousParty();
  });

  PluginManager.registerCommand(pluginName, 'JoinAllMember', (_) => {
    $gameParty.joinAllDevidedParties();
  });

  SceneManager.isCurrentScene = function (sceneClass) {
    return !this.isSceneChanging() && this.isCurrentSceneStarted() && this._scene.constructor === sceneClass;
  };
})();
