import { Container, interfaces } from 'inversify';
import { ServiceIdentifiers } from './ServiceIdentifiers';

import { controlFlowTransformersModule } from './modules/node-transformers/ControlFlowTransformersModule';
import { customNodesModule } from './modules/custom-nodes/CustomNodesModule';
import { obfuscatingTransformersModule } from './modules/node-transformers/ObfuscatingTransformersModule';
import { nodeTransformersModule } from './modules/node-transformers/NodeTransformersModule';
import { stackTraceAnalyzerModule } from './modules/stack-trace-analyzer/StackTraceAnalyzerModule';
import { storagesModule } from './modules/storages/StoragesModule';

import { TInputOptions } from '../types/options/TInputOptions';

import { IInversifyContainerFacade } from '../interfaces/container/IInversifyContainerFacade';
import { IJavaScriptObfuscator } from '../interfaces/IJavaScriptObfsucator';
import { IObfuscationEventEmitter } from '../interfaces/event-emitters/IObfuscationEventEmitter';
import { IObfuscationResult } from '../interfaces/IObfuscationResult';
import { IObfuscator } from '../interfaces/IObfuscator';
import { IOptions } from '../interfaces/options/IOptions';
import { ISourceMapCorrector } from '../interfaces/ISourceMapCorrector';

import { JavaScriptObfuscatorInternal } from '../JavaScriptObfuscatorInternal';
import { ObfuscationEventEmitter } from '../event-emitters/ObfuscationEventEmitter';
import { ObfuscationResult } from '../ObfuscationResult';
import { Obfuscator } from '../Obfuscator';
import { Options } from "../options/Options";
import { SourceMapCorrector } from '../SourceMapCorrector';

export class InversifyContainerFacade implements IInversifyContainerFacade {
    /**
     * @type {interfaces.Container}
     */
    private readonly container: interfaces.Container;

    /**
     * @param options
     */
    constructor (options: TInputOptions) {
        this.container = new Container();

        this.container
            .bind<IOptions>(ServiceIdentifiers.IOptions)
            .toDynamicValue(() => {
                return new Options(options);
            })
            .inSingletonScope();

        this.container
            .bind<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator)
            .to(JavaScriptObfuscatorInternal)
            .inSingletonScope();

        this.container
            .bind<IObfuscator>(ServiceIdentifiers.IObfuscator)
            .to(Obfuscator)
            .inSingletonScope();

        this.container
            .bind<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult)
            .to(ObfuscationResult)
            .inSingletonScope();

        this.container
            .bind<IObfuscationResult>(ServiceIdentifiers.Factory__IObfuscationResult)
            .toFactory<IObfuscationResult>((context: interfaces.Context) => {
                return (obfuscatedCode: string, sourceMap: string) => {
                    const obfuscationResult: IObfuscationResult = context.container
                        .get<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult);

                    obfuscationResult.initialize(obfuscatedCode, sourceMap);

                    return obfuscationResult;
                };
            });

        this.container
            .bind<ISourceMapCorrector>(ServiceIdentifiers.ISourceMapCorrector)
            .to(SourceMapCorrector)
            .inSingletonScope();

        this.container
            .bind<IObfuscationEventEmitter>(ServiceIdentifiers.IObfuscationEventEmitter)
            .to(ObfuscationEventEmitter)
            .inSingletonScope();

        // modules
        this.container.load(storagesModule);
        this.container.load(stackTraceAnalyzerModule);
        this.container.load(customNodesModule);
        this.container.load(nodeTransformersModule);
        this.container.load(controlFlowTransformersModule);
        this.container.load(obfuscatingTransformersModule);
    }

    /**
     * @param serviceIdentifier
     * @returns {T}
     */
    public get <T> (serviceIdentifier: interfaces.ServiceIdentifier<T>): T {
        return this.container.get<T>(serviceIdentifier);
    }

    /**
     * @param serviceIdentifier
     * @param named
     * @returns {T}
     */
    public getNamed <T> (serviceIdentifier: interfaces.ServiceIdentifier<T>, named: string | number | symbol): T {
        return this.container.getNamed<T>(serviceIdentifier, named);
    }
}
