package ru.yandex.webmaster3.storage.searchquery;

import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import com.google.common.collect.TreeRangeSet;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.Days;
import org.joda.time.LocalDate;

/**
 * @author aherman
 */
public class RangeFactory {
    protected static Pair<LocalDate, LocalDate> normalize(LocalDate dateFrom, LocalDate dateTo) {
        if (dateFrom.isAfter(dateTo)) {
            LocalDate t = dateFrom;
            dateFrom = dateTo;
            dateTo = t;
        }

        return Pair.of(dateFrom, dateTo);
    }

    public static RangeSet<LocalDate> singleRange(LocalDate dateFrom, LocalDate dateTo) {
        Pair<LocalDate, LocalDate> datePair = normalize(dateFrom, dateTo);
        dateFrom = datePair.getLeft();
        dateTo = datePair.getRight();

        RangeSet<LocalDate> result = TreeRangeSet.create();
        result.add(Range.closed(dateFrom, dateTo));
        return result;
    }

    public static RangeSet<LocalDate> doubleRange(LocalDate dateFrom, LocalDate dateTo) {
        Pair<LocalDate, LocalDate> datePair = normalize(dateFrom, dateTo);
        dateFrom = datePair.getLeft();
        dateTo = datePair.getRight();

        RangeSet<LocalDate> result = TreeRangeSet.create();
        result.add(Range.closed(dateFrom, dateTo));

        int daysBetween = Days.daysBetween(dateFrom, dateTo).getDays() + 1;
        LocalDate previousDateFrom = dateFrom.minusDays(daysBetween);
        LocalDate previousDateTo = dateFrom.minusDays(1);
        result.add(Range.closed(previousDateFrom, previousDateTo));

        return result;
    }

    @SuppressWarnings("Duplicates")
    public static RangeSet<LocalDate> createRanges(LocalDate start, LocalDate end, AggregatePeriod period, boolean snapStartToPeriod, boolean snapEndToPeriod) {
        Pair<LocalDate, LocalDate> datePair = normalize(start, end);
        start = datePair.getLeft();
        end = datePair.getRight();

        RangeSet<LocalDate> result = TreeRangeSet.create();
        LocalDate periodStart = getPeriodStart(start, period);

        do {
            LocalDate periodEnd = getPeriodEnd(periodStart, period);
            result.add(Range.closed(periodStart, periodEnd));
            switch (period) {
                case WEEK: periodStart = periodStart.plusWeeks(1); break;
                case MONTH: periodStart = periodStart.plusMonths(1); break;
                default: periodStart = periodStart.plusDays(1);
            }
        } while (periodStart.isEqual(end) || periodStart.isBefore(end));

        if (!snapStartToPeriod) {
            result = result.subRangeSet(Range.closed(start, result.span().upperEndpoint()));
        }
        if (!snapEndToPeriod) {
            result = result.subRangeSet(Range.closed(result.span().lowerEndpoint(), end));
        }
        return result;
    }

    @SuppressWarnings("Duplicates")
    public static RangeSet<LocalDate> createRanges(LocalDate end, AggregatePeriod period, int numPeriods, boolean snapEndToPeriod) {
        RangeSet<LocalDate> result = TreeRangeSet.create();
        LocalDate periodStart;
        switch (period) {
            case WEEK: periodStart = getPeriodStart(end.minusWeeks(numPeriods - 1), period); break;
            case MONTH: periodStart = getPeriodStart(end.minusMonths(numPeriods - 1), period); break;
            default: periodStart = end.minusDays(numPeriods - 1); break;
        }

        do {
            LocalDate periodEnd = getPeriodEnd(periodStart, period);
            result.add(Range.closed(periodStart, periodEnd));
            switch (period) {
                case WEEK: periodStart = periodStart.plusWeeks(1); break;
                case MONTH: periodStart = periodStart.plusMonths(1); break;
                default: periodStart = periodStart.plusDays(1);
            }
        } while (periodStart.isEqual(end) || periodStart.isBefore(end));

        if (!snapEndToPeriod) {
            result = result.subRangeSet(Range.closed(result.span().lowerEndpoint(), end));
        }
        return result;
    }

    public static LocalDate getPeriodStart(LocalDate date, AggregatePeriod period) {
        LocalDate intervalStart;
        switch (period) {
            case WEEK: intervalStart = date.dayOfWeek().withMinimumValue(); break;
            case MONTH: intervalStart = date.dayOfMonth().withMinimumValue(); break;
            default: intervalStart = date;
        }
        return intervalStart;
    }

    public static LocalDate getPeriodEnd(LocalDate date, AggregatePeriod period) {
        LocalDate intervalEnd;
        switch (period) {
            case WEEK: intervalEnd = date.dayOfWeek().withMaximumValue(); break;
            case MONTH: intervalEnd = date.dayOfMonth().withMaximumValue(); break;
            default: intervalEnd = date;
        }
        return intervalEnd;
    }
}
