//=============================================================================
// RPG Maker MZ - Sprite_Scrollable
//
// Copyright 2024 Panzer-IV. All rights reserved.
// This source code or any portion thereof must not be
// reproduced or used without licensed in any manner whatsoever.
//=============================================================================
/*:
 * @target MZ
 * @plugindesc スクロール対応スプライト
 * @author 四号戦車
 * @base DebugSwitch
 * @base Sprite_InputHandle
 * @orderAfter DebugSwitch
 * @orderAfter Sprite_InputHandle
 *
 * @param comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @help Sprite_Scrollable.js
 *
 * Version: 0.0.1
 *
 * 内部のDisplayObjectの展開領域に関わらず固定の表示領域にクロップするSpriteです。
 */
(() => {
	"use strict";

    //========================================================================
    // プラグイン定義
    //========================================================================
    const pluginName = "Sprite_Scrollable";
    const script = document.currentScript;
    const param  = PluginManagerEx.createParameter(script);

    //========================================================================
    // 定数定義
    //========================================================================

    //========================================================================
    // コアスクリプト変更部 (Sprite)
    //========================================================================

    //========================================================================
    // クラス定義 (Sprite_Scrollable)
    //========================================================================
    class Sprite_Scrollable extends Sprite_InputHandle {
        constructor(width, height, mask) {
            super();
            this.reservedMask = mask;
            this.container = new Sprite();
            this.container.zIndex = 10;
            this.addChildToRoot(this.container);
            this.innerRect = null;
            this.overallRect = this.container.getBounds();
            this.mergin = null;
            this.setMergin(0, 0, 0, 0);
            this.setFrame(0, 0, width, height);

            this._enableInput = true;
            this._enableScroll = true;
            this._enableScrollX = true;
            this._enableScrollY = true;

            this._limitInBounds = true;

            this._scrollX = 0;
            this._scrollY = 0;
            this.clearScrollStatus();
        }

        clearScrollStatus() {
            this._scrollTargetX = 0;
            this._scrollTargetY = 0;
            this._scrollDuration = 0;
            this._scrollAccelX = 0;
            this._scrollAccelY = 0;
            this._scrollTouching = false;
            this._scrollLastTouchX = 0;
            this._scrollLastTouchY = 0;
        }

        setFrame(x, y, width, height) {
            super.setFrame(x, y, width, height);
            this.innerRect = new Rectangle(x, y, width, height);

            let mask = this.reservedMask;
            if (!mask) {
                mask = new Sprite(new Bitmap(width, height));
                mask.bitmap.fillAll('#FFFFFF');
            }

            if (!!this.mask) {
                this.removeChild(this.mask);
            }
            super.addChild(mask);
            this.mask = mask;
        }

        getBounds(skipUpdate, rect) {
            // transformの更新のため呼び出し（結果は捨てる）
            this.container.getBounds();
            const b = super.getBounds(skipUpdate);
            const bounds = !!this.innerRect ? this.innerRect : new Rectangle(this.x, this.y, b.width, b.height);

            bounds.x = this.x;
            bounds.y = this.y;

            if (!!rect) {
                rect.x = bounds.x;
                rect.y = bounds.y;
                rect.width = bounds.width;
                rect.height = bounds.height;
                return rect;
            } else {
                return bounds;
            }
        }

        enableInput(enable) {
            this._enableInput = enable;
        }

        enableScroll(enable) {
            this._enableScroll = enable;
        }

        enableScrollX(enable) {
            this._enableScrollX = enable;
        }

        enableScrollY(enable) {
            this._enableScrollY = enable;
        }

        enableLimit(enable) {
            this._limitInBounds = enable;
        }

        setMergin(left, top, right, bottom) {
            this.mergin = {
                top: top,
                bottom: bottom,
                left: left,
                right: right,
            };
        }

        scrollX() {
            return this._scrollX;
        }

        scrollY() {
            return this._scrollY;
        }

        scrollTo(x, y) {
            const scrollX = this._limitInBounds ? x.clamp(-this.mergin.left, this.maxScrollX()) : x;
            const scrollY = this._limitInBounds ? y.clamp(-this.mergin.top, this.maxScrollY()) : y;
            if (this._scrollX !== scrollX || this._scrollY !== scrollY) {
                if (this._enableScrollX) {
                    this._scrollX = scrollX;
                }

                if (this._enableScrollY) {
                    this._scrollY = scrollY;
                }
                this.updateOrigin();
            }
        }

        scrollBy(x, y) {
            this.scrollTo(this._scrollX + x, this._scrollY + y);
        }

        smoothScrollTo(x, y, duration) {
            this._scrollTargetX = this._limitInBounds ? x.clamp(0, this.maxScrollX()) : x;
            this._scrollTargetY = this._limitInBounds ? y.clamp(0, this.maxScrollY()) : y;
            this._scrollDuration = duration || Input.keyRepeatInterval;
        }

        smoothScrollBy(x, y, duration) {
            if (this._scrollDuration === 0) {
                this._scrollTargetX = this.scrollX();
                this._scrollTargetY = this.scrollY();
            }
            this.smoothScrollTo(this._scrollTargetX + x, this._scrollTargetY + y, duration);
        }

        setScrollAccel(x, y) {
            this._scrollAccelX = x;
            this._scrollAccelY = y;
        }

        overallWidth() {
            return this.overallRect.width + this.mergin.left + this.mergin.right;
        }

        overallHeight() {
            return this.overallRect.height + this.mergin.top + this.mergin.bottom;
        }

        maxScrollX() {
            return Math.max(0, this.overallWidth() - this.innerRect.width);
        }

        maxScrollY() {
            return Math.max(0, this.overallHeight() - this.innerRect.height);
        }

        update() {
            super.update();
            this.overallRect = this.container.getBounds();

            if (!this._enableScroll) {
                return;
            }

            if (this._enableInput) {
                this.processWheelScroll();
                this.processTouchScroll();
            }
            this.updateSmoothScroll();
            this.updateScrollAccel();
            this.updateOrigin();
        }

        processWheelScroll() {
            if (this.isWheelScrollEnabled() && this.isTouchedInsideFrame()) {
                const threshold = 20;
                if (TouchInput.wheelY >= threshold || TouchInput.wheelY <= -threshold) {
                    this.smoothScrollBy(0, TouchInput.wheelY);
                }
            }
        }

        processTouchScroll() {
            if (this.isTouchScrollEnabled()) {
                if (TouchInput.isTriggered() && this.isTouchedInsideFrame()) {
                    this.onTouchScrollStart();
                }
                if (this._scrollTouching) {
                    if (TouchInput.isReleased()) {
                        this.onTouchScrollEnd();
                    } else if (TouchInput.isMoved()) {
                        this.onTouchScroll();
                    }
                }
            }
        }

        isWheelScrollEnabled() {
            return this.isScrollEnabled();
        }

        isTouchScrollEnabled() {
            return this.isScrollEnabled();
        }

        isScrollEnabled() {
            return true;
        }

        isTouchedInsideFrame() {
            // transformの更新（結果は捨てる）
            this.getBounds();

            // スクロール量の補正をかける
            const touchPos = new Point(TouchInput.x, TouchInput.y + this.container.y);
            const localPos = this.worldTransform.applyInverse(touchPos);

            const rect = super.getBounds(false);
            const start = this.worldTransform.applyInverse(new Point(rect.x, rect.y));
            const frame = new Rectangle(start.x, start.y, this.innerRect.width, this.innerRect.height);
            return frame.contains(localPos.x, localPos.y);
        }

        onTouchScrollStart() {
            this._scrollTouching = true;
            this._scrollLastTouchX = TouchInput.x;
            this._scrollLastTouchY = TouchInput.y;
            this.setScrollAccel(0, 0);
        }

        onTouchScroll() {
            let accelX = this._scrollLastTouchX - TouchInput.x;
            if (this.overallWidth() <= this.width) {
                accelX = 0;
            }
            let accelY = this._scrollLastTouchY - TouchInput.y;
            if (this.overallHeight() <= this.height) {
                accelY = 0;
            }
            this.setScrollAccel(-accelX, accelY);
            this._scrollLastTouchX = TouchInput.x;
            this._scrollLastTouchY = TouchInput.y;
        }

        onTouchScrollEnd() {
            this._scrollTouching = false;
        }

        updateSmoothScroll() {
            if (this._scrollDuration > 0) {
                const d = this._scrollDuration;
                const deltaX = (this._scrollTargetX - this._scrollX) / d;
                const deltaY = (this._scrollTargetY - this._scrollY) / d;
                this.scrollBy(deltaX, deltaY);
                this._scrollDuration--;
            }
        }

        updateScrollAccel() {
            if (this._scrollAccelX !== 0 || this._scrollAccelY !== 0) {
                this.scrollBy(this._scrollAccelX, this._scrollAccelY);
                this._scrollAccelX *= 0.92;
                this._scrollAccelY *= 0.92;
                if (Math.abs(this._scrollAccelX) < 1) {
                    this._scrollAccelX = 0;
                }
                if (Math.abs(this._scrollAccelY) < 1) {
                    this._scrollAccelY = 0;
                }
            }
        }

        updateOrigin() {
            this.container.x = this._scrollX;
            this.container.y = -this._scrollY;
        }

        addChild(child) {
            this.container.addChild(child);
        }

        addChildAt(child, index) {
            this.container.addChildAt(child, index);
        }

        removeChild(child) {
            return this.container.removeChild(child);
        }

        removeChildAt(index) {
            return this.container.removeChildAt(index);
        }

        removeChildren(beginIndex, endIndex) {
            return this.container.removeChildren(beginIndex, endIndex);
        }

        swapChildren(child, child2) {
            return this.container.swapChildren(child, child2);
        }

        calculateBounds() {
            super.calculateBounds();
            this.overallRect = this.container.getBounds();
        }

        addChildToRoot(child) {
            super.addChild(child);
            super.sortChildren();
        }
    }
    window.Sprite_Scrollable = Sprite_Scrollable;

})();
