import {Injectable} from "@angular/core";
import {Subject} from "rxjs/Subject";
import {Observable} from "rxjs/Observable";
import {PrivateDiagramBranchContext} from "../branch/PrivateDiagramBranchContext";
import {BranchTuple} from "../branch/BranchTuple";
import {BranchIndexLoaderServiceA} from "../branch-loader/BranchIndexLoaderServiceA";
import {DiagramCoordSetService} from "../../DiagramCoordSetService";
import {LocalBranchStorageService} from "../branch-loader/LocalBranchStorageService";
import {BranchIndexResultI} from "../branch-loader/BranchIndexLoaderService";
import {ModelCoordSet} from "../tuples";
import {PrivateDiagramCoordSetService, PrivateDiagramTupleService} from "../services";

import * as moment from "moment";
import {
    ComponentLifecycleEventEmitter,
    TupleSelector,
    VortexStatusService
} from "@synerty/vortexjs";
import {BranchKeyToIdMapTuple} from "./BranchKeyToIdMapTuple";
import {BranchDetailTuple} from "@peek/peek_plugin_branch";
import {UserService} from "@peek/peek_core_user";
import {Ng2BalloonMsgService} from "@synerty/ng2-balloon-msg";
import {DiagramBranchDetailsI} from "../../DiagramBranchService";
import {PrivateDiagramLookupService} from "../services/PrivateDiagramLookupService";

export interface PopupEditBranchSelectionArgs {
    modelSetKey: string;
    coordSetKey: string;
}

/** Diagram Branch Service
 *
 * This service notifies the popup service that an item has been selected.
 *
 */
@Injectable()
export class PrivateDiagramBranchService extends ComponentLifecycleEventEmitter {

    private _startEditingWithContextObservable = new Subject<PrivateDiagramBranchContext>();
    private _startEditingObservable = new Subject<void>();
    private _stopEditingObservable = new Subject<void>();

    private _popupEditBranchSelectionSubject: Subject<PopupEditBranchSelectionArgs>
        = new Subject<PopupEditBranchSelectionArgs>();


    private coordSetService: PrivateDiagramCoordSetService;

    private branchIdMapByCoordSetId: { [coordSetId: number]: BranchKeyToIdMapTuple } = {};

    private enabledBranches: BranchDetailTuple[] = [];

    private activeBranchContext: PrivateDiagramBranchContext | null = null;

    constructor(private vortexStatusService: VortexStatusService,
                private balloonMsg: Ng2BalloonMsgService,
                private userService: UserService,
                private lookupService: PrivateDiagramLookupService,
                coordSetService: DiagramCoordSetService,
                private branchLocalLoader: LocalBranchStorageService,
                private branchIndexLoader: BranchIndexLoaderServiceA,
                private tupleService: PrivateDiagramTupleService) {
        super();

        this.coordSetService = <PrivateDiagramCoordSetService>coordSetService;

        let tupleSelector = new TupleSelector(BranchKeyToIdMapTuple.tupleName, {});

        this.tupleService.offlineObserver
            .subscribeToTupleSelector(tupleSelector)
            .takeUntil(this.onDestroyEvent)
            .subscribe((tuples: BranchKeyToIdMapTuple[]) => {
                this.branchIdMapByCoordSetId = {};
                for (let tuple of tuples) {
                    this.branchIdMapByCoordSetId[tuple.coordSetId] = tuple;
                }
            });

    }

    areBranchesActive(coordSetId: number): boolean {
        return this.enabledBranches.length != 0;
    }

    getVisibleBranchIds(coordSetId: number): number[] {
        let keyIdMapTuple = this.branchIdMapByCoordSetId[coordSetId];
        if (keyIdMapTuple == null)
            return [];

        let ids = [];
        for (let branch of this.enabledBranches) {
            let branchId = keyIdMapTuple.keyIdMap[branch.key];
            if (branchId != null)
                ids.push(branchId);
        }
        return ids;
    }

    getDiagramBranchKeys(coordSetId: number): string[] {
        let keyIdMapTuple = this.branchIdMapByCoordSetId[coordSetId];
        if (keyIdMapTuple == null)
            return [];

        return Object.keys(keyIdMapTuple.keyIdMap);
    }


    getBranch(modelSetKey: string, coordSetKey: string,
              branchKey: string): Promise<PrivateDiagramBranchContext | null> {
        return this._loadBranchContext(modelSetKey, coordSetKey, branchKey, false);
    }

    getOrCreateBranch(modelSetKey: string, coordSetKey: string,
                      branchKey: string): Promise<PrivateDiagramBranchContext> {
        return this._loadBranchContext(modelSetKey, coordSetKey, branchKey, true);

    }


    private _loadBranch(modelSetKey: string, coordSetKey: string,
                        branchKey: string): Promise<BranchTuple | null> {
        if (!this.coordSetService.isReady())
            throw new Error("CoordSet service is not initialised yet");

        let coordSet: ModelCoordSet = this.coordSetService
            .coordSetForKey(modelSetKey, coordSetKey);

        let indexBranch: BranchTuple | null = null;
        let localBranch: BranchTuple | null = null;

        let promises = [];

        // Load the branch from the index
        promises.push(
            this.branchIndexLoader
                .getBranches(modelSetKey, coordSet.id, [branchKey])
                .then((results: BranchIndexResultI) => {
                    // This will be null if it didn't find one.
                    if (results[branchKey] != null)
                        indexBranch = results[branchKey][0];
                })
        );

        // Load
        promises.push(
            this.branchLocalLoader.loadBranch(modelSetKey, coordSet.id, branchKey)
                .then((branch: BranchTuple | null) => {
                    localBranch = branch;
                })
        );

        let prom: any = Promise.all(promises)
            .then(() => {
                let branch = null;
                if (localBranch != null && indexBranch != null) {
                    if (moment(localBranch.updatedDate).isAfter(indexBranch.updatedDate))
                        branch = localBranch;
                    else
                        branch = indexBranch;

                } else if (localBranch != null) {
                    branch = localBranch;

                } else if (indexBranch != null) {
                    branch = indexBranch;

                } else {
                    return null;
                }

                branch.linkDisps(this.lookupService);
                return branch;

            });

        return prom;
    }


    private _loadBranchContext(modelSetKey: string, coordSetKey: string,
                               branchKey: string,
                               createIfMissing: boolean): Promise<PrivateDiagramBranchContext | null> {
        if (!this.coordSetService.isReady())
            throw new Error("CoordSet service is not initialised yet");

        let coordSet: ModelCoordSet = this.coordSetService
            .coordSetForKey(modelSetKey, coordSetKey);

        let prom: any = this._loadBranch(modelSetKey, coordSetKey, branchKey)
            .then((branch: BranchTuple | null) => {
                if (branch == null) {
                    if (!createIfMissing)
                        return null;

                    branch = BranchTuple.createBranch(coordSet.id, branchKey);
                    branch.linkDisps(this.lookupService);
                }


                return new PrivateDiagramBranchContext(
                    this.vortexStatusService,
                    this.balloonMsg,
                    this.lookupService, branch,
                    coordSet.modelSetId, modelSetKey, coordSetKey,
                    this.tupleService,
                    this.branchLocalLoader,
                    this.userService.userDetails
                );

            });

        return prom;
    }

    // ---------------
    // Layer Select Popup
    /** This method is called from the diagram-toolbar component */
    popupEditBranchSelection(modelSetKey: string, coordSetKey: string): void {
        this._popupEditBranchSelectionSubject.next({
            modelSetKey: modelSetKey,
            coordSetKey: coordSetKey
        })
    }

    /** This observable is subscribed to by the create/edit branch popup */
    get popupEditBranchSelectionObservable(): Observable<PopupEditBranchSelectionArgs> {
        return this._popupEditBranchSelectionSubject;
    }


    startEditing(modelSetKey: string, coordSetKey: string,
                 branchKey: string): Promise<void> {
        // If we're already editing this branch then do nothing
        if (this.activeBranchContext != null
            && this.activeBranchContext.modelSetKey == modelSetKey
            && this.activeBranchContext.coordSetKey == coordSetKey
            && this.activeBranchContext.branchTuple.key == branchKey) {
            return;
        }

        let prom: any = this.getOrCreateBranch(modelSetKey, coordSetKey, branchKey)
            .catch(e => this._startEditingWithContextObservable.error(e))
            .then((context: any) => {
                this.activeBranchContext = context;
                this._startEditingWithContextObservable.next(context);
                this._startEditingObservable.next();
            })
            .then(() => null);
        return prom;
    }

    startEditingWithContextObservable(): Observable<PrivateDiagramBranchContext> {
        return this._startEditingWithContextObservable;
    }

    startEditingObservable(): Observable<void> {
        return this._startEditingObservable;
    }

    stopEditing(): void {
        this.activeBranchContext = null;
        this._stopEditingObservable.next();
    }

    stopEditingObservable(): Observable<void> {
        return this._stopEditingObservable;
    }

    // ========================================================================
    // Methods for the public class


    setVisibleBranches(commonBranches: BranchDetailTuple[]): void {
        this.enabledBranches = commonBranches;

    }

    getActiveBranchDetails(): Promise<DiagramBranchDetailsI | null> {
        if (this.activeBranchContext == null)
            return Promise.resolve(null);

        return Promise.resolve({
            modelSetKey: this.activeBranchContext.modelSetKey,
            coordSetKey: this.activeBranchContext.coordSetKey,
            branchKey: this.activeBranchContext.branchTuple.key,
            updatedByUser: this.activeBranchContext.branchTuple.updatedByUser,
            createdDate: this.activeBranchContext.branchTuple.createdDate,
            updatedDate: this.activeBranchContext.branchTuple.updatedDate,
            anchorKeys: this.activeBranchContext.branchTuple.anchorDispKeys,
        });
    }


}