"use strict";
/**
 * Business hours calculation utilities.
 * Ported from steedos-platform packages/core/src/holidays/business_hours.ts
 */
const moment = require('moment');

// BusinessHoursCheckedType enum values
const BusinessHoursCheckedType = {
    onAm: 1,       // 上午班
    onPm: 2,       // 下午班
    offDay: 0,     // 非工作日，即节假日、周未等
    offAm: -1,     // 工作日，下班时间，非午休，早上0点到上午上班时间
    offPm: -2,     // 工作日，下班时间，非午休，下午下班时间到第二天早上0点
    offLunch: -3   // 工作日，午休时间
};

/**
 * 把09:33这种字符串解析出来对应的时间值
 * @param {string} str
 * @param {number} digitsForHours
 * @returns {object} { hours, minutes, valueToHours, valueToMinutes }
 */
function getStringTimeValue(str, digitsForHours = 2) {
    str = str.trim();
    if (!/^\d{1,2}:\d{1,2}$/.test(str)) {
        throw new Error("getStringTimeValue:Time format error, please enter HH:MM this format of 24 hours time character.");
    }
    const splits = str.split(":");
    const h = parseInt(splits[0]);
    const m = parseInt(splits[1]);
    if (h > 24 || m > 60) {
        throw new Error("getStringTimeValue:Time format error, please enter HH:MM this format of 24 hours time character.");
    } else if (h === 24 && m > 0) {
        throw new Error("getStringTimeValue:Time format error, please enter HH:MM this format of 24 hours time character.");
    }
    const valueToMinutes = h * 60 + m;
    const valueToHours = Number((valueToMinutes / 60).toFixed(digitsForHours));
    return { hours: h, minutes: m, valueToHours, valueToMinutes };
}

/**
 * 计算09:00-18:00这种开始时间结束时间代表的每天工作时间长度
 */
function computeBusinessHoursPerDay(start, end, lunch_start, lunch_end, digitsForHours = 2) {
    const startValue = getStringTimeValue(start, digitsForHours);
    const endValue = getStringTimeValue(end, digitsForHours);
    const lunchStartValue = getStringTimeValue(lunch_start, digitsForHours);
    const lunchEndValue = getStringTimeValue(lunch_end, digitsForHours);
    if (startValue && endValue && lunchStartValue && lunchEndValue) {
        let computedMinutes = endValue.valueToMinutes - startValue.valueToMinutes;
        const computedAmMinutes = lunchStartValue.valueToMinutes - startValue.valueToMinutes;
        const computedPmMinutes = endValue.valueToMinutes - lunchEndValue.valueToMinutes;
        const computedLunchMinutes = lunchEndValue.valueToMinutes - lunchStartValue.valueToMinutes;
        computedMinutes = computedMinutes - computedLunchMinutes;
        if (computedMinutes <= 0 || computedLunchMinutes <= 0) {
            throw new Error("computeBusinessHoursPerDay:The end or lunch_end time value must be later than the start or lunch_start time.");
        } else if (lunchStartValue.valueToMinutes <= startValue.valueToMinutes || lunchEndValue.valueToMinutes >= endValue.valueToMinutes) {
            throw new Error("computeBusinessHoursPerDay:The lunch time must between the working time.");
        } else {
            const computedHours = Number((computedMinutes / 60).toFixed(digitsForHours));
            const computedAmHours = Number((computedAmMinutes / 60).toFixed(digitsForHours));
            const computedPmHours = Number((computedPmMinutes / 60).toFixed(digitsForHours));
            const computedLunchHours = Number((computedLunchMinutes / 60).toFixed(digitsForHours));
            return {
                computedHours, computedMinutes,
                computedAmHours, computedAmMinutes,
                computedPmHours, computedPmMinutes,
                computedLunchHours, computedLunchMinutes,
                startValue, endValue, lunchStartValue, lunchEndValue
            };
        }
    } else {
        throw new Error("computeBusinessHoursPerDay:start or end is not valid.");
    }
}

/**
 * 根据businessHours取其对应的每天工作时间长度，并把值缓存到其computedPerDay属性中
 */
function getBusinessHoursPerDay(businessHours, digitsForHours = 2) {
    if (businessHours.computedPerDay) {
        return businessHours.computedPerDay;
    }
    const computedPerDay = computeBusinessHoursPerDay(
        businessHours.start, businessHours.end,
        businessHours.lunch_start, businessHours.lunch_end,
        digitsForHours
    );
    businessHours.computedPerDay = computedPerDay;
    return computedPerDay;
}

/**
 * 计算某个日期是不是工作日
 */
function computeIsBusinessDay(date, holidays, workingDays) {
    const value = moment.utc(date);
    value.hours(0);
    value.minutes(0);
    value.seconds(0);
    value.milliseconds(0);
    const holiday = holidays.find((item) => {
        return item.date && item.date.getTime() === value.toDate().getTime();
    });
    if (holiday) {
        switch (holiday.type) {
            case "adjusted_working_day":
                return true;
            case "adjusted_holiday":
                return false;
            case "public":
                return false;
        }
    } else {
        return workingDays.indexOf(value.day().toString()) > -1;
    }
}

/**
 * 计算某个时间点是不是工作时间（只判断时间点，不判断日期）
 */
function computeIsBusinessHours(date, businessHours, digitsForHours = 2) {
    const utcOffset = businessHours.utc_offset;
    const businessHoursPerDay = getBusinessHoursPerDay(businessHours, digitsForHours);
    const startMoment = moment.utc(date);
    startMoment.hours(businessHoursPerDay.startValue.hours - utcOffset);
    startMoment.minutes(businessHoursPerDay.startValue.minutes);
    const endMoment = moment.utc(date);
    endMoment.hours(businessHoursPerDay.endValue.hours - utcOffset);
    endMoment.minutes(businessHoursPerDay.endValue.minutes);
    const lunchStartMoment = moment.utc(date);
    lunchStartMoment.hours(businessHoursPerDay.lunchStartValue.hours - utcOffset);
    lunchStartMoment.minutes(businessHoursPerDay.lunchStartValue.minutes);
    const lunchEndMoment = moment.utc(date);
    lunchEndMoment.hours(businessHoursPerDay.lunchEndValue.hours - utcOffset);
    lunchEndMoment.minutes(businessHoursPerDay.lunchEndValue.minutes);
    const startTimeValue = startMoment.toDate().getTime();
    const endTimeValue = endMoment.toDate().getTime();
    const lunchStartTimeValue = lunchStartMoment.toDate().getTime();
    const lunchEndTimeValue = lunchEndMoment.toDate().getTime();
    const dateTimeValue = date.getTime();
    if (dateTimeValue <= endTimeValue && dateTimeValue >= startTimeValue) {
        if (dateTimeValue <= lunchStartTimeValue) {
            return BusinessHoursCheckedType.onAm;
        } else if (dateTimeValue >= lunchEndTimeValue) {
            return BusinessHoursCheckedType.onPm;
        } else {
            return BusinessHoursCheckedType.offLunch;
        }
    } else {
        if (dateTimeValue > endTimeValue) {
            return BusinessHoursCheckedType.offPm;
        } else {
            return BusinessHoursCheckedType.offAm;
        }
    }
}

/**
 * 计算某个时间点是不是工作时间，包括节假日及非工作时间都要排除
 */
function computeIsBusinessDate(date, holidays, businessHours, digitsForHours = 2) {
    if (computeIsBusinessDay(date, holidays, businessHours.working_days)) {
        return computeIsBusinessHours(date, businessHours, digitsForHours);
    } else {
        return BusinessHoursCheckedType.offDay;
    }
}

/**
 * 根据来源时间所在日期计算下一个工作日的开始时间
 */
function computeNextBusinessDate(source, holidays, businessHours, digitsForHours = 2) {
    const utcOffset = businessHours.utc_offset;
    const businessHoursPerDay = getBusinessHoursPerDay(businessHours, digitsForHours);
    const workingDays = businessHours.working_days;
    const sourceMoment = moment.utc(source);
    sourceMoment.hours(businessHoursPerDay.startValue.hours - utcOffset);
    sourceMoment.minutes(businessHoursPerDay.startValue.minutes);
    let startMoment = null;
    const maxCount = 365;
    for (let i = 0; i < maxCount; i++) {
        sourceMoment.add(1, 'd');
        if (computeIsBusinessDay(sourceMoment.toDate(), holidays, workingDays)) {
            startMoment = sourceMoment;
            break;
        }
    }
    if (startMoment) {
        const start = startMoment.toDate();
        startMoment.hours(businessHoursPerDay.endValue.hours - utcOffset);
        startMoment.minutes(businessHoursPerDay.endValue.minutes);
        const end = startMoment.toDate();
        return { start, end };
    } else {
        return null;
    }
}

module.exports = {
    BusinessHoursCheckedType,
    getStringTimeValue,
    computeBusinessHoursPerDay,
    getBusinessHoursPerDay,
    computeIsBusinessDay,
    computeIsBusinessHours,
    computeIsBusinessDate,
    computeNextBusinessDate
};
