Source: System.js

import events from 'events';
import Promise from 'bluebird';
import beof from 'beof';
import Reference from './Reference';
import Context from './Context';
import Concern from './Concern';
import Signal from './Signal';
import Address from './Address';
import RefState from './RefState';
import MemoryStateProvider from './MemoryStateProvider';
import ReferenceProvider from './ReferenceProvider';

const FakeBus = {

    on: function() {},
    emit: function() {}

}

class ConcernFactory {

    /**
     * create a Concern
     * @param {Context} context 
     */
    create(context) {

    }

}

/**
 * System represents a collection of related Concerns that share a parent Context.
 * Use them to create to represent the root of a tree your application will
 * branch into.
 * @param {Context} parent 
 * @implements {Context}
 */
class System {

    constructor(name = 'main', parent = null) {

        beof({ name }).string();
        beof({ parent }).optional().interface(Context);

        this._parent = parent;
        this.name = `/${name}`;
        this._refs = [];
        this._bus = FakeBus;
        this._memStateProvider = new MemoryStateProvider();
        this._protocols = {};

    }

    /**
     * create a new System
     * @param {string} name 
     * @returns {System}
     */
    static create(name) {

        return (new System(name)).setEventBus(new events.EventEmitter());

    }

    /**
     * setEventBus allows the EventEmitter to be set.
     * @param {events.EventEmitter} bus
     */
    setEventBus(bus) {

        beof({ bus }).instance(events.EventEmitter);

        this._bus = bus;
        return this;

    }

    /**
     * setReferenceProvider allows a provider to be configured for 
     * a protocol.
     * @param {string} protocol
     * @param {ReferenceProvider} provider 
     */
    setReferenceProvider(protocol, provider) {

        beof({ protocol }).string();
        beof({ provider }).interface(ReferenceProvider);

    }

    path() {

        return this.name;

    }

    subscribe(event, handler) {

        beof({ event }).function();
        beof({ handler }).function();

        if(this._parent)
          return this._parent.subscribe(event);

        this._bus.on(event.name, handler);
        return this;

    }

    publish(event) {

        beof({ event }).object();

        if(this._parent) 
          return this._parent.publish(event);

        this._bus.emit(event.constructor.name, event);

    }

    select(path) {

        beof({ path }).string();

        var address = Address.fromString(path);
        var refs = this._refs.slice();
        var parent = this.parent;

        var next = ref => {

            if (!ref) {

                if (parent)
                    return parent.select(address);

                if (this._protocols.hasOwnProperty(address.protocol)) {

                    var r = this._protocols[address.protocol].select(path, this);
                    this._refs.push(r);
                    return;

                }

                return new Reference(this._memStateProvider.provide(RefState.UNKNOWN_STATE,
                    path, new Concern(), this));

            }

            if (address.is(ref.path()))
                return ref;

            if (address.isBelow(ref.path()))
                return ref.select(address);

            return next(refs.pop());

        }

        return next(refs.pop());

    }

    /**
     * concernOf considers a Concern part of this system when it activates.
     * @param {System.ConcernFactory} factory 
     * @param {string} name
     * @returns {Reference}
     */
    concernOf(factory, name) {

        beof({ factory }).interface(ConcernFactory);
        beof({ name }).string();

        var ctx = new System(name, this);

        var ref = new Reference(
            this._memStateProvider.provide(RefState.READY_STATE,
                `${this.name}/${name}`,
                factory.create(ctx), ctx));

        ctx.setEventBus(this._bus);

        this._refs.push(ref);
        ref.accept(new Signal.Start());
        return ref;

    }

}

System.ConcernFactory = ConcernFactory;
export default System;