import { Pipe } from "@angular/core";
import { PaginationService } from "./pagination.service";
import { PaginationInstance } from './pagination-instance';

const LARGE_NUMBER = Number.MAX_SAFE_INTEGER;

interface PipeState {
    collection: any[];
    size: number;
    start: number;
    end: number;
    slice: any[];
}

@Pipe({
    name: 'paginate',
    pure: false
})
export class PaginatePipe {
    private state: { [ id: string ]: PipeState } = {};

    constructor(private service: PaginationService) {
    }

    public transform(collection: any[], args: any): any {
        if (args instanceof Array) {
            args = args[ 0 ];
        }
        if (!(collection instanceof Array)) {
            let _id = args.id || this.service.defaultId;
            if (this.state[ _id ]) {
                return this.state[ _id ].slice;
            } else {
                return collection;
            }
        }

        let serverSideMode = args.totalItems && args.totalItems !== collection.length;
        let instance = this.createInstance(collection, args);
        let id = instance.id;
        let start, end;
        let perPage = instance.itemsPerPage;

        this.service.register(instance);

        if (!serverSideMode && collection instanceof Array) {
            perPage = +perPage || LARGE_NUMBER;
            start = (instance.currentPage - 1) * perPage;
            end = start + perPage;

            let isIdentical = this.stateIsIdentical(id, collection, start, end);
            if (isIdentical) {
                return this.state[ id ].slice;
            } else {
                let slice = collection.slice(start, end);
                this.saveState(id, collection, slice, start, end);
                this.service.change.emit(id);
                return slice;
            }
        }
        this.saveState(id, collection, collection, start, end);
        return collection;
    }

    private createInstance(collection: any[], args: any): PaginationInstance {
        let config = args;
        this.checkConfig(config);
        return {
            id: config.id || this.service.defaultId(),
            itemsPerPage: config.itemsPerPage || 0,
            currentPage: config.currentPage || 1,
            totalItems: config.totalItems || collection.length
        };
    }

    private checkConfig(config: any): void {
        const required = [ 'itemsPerPage', 'currentPage' ];
        const missing = required.filter(prop => !(prop in config));
        if (0 < missing.length) {
            throw new Error(`PaginatePipe: Argument is missing the following required properties: ${missing.join(', ')}`);
        }
    }

    private saveState(id: string, collection: any[], slice: any[], start: number, end: number) {
        this.state[ id ] = {
            collection,
            size: collection.length,
            slice,
            start,
            end
        };
    }

    private stateIsIdentical(id: string, collection: any[], start: number, end: number): boolean {
        let state = this.state[ id ];
        if (!state) {
            return false;
        }
        let isMetaDataIdentical = state.size === collection.length &&
            state.start === start &&
            state.end === end;

        if (!isMetaDataIdentical) {
            return false;
        }
        return state.slice.every((element, index) => element === collection[ start + index ]);
    }
}
