import { EntityManager } from './../data/entity-manager';
import { ServiceBase } from "../data/service-base";
import * as breeze from 'breeze-client';
import { Container, observable, BindingEngine, Disposable, computedFrom } from 'aurelia-framework';

export class ManyToManyRelationHelper<TLinkEntity extends breeze.Entity, TTargetEntity extends breeze.Entity> {
    public selectedEntities: TTargetEntity[];
    @observable
    public selectedEntitiesId: number[];
    private previousSelectedEntitiesId: number[] = [];
    private isCancelling = false;
    private isInitializing = false;
    private innerService: ServiceBase<TTargetEntity>;
    private dataObserveSubscription: Disposable;
    private bindingEngine: BindingEngine;
    private linkEntitiesArray: TLinkEntity[];
    private linkEntityTypeName: string;
    private targetEntityTypeName: string;
    private entity: breeze.Entity | AnyObject;
    private targetEntitySelector: (entity: TLinkEntity) => TTargetEntity;
    private targetEntityIdSelector: (entity: TLinkEntity) => number;
    private createMapEntityKey: (targetEntityId: number) => any;
    private createTagFn: (value: string) => breeze.Entity;

    public manager: breeze.EntityManager;
    public entityChangedKey: number;

    @computedFrom("targetEntityTypeName")
    private get service(): ServiceBase<TTargetEntity> {
        if (this.innerService == null) {
            this.innerService = new ServiceBase<TTargetEntity>(this.targetEntityTypeName);
        }
        return this.innerService;
    }

    public constructor(entity: breeze.Entity | AnyObject, fkPropertyName: string);
    public constructor(entity: breeze.Entity | AnyObject, fkPropertyName: string, createTagFnOrStr: string | ((value: string) => breeze.Entity), propertyTarget: string);
    public constructor(
        entity: breeze.Entity | AnyObject,
        linkEntitiesArray: TLinkEntity[],
        linkEntityTypeName: string,
        targetEntityTypeName: string,
        targetEntitySelector: (entity: TLinkEntity) => TTargetEntity,
        targetEntityIdSelector: (entity: TLinkEntity) => number,
        createMapEntityKey: (targetEntityId: number) => any);

    public constructor(...args: AnyObject[]) {
        this.manager = Container.instance.get(EntityManager).manager;
        this.bindingEngine = <BindingEngine>Container.instance.get(BindingEngine);

        if (args.length == 7) {
            this.explicitConstructor(args);
        } else {
            // Fallback to any arguments length
            this.autoConstructor(args);
        }

        let tagFieldName = this.service.displayNames[0];
        if (args.length === 4) {
            // createTagFn is given, contains field name OR function
            if (typeof args[2] === 'string') {
                tagFieldName = args[2];
            } else {
                this.createTagFn = args[2];
            }
        }
        this.createTagFn = this.createTagFn || function (value: string) {
            return this.service.createEntity({
                [tagFieldName]: value
            } as any, true);
        };

        this.entityChangedKey = this.manager.entityChanged.subscribe((data) => {
            if (data.entity.entityType.shortName == this.linkEntityTypeName && (data.entityAction == breeze.EntityAction.RejectChanges || data.entityAction == breeze.EntityAction.Detach)) {
                this.cancel();
            }
        });

        var self = this;
        this.initialize(self);
    }
    autoConstructor(args: AnyObject[]) {
        this.entity = args[0];
        let fkPropertyName = args[1];
        var navProp = (this.entity as breeze.Entity).entityType.navigationProperties.find(fkp => fkp.name == fkPropertyName);
        this.linkEntitiesArray = this.entity[fkPropertyName];
        this.bindingEngine.collectionObserver(this.linkEntitiesArray).subscribe(() => {
            this.cancel();
        });
        this.linkEntityTypeName = navProp.entityType.shortName;
        let inverseFkName = navProp.invForeignKeyNames[0];

        let fkPropTargets = navProp.entityType.foreignKeyProperties.filter(p => p.name != inverseFkName && p.name != 'createdById' && p.name != 'updatedById');
        let fkPropTarget: breeze.DataProperty;
        if (args[3] != null) {
            fkPropTarget = fkPropTargets.find(p => p.name == args[3]);
        }
        else {
            fkPropTarget = fkPropTargets[0];
        }
        let fkPropTargetName = fkPropTarget.relatedNavigationProperty.name;
        let fkPropTargetIdName = fkPropTarget.name;  
        this.targetEntityTypeName = fkPropTarget.relatedNavigationProperty.entityType.shortName;
        this.targetEntitySelector = (e) => e[fkPropTargetName];
        this.targetEntityIdSelector = (e) => e[fkPropTarget.name];
        this.createMapEntityKey = (id) => {
            let initialValues = {};
            initialValues[inverseFkName] = this.entity.id;
            initialValues[fkPropTargetIdName] = id;
            return initialValues;
        };
    }

    explicitConstructor(args: AnyObject[]) {
        this.entity = args[0];
        this.linkEntitiesArray = args[1];
        this.linkEntityTypeName = args[2];
        this.targetEntityTypeName = args[3];
        this.targetEntitySelector = args[4];
        this.targetEntityIdSelector = args[5];
        this.createMapEntityKey = args[6];
    }

    public unbind() {
        this.manager.entityChanged.unsubscribe(this.entityChangedKey);
        if (this.dataObserveSubscription) this.dataObserveSubscription.dispose();
    }
    public addLinkEntity(entity: TTargetEntity) {
        var self = this;
        if (entity) {
            var oldIds = self.selectedEntitiesId.concat();
            self.selectedEntities.push(entity);
            self.selectedEntitiesId.push((<any>entity).id);
            var newIds = self.selectedEntitiesId.concat();
            this.selectedEntitiesIdChanged(newIds, oldIds);
        }
    }
    public cancel() {
        this.isCancelling = true;
        this.initializeData(this);
        this.isCancelling = false;
    }
    public reset() {
        this.initializeData(this);
    }

    selectedEntitiesIdChanged(newValue: Array<number>, oldValue: Array<Number>) {
        if (!this.isInitializing) {
            if (this.selectedEntitiesId == null) this.selectedEntitiesId = [];
            this.observeData();
            if (newValue.equals(this.previousSelectedEntitiesId) === false && !this.isCancelling) {
                if (this.entity.isEditable && this.entity.entityAspect.entityState != breeze.EntityState.Added) {
                    console.log("set entity as modified");
                    this.entity.entityAspect.setModified();
                    //} else if (!this.entity.isEditable && this.entity.entityAspect.entityState != breeze.EntityState.Added) { 
                    //    console.warn("set entity as modified");
                    //    this.entity.entityAspect.setModified();
                }
            }
            // Recover string ids of new tags so that they don't get remove immediatly
            let newTags = newValue.reduce((newTags, val, index) => {
                var searchEntity = this.linkEntitiesArray.filter((emp) => this.targetEntityIdSelector(emp) == val);
                if (searchEntity == null || searchEntity.length === 0) {
                    if (typeof val === 'string') {
                        // Create on the fly
                        newTags.push(val);
                        val = (this.createTagFn(val) as any).id;
                    }

                    var initialValues = this.createMapEntityKey(val);
                    console.log("create a linkedentity");
                    // call createdlinkedEntity to add a new map record in breeze cache
                    this.service.createLinkedEntity(this.linkEntityTypeName, initialValues);
                }
                return newTags;
            }, []);
            var entitiesToRemove: TLinkEntity[] = [];
            this.linkEntitiesArray.forEach((existingEntity) => {
                if (newValue.filter(x => x == this.targetEntityIdSelector(existingEntity) || newTags.find(newTag => newTag === x)).length == 0) {
                    console.log("push an entity to remove", existingEntity);
                    entitiesToRemove.push(existingEntity);
                }
            });
            if (entitiesToRemove.length > 0) {
                entitiesToRemove.forEach((entityToRemove) => {
                    if (entityToRemove.entityAspect.entityState === breeze.EntityState.Added) {
                        console.log("set entity to be detached");
                        entityToRemove.entityAspect.setDetached();

                    } else {
                        console.log("set entity to be deleted");
                        entityToRemove.entityAspect.setDeleted();
                    }
                });
            }
            this.previousSelectedEntitiesId = [];
            newValue.forEach(id => this.previousSelectedEntitiesId.push(id));
        }
    }
    initialize(self: ManyToManyRelationHelper<TLinkEntity, TTargetEntity>) {
        this.initializeData(this);
    }

    initializeData(self: ManyToManyRelationHelper<TLinkEntity, TTargetEntity>) {
        this.isInitializing = true;
        if (this.selectedEntitiesId == null) this.selectedEntitiesId = [];
        if (this.targetEntitySelector != null) var entities = this.linkEntitiesArray.map(this.targetEntitySelector);
        this.selectedEntities = entities;
        if (entities != null && entities.length > 0 && entities[0] != null) {
            var entitiesId = entities.map(x => (<any>x).id);
            entitiesId.forEach(id => self.previousSelectedEntitiesId.push(id));
            this.selectedEntitiesId.splice(0);
            self.selectedEntitiesId.push(...entitiesId);
        }
        else {
            this.selectedEntitiesId.splice(0);
        }
        this.observeData();
        this.isInitializing = false;
    }

    observeData() {
        if (this.dataObserveSubscription) this.dataObserveSubscription.dispose();

        this.dataObserveSubscription = this.bindingEngine
            .collectionObserver(this.selectedEntitiesId)
            .subscribe(splices => this.dataChanged(splices));
    }
    dataChanged(splices) {
        if (splices.length > 0) {
            var newValue = this.selectedEntitiesId.slice(0);
            var oldValue = this.selectedEntitiesId.slice(0);

            if (splices[0].addedCount > 0) {
                oldValue.splice(splices[0].index, splices[0].addedCount);
            }

            if (splices[0].removed && splices[0].removed.length > 0) {
                splices[0].removed.forEach(r => oldValue.push(r));
            }
            this.selectedEntitiesIdChanged(newValue, oldValue);
        }
    }
}
