import AbstractStoreService from '../Framework/Services/AbstractStoreService';
import AbstractUIService from '../Framework/Services/AbstractUIService';
import AbstractConfiguratorService from '../Framework/Services/AbstractConfiguratorService';
import AbstractAsyncService from '../Framework/Services/AbstractAsyncService';
import AbstractInteractionService from '../Framework/Services/AbstractInteractionService';
import AbstractAdminareaCommunicationService from '../Framework/Services/AbstractAdminareaCommunicationService';
import AbstractVisualizationService from '../Framework/Services/AbstractVisualizationService';

/**
 * Map of the provided services
 * @type {{}}
 * @private
 */
const services = {};

/**
 * Runs the init function after all the services are added
 * this provides the opportunity to access other services while doing the custom initialization
 * @private
 */
const runInitForCurrentServices = () => {
    const keys = Object.keys(services);
    for (let k of keys) {
        const service = services[k];
        service.init && service.init();
    }
};

export default class ServiceLocator {
    /**
     * Instantiates the given services then calls the runInitForCurrentServices function
     * @param {{}} services The object that contains all the services
     */
    static useServiceMap(services) {
        const keys = Object.keys(services);
        for (let k of keys) {
            const Service = services[k];
            this.provide(new Service());
        }
        runInitForCurrentServices();
    }

    static provide(serviceInstance) {
        if (serviceInstance.constructor && serviceInstance.constructor.serviceName) {
            services[serviceInstance.constructor.serviceName] = serviceInstance;
        } else {
            throw new TypeError('The service must have a serviceName property in its constructor!');
        }
    }

    static getServiceInstanceByName(serviceName) {
        if (services[serviceName]) {
            return services[serviceName];
        } else {
            throw new TypeError(`Service does not exist: ${serviceName}`);
        }
    }

    static getServiceInstanceByConstructor(constructor) {
        if (constructor.serviceName) {
            return this.getServiceInstanceByName(constructor.serviceName);
        } else {
            throw new TypeError('Missing service name parameter in the constructor!');
        }
    }

    /**
     *
     * @returns {AbstractStoreService}
     */
    static getStoreService() {
        return this.getServiceInstanceByConstructor(AbstractStoreService);
    }

    /**
     *
     * @returns {AbstractUIService}
     */
    static getUIService() {
        return this.getServiceInstanceByConstructor(AbstractUIService);
    }

    /**
     *
     * @returns {AbstractInteractionService}
     */
    static getInteractionService() {
        return this.getServiceInstanceByConstructor(AbstractInteractionService);
    }

    /**
     *
     * @returns {AbstractConfiguratorService}
     */
    static getConfiguratorService() {
        return this.getServiceInstanceByConstructor(AbstractConfiguratorService);
    }

    /**
     *
     * @returns {AbstractAsyncService}
     */
    static getAsyncService() {
        return this.getServiceInstanceByConstructor(AbstractAsyncService);
    }

    /**
     *
     * @returns {AbstractAdminareaCommunicationService}
     */
    static getAdminareaService() {
        return this.getServiceInstanceByConstructor(AbstractAdminareaCommunicationService);
    }

    /**
     *
     * @returns {AbstractVisualizationService}
     */
    static getVisualizationService() {
        return this.getServiceInstanceByConstructor(AbstractVisualizationService);
    }
}

/**
 * Service collection to get them in runtime
 */
export class Services {
    /**
     *
     * @returns {AbstractStoreService}
     */
    static get store() {
        return ServiceLocator.getStoreService();
    }

    /**
     *
     * @returns {AbstractUIService}
     */
    static get ui() {
        return ServiceLocator.getUIService();
    }

    /**
     *
     * @returns {AbstractInteractionService}
     */
    static get interaction() {
        return ServiceLocator.getInteractionService();
    }

    /**
     *
     * @returns {AbstractConfiguratorService}
     */
    static get configurator() {
        return ServiceLocator.getConfiguratorService();
    }

    /**
     *
     * @returns {AbstractAsyncService}
     */
    static get async() {
        return ServiceLocator.getAsyncService();
    }

    /**
     *
     * @returns {AbstractAdminareaCommunicationService}
     */
    static get adminArea() {
        return ServiceLocator.getAdminareaService();
    }

    /**
     *
     * @returns {AbstractVisualizationService}
     */
    static get visualization() {
        return ServiceLocator.getVisualizationService();
    }

    /**
     *
     * @returns {DesignDataService}
     */
    static get designData() {
        return this.get('DesignDataService');
    }

    /**
     *
     * @returns {AnalyticsService}
     */
    static get analytics() {
        return this.get('AnalyticsService');
    }

    /**
     *
     * @returns {DesignerService}
     */
    static get designer() {
        return this.get('DesignerService');
    }

    /**
     *
     * @param {string} serviceName
     * @returns {*}
     */
    static get(serviceName) {
        return ServiceLocator.getServiceInstanceByName(serviceName);
    }
}

// expose Services to the window in dev mode so it can be used from the console
if (process.env.NODE_ENV !== 'production') {
    window.Services = Services;
}
