Source: RefState.js

import Promise from 'bluebird';
import beof from 'beof';
import Concern from './Concern';
import Context from './Context';
import StateProvider from './StateProvider';
import UnhandledMessage from './UnhandledMessage';
import ReceiveError from './ReceiveError';
import Signal from './Signal';
import IgnoredMessage from './IgnoredMessage';
import StoppedConcernError from './StoppedConcernError';

const keyify = function(msg) {

    switch (typeof msg) {

        case 'function':
            return msg.name;

        case 'object':
            return msg.constructor.name;

        default:
            return '' + msg;

    }

}

/**
 * RefState is really a Concern's state but because we abstract away the
 * state management to the Reference implementation subclasses of this class
 * refer to the state the Reference is in.
 * @abstract
 * @param {string} path
 * @param {Concern} concern
 * @param {Context} context
 * @param {StateProvider} states
 */
class RefState {

    constructor(path, concern, context, states) {

        beof({ path }).string();
        beof({ concern }).interface(Concern);
        beof({ context }).interface(Context);
        beof({ states }).interface(StateProvider);

        this._path = path;
        this._concern = concern;
        this._context = context;
        this._states = states;

    }

    static equals(o, state) {

        //example
        //{
        // type: 'state',
        // state: 'Active',
        // path: '/lib/tasks/generate_events.js#/main/posts/comments',
        //}
        if (typeof o === 'object')
            if (o.type === 'state')
                if (typeof o.path === 'string')
                    if (o.state === state.name)
                        return true;

    }

    path() {

        return this._path;

    }

    toString() {

        return JSON.stringify({
            type: 'state',
            state: this.constructor.name,
            path: this.path()
        });

    }

}



/**
 * Paused
 */
class Paused extends RefState {

    constructor(path, concern, context, states) {

        super(path, concern, context, states);
        this._q = [];

    }

    getState(m) {

        var nextState = this;

        if (m instanceof Signal.Resume) {

            nextState = new this._states.provide(RefState.ACTIVE_STATE,
                this._path, this._concern, this._context);

            return Promise.resolve(this._q).
            each((mcb) => mcb(nextState)).
            then(() => nextState);

        }

        if (m instanceof Signal.Stop)
            return Promise.resolve(this._states.provide(RefState.STOPPED_STATE,
                this._path, this._concern, this._context));

        return Promise.resolve(nextState);

    }

    accept(msg, sender) {

        this._q.push(function do_accept(active) {
            active.accept(msg, sender);
        });

    }

    acceptAndPromise(msg, sender) {

        var q = this._q;

        return new Promise(function accept_and_promise_promise_fn(resolve, reject) {

            q.push(function do_accept_and_promise(active) {

                return resolve(active.acceptAndPromise(msg, sender));

            });

        });

    }

}

/**
 * Active represents the Concern in an active state, meaning it is able to
 * do whatever it was created to do. From this state the Concern can
 * become Paused or Stopped.
 *
 */
class Active extends RefState {

    action(msg, sender) {

        return receiver.receive(msg, sender);

    }

    _handle(sender, msg, receiver) {

        var actions = this.action(msg, sender);
        var action;

        if (!actions) return null;

        if (typeof actions === 'object')
            action = actions[keyify(msg)];

        if (typeof actions === 'function')
            action = actions;

        if (typeof action === 'function')
            return action(msg, sender);

        return null;

    }

    getState(m) {

        var nextState = this;

        if (m instanceof Signal.Pause)
            nextState = this._states.provide(RefState.PAUSED_STATE,
                this._path, this._concern, this._context);

        if (m instanceof Signal.Stop)
            nextState = this._states.provide(RefState.STOPPED_STATE,
                this._path, this._concern, this._context);

        return Promise.resolve(nextState);

    }

    accept(msg, sender) {

        this._promise = this.acceptAndPromise(msg, sender);
        this._promise.catch(e => this._context.publish(new ReceiveError(e, sender, msg, this)));

    }

    acceptAndPromise(msg, sender) {

        return this._promise = this._promise.
        then(Promise.resolve(this._handle(sender, msg, this._concern)));

    }

}

/**
 * Ready
 */
class Ready extends RefState {


    getState(m) {

        var nextState = this;

        if (m instanceof Signal.Start) {

            nextState = this._states.provide(RefState.ACTIVE_STATE,
                this._path, this._concern, this._context);

            return Promise.resolve(this._concern.onStart()).then(() => nextState);

        }

        if (m instanceof Signal.Stop)
            return this._states.provide(RefState.STOPPED_STATE,
                this._path, this._concern, this._context);

        return nextState;

    }

    accept(msg, sender) {

        this._context.publish(new IgnoredMessage(sender, msg, this._concern));

    }

    acceptAndPromise(msg, sender) {

        return Promise.resolve(this.accept(msg, sender));

    }

}

/**
 * Stopped
 */
class Stopped extends RefState {

    accept(msg, sender) {

        this._context.publish(new StoppedConcernError(sender, msg, this._concern));

    }

    acceptAndPromise(msg, sender) {

        return Promise.resolve(this.accept(msg, sender));

    }

}

/**
 * Unknown represents the state Concern we could not find or don't know about.
 * This state can not transition to another state and serves only to avoid
 * throwing errors.
 */
class Unknown extends RefState {

    getState(m) {

        return Promise.resolve(this);

    }

    accept(msg, sender) {

        this._context.publish(new IgnoredMessage(sender, msg, this._concern));
    }

    acceptAndPromise(msg, sender) {

        return Promise.resolve(this.accept(msg, sender));

    }


}


RefState.Ready = Ready;
RefState.Active = Active;
RefState.Paused = Paused;
RefState.Stopped = Stopped;
RefState.Unknown = Unknown;
RefState.ACTIVE_STATE = 'Active';
RefState.READY_STATE = 'Ready';
RefState.PAUSED_STATE = 'Paused';
RefState.STOPPED_STATE = 'Stopped';
RefState.UNKNOWN_STATE = 'Unknown';

export default RefState