// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/***********************************************************************
 * Copyright (c) 1997-2009, International Business Machines Corporation
 * and others. All Rights Reserved.
 ***********************************************************************/

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING

#include "unicode/datefmt.h"
#include "unicode/smpdtfmt.h"
#include "tsdate.h"
#include "putilimp.h"
#include "cstring.h"

#include <float.h>
#include <stdlib.h>
#include <math.h>

IntlTestDateFormat::~IntlTestDateFormat() {}

/**
 * This test does round-trip testing (format -> parse -> format -> parse -> etc.) of
 * DateFormat.
 */
// par is ignored throughout this file
void IntlTestDateFormat::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
{
    if (exec) logln("TestSuite DateFormat");
    switch (index) {
        case 0: name = "GenericTest";
            if (exec) {
                logln(name);
                fFormat = DateFormat::createInstance();
                fTestName = "createInstance";
                fLimit = 3;
                testFormat(/* par */);
            }
            break;
        case 1: name = "DefaultLocale";
            if (exec) {
                logln(name);
                testLocale(/*par, */Locale::getDefault(), "Default Locale");
            }
            break;

        case 2: name = "TestAvailableLocales";
            if (exec) {
                logln(name);
                testAvailableLocales(/* par */);
            }
            break;

        case 3: name = "MonsterTest";
            if (exec) {
                logln(name);
                monsterTest(/*par*/);
            }
            break;

        default: name = ""; break;
    }
}

void
IntlTestDateFormat::testLocale(/*char* par, */const Locale& locale, const UnicodeString& localeName)
{
    DateFormat::EStyle timeStyle, dateStyle;
    
    // For patterns including only time information and a timezone, it may take
    // up to three iterations, since the timezone may shift as the year number
    // is determined.  For other patterns, 2 iterations should suffice.
    fLimit = 3;

    for(timeStyle = (DateFormat::EStyle)0; 
        timeStyle < (DateFormat::EStyle)4; 
        timeStyle = (DateFormat::EStyle) (timeStyle+1))
    {
        fTestName = (UnicodeString) "Time test " + (int32_t) timeStyle + " (" + localeName + ")";
        fFormat = DateFormat::createTimeInstance(timeStyle, locale);
        testFormat(/* par */);
    }

    fLimit = 2;

    for(dateStyle = (DateFormat::EStyle)0; 
        dateStyle < (DateFormat::EStyle)4; 
        dateStyle = (DateFormat::EStyle) (dateStyle+1))
    {
        fTestName = (UnicodeString) "Date test " + (int32_t) dateStyle + " (" + localeName + ")";
        fFormat = DateFormat::createDateInstance(dateStyle, locale);
        testFormat(/* par */);
    }

    for(dateStyle = (DateFormat::EStyle)0; 
        dateStyle < (DateFormat::EStyle)4; 
        dateStyle = (DateFormat::EStyle) (dateStyle+1))
    {
        for(timeStyle = (DateFormat::EStyle)0; 
            timeStyle < (DateFormat::EStyle)4; 
            timeStyle = (DateFormat::EStyle) (timeStyle+1))
        {
            fTestName = (UnicodeString) "DateTime test " + (int32_t) dateStyle + "/" + (int32_t) timeStyle + " (" + localeName + ")";
            fFormat = DateFormat::createDateTimeInstance(dateStyle, timeStyle, locale);
            testFormat(/* par */);
        }
    }
}

void IntlTestDateFormat::testFormat(/* char* par */)
{
    if (fFormat == 0)
    {
        dataerrln("FAIL: DateFormat creation failed");
        return;
    }

    describeTest();

    UDate now = Calendar::getNow();
    tryDate(0);
    tryDate(1278161801778.0);
    tryDate(5264498352317.0);   // Sunday, October 28, 2136 8:39:12 AM PST
    tryDate(9516987689250.0);   // In the year 2271
    tryDate(now);
    // Shift 6 months into the future, AT THE SAME TIME OF DAY.
    // This will test the DST handling.
    tryDate(now + 6.0*30*ONEDAY);

    UDate limit = now * 10; // Arbitrary limit
    for (int32_t i=0; i<3; ++i)
        tryDate(uprv_floor(randDouble() * limit));

    delete fFormat;
}

void
IntlTestDateFormat::describeTest()
{
    // Assume it's a SimpleDateFormat and get some info
    SimpleDateFormat *s = dynamic_cast<SimpleDateFormat*>(fFormat);
    UnicodeString str;
    logln(fTestName + " Pattern " + s->toPattern(str));
}

void IntlTestDateFormat::tryDate(UDate theDate)
{
    const int32_t DEPTH = 10;
    UDate date[DEPTH];
    UnicodeString string[DEPTH];

    int32_t dateMatch = 0;
    int32_t stringMatch = 0;
    UBool dump = false;
#if defined (U_CAL_DEBUG)
    dump = true;
#endif
    int32_t i;

    date[0] = theDate;
    fFormat->format(theDate, string[0]);

    UErrorCode status = U_ZERO_ERROR;
    const char* locID = "??";
    Locale loc = fFormat->getCalendar()->getLocale(ULOC_VALID_LOCALE, status);
    if (U_SUCCESS(status)) {
        locID = loc.getName();
    }

    for (i=1; i<DEPTH; ++i)
    {
        status = U_ZERO_ERROR;
        date[i] = fFormat->parse(string[i-1], status);
        if (U_FAILURE(status))
        {
            describeTest();
            errln("**** FAIL, locale " + UnicodeString(locID,-1,US_INV) +
                    ": Parse of " + prettify(string[i-1], false) + " failed.");
            dump = true;
            break;
        }
        fFormat->format(date[i], string[i]);
        if (dateMatch == 0 && date[i] == date[i-1])
            dateMatch = i;
        else if (dateMatch > 0 && date[i] != date[i-1])
        {
            describeTest();
            errln("**** FAIL, locale " + UnicodeString(locID,-1,US_INV) +
                    ": Date mismatch after match for " + string[i]);
            dump = true;
            break;
        }
        if (stringMatch == 0 && string[i] == string[i-1])
            stringMatch = i;
        else if (stringMatch > 0 && string[i] != string[i-1])
        {
            describeTest();
            errln("**** FAIL, locale " + UnicodeString(locID,-1,US_INV) +
                    ": String mismatch after match for " + string[i]);
            dump = true;
            break;
        }
        if (dateMatch > 0 && stringMatch > 0)
            break;
    }
    if (i == DEPTH)
        --i;

    if (stringMatch > fLimit || dateMatch > fLimit)
    {
        describeTest();
        errln((UnicodeString)"**** FAIL: No string and/or date match within " + fLimit
            + " iterations for the Date " + string[0] + "\t(" + theDate + ").");
        dump = true;
    }

    if (dump)
    {
        for (int32_t k=0; k<=i; ++k)
        {
            logln((UnicodeString)"" + k + ": " + date[k] + " F> " +
                  string[k] + " P> ");
        }
    }
}

// Return a random double from 0.01 to 1, inclusive
double IntlTestDateFormat::randDouble()
{
    // Assume 8-bit (or larger) rand values.  Also assume
    // that the system rand() function is very poor, which it always is.
    double d=0.0;
    uint32_t i;
    char* poke = (char*)&d;
    do {
        do {
            for (i=0; i < sizeof(double); ++i)
            {
                poke[i] = (char)(rand() & 0xFF);
            }
        } while (uprv_isNaN(d) || uprv_isInfinite(d));

        if (d < 0.0)
            d = -d;
        if (d > 0.0)
        {
            double e = uprv_floor(log10(d));
            if (e < -2.0)
                d *= uprv_pow10((int32_t)(-e-2));
            else if (e > -1.0)
                d /= uprv_pow10((int32_t)(e+1));
        }
    // While this is not a real normalized number make another one.
    } while (uprv_isNaN(d) || uprv_isInfinite(d)
        || !((-DBL_MAX < d && d < DBL_MAX) || (d < -DBL_MIN && DBL_MIN < d)));
    return d;
}

void IntlTestDateFormat::testAvailableLocales(/* char* par */)
{
    int32_t count = 0;
    const Locale* locales = DateFormat::getAvailableLocales(count);
    logln((UnicodeString)"" + count + " available locales");
    if (locales && count)
    {
        UnicodeString name;
        UnicodeString all;
        for (int32_t i=0; i<count; ++i)
        {
            if (i!=0) all += ", ";
            all += locales[i].getName();
        }
        logln(all);
    }
    else dataerrln((UnicodeString)"**** FAIL: Zero available locales or null array pointer");
}

void IntlTestDateFormat::monsterTest(/*char *par*/)
{
    int32_t count;
    const Locale* locales = DateFormat::getAvailableLocales(count);
    if (locales && count)
    {
        if (quick && count > 3) {
            logln("quick test: testing just 3 locales!");
            count = 3;
        }
        for (int32_t i=0; i<count; ++i)
        {
            UnicodeString name = UnicodeString(locales[i].getName(), "");
            logln((UnicodeString)"Testing " + name + "...");
            testLocale(/*par, */locales[i], name);
        }
    }
}

#endif /* #if !UCONFIG_NO_FORMATTING */
