import {Injectable} from "@angular/core";
import {GridCache} from "./GridCache.web";
import {LinkedGrid} from "./LinkedGrid.web";
import {Subject} from "rxjs/Subject";
import {assert, dictKeysFromObject} from "../DiagramUtil";

/** Grid Observable
 *
 * This class keeps track of the different diagram models that are observing grid updates
 *
 * When a grid update comes in from the cache, this class will notify the relevent models.
 *
 */
@Injectable()
export class GridObservable {
    private subjectByCanvasId: { [canvasId: number]: Subject<LinkedGrid> } = {};
    private gridKeysByCanvasId: { [canvasId: number]: string[] } = {};
    private canvasIdsByGidKey: { [gridKey: string]: number[] } = {};

    private lastObservedKeysStr = "";

    constructor(private gridCache: GridCache) {

        // This is a global service, there is no point at which this will unsubscribe
        this.gridCache.observable.subscribe(
            (grid: LinkedGrid) => this.processGridUpdates(grid)
        );

    }

    isReady(): boolean {
        return this.gridCache.isReady();
    }

    /** Unsubscribe Canvas.
     *
     * The canvas component must call this to tear down and release resources used
     * for it.
     */
    unsubscribeCanvas(canvasId: number): void {
        /* 1) Delete the Subject
         * 2) Update the data structures
         * 3) Notify the grid cache
         */
        delete this.gridKeysByCanvasId[canvasId];
        delete this.subjectByCanvasId[canvasId];
        this.rebuildReverseLookup();
        this.updateGridCacheWatchedKeys();
    }

    observableForCanvas(canvasId: number): Subject<LinkedGrid> {
        // Each canvas should only request the subject once
        assert(!this.subjectByCanvasId.hasOwnProperty(canvasId),
            `Canvas ${canvasId} has already requested a subhect`);

        this.subjectByCanvasId[canvasId] = new Subject<LinkedGrid>();
        return this.subjectByCanvasId[canvasId];

    }

    updateDiagramWatchedGrids(canvasId: number, gridKeys: string[],
                              forceCacheFlush = false): void {
        if (forceCacheFlush) {
            this.gridCache.flushCache();
            this.lastObservedKeysStr = '';
        }

        this.gridKeysByCanvasId[canvasId] = gridKeys;
        this.rebuildReverseLookup();
        this.updateGridCacheWatchedKeys();
    }

    resetAllDispComputedProperties(): void {
        this.gridCache.resetAllComputedProperties();
    }

    private processGridUpdates(grid: LinkedGrid) {
        // If we have nothing waiting for this grid, Just disregard it.
        if (!this.canvasIdsByGidKey.hasOwnProperty(grid.gridKey))
            return;

        // Notify the subjects for the cavases watching this grid key
        let canvasIds = this.canvasIdsByGidKey[grid.gridKey];
        for (let canvasId of canvasIds) {
            this.subjectByCanvasId[canvasId].next(grid);
        }

    }

    private updateGridCacheWatchedKeys() {
        let uniqueKeysList = dictKeysFromObject(this.canvasIdsByGidKey).sort();
        let uniqueKeysStr = uniqueKeysList.join(',');

        if (this.lastObservedKeysStr == uniqueKeysStr)
            return;

        this.lastObservedKeysStr = uniqueKeysStr;
        this.gridCache.updateWatchedGrids(uniqueKeysList);
    }

    private rebuildReverseLookup() {
        let newDict = {};

        // Iterate through the canvasIds
        for (let canvasId of dictKeysFromObject(this.gridKeysByCanvasId)) {
            let gridKeys = this.gridKeysByCanvasId[canvasId];

            // Iterate through the gridKeys
            for (let gridKey of gridKeys) {
                // Get the existing array or create one
                let array: any[] = null;
                if (newDict.hasOwnProperty(gridKey)) {
                    array = newDict[gridKey];
                } else {
                    newDict[gridKey] = array = [];
                }
                // Add the item to the array
                array.push(canvasId);
            }
        }

        // Assign the recompiled value back to the class variable
        this.canvasIdsByGidKey = newDict;
    }

}