// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser.picker;

import android.os.Build;
import android.widget.DatePicker;
import android.widget.DatePicker.OnDateChangedListener;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

/**
 * Sets the current, min, and max values on the given DatePicker.
 */
public class DateDialogNormalizer {

    /**
     * Stores a date (year-month-day) and the number of milliseconds corresponding to that date
     * according to the DatePicker's calendar.
     */
    private static class DateAndMillis {
        /**
         * Number of milliseconds from the epoch (1970-01-01) to the beginning of year-month-day
         * in the default time zone (TimeZone.getDefault()) using the Julian/Gregorian split
         * calendar. This value is interopable with {@link DatePicker#getMinDate} and
         * {@link DatePicker#setMinDate}.
         */
        public final long millisForPicker;

        public final int year;
        public final int month;  // 0-based
        public final int day;

        DateAndMillis(long millisForPicker, int year, int month, int day) {
            this.millisForPicker = millisForPicker;
            this.year = year;
            this.month = month;
            this.day = day;
        }

        static DateAndMillis create(long millisUtc) {
            // millisUtc uses the Gregorian calendar only, so disable the Julian changeover date.
            GregorianCalendar utcCal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
            utcCal.setGregorianChange(new Date(Long.MIN_VALUE));
            utcCal.setTimeInMillis(millisUtc);
            int year = utcCal.get(Calendar.YEAR);
            int month = utcCal.get(Calendar.MONTH);
            int day = utcCal.get(Calendar.DAY_OF_MONTH);
            return create(year, month, day);
        }

        static DateAndMillis create(int year, int month, int day) {
            // By contrast, millisForPicker uses the default Gregorian/Julian changeover date.
            Calendar defaultTimeZoneCal = Calendar.getInstance(TimeZone.getDefault());
            defaultTimeZoneCal.clear();
            defaultTimeZoneCal.set(year, month, day);
            long millisForPicker = defaultTimeZoneCal.getTimeInMillis();
            return new DateAndMillis(millisForPicker, year, month, day);
        }
    }

    private static void setLimits(DatePicker picker, long currentMillisForPicker,
            long minMillisForPicker, long maxMillisForPicker) {
        // On Lollipop only (not KitKat or Marshmallow), DatePicker has terrible performance for
        // large date ranges. This causes problems when the min or max date isn't set in HTML, in
        // which case these values default to the min and max possible values for the JavaScript
        // Date object (1CE and 275760CE). As a workaround, limit the date range to 5000 years
        // before and after the current date. In practice, this doesn't limit users since scrolling
        // through 5000 years in the DatePicker is highly impractical anyway. See
        // http://crbug.com/441060
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
                || Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
            final long maxRangeMillis = 5000L * 365 * 24 * 60 * 60 * 1000;
            minMillisForPicker = Math.max(minMillisForPicker,
                    currentMillisForPicker - maxRangeMillis);
            maxMillisForPicker = Math.min(maxMillisForPicker,
                    currentMillisForPicker + maxRangeMillis);
        }

        // On KitKat and earlier, DatePicker requires the minDate is always less than maxDate, even
        // during the process of setting those values (eek), so set them in an order that preserves
        // this invariant throughout.
        if (minMillisForPicker > picker.getMaxDate()) {
            picker.setMaxDate(maxMillisForPicker);
            picker.setMinDate(minMillisForPicker);
        } else {
            picker.setMinDate(minMillisForPicker);
            picker.setMaxDate(maxMillisForPicker);
        }
    }

    /**
     * Sets the current, min, and max values on the given DatePicker and ensures that
     * min <= current <= max, adjusting current and max if needed.
     *
     * @param year The current year to set.
     * @param month The current month to set. 0-based.
     * @param day The current day to set.
     * @param minMillisUtc The minimum allowed date, in milliseconds from the epoch according to a
     *                     proleptic Gregorian calendar (no Julian switch).
     * @param maxMillisUtc The maximum allowed date, in milliseconds from the epoch according to a
     *                     proleptic Gregorian calendar (no Julian switch).
     */
    public static void normalize(DatePicker picker, final OnDateChangedListener listener,
            int year, int month, int day, long minMillisUtc, long maxMillisUtc) {
        DateAndMillis currentDate = DateAndMillis.create(year, month, day);
        DateAndMillis minDate = DateAndMillis.create(minMillisUtc);
        DateAndMillis maxDate = DateAndMillis.create(maxMillisUtc);

        // Ensure min <= current <= max, adjusting current and max if needed.
        if (maxDate.millisForPicker < minDate.millisForPicker) {
            maxDate = minDate;
        }
        if (currentDate.millisForPicker < minDate.millisForPicker) {
            currentDate = minDate;
        } else if (currentDate.millisForPicker > maxDate.millisForPicker) {
            currentDate = maxDate;
        }

        setLimits(picker, currentDate.millisForPicker, minDate.millisForPicker,
                maxDate.millisForPicker);
        picker.init(currentDate.year, currentDate.month, currentDate.day, listener);
    }
}
