import {DatePipe} from "@angular/common";
import {CalendarBase} from "../calendarbase";
import {DateUnit} from "../dateunit";
import {Row, Cell} from "../elements";

export class Jalali implements CalendarBase {
    weekDayNames = ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه', 'شنبه'];
    weekFirstDay = 6;
    monthNames = ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'ابان', 'آذر', 'دی', 'بهمن', 'اسفند'];
    private monthLength = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
    private leapYearMonthLength = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30];

    getNameOfMonth(num: number): string {
        return this.monthNames[num];
    }

    dateToString(date: Date, format: string): string {
        let res;
        if(date.getFullYear() > 1800) {
            res = date
        } else {
            res = Jalali.jalaliToGregorian(+date.getFullYear(),+date.getMonth()+1, +date.getDate());
            res.setMonth(res.getMonth()-1);
        }
        let output = new DatePipe('fa-fa').transform(res, format);
        return output.replace('ه‍.ش.', '');
    }

    dateToDateUnit(date: Date): DateUnit {
        let uDate: DateUnit = new DateUnit();
        if (date.getFullYear() > 1800) {
            let tempDate = Jalali.gregorianToJalali(date.getFullYear(), date.getMonth()+1, date.getDate());
            uDate.year = tempDate.jy;
            uDate.month = tempDate.jm-1;
            uDate.day = tempDate.jd;
        } else {
            uDate.year = date.getFullYear();
            uDate.month = date.getMonth();
            uDate.day = date.getDate();
        }

        uDate.hour = date.getHours();
        uDate.minute = date.getMinutes();
        uDate.seconds = date.getSeconds();

        return uDate;
    }

    dateUnitToDate(date: DateUnit): Date {
        return new Date(date.year, date.month, date.day, date.hour, date.minute, date.seconds);
    }

    getMonthLength(date: DateUnit) {
        let r = Jalali.jalaliCal(date.year);
        if (r.leap) {
            return this.monthLength[date.month];
        }
        return this.leapYearMonthLength[date.month];
    }

    weekHeaders() {
        let row = new Row();
        for (let i = 0; i < 7; i++) {
            row.cells[i] = new Cell;
            let index = i + this.weekFirstDay;
            index -= (i + this.weekFirstDay < 7) ? 0 : 7;
            row.cells[i].content = this.weekDayNames[index];
        }
        return row;
    }

    dayNumberOfMonthFirst(date: DateUnit): number {
        let temp = this.dateUnitToDate(date);
        return Jalali.jalaliToGregorian(temp.getFullYear(), temp.getMonth(), 1).getDay();
    }

    public static jalaliToGregorian(year, month, day) {
        let r = Jalali.jalaliCal(year);
        let d = Jalali.div((r.gy + Jalali.div(3 - 8, 6) + 100100) * 1461, 4)
            + Jalali.div(153 * Jalali.mod(3 + 9, 12) + 2, 5)
            + r.march - 34840408;
        d = d - Jalali.div(Jalali.div(r.gy + 100100 + Jalali.div(3 - 8, 6), 100) * 3, 4) + 752;

        let result = d + (month - 1) * 31 - Jalali.div(month, 7) * (month - 7) + day - 1;

        let j, i, gd, gm, gy;
        j = 4 * result + 139361631;
        j = j + Jalali.div(Jalali.div(4 * result + 183187720, 146097) * 3, 4) * 4 - 3908;
        i = Jalali.div(Jalali.mod(j, 1461), 4) * 5 + 308;
        gd = Jalali.div(Jalali.mod(i, 153), 5) + 1;
        gm = Jalali.mod(Jalali.div(i, 153), 12) + 1;
        gy = Jalali.div(j, 1461) - 100100 + Jalali.div(8 - gm, 6);
        return new Date(gy, gm, gd);
    }

    public static gregorianToJalali(year, month, day) {

        let d = Jalali.div((year + Jalali.div(month - 8, 6) + 100100) * 1461, 4)
            + Jalali.div(153 * Jalali.mod(month + 9, 12) + 2, 5)
            + day - 34840408;
        d = d - Jalali.div(Jalali.div(year + 100100 + Jalali.div(month - 8, 6), 100) * 3, 4) + 752;

        let j0, i0, gd0, gm0, gy0;

        j0 = 4 * d + 139361631;
        j0 = j0 + Jalali.div(Jalali.div(4 * d + 183187720, 146097) * 3, 4) * 4 - 3908;
        i0 = Jalali.div(Jalali.mod(j0, 1461), 4) * 5 + 308;
        gd0 = Jalali.div(Jalali.mod(i0, 153), 5) + 1;
        gm0 = Jalali.mod(Jalali.div(i0, 153), 12) + 1;
        gy0 = Jalali.div(j0, 1461) - 100100 + Jalali.div(8 - gm0, 6);
        let d2g = {
            gy: gy0
            , gm: gm0
            , gd: gd0
        };


        // Calculate Gregorian year (gy).
        let gy = d2g.gy;
        let jy = gy - 621;

        let r = Jalali.jalaliCal(jy);

        let jdn1f = Jalali.div((gy + Jalali.div(3 - 8, 6) + 100100) * 1461, 4)
            + Jalali.div(153 * Jalali.mod(3 + 9, 12) + 2, 5)
            + r.march - 34840408;
        jdn1f = jdn1f - Jalali.div(Jalali.div(gy + 100100 + Jalali.div(3 - 8, 6), 100) * 3, 4) + 752;

        let jd;
        let jm;
        let k;

        // Find number of days that passed since 1 Farvardin.
        k = d - jdn1f;
        if (k >= 0) {
            if (k <= 185) {
                // The first 6 months.
                jm = 1 + Jalali.div(k, 31);
                jd = Jalali.mod(k, 31) + 1;
                return {
                    jy: jy
                    , jm: jm
                    , jd: jd
                }
            } else {
                // The remaining months.
                k -= 186
            }
        } else {
            // Previous Jalali year.
            jy -= 1;
            k += 179;
            if (r.leap === 1)
                k += 1;
        }
        jm = 7 + Jalali.div(k, 30);
        jd = Jalali.mod(k, 30) + 1;
        return {
            jy: jy
            , jm: jm
            , jd: jd
        }
    }

    private static jalaliCal(jy) {
        let breaks = [-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210
            , 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
        ]
            , bl = breaks.length
            , gy1 = jy + 621
            , leapJ = -14
            , jp = breaks[0]
            , jm1
            , jump
            , leap
            , leapG
            , march
            , n
            , i1;

        if (jy < jp || jy >= breaks[bl - 1])
            throw new Error('Invalid Jalali year ' + jy);

        // Find the limiting years for the Jalali year jy.
        for (i1 = 1; i1 < bl; i1 += 1) {
            jm1 = breaks[i1];
            jump = jm1 - jp;
            if (jy < jm1)
                break;
            leapJ = leapJ + Jalali.div(jump, 33) * 8 + Jalali.div(Jalali.mod(jump, 33), 4);
            jp = jm1
        }
        n = jy - jp;

        // Find the number of leap years from AD 621 to the beginning
        // of the current Jalali year in the Persian calendar.
        leapJ = leapJ + Jalali.div(n, 33) * 8 + Jalali.div(Jalali.mod(n, 33) + 3, 4);
        if (Jalali.mod(jump, 33) === 4 && jump - n === 4)
            leapJ += 1;

        // And the same in the Gregorian calendar (until the year gy).
        leapG = Jalali.div(gy1, 4) - Jalali.div((Jalali.div(gy1, 100) + 1) * 3, 4) - 150;

        // Determine the Gregorian date of Farvardin the 1st.
        march = 20 + leapJ - leapG;

        // Find how many years have passed since the last leap year.
        if (jump - n < 6)
            n = n - jump + Jalali.div(jump + 4, 33) * 33;
        leap = Jalali.mod(Jalali.mod(n + 1, 33) - 1, 4);
        if (leap === -1) {
            leap = 4
        }

        return {
            leap: leap
            , gy: gy1
            , march: march
        };
    }

    private static div(a, b): number {
        return ~~(a / b);
    }

    private static mod(a, b): number {
        return a - ~~(a / b) * b;
    }
}
