import Events from 'events'
import { merge } from 'lodash'
import { Executor } from './application/loader/Executor'

type ApplicationOptions = {
    debug: boolean
    environment: string
    language: string
    public: boolean
    scripts: []
    lazyLoad: object
    site: {
        id: string | number | null
        directory: string | null,
        catalog: {
            compare: string | null
        },
        csrf: string | null
    }
    styles: [],
    template: {
        id: string | number | null
        directory: string | null
    }
}

interface ExecutorOptions extends Object {
    name: string,
    loader: 'default' | 'lazy',
    options: {[index: string]: any},
    level: 'important' | 'system' | 'component' | 'high' | 'interface' | 'main' | 'low'
}

interface ExecutorData extends Object {
    handler: () => any
    options: ExecutorOptions
}

export class Application extends Events
{
    enabled: boolean = false
    classes: {[index: string]: any} = {}
    context: {[index: string]: any} = {}
    options: ApplicationOptions = {
        debug: false,
        environment: 'desktop',
        language: 'ru',
        public: true,
        scripts: [],
        lazyLoad: {},
        site: {
            id: null,
            directory: null,
            catalog: {
                compare: null
            },
            csrf: null
        },
        styles: [],
        template: {
            id: null,
            directory: null
        }
    }
    actions: {[index: string]: any} = {}
    basket: {[index: string]: any} = {}
    catalog: {[index: string]: any} = {}
    compare: {[index: string]: any} = {}
    components: {[index: string]: any} = {}
    forms: {[index: string]: any} = {}
    libraries: {[index: string]: any} = {}
    loaders: {[index: string]: any} = {}
    modal: {[index: string]: any} = {}
    navigation: {[index: string]: any} = {}
    rating: {[index: string]: any} = {}
    request: {[index: string]: any} = {}
    resources: {[index: string]: any} = {}
    vueComponents: {[index: string]: any} = {}
    ui: {[index: string]: any} = {}
    executors: {
        important: ExecutorData[],
        system: ExecutorData[],
        component: ExecutorData[],
        high: ExecutorData[],
        interface: ExecutorData[],
        main: ExecutorData[],
        low: ExecutorData[]
    }

    constructor()
    {
        super()
        this.executors = {
            important: [],
            system: [],
            component: [],
            high: [],
            interface: [],
            main: [],
            low: []
        }
    }

    configure(options: ApplicationOptions): void
    {
        merge(this.options, options)
        this.emit('configure', this.options)
    }

    initialize(options: ApplicationOptions): void
    {
        this.configure(options)
        this.emit('initialize', this.options)
    }

    isDebug(): boolean
    {
        return this.options.debug
    }

    load(handler: any, options: object): void
    {
        if (typeof handler === 'function') {
            if (this.enabled)
                this.executeScript(handler, this.loadOptionsConfigure(options))
            else
                this.storeScript(handler, this.loadOptionsConfigure(options))
        }
    }

    loadOptionsConfigure(options: object): ExecutorOptions
    {
        return Object.assign({
            name: '[Anonymous]',
            loader: 'default',
            options: {},
            level: 'main'
        }, options) as ExecutorOptions
    }

    storeScript(handler: () => any, options: ExecutorOptions): void
    {
        if (options.level === 'important')
            this.executors.important.push({ handler, options })
        else if (options.level === 'system')
            this.executors.system.push({ handler, options })
        else if (options.level === 'component')
            this.executors.component.push({ handler, options })
        else if (options.level === 'high')
            this.executors.high.push({ handler, options })
        else if (options.level === 'interface')
            this.executors.interface.push({ handler, options })
        else if (options.level === 'low')
            this.executors.low.push({ handler, options })
        else
            this.executors.main.push({ handler, options })
    }

    executeScript(handler: () => any, options: ExecutorOptions): void
    {
        const executor = new Executor(options.name, handler, options.options)
        const loader = this.loaders.getByName(options.loader)

        if (loader === undefined)
            throw new Error(`[Application] No loader defined with name "${options.loader}" for executor "${options.name}"`)

        loader.load(executor)
    }

    executeStoredScripts(): void
    {
        this.enabled = true;
        this.executeStoredScripsGroup(this.executors.important)
        this.executors.important = []
        this.executeStoredScripsGroup(this.executors.system)
        this.executors.system = []
        this.executeStoredScripsGroup(this.executors.component)
        this.executors.component = []
        this.executeStoredScripsGroup(this.executors.high)
        this.executors.high = []
        this.executeStoredScripsGroup(this.executors.interface)
        this.executors.interface = []
        this.executeStoredScripsGroup(this.executors.main)
        this.executors.main = []
        this.executeStoredScripsGroup(this.executors.low)
        this.executors.low = []
        this.emit('load')
    }

    executeStoredScripsGroup(group: Array<ExecutorData>): void
    {
        for (const script of group)
            this.executeScript(script.handler, script.options)
    }
}
