// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
 *******************************************************************************
 * Copyright (C) 1996-2014, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.dev.test.calendar;

import java.util.Date;
import java.util.Locale;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.LocaleUtility;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.EasterHoliday;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.Holiday;
import com.ibm.icu.util.RangeDateRule;
import com.ibm.icu.util.SimpleDateRule;
import com.ibm.icu.util.SimpleHoliday;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;

/**
 * Tests for the <code>Holiday</code> class.
 */
@RunWith(JUnit4.class)
public class HolidayTest extends TestFmwk {

    // Do not use Before annotation, because TestFmwk's Before
    // method must be executed first to initialize default time zone
    @Override
    protected void localTestInitialize() {
        cal = new GregorianCalendar(1, 0, 1);
        longTimeAgo = cal.getTime();
        now = new Date();
    }

    private Calendar cal;
    private Date longTimeAgo;
    private Date now;
    private static long awhile = 3600L * 24 * 28; // 28 days

    @Test
    public void TestAPI() {
        {
            // getHolidays
            Holiday[] holidays = Holiday.getHolidays();
            exerciseHolidays(holidays, Locale.getDefault());
        }

        {
            // getHolidays(Locale)
            String[] localeNames =
            {
                "en_US",
                "da",
                "da_DK",
                "de",
                "de_AT",
                "de_DE",
                "el",
                "el_GR",
                "en",
                "en_CA",
                "en_GB",
                "es",
                "es_MX",
                "fr",
                "fr_CA",
                "fr_FR",
                "it",
                "it_IT",
                "iw",
                "iw_IL",
                "ja",
                "ja_JP",
            };

            for (int i = 0; i < localeNames.length; ++i) {
                Locale locale = LocaleUtility.getLocaleFromName(localeNames[i]);
                Holiday[] holidays = Holiday.getHolidays(locale);
                exerciseHolidays(holidays, locale);
            }
        }
    }

    void exerciseHolidays(Holiday[] holidays, Locale locale) {
        for (int i = 0; i < holidays.length; ++i) {
            exerciseHoliday(holidays[i], locale);
        }
    }

    void exerciseHoliday(Holiday h, Locale locale) {
        logln("holiday: " + h.getDisplayName());
        logln("holiday in " + locale + ": " + h.getDisplayName(locale));

        Date first = h.firstAfter(longTimeAgo);
        logln("firstAfter: " + longTimeAgo + " is " + first);
        if (first == null) {
            first = longTimeAgo;
        }
        first.setTime(first.getTime() + awhile);

        Date second = h.firstBetween(first, now);
        logln("firstBetween: " + first + " and " + now + " is " + second);
        if (second == null) {
            second = now;
        } else {
            if(second.after(now)) {
                errln("Error: "+h.getDisplayName()+".firstBetween("+first+", "+now+")="+second);
            }
        }

        logln("is on " + first + ": " + h.isOn(first));
        logln("is on " + now + ": " + h.isOn(now));
        logln(
              "is between "
              + first
              + " and "
              + now
              + ": "
              + h.isBetween(first, now));
        logln(
              "is between "
              + first
              + " and "
              + second
              + ": "
              + h.isBetween(first, second));

        //        logln("rule: " + h.getRule().toString());

        //        h.setRule(h.getRule());


        logln("HolidayCalendarDemo test");
        {
            final Calendar c = Calendar.getInstance(TimeZone.GMT_ZONE); // Temporary copy
            c.set(2014, 10, 8); // when this test was hit
            final Date fStartOfMonth = startOfMonth((Calendar)c.clone());

            // Stash away a few useful constants for this calendar and display
            //minDay = c.getMinimum(Calendar.DAY_OF_WEEK);
            //daysInWeek = c.getMaximum(Calendar.DAY_OF_WEEK) - minDay + 1;

            //firstDayOfWeek = Calendar.getInstance(fDisplayLocale).getFirstDayOfWeek();

            // Stash away a Date for the start of this month

            // Find the day of week of the first day in this month
            c.setTime(fStartOfMonth);
            //firstDayInMonth = c.get(Calendar.DAY_OF_WEEK);

            // Now find the # of days in the month
            c.roll(Calendar.DATE, false);
            // final int daysInMonth = c.get(Calendar.DATE);

            // Finally, find the end of the month, i.e. the start of the next one
            c.roll(Calendar.DATE, true);
            c.add(Calendar.MONTH, 1);
            c.getTime();        // JDK 1.1.2 bug workaround
            c.add(Calendar.SECOND, -1);
            Date endOfMonth = c.getTime();

            //
            // Calculate the number of full or partial weeks in this month.
            // To do this I can just reuse the code that calculates which
            // calendar cell contains a given date.
            //
            //numWeeks = dateToCell(daysInMonth).y - dateToCell(1).y + 1;

            // Remember which holidays fall on which days in this month,
            // to save the trouble of having to do it later
            //fHolidays.setSize(0);
            int patience=100;

            //for (int h = 0; h < fAllHolidays.length; h++)
            {
                Date d = fStartOfMonth;
                while ( (d = h.firstBetween(d, endOfMonth) ) != null)
                {
                    if(--patience <= 0) {
                        errln("Patience exceeded for " + h.getDisplayName() +" at " + d);
                        break;
                    }
                    if(d.after(endOfMonth)) {
                        errln("Error: for " + h.getDisplayName()+": " + d +" is after end of month " + endOfMonth);
                        break;
                    }
                    c.setTime(d);
                    logln("New date: " + d);
//                    fHolidays.addElement( new HolidayInfo(c.get(Calendar.DATE),
//                                            fAllHolidays[h],
//                                            fAllHolidays[h].getDisplayName(fDisplayLocale) ));

                    d.setTime( d.getTime() + 1000 );    // "d++"
                }
            }
//            dirty = false;
        }
        // end HolidayDemoApplet code

    }

    // from HolidayCalendarDemo
    private Date startOfMonth(/*Date dateInMonth,*/ Calendar fCalendar)
    {
        //synchronized(fCalendar) {
        //    fCalendar.setTime(dateInMonth);             // TODO: synchronization

            int era = fCalendar.get(Calendar.ERA);
            int year = fCalendar.get(Calendar.YEAR);
            int month = fCalendar.get(Calendar.MONTH);

            fCalendar.clear();
            fCalendar.set(Calendar.ERA, era);
            fCalendar.set(Calendar.YEAR, year);
            fCalendar.set(Calendar.MONTH, month);
            fCalendar.set(Calendar.DATE, 1);

            return fCalendar.getTime();
       // }
    }

    @Test
    public void TestCoverage(){
        Holiday[] h = { new EasterHoliday("Ram's Easter"),
                        new SimpleHoliday(2, 29, 0, "Leap year", 1900, 2100)};
        exerciseHolidays(h, Locale.getDefault());

        RangeDateRule rdr = new RangeDateRule();
        rdr.add(new SimpleDateRule(7, 10));
        Date mbd = getDate(1953, Calendar.JULY, 10);
        Date dbd = getDate(1958, Calendar.AUGUST, 15);
        Date nbd = getDate(1990, Calendar.DECEMBER, 17);
        Date abd = getDate(1992, Calendar.SEPTEMBER, 16);
        Date xbd = getDate(1976, Calendar.JULY, 4);
        Date ybd = getDate(2003, Calendar.DECEMBER, 8);
        rdr.add(new SimpleDateRule(Calendar.JULY, 10, Calendar.MONDAY, false));
        rdr.add(dbd, new SimpleDateRule(Calendar.AUGUST, 15, Calendar.WEDNESDAY, true));
        rdr.add(xbd, null);
        rdr.add(nbd, new SimpleDateRule(Calendar.DECEMBER, 17, Calendar.MONDAY, false));
        rdr.add(ybd, null);

        logln("first after " + mbd + " is " + rdr.firstAfter(mbd));
        logln("first between " + mbd + " and " + dbd + " is " + rdr.firstBetween(mbd, dbd));
        logln("first between " + dbd + " and " + nbd + " is " + rdr.firstBetween(dbd, nbd));
        logln("first between " + nbd + " and " + abd + " is " + rdr.firstBetween(nbd, abd));
        logln("first between " + abd + " and " + xbd + " is " + rdr.firstBetween(abd, xbd));
        logln("first between " + abd + " and " + null + " is " + rdr.firstBetween(abd, null));
        logln("first between " + xbd + " and " + null + " is " + rdr.firstBetween(xbd, null));

        //getRule, setRule
        logln("The rule in the holiday: " + h[1].getRule());
        exerciseHoliday(h[1], Locale.getDefault());
        h[1].setRule(rdr);
        logln("Set the new rule to the SimpleHoliday ...");
        if (!rdr.equals(h[1].getRule())) {
            errln("FAIL: getRule and setRule not matched.");
        }
        exerciseHoliday(h[1], Locale.getDefault());
    }

    @Test
    public void TestEaster(){
        // Verify that Easter is working. Should be April 20, 2014
        final Holiday h = new EasterHoliday("Easter Sunday");
        final Date beginApril = getDate(2014, Calendar.APRIL, 1);
        final Date endApril   = getDate(2014, Calendar.APRIL, 30);
        final Date expect     = getDate(2014, Calendar.APRIL, 20);
        final Date actual     = h.firstBetween(beginApril, endApril);

        if(actual == null) {
            errln("Error: Easter 2014 should be on " + expect + " but got null.");
        } else {
            Calendar c = Calendar.getInstance(TimeZone.GMT_ZONE, Locale.US);
            c.setTime(actual);
            assertEquals("Easter's year:  ", 2014, c.get(Calendar.YEAR));
            assertEquals("Easter's month: ", Calendar.APRIL, c.get(Calendar.MONTH));
            assertEquals("Easter's date:  ", 20, c.get(Calendar.DATE));
        }
    }

    @Test
    public void TestIsOn() {
        // jb 1901
        SimpleHoliday sh = new SimpleHoliday(Calendar.AUGUST, 15, "Doug's Day", 1958, 2058);

        Calendar gcal = new GregorianCalendar();
        gcal.clear();
        gcal.set(Calendar.YEAR, 2000);
        gcal.set(Calendar.MONTH, Calendar.AUGUST);
        gcal.set(Calendar.DAY_OF_MONTH, 15);

        Date d0 = gcal.getTime();
        gcal.add(Calendar.SECOND, 1);
        Date d1 = gcal.getTime();
        gcal.add(Calendar.SECOND, -2);
        Date d2 = gcal.getTime();
        gcal.add(Calendar.DAY_OF_MONTH, 1);
        Date d3 = gcal.getTime();
        gcal.add(Calendar.SECOND, 1);
        Date d4 = gcal.getTime();
        gcal.add(Calendar.SECOND, -2);
        gcal.set(Calendar.YEAR, 1957);
        Date d5 = gcal.getTime();
        gcal.set(Calendar.YEAR, 1958);
        Date d6 = gcal.getTime();
        gcal.set(Calendar.YEAR, 2058);
        Date d7 = gcal.getTime();
        gcal.set(Calendar.YEAR, 2059);
        Date d8 = gcal.getTime();

        Date[] dates = { d0, d1, d2, d3, d4, d5, d6, d7, d8 };
        boolean[] isOns = { true, true, false, true, false, false, true, true, false };
        for (int i = 0; i < dates.length; ++i) {
            Date d = dates[i];
            logln("\ndate: " + d);
            boolean isOn = sh.isOn(d);
            logln("isOnDate: " + isOn);
            if (isOn != isOns[i]) {
                errln("date: " + d + " should be on Doug's Day!");
            }
            Date h = sh.firstAfter(d);
            logln("firstAfter: " + h);
        }
    }

    @Test
    public void TestDisplayName() {
        Holiday[] holidays = Holiday.getHolidays(ULocale.US);
        for (int i = 0; i < holidays.length; ++i) {
            Holiday h = holidays[i];
            // only need to test one
            // if the display names differ, we're using our data.  We know these names
            // should differ for this holiday (not all will).
            if ("Christmas".equals(h.getDisplayName(ULocale.US))) {
                if ("Christmas".equals(h.getDisplayName(ULocale.GERMANY))) {
                    errln("Using default name for holidays");
                }
            }
        }
    }
}
