import { ChangeDetectorRef, Directive, EventEmitter, Input, Output } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';

import { PaginationService } from './pagination.service';
import { PaginationInstance } from './pagination-instance';

export interface Page {
    label: string;
    value: any;
}

@Directive({
    selector: 'pagination-template,[pagination-template]',
    exportAs: 'paginationApi'
})
export class PaginationControlsDirective {
    @Input() id: string;
    @Input() maxSize: number = 7;
    @Output() pageChange: EventEmitter<number> = new EventEmitter<number>();
    pages: Page[] = [];

    private changeSub: Subscription;

    constructor(private service: PaginationService,
        private changeDetectorRef: ChangeDetectorRef) {
        this.changeSub = this.service.change
            .subscribe(id => {
                if (this.id === id) {
                    this.updatePageLinks();
                    this.changeDetectorRef.markForCheck();
                    this.changeDetectorRef.detectChanges();
                }
            });
    }

    ngOnInit() {
        if (this.id === undefined) {
            this.id = this.service.defaultId();
        }
        this.updatePageLinks();
    }

    ngOnChanges(changes: any) {
        this.updatePageLinks();
    }

    ngOnDestroy() {
        this.changeSub.unsubscribe();
    }

    previous() {
        this.checkValidId();
        this.setCurrent(this.getCurrent() - 1);
    }

    next() {
        this.checkValidId();
        this.setCurrent(this.getCurrent() + 1);
    }

    isFirstPage(): boolean {
        return this.getCurrent() === 1;
    }

    isLastPage(): boolean {
        return this.getLastPage() === this.getCurrent();
    }

    setCurrent(page: number) {
        this.pageChange.emit(page);
    }

    getCurrent(): number {
        return this.service.getCurrentPage(this.id);
    }

    getLastPage(): number {
        let inst = this.service.getInstance(this.id);
        if (inst.totalItems < 1) {
            return 1;
        }
        return Math.ceil(inst.totalItems / inst.itemsPerPage);
    }

    private checkValidId() {
        if (!this.service.getInstance(this.id).id) {
            console.warn(`PaginationControlsDirective: the specified id "${this.id}" does not match any registered PaginationInstance`);
        }
    }

    private updatePageLinks() {
        let inst = this.service.getInstance(this.id);
        const correctedCurrentPage = this.outOfBoundCorrection(inst);

        if (correctedCurrentPage !== inst.currentPage) {
            setTimeout(() => {
                this.setCurrent(correctedCurrentPage);
                this.pages = this.createPageArray(inst.currentPage, inst.itemsPerPage, inst.totalItems, this.maxSize);
            });
        } else {
            this.pages = this.createPageArray(inst.currentPage, inst.itemsPerPage, inst.totalItems, this.maxSize);
        }
    }

    private outOfBoundCorrection(instance: PaginationInstance): number {
        const totalPages = Math.ceil(instance.totalItems / instance.itemsPerPage);
        if (totalPages < instance.currentPage && 0 < totalPages) {
            return totalPages;
        } else if (instance.currentPage < 1) {
            return 1;
        }

        return instance.currentPage;
    }

    private createPageArray(currentPage: number, itemsPerPage: number, totalItems: number, paginationRange: number): Page[] {
        paginationRange = +paginationRange;
        let pages = [];
        const totalPages = Math.ceil(totalItems / itemsPerPage);
        const halfWay = Math.ceil(paginationRange / 2);

        const isStart = currentPage <= halfWay;
        const isEnd = totalPages - halfWay < currentPage;
        const isMiddle = !isStart && !isEnd;

        let ellipsesNeeded = paginationRange < totalPages;
        let i = 1;

        while (i <= totalPages && i <= paginationRange) {
            let label;
            let pageNumber = this.calculatePageNumber(i, currentPage, paginationRange, totalPages);
            let openingEllipsesNeeded = (i === 2 && (isMiddle || isEnd));
            let closingEllipsesNeeded = (i === paginationRange - 1 && (isMiddle || isStart));
            if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
                label = '...';
            } else {
                label = pageNumber;
            }
            pages.push({
                label: label,
                value: pageNumber
            });
            i++;
        }
        return pages;
    }

    private calculatePageNumber(i: number, currentPage: number, paginationRange: number, totalPages: number) {
        let halfWay = Math.ceil(paginationRange / 2);
        if (i === paginationRange) {
            return totalPages;
        } else if (i === 1) {
            return i;
        } else if (paginationRange < totalPages) {
            if (totalPages - halfWay < currentPage) {
                return totalPages - paginationRange + i;
            } else if (halfWay < currentPage) {
                return currentPage - halfWay + i;
            } else {
                return i;
            }
        } else {
            return i;
        }
    }
}
