import { Injectable, Injector, Type } from '@angular/core';
import { Hypermedia } from 'first-npm-package-nicule/core';

import { BindDecorator } from '../binders/bind.decorator';
import { getPropertyMetadata } from '../google-decorator-factories';

class ReflectiveModelBinder {
    binders: Array<{ name: string; annotation: BindDecorator; }>;

    constructor(private type: Type<any>,
                private injector: Injector) {
        this.binders = this.getPropertyBinders(type);
    }

    getPropertyBinders<T>(type: Type<T>): Array<{ name: string; annotation: BindDecorator; }> {
        const metadata = getPropertyMetadata(type) || {};

        return Object
            .keys(metadata)
            .map(name => ({ name, annotation: (metadata[name].find(annotation => annotation.binding) || {}).binding }))
            .filter(({ annotation }) => annotation)
            .sort((a, b) => {
                if (a.annotation.priority === b.annotation.priority) {
                    return 0;
                }

                return a.annotation.priority < b.annotation.priority ? -1 : 1;
            });
    }

    bind<T>(hypermedia: Hypermedia): T {
        const typeInstance = new this.type();

        this.binders.forEach(binder => {
            const bindingResult = binder.annotation.bind(hypermedia, binder.name, this.injector, typeInstance);

            typeInstance[binder.name] = bindingResult === undefined ? typeInstance[binder.name] : bindingResult;
        });

        return typeInstance as T;
    }
}

@Injectable()
export class ReflectiveModelBinderFactory {
    constructor(private injector: Injector) { }

    make<T>(type: Type<T>): ReflectiveModelBinder {
        return new ReflectiveModelBinder(type, this.injector);
    }
}
