Source: animation/Updater.js

import Interval from "./Interval";
import Invariance from "./Invariance";
import OrderedMap from "../OrderedMap";


/**
 * @summary アニメーションパラメータの更新管理
 *
 * @classdesc
 * <p>アニメーション関数と結合しているアニメーションパラメータを更新するための機能を提供する。</p>
 *
 * @memberof mapray.animation
 */
class Updater
{

    /**
     */
    constructor()
    {
        // 前回更新した時刻 (一度も更新していないときは null)
        this._prev_time = null;

        // Curve -> Tracked 辞書
        this._track_binders = new Map();

        // パラメータがまだ更新されていない、またはアニメーション関数値と
        // 矛盾する可能性のある Binder インスタンス
        this._dirty_binders = new Set();

        // 関数値が変化した Curve を管理
        this._vary_curves = new VaryCurves();
    }


    /**
     * @summary アニメーションパラメータを更新
     *
     * @desc
     * <p>時刻 time でのアニメーション関数値をアニメーションパラメータに設定する。</p>
     *
     * @param {mapray.animation.Time} time  時刻
     */
    update( time )
    {
        this._update_dirty_binders( time );

        if ( this._prev_time !== null ) {
            const vary_curves = this._vary_curves.getVaryCurves( this._prev_time, time );
            for ( let curve of vary_curves ) {
                let tracked = this._track_binders.get( curve );
                tracked.updateBinders( time );
            }
        }

        this._flush_dirty_binders();

        // 前回の時刻を更新
        this._prev_time = time;
    }


    /**
     * @summary Binder インスタンスの登録
     *
     * @desc
     * <p>事前条件: binder は this で管理されていない</p>
     *
     * @param {mapray.animation.Binder} binder
     *
     * @package
     */
    _$register( binder )
    {
        // 最初は dirty_binders に登録
        this._dirty_binders.add( binder );
    }


    /**
     * @summary Binder インスタンスの抹消
     *
     * @desc
     * <p>事前条件: binder は this で管理されている</p>
     *
     * @param {mapray.animation.Binder} binder
     *
     * @package
     */
    _$unregister( binder )
    {
        if ( this._dirty_binders.delete( binder ) ) {
            // binder は dirty_binders 側に存在していた
        }
        else {
            // binder は track_binders 側に存在する
            let tracked = this._track_binders.get( binder._$curve );
            tracked.unregister( binder );
        }
    }


    /**
     * @summary _dirty_binders のすべてのパラメータを更新
     *
     * @param {mapray.animation.Time} time
     *
     * @private
     */
    _update_dirty_binders( time )
    {
        for ( let binder of this._dirty_binders ) {
            binder._$update( time );
        }
    }


    /**
     * @summary _dirty_binders から _track_binders へ Binder を移動
     *
     * @private
     */
    _flush_dirty_binders()
    {
        for ( let binder of Array.from( this._dirty_binders ) ) {
            // dirty_binders から binder を削除
            this._dirty_binders.delete( binder );

            // track_binders へ binder を追加
            let   curve = binder._$curve;
            let tracked = this._track_binders.get( curve );

            if ( tracked === undefined ) {
                // curve を使っている Binder インスタンスが初めて登録される
                tracked = new Tracked( this, curve, binder );
                this._track_binders.set( curve, tracked );
            }
            else {
                // 2つ目以降の Binder インスタンス
                tracked.register( binder );
            }
        }
    }

}


/**
 * @summary 追跡されたバインダ
 *
 * @memberof mapray.animation.Updater
 * @private
 */
class Tracked {

    /**
     * @desc
     * <p>updater._track_binders に追加されるときに呼び出される。</p>
     * <p>updater._vary_curves も更新する。</p>
     *
     * @param {mapray.animation.Updater} updater      this を管理する Updater インスタンス
     * @param {mapray.animation.Curve}   curve        対象とする Curve インスタンス
     * @param {mapray.animation.Binder}  init_binder  最初の Binder インスタンス
     */
    constructor( updater, curve, init_binder )
    {
        this._updater = updater;
        this._curve   = curve;

        // curve と結合している Binder インスタンスの集合
        this._binders = new Set( [init_binder] );

        // curve の現在の不変性
        this._invariance = curve.getInvariance( Interval.UNIVERSAL );

        // アニメーション関数値の変更管理
        this._listener = interval => { this._onValueChange( interval ); };
        curve.addValueChangeListener( this._listener );

        // vary_curves に curve を追加
        updater._vary_curves.addCurve( curve, this._invariance );
    }


    /**
     * @summary Curve を持つ Binder を登録
     *
     * @param {mapray.animation.Binder} binder
     */
    register( binder )
    {
        // Assert: !this._binders.has( binder )
        this._binders.add( binder );
    }


    /**
     * @summary Curve を持つ Binder を抹消
     *
     * @desc
     * <p>this が updater._track_binders から削除されることがある。</p>
     * <p>curve が updater._vary_curves から削除されることがある。</p>
     *
     * @param {mapray.animation.Binder} binder
     */
    unregister( binder )
    {
        // Assert: this._binders.has( binder )
        this._binders.delete( binder );

        if ( this._binders.size > 0 ) {
            // まだ this._curve と関連した Binder インスタンスが this に存在する
            return;
        }

        // this._curve と関連した唯一の Binder インスタンスが this から削除された

        // vary_curves から curve を削除
        this._updater._vary_curves.removeCurve( this._curve, this._invariance );

        // 変更を追跡する必要はなくなったのでリスナーを削除
        this._curve.removeValueChangeListener( this._listener );

        // this を this._updater から削除
        this._updater._track_binders.delete( this._curve );
    }


    /**
     * @summary アニメーションパラメータを更新
     *
     * @desc
     * <p>時刻 time でのアニメーション関数値をアニメーションパラメータに設定する。</p>
     *
     * @param {mapray.animation.Time} time  時刻
     */
    updateBinders( time )
    {
        for ( let binder of this._binders ) {
            binder._$update( time );
        }
    }


    /**
     * @summary アニメーション間数値が変更された
     *
     * @param {mapray.animation.Interval} chg_ival  変更された時刻区間
     *
     * @private
     */
    _onValueChange( chg_ival )
    {
        // assert: !chg_ival.isEmpty()
        // assert: this._updater._prev_time != null

        if ( chg_ival.includesTime( this._updater._prev_time ) ) {
            // 現在設定されているパラメータ値と矛盾が生じる
            // this._binders を dirty_binders に移動
            this._move_to_dirty_binders();
        }
        else {
            // 不変性情報を部分的に更新
            let subinvr = this._curve.getInvariance( chg_ival );
            this._updater._vary_curves.modifyCurve( this._curve, chg_ival, subinvr, this._invariance );
            this._invariance._$modify( subinvr );
        }
    }


    /**
     * @summary this を dirty_binders に移動
     *
     * @desc
     * <p>this は updater._track_binders, updater._vary_curves から削除される。</p>
     *
     * @private
     */
    _move_to_dirty_binders()
    {
        for ( let binder of Array.from( this._binders ) ) {
            // this から binder を削除
            this.unregister( binder );
            // dirty_binders に binder を追加
            this._updater._dirty_binders.add( binder );
        }
    }

}


/**
 * @summary 時刻間での関数値が変化した Curve を得る
 *
 * @memberof mapray.animation.Updater
 * @private
 */
class VaryCurves {

    /**
     */
    constructor()
    {
        // 連続変化の時刻区間 (すべて Proper)
        // OrderedMap<Time, ContCurves>
        //   外から内部に入る、内部で動くとき変化する
        //   下限が '[' のとき、左から下限に入る、下限から左へ出るとき変化する
        //   上限が ']' のとき、右から上限に入る、上限から右へ出るとき変化する
        this._continuous = createTimeMap();

        // 単発変化の時刻 []
        // OrderedMap<Time, Set<Curve>>
        //   点を通過、点に入る、点から出るとき変化する
        this._oneshot = createTimeMap();

        // 単発変化の時刻 [)
        // OrderedMap<Time, Set<Curve>>
        //   点を通過、左から点に入る、点から左へ出るとき変化する
        this._oneshot_L = createTimeMap();

        // 単発変化の時刻 (]
        // OrderedMap<Time, Set<Curve>>
        //   点を通過、右から点に入る、点から右へ出るとき変化する
        this._oneshot_R = createTimeMap();
    }


    /**
     * @summary Curve を追加
     *
     * @desc
     * <p>データベースに不変性が invariance である curve を追加する。</p>
     *
     * <p>事前条件: curve は filter 内に存在しない</p>
     *
     * @param {mapray.animation.Curve}        curve     追加する Curve インスタンス
     * @param {mapray.animation.Invariance} invariance  curve の不変性情報
     * @param {mapray.animation.Interval}    [filter]   追加する時刻区間 (前回の不変性情報で整列済み)
     */
    addCurve( curve, invariance, filter = Interval.UNIVERSAL )
    {
        const invr_ivals = invariance.getNarrowed( filter )._$getArray();

        if ( invr_ivals.length == 0 ) {
            // 全時刻区間が変化
            this._addToGeneric( curve, filter );
        }
        else {
            // 不変区間が存在する場合
            const lastIndex = invr_ivals.length - 1;

            // 最初
            {
                const invr_ival = invr_ivals[0];
                const vary_ival = filter.getIntersection( invr_ival.getPrecedings() );
                if ( !vary_ival.isEmpty() ) {
                    this._addToGeneric( curve, vary_ival );
                }
                else if ( isSameInterval_L( filter, invr_ival ) &&
                          !this._hasContiguous_L( invr_ival, curve ) ) {
                    // invr_ival と filter の左が一致して、curve を持った invr_ival の左隣接が存在しない
                    if ( invr_ival.l_open ) {
                        this._addToOneshotGroup( curve, invr_ival, "_oneshot_R" );
                    }
                    else if ( !invr_ival.getPrecedings().isEmpty() ) {
                        this._addToOneshotGroup( curve, invr_ival, "_oneshot_L" );
                    }
                }
            }

            // 中間
            for ( let i = 0; i < lastIndex; ++i ) {
                const lower = invr_ivals[i    ].getFollowings();
                const upper = invr_ivals[i + 1].getPrecedings();
                const vary_ival = lower.getIntersection( upper );

                if ( vary_ival.isEmpty() ) {
                    //  lower と upper に間がない
                    this._addToOneshotGroup( curve, lower, lower.l_open ?
                                             "_oneshot_R" : "_oneshot_L" );
                }
                else {
                    // lower と upper に間がある
                    this._addToGeneric( curve, vary_ival );
                }
            }

            // 最後
            {
                const invr_ival = invr_ivals[lastIndex];
                const vary_ival = filter.getIntersection( invr_ival.getFollowings() );
                if ( !vary_ival.isEmpty() ) {
                    this._addToGeneric( curve, vary_ival );
                }
                else if ( isSameInterval_R( filter, invr_ival ) &&
                          !this._hasContiguous_R( invr_ival, curve ) ) {
                    if ( invr_ival.u_open ) {
                        this._addToOneshotGroup( curve, invr_ival, "_oneshot_L" );
                    }
                    else if ( !invr_ival.getFollowings().isEmpty() ) {
                        this._addToOneshotGroup( curve, invr_ival, "_oneshot_R" );
                    }
                }
            }
        }
    }


    /**
     * @summary interval の左の隣接する区間は存在するか?
     *
     * @desc
     * <p>continuous の中に interval の左に隣接する区間があり、かつ
     *    curve を持っているものは存在するかどうかを返す。</p>
     *
     * @param {mapray.animation.Interval} interval (!= Φ)
     * @param {mapray.animation.Curve}    curve
     *
     * @return {boolean}
     *
     * @private
     */
    _hasContiguous_L( interval, curve )
    {
        const map = this._continuous;

        // interval の左のアイテム
        const it1 = map.findLower( interval.lower );
        const it0 = (it1 !== null) ? it1.findPredecessor() : map.findLast();

        if ( it0 !== null ) {
            const pred      = it0.value; // ContCurves
            const pred_ival = pred.interval;

            if ( pred_ival.upper.equals( interval.lower ) &&
                 (interval.l_open && !pred_ival.u_open ||
                  !interval.l_open && pred_ival.u_open) ) {
                // 隣接している
                if ( pred.curves.has( curve ) ) {
                    // 隣接して curve を持っている
                    return true;
                }
            }
        }

        return false;
    }


    /**
     * @summary interval の右の隣接する区間は存在するか?
     *
     * @desc
     * <p>continuous の中に interval の右に隣接する区間があり、かつ
     *    curve を持っているものは存在するかどうかを返す。</p>
     *
     * @param {mapray.animation.Interval} interval (!= Φ)
     * @param {mapray.animation.Curve}    curve
     *
     * @return {boolean}
     *
     * @private
     */
    _hasContiguous_R( interval, curve )
    {
        const map = this._continuous;

        // interval の右のアイテム
        const it = map.findLower( interval.upper );
        if ( it === null ) {
            // interval の右に区間が存在しない
            return false;
        }

        const succ      = it.value; // ContCurves
        const succ_ival = succ.interval;

        if ( !succ_ival.lower.equals( interval.upper ) ) {
            // 境界が不一致
            return false;
        }

        if ( succ_ival.l_open && interval.u_open ||
             !succ_ival.l_open && !interval.u_open ) {
            // 重複または離れている
            return false;
        }

        return succ.curves.has( curve );
    }


    /**
     * @summary Curve を削除
     *
     * @desc
     * <p>filter 範囲の curve を削除する。filter は整列済みを想定しているので区間の分割は行わない。</p>
     *
     * <p>事前条件: curve は invariance と矛盾しない状態で this に存在する</p>
     * <p>事前条件: !filter.isEmpty()</p>
     *
     * <p>todo: 削除後に隣接区間が同じ集合になったときの統合処理</p>
     *
     * @param {mapray.animation.Curve}        curve     削除する Curve インスタンス
     * @param {mapray.animation.Invariance} invariance  curve の不変性情報
     * @param {mapray.animation.Interval}    [filter]   削除する時刻区間 (invariance で整列済み)
     */
    removeCurve( curve, invariance, filter = Interval.UNIVERSAL )
    {
        const invr_ivals = invariance.getNarrowed( filter )._$getArray();

        if ( invr_ivals.length == 0 ) {
            // filter の全時刻区間が変化
            this._removeForGeneric( curve, filter );
        }
        else {
            // 不変区間が存在する場合
            const lastIndex = invr_ivals.length - 1;

            // 最初
            {
                const invr_ival = invr_ivals[0];
                const vary_ival = filter.getIntersection( invr_ival.getPrecedings() );
                if ( !vary_ival.isEmpty() ) {
                    this._removeForGeneric( curve, vary_ival );
                }
                else {
                    if ( isSameInterval_L( filter, invr_ival ) ) {
                        // oneshot_L/R の可能性に対応
                        this._removeForOneshotGroup( curve, filter );
                    }
                }
            }

            // 中間
            for ( let i = 0; i < lastIndex; ++i ) {
                const lower = invr_ivals[i    ].getFollowings();
                const upper = invr_ivals[i + 1].getPrecedings();
                const vary_ival = lower.getIntersection( upper );

                if ( vary_ival.isEmpty() ) {
                    // ai と bi に間がない
                    this._removeForOneshotGroup( curve, lower );
                }
                else {
                    // ai と bi に間がある
                    this._removeForGeneric( curve, vary_ival );
                }
            }

            // 最後
            {
                const invr_ival = invr_ivals[lastIndex];
                const vary_ival = filter.getIntersection( invr_ival.getFollowings() );
                if ( !vary_ival.isEmpty() ) {
                    this._removeForGeneric( curve, vary_ival );
                }
                else {
                    if ( isSameInterval_R( filter, invr_ival ) ) {
                        // oneshot_L/R の可能性に対応
                        const upper = filter.upper;
                        this._removeForOneshotGroup( curve, new Interval( upper, upper ) );
                    }
                }
            }
        }
    }


    /**
     * @summary Curve を変更
     *
     * @param {mapray.animation.Curve}      curve
     * @param {mapray.animation.Interval}   chg_ival  更新時刻区間 (!= Φ)
     * @param {mapray.animation.Invariance} sub_invr  更新部分の Invariance
     * @param {mapray.animation.Invariance} old_invr  以前の Invariance
     */
    modifyCurve( curve, chg_ival, sub_invr, old_invr )
    {
        // old_invr を基にして chg_ival を整列拡張
        const aligned = old_invr._$expandIntervalByAlignment( chg_ival );

        // this のデータベースから aligned 区間の curve を削除
        this.removeCurve( curve, old_invr, aligned );

        // aligned 区間で sub_invr を追加
        this.addCurve( curve, sub_invr, aligned );
    }


    /**
     * @summary 関数値が変化した Curve インスタンス集合を取得
     *
     * @desc
     * <p>時刻が prev_time から time に連続変化したときに、関数値が変化する可能性のある
     *    Curve インスタンスを得るための列挙可能オブジェクトを返す。</p>
     *
     * @param {mapray.animation.Time} prev_time  始点時刻
     * @param {mapray.animation.Time} time       終点時刻
     *
     * @return {iterable.<mapray.animation.Curve>}
     */
    getVaryCurves( prev_time, time )
    {
        if ( prev_time.lessThan( time ) ) {
            // 順再生
            return this._collectCurves( prev_time, time );
        }
        else if ( time.lessThan( prev_time ) ) {
            // 逆再生
            return this._collectCurves( time, prev_time );
        }
        else {
            // 時刻変わらず
            return [];
        }
    }


    /**
     * @summary curve を continuous または oneshot へ追加
     *
     * @desc
     * <p>curve を continuous または oneshot に登録する。
     *    ただし interval.isEmpty() のときは何もしない。</p>
     *
     * @param {mapray.animation.Curve}       curve  追加する Curve インスタンス
     * @param {mapray.animation.Interval} interval  変化する時刻区間
     *
     * @private
     */
    _addToGeneric( curve, interval )
    {
        if ( interval.isProper() ) {
            this._addToContinuous( curve, interval );
        }
        else if ( interval.isSingle() ) {
            this._addToOneshotGroup( curve, interval, "_oneshot" );
        }
    }


    /**
     * @summary curve を oneshot 族へ追加
     *
     * @param {mapray.animation.Curve}       curve  追加する Curve インスタンス
     * @param {mapray.animation.Interval} interval  lower が時刻として使われる
     * @param {string}                       pname  oneshot 族のプロパティ名
     *
     * @private
     */
    _addToOneshotGroup( curve, interval, pname )
    {
        let  map = this[pname];  // OrderedMap<Time, Set<Curve>>
        let time = interval.lower;

        let item = map.findEqual( time );
        if ( item === null ) {
            // 新規の時刻
            // 空の Set<Curve> インスタンスを追加
            item = map.insert( time, new Set() );
        }

        item.value.add( curve );
    }


    /**
     * @summary curve を continuous へ追加
     *
     * @desc
     * <p>事前条件: interval.isProper()</p>
     *
     * @param {mapray.animation.Curve}       curve  追加する Curve インスタンス
     * @param {mapray.animation.Interval} interval  変化する時刻区間
     *
     * @private
     */
    _addToContinuous( curve, interval )
    {
        // assert: interval.isProper()

        // A は追加する連続区間
        // T は登録済みの連続区間 (A と交差する区間の中で一番左側の区間)

        // 交差タイプ
        //
        // (1) 登録済みの区間に A と交差する区間はない
        //     A: ====
        //
        // (2) T は A の左から飛び出し、右からは飛び出していない
        //     A:  ====
        //     T: ====
        //
        // (3) T は A の右から飛び出し、左からは飛び出していない
        //     A: ====
        //     T:  ====
        //
        // (4) T は A の左右どちらからも飛び出している
        //     A:  ===
        //     T: =====
        //
        // (5) T は A の左右どちらからも飛び出していない
        //     A: =====
        //     T:  ===

        // 記号の意味
        //
        //   $X は X.getPrecedings()
        //   X$ は X.getFollowings()

        let A_ival  = interval;  // A
        let A_curve = curve;

        for (;;) {
            // assert: A != Φ

            let target = this._removeFirstCrossInContinuous( A_ival );

            if ( target !== null ) {
                let T_ival   = target.cc.interval;  // T: A と交差する区間で一番左側の区間
                let T_curves = target.cc.curves;    // T の Curve インタンス集合
                let A_x_T    = target.cross;        // A∩T

                let $A_x_T = A_ival.getPrecedings().getIntersection( T_ival );  // $A∩T

                if ( $A_x_T.isEmpty() ) {
                    // 交差タイプ (3) または (5)

                    let A_x_$T = A_ival.getIntersection( T_ival.getPrecedings() );  // A∩$T
                    if ( !A_x_$T.isEmpty() ) {
                        // (d) A∩$T != Φ なら、A∩$T として、A_curve を新規登録
                        this._addForContinuous( A_x_$T, A_curve );
                    }
                }
                else {
                    // 交差タイプ (2) または (4)

                    // (b) $A∩T として、T_curves を新規登録
                    this._addForContinuous( $A_x_T, new Set( T_curves ) );
                }

                let A$_x_T = A_ival.getFollowings().getIntersection( T_ival );  // A$∩T

                if ( A$_x_T.isEmpty() ) {
                    // 交差タイプ (2) または (5)
                    
                    // (a) A∩T として、T_curves に A_curve を加えた集合を新規登録
                    this._addForContinuous( A_x_T, T_curves.add( A_curve ) );

                    // (z) A∩T$ != Φ なら、A を A∩T$ として再処理
                    let A_x_T$ = A_ival.getIntersection( T_ival.getFollowings() );  // A∩T$

                    if ( !A_x_T$.isEmpty() ) {
                        A_ival = A_x_T$;
                        continue;
                    }
                }
                else {
                    // 交差タイプ (3) または (4)

                    // (c) A$∩T として、T_curves を新規登録
                    this._addForContinuous( A$_x_T, new Set( T_curves ) );

                    // (a) A∩T として、T_curves に A_curve を加えた集合を新規登録
                    this._addForContinuous( A_x_T, T_curves.add( A_curve ) );
                }
            }
            else {
                // 交差タイプ (1)

                // A として、A_curve を新規登録
                this._addForContinuous( A_ival, A_curve );
            }

            break;
        }
    }


    /**
     * @summary 最初に interval と交差する要素を削除
     *
     * @desc
     * <p></p>
     * <p>this._continuous 内の時刻区間の中から、最初に interval と交差する要素を削除しする。
     *    その交差区間と要素が持っていた ContCurves インスタンスを返す。</p>
     * <p>ただし、交差する区間が存在しなければ null を返す。</p>
     *
     * @param {mapray.animation.Interval} interval  時刻区間
     *
     * @return {!object}  { cc: ContCurves, cross: Interval }
     *
     * @private
     */
    _removeFirstCrossInContinuous( interval )
    {
        let map = this._continuous;  // 各区間は Proper 前提

        let t1 = map.findUpper( interval.lower );
        let t0 = (t1 !== null) ? t1.findPredecessor() : map.findLast();

        if ( t0 !== null ) {
            let cross = interval.getIntersection( t0.value.interval );
            if ( !cross.isEmpty() ) {
                let cc = t0.value;
                map.remove( t0 );
                return { cc, cross };
            }
        }

        if ( t1 !== null ) {
            let cross = interval.getIntersection( t1.value.interval );
            if ( !cross.isEmpty() ) {
                let cc = t1.value;
                map.remove( t1 );
                return { cc, cross };
            }
        }

        // 交差は存在しない
        return null;
    }


    /**
     * @summary curves を continuous または oneshot へ追加
     *
     * @desc
     * <p>interval.isSingle() のときは curves を this._oneshot の Curve 集合に追加する。</p>
     * <p>interval.isProper() のときは ContCurves インスタンスを新規に this._continuous へ追加する。</p>
     *
     * <p>事前条件: !interval.isEmpty()</p>
     *
     * @param {mapray.animation.Interval} interval  時刻区間、または時刻 (lower を時刻とする)
     * @param {mapray.animation.Curve|Set}  curves  追加する Curve インスタンス、またはその集合
     *
     * @private
     */
    _addForContinuous( interval, curves )
    {
        let time = interval.lower;  // 時刻へ変換

        if ( interval.isSingle() ) {
            // oneshot: curves を Curve 集合へ追加
            let it = this._oneshot.findEqual( time );
            if ( it === null ) {
                // 新規の時刻
                // 空の Set<Curve> インスタンスを追加
                it = this._oneshot.insert( time, new Set() );
            }

            let dst_set = it.value;

            if ( curves instanceof Set ) {
                // 複数 Curve
                for ( let curve of curves ) {
                    dst_set.add( curve );
                }
            }
            else {
                // 単一 Curve
                dst_set.add( curves );
            }
        }
        else {
            // continuous: curves を新規追加
            this._continuous.insert( time, new ContCurves( interval, curves ) );
        }
    }


    /**
     * @summary curve を continuous または oneshot から削除
     *
     * @desc
     * <p>curve を continuous または oneshot から削除するe。
     *    ただし interval.isEmpty() のときは何もしない。</p>
     *
     * @param {mapray.animation.Curve}       curve  削除する Curve インスタンス
     * @param {mapray.animation.Interval} interval  変化する時刻区間
     *
     * @private
     */
    _removeForGeneric( curve, interval )
    {
        if ( interval.isProper() ) {
            this._removeForContinuous( curve, interval );
        }
        else if ( interval.isSingle() ) {
            this._removeForOneshotGroup( curve, interval );
        }
    }


    /**
     * @summary curve を oneshot 族から削除
     *
     * @desc
     * <p>ある oneshot 族の時刻 interval.lower に curve
     *    が存在すれば削除し、存在しなければ何もしない。</p>
     *
     * @param {mapray.animation.Curve}       curve  削除する Curve インスタンス
     * @param {mapray.animation.Interval} interval  lower が時刻として使われる
     *
     * @private
     */
    _removeForOneshotGroup( curve, interval )
    {
        const time = interval.lower;

        for ( const pname of ["_oneshot", "_oneshot_L", "_oneshot_R"] ) {
            const  map = this[pname];  // OrderedMap<Time, Set<Curve>>
            const item = map.findEqual( time );

            if ( item !== null ) {
                const curves = item.value;

                // curve を削除し、空になったら curves も削除
                if ( curves.has( curve ) ) {
                    curves.delete( curve );
                    if ( curves.size == 0 ) {
                        map.remove( item );
                    }
                }

                // curve は複数に所属することはないので、1つ削除したら終了
                break;
            }
        }
    }


    /**
     * @summary curves を continuous または oneshot から削除
     *
     * @desc
     * <p>interval 区間にある continuous と oneshot の curve を削除する。</p>
     * <p>事前条件: interval.isProper()</p>
     *
     * @param {mapray.animation.Curve}       curve  削除する Curve インスタンス
     * @param {mapray.animation.Interval} interval  時刻区間
     *
     * @private
     */
    _removeForContinuous( curve, interval )
    {
        // this._continuous
        {
            let map = this._continuous;  // 各区間は Proper 前提
            let it1 = map.findUpper( interval.lower );
            let it0 = (it1 !== null) ? it1.findPredecessor() : map.findLast();
            let end = map.findUpper( interval.upper );

            for ( let it = (it0 || it1); it !== end; ) {
                let curves = it.value.curves;
                curves.delete( curve );
                it = (curves.size == 0) ? map.remove( it ) : it.findSuccessor();
            }
        }

        // this._oneshot
        {
            let map = this._oneshot;
            let it0 = map.findLower( interval.lower );
            let end = map.findUpper( interval.upper );

            for ( let it = it0; it !== end; ) {
                let curves = it.value;
                curves.delete( curve );
                it = (curves.size == 0) ? map.remove( it ) : it.findSuccessor();
            }
        }
    }


    /**
     * @summary Curve インスタンスを収集
     *
     * @desc
     * 事前条件: t1.lessThan( t2 )
     *
     * @param {mapray.animation.Time} t1
     * @param {mapray.animation.Time} t2
     *
     * @return {iterable.<mapray.animation.Curve>}
     *
     * @private
     */
    _collectCurves( t1, t2 )
    {
        let curves = new Set();  // Set<Curve>

        this._collectContinuousCurves( t1, t2, curves );
        this._collectOneshotCurves( t1, t2, "_oneshot",   true,  true,  curves );
        this._collectOneshotCurves( t1, t2, "_oneshot_L", false, true,  curves );
        this._collectOneshotCurves( t1, t2, "_oneshot_R", true,  false, curves );

        return curves;
    }


    /**
     * @summary continuous から Curve インスタンスを収集
     *
     * @desc
     * 事前条件: t1.lessThan( t2 )
     *
     * @param {mapray.animation.Time}        t1
     * @param {mapray.animation.Time}        t2
     * @param {Set.<mapray.animation.Curve>} curves
     *
     * @private
     */
    _collectContinuousCurves( t1, t2, curves )
    {
        // 時刻区間 [t1, ∞) と交差する最初の時刻区間
        let it_A = this._continuous.findLower( t1 );
        if ( it_A !== null ) {
            // it_A != null でも、時刻区間 Pred と交差している可能性もある
            let it_Pred = it_A.findPredecessor();
            if ( it_Pred !== null ) {
                if ( it_Pred.value.interval.includesTime( t1 ) ) {
                    // Pred と [t1, ∞) が交差するので it_Pred に置き換える
                    it_A = it_Pred;
                }
            }
        }
        else {
            // it_A == null でも、時刻区間 Last と交差している可能性がある
            let it_Last = this._continuous.findLast();
            if ( it_Last !== null ) {
                if ( it_Last.value.interval.includesTime( t1 ) ) {
                    // Last と [t1, ∞) が交差するので it_Last に置き換える
                    it_A = it_Last;
                }
            }
        }

        // 時刻区間 (∞, t2] と交差する最後の時刻区間の直後
        let it_Z = this._continuous.findLower( t2 );
        if ( it_Z !== null ) {
            let Z_ival = it_Z.value.interval;
            if ( Z_ival.lower.equals( t2 ) && !Z_ival.l_open ) {
                // it_Z の最小時刻がギリギリ (∞, t2] に入るので it_Z の直後を選ぶ
                it_Z = it_Z.findSuccessor();
            }
        }

        // assert: it_A != null || it_Z == null

        // Curve インスタンスを収集
        for ( let it = it_A; it !== it_Z; it = it.findSuccessor() ) {
            for ( let curve of it.value.curves ) {
                curves.add( curve );
            }
        }
    }


    /**
     * @summary oneshot から Curve インスタンスを収集
     *
     * @desc
     * 事前条件: t1.lessThan( t2 )
     *
     * @param {mapray.animation.Time}        t1
     * @param {mapray.animation.Time}        t2
     * @param {string}                       pname
     * @param {boolean}                      closed1
     * @param {boolean}                      closed2
     * @param {Set.<mapray.animation.Curve>} curves
     *
     * @private
     */
    _collectOneshotCurves( t1, t2, pname, closed1, closed2, curves )
    {
        let map = this[pname];  // OrderedMap<Time, Set<Curve>>

        // 時刻区間 [t1, ∞) に含まれる最初の時刻
        let it_A = closed1 ? map.findLower( t1 ) : map.findUpper( t1 );

        // 時刻区間 (∞, t2] に含まれる最後の時刻区間の直後
        let it_Z = closed1 ? map.findUpper( t2 ) : map.findLower( t2 );

        // assert: it_A != null || it_Z == null

        // Curve インスタンスを収集
        for ( let it = it_A; it !== it_Z; it = it.findSuccessor() ) {
            for ( let curve of it.value ) {
                curves.add( curve );
            }
        }
    }

}


/**
 * summary VaryCurves#continuous の値
 *
 * @memberof mapray.animation.Updater.VaryCurves
 * @private
 */
class ContCurves {

    /**
     * @param {mapray.animation.Interval} interval  継続的に変化する時刻区間
     * @param {mapray.animation.Curve|Set}  curves  初期の Curve インスタンス、またはその集合
     */
    constructor( interval, curves )
    {
        // assert: interval.isProper()
        this.interval = interval;
        this.curves   = new Set( (curves instanceof Set) ? curves : [curves] );
    }

}


/**
 * @summary Time を昇順に順序付ける辞書を生成
 *
 * @private
 */
function
createTimeMap()
{
    return new OrderedMap( (a, b) => a.lessThan( b ) );
}


/**
 * @summary a と b の左側は同じか?
 *
 * @param {mapray.animation.Interval} a
 * @param {mapray.animation.Interval} b
 *
 * @private
 */
function
isSameInterval_L( a, b )
{
    return a.lower.equals( b.lower ) && (a.l_open && b.l_open || !a.l_open && !b.l_open);
}


/**
 * @summary a と b の右側は同じか?
 *
 * @param {mapray.animation.Interval} a
 * @param {mapray.animation.Interval} b
 *
 * @private
 */
function
isSameInterval_R( a, b )
{
    return a.upper.equals( b.upper ) && (a.u_open && b.u_open || !a.u_open && !b.u_open);
}


export default Updater;