core_App.js

import { Router } from "./Router.js";
import { States } from "./States.js";
import { View } from "./View.js";
import { L18n } from "./L18n.js";
import { Events } from "./Events.js";

import { Helper } from "../util/Helper.js";
import { PathObject } from "../util/PathObject.js";


const VERSION = '0.9.99';

const DEFAULT_SETTINGS = {
    "app" : {
        "id" : null,
        "title" : null,
        "sayHello" : true
    },
    "l18n" : {
        "defaultLanguage" : "en"
    },
    "router" : {
        "isEnabled" : true,
        "basePath" : null
    },
    "states" : {
        "basePath" : "",
        "useDefaultIndexState" : true,
        "useDefaultNotFoundState" : true
    }
};

/**
 * App
 * The App class is the logical core unit of every InfrontJS application.
 */
class App extends Events
{
    static POOL = {};

    /**
     *
     * @param {string|null} uid - Get instance by given uid. If no uid is given, the first app from pool is returned.
     * @returns {(App|null)}
     */
    static get( uid = null )
    {
        if ( uid && App.POOL.hasOwnProperty( uid ) )
        {
            return App.POOL[ uid ];
        }
        else if ( null === uid &&  Object.keys( App.POOL ).length > 0 )
        {
            return App.POOL[ Object.keys( App.POOL )[ 0 ] ];
        }
        else
        {
            return null;
        }
    }

    /**
     * Create an app instance
     * @param {HTMLElement} [container=document.body] - The root container of the application.
     * @param {object=} settings - Application settings object.
     * @param {object=} settings.app - App settings.
     * @param {string|null} [settings.app.title=null] - App's title, if set it will be set to the title header value.
     * @param {string|null} [settings.app.id=null] - Unique id of app instance. If not set, it will be auto generated.
     */
    constructor( container = null, settings = {} )
    {
        super();

        this.container = container;

        // @todo Replace spread logic with lodash merge function (inline)
        this.settings = new PathObject( { ...DEFAULT_SETTINGS, ...settings } );
        if ( null === this.settings.get( 'app.id', null ) )
        {
            this.settings.set( 'app.id', Helper.createUid() );
        }

        if ( typeof window === 'undefined'  )
        {
            throw new Error( 'InfrontJS works only in browser mode.' );
        }

        // If container property is a string, check if it is a querySelector
        if ( this.container !== null && false === this.container instanceof HTMLElement )
        {
            throw new Error( 'Invalid app container.' );
        }
        else if ( this.container === null )
        {
            const body = document.querySelector( 'body' );
            const customContainer = document.createElement( 'section' );
            body.appendChild( customContainer );
            this.container = customContainer;
        }

        this.container.setAttribute( 'data-ifjs-app-id', this.settings.get( 'app.id') );

        // Init core components
        this.initRouter();
        this.initL18n();
        this.initStates();
        this.initView();

        // Add app to global app pool
        App.POOL[ this.uid ] = this;

        if ( true === this.settings.get( 'app.sayHello' ) && console )
        {
            console && console.log( "%cĀ»InfrontJSĀ« Version " + VERSION, "font-family: monospace sans-serif; background-color: black; color: white;" );
        }

        this.emit( Events.EVENT.READY );
    }

    initL18n()
    {
        this.l18n = new L18n( this );

    }
    initStates()
    {
        this.states = new States( this );
    }

    initRouter()
    {
        this.router = new Router( this );
    }

    initView()
    {
        this.view = new View( this );
    }

    /**
     * Get InfrontJS version
     *
     * @returns {string} - Version string
     */
    getVersion()
    {
        return VERSION;
    }

    /**
     * Run application logic. This activates the InfrontJS application logic.
     * Note:
     * This is an asynchronous function providing the possibility to e.g. loading assets etc.
     *
     * @param {string|null} [route=null] - If route is set, this route is set initially.
     * @returns {Promise<void>}
     */
    async run( route = null )
    {
        if ( this.settings.get( 'app.title' ) )
        {
            this.view.setWindowTitle( this.settings.get( 'app.title' ) );
        }

        this.router.enable();
        if ( route )
        {
            // @todo Fix this for "url" router mode
            this.router.redirect( route, ( this.router.resolveRoute( route ) === this.router.resolveRoute( location.hash ) ) );
        }
        else
        {
            this.router.process();
        }
    }

    /**
     * Destorys InfrontJS application instance
     * @returns {Promise<void>}
     */
    async destroy()
    {
        // @todo Implement logic, set innerHTML to zero ... etc
    }
}

export { App };