/*
 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package datatype;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.namespace.QName;

import org.testng.Assert;
import org.testng.AssertJUnit;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

/*
 * @test
 * @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest
 * @run testng/othervm -DrunSecMngr=true datatype.DurationTest
 * @run testng/othervm datatype.DurationTest
 * @summary Test Duration.
 */
@Listeners({jaxp.library.BasePolicy.class})
public class DurationTest {

    private final static boolean DEBUG = true;

    protected Duration duration = null;

    @BeforeMethod
    public void setUp() {
        try {
            duration = DatatypeFactory.newInstance().newDuration(100);
        } catch (DatatypeConfigurationException dce) {
            dce.printStackTrace();
            Assert.fail("Failed to create instance of DatatypeFactory " + dce.getMessage());
        }
    }

    @Test
    public void testDurationSubtract() {
        try {
            Duration bigDur = DatatypeFactory.newInstance().newDuration(20000);
            Duration smallDur = DatatypeFactory.newInstance().newDuration(10000);
            if (smallDur.subtract(bigDur).getSign() != -1) {
                Assert.fail("smallDur.subtract(bigDur).getSign() is not -1");
            }
            if (bigDur.subtract(smallDur).getSign() != 1) {
                Assert.fail("bigDur.subtract(smallDur).getSign() is not 1");
            }
            if (smallDur.subtract(smallDur).getSign() != 0) {
                Assert.fail("smallDur.subtract(smallDur).getSign() is not 0");
            }
        } catch (DatatypeConfigurationException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testDurationMultiply() {
        int num = 5000; // millisends. 5 seconds
        int factor = 2;
        try {
            Duration dur = DatatypeFactory.newInstance().newDuration(num);
            if (dur.multiply(factor).getSeconds() != 10) {
                Assert.fail("duration.multiply() return wrong value");
            }
            // factor is 2*10^(-1)
            if (dur.multiply(new BigDecimal(new BigInteger("2"), 1)).getSeconds() != 1) {
                Assert.fail("duration.multiply() return wrong value");
            }
            if (dur.subtract(DatatypeFactory.newInstance().newDuration(1000)).multiply(new BigDecimal(new BigInteger("2"), 1)).getSeconds() != 0) {
                Assert.fail("duration.multiply() return wrong value");
            }
        } catch (DatatypeConfigurationException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testDurationAndCalendar1() {
        int year = 1;
        int month = 2;
        int day = 3;
        int hour = 4;
        int min = 5;
        int sec = 6;
        String lexicalRepresentation = "P" + year + "Y" + month + "M" + day + "DT" + hour + "H" + min + "M" + sec + "S";
        try {
            Duration dur = DatatypeFactory.newInstance().newDuration(lexicalRepresentation);
            System.out.println(dur.toString());
            AssertJUnit.assertTrue("year should be 1", dur.getYears() == year);
            AssertJUnit.assertTrue("month should be 2", dur.getMonths() == month);
            AssertJUnit.assertTrue("day should be 3", dur.getDays() == day);
            AssertJUnit.assertTrue("hour should be 4", dur.getHours() == hour);
            AssertJUnit.assertTrue("minute should be 5", dur.getMinutes() == min);
            AssertJUnit.assertTrue("second should be 6", dur.getSeconds() == sec);
        } catch (DatatypeConfigurationException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testDurationAndCalendar2() {
        try {
            AssertJUnit.assertTrue("10.00099S means 10 sec since it will be rounded to zero", DatatypeFactory.newInstance().newDuration("PT10.00099S")
                    .getTimeInMillis(new Date()) == 10000);
            AssertJUnit.assertTrue("10.00099S means 10 sec since it will be rounded to zero", DatatypeFactory.newInstance().newDuration("-PT10.00099S")
                    .getTimeInMillis(new Date()) == -10000);
            AssertJUnit.assertTrue("10.00099S means 10 sec since it will be rounded to zero", DatatypeFactory.newInstance().newDuration("PT10.00099S")
                    .getTimeInMillis(new GregorianCalendar()) == 10000);
            AssertJUnit.assertTrue("10.00099S means 10 sec since it will be rounded to zero", DatatypeFactory.newInstance().newDuration("-PT10.00099S")
                    .getTimeInMillis(new GregorianCalendar()) == -10000);
        } catch (DatatypeConfigurationException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testDurationAndCalendar3() {
        try {
            Calendar cal = new GregorianCalendar();
            cal.set(Calendar.SECOND, 59);
            DatatypeFactory.newInstance().newDuration(10000).addTo(cal);
            AssertJUnit.assertTrue("sec will be 9", cal.get(Calendar.SECOND) == 9);

            Date date = new Date();
            date.setSeconds(59);
            DatatypeFactory.newInstance().newDuration(10000).addTo(date);
            AssertJUnit.assertTrue("sec will be 9", date.getSeconds() == 9);
        } catch (DatatypeConfigurationException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testEqualsWithDifferentObjectParam() {

        AssertJUnit.assertFalse("equals method should return false for any object other than Duration", duration.equals(new Integer(0)));
    }

    @Test
    public void testEqualsWithNullObjectParam() {

        AssertJUnit.assertFalse("equals method should return false for null parameter", duration.equals(null));
    }

    @Test
    public void testEqualsWithEqualObjectParam() {
        try {
            AssertJUnit.assertTrue("equals method is expected to return true", duration.equals(DatatypeFactory.newInstance().newDuration(100)));
        } catch (DatatypeConfigurationException dce) {
            dce.printStackTrace();
            Assert.fail("Failed to create instance of DatatypeFactory " + dce.getMessage());
        }
    }

    /**
     * Inspired by CR 5077522 Duration.compare makes mistakes for some values.
     */
    @Test
    public void testCompareWithInderterminateRelation() {

        final String[][] partialOrder = { // partialOrder
        { "P1Y", "<>", "P365D" }, { "P1Y", "<>", "P366D" }, { "P1M", "<>", "P28D" }, { "P1M", "<>", "P29D" }, { "P1M", "<>", "P30D" }, { "P1M", "<>", "P31D" },
                { "P5M", "<>", "P150D" }, { "P5M", "<>", "P151D" }, { "P5M", "<>", "P152D" }, { "P5M", "<>", "P153D" }, { "PT2419200S", "<>", "P1M" },
                { "PT2678400S", "<>", "P1M" }, { "PT31536000S", "<>", "P1Y" }, { "PT31622400S", "<>", "P1Y" }, { "PT525600M", "<>", "P1Y" },
                { "PT527040M", "<>", "P1Y" }, { "PT8760H", "<>", "P1Y" }, { "PT8784H", "<>", "P1Y" }, { "P365D", "<>", "P1Y" }, };

        DatatypeFactory df = null;
        try {
            df = DatatypeFactory.newInstance();
        } catch (DatatypeConfigurationException ex) {
            ex.printStackTrace();
            Assert.fail(ex.toString());
        }

        boolean compareErrors = false;

        for (int valueIndex = 0; valueIndex < partialOrder.length; ++valueIndex) {
            Duration duration1 = df.newDuration(partialOrder[valueIndex][0]);
            Duration duration2 = df.newDuration(partialOrder[valueIndex][2]);
            int cmp = duration1.compare(duration2);
            int expected = ">".equals(partialOrder[valueIndex][1]) ? DatatypeConstants.GREATER
                    : "<".equals(partialOrder[valueIndex][1]) ? DatatypeConstants.LESSER : "==".equals(partialOrder[valueIndex][1]) ? DatatypeConstants.EQUAL
                            : DatatypeConstants.INDETERMINATE;

            // just note any errors, do not fail until all cases have been
            // tested
            if (expected != cmp) {
                compareErrors = true;
                System.err.println("returned " + cmp2str(cmp) + " for durations \'" + duration1 + "\' and " + duration2 + "\', but expected "
                        + cmp2str(expected));
            }
        }

        if (compareErrors) {
            // TODO; fix bug, these tests should pass
            if (false) {
                Assert.fail("Errors in comparing indeterminate relations, see Stderr");
            } else {
                System.err.println("Please fix this bug: " + "Errors in comparing indeterminate relations, see Stderr");
            }
        }
    }

    public static String cmp2str(int cmp) {
        return cmp == DatatypeConstants.LESSER ? "LESSER" : cmp == DatatypeConstants.GREATER ? "GREATER" : cmp == DatatypeConstants.EQUAL ? "EQUAL"
                : cmp == DatatypeConstants.INDETERMINATE ? "INDETERMINATE" : "UNDEFINED";
    }

    /**
     * Inspired by CR 6238220 javax.xml.datatype.Duration has no clear
     * description concerning return values range.
     */
    @Test
    public void testNormalizedReturnValues() throws Exception {

        final Object[] TEST_VALUES = {
                // test 61 seconds -> 1 minute, 1 second
                true, // isPositive,
                BigInteger.ZERO, // years,
                BigInteger.ZERO, // months
                BigInteger.ZERO, // days
                BigInteger.ZERO, // hours
                BigInteger.ZERO, // minutes
                new BigDecimal(61), // seconds
                61000L, // durationInMilliSeconds,
                "P0Y0M0DT0H0M61S", // lexicalRepresentation

                // test - 61 seconds -> - 1 minute, 1 second
                false, // isPositive,
                BigInteger.ZERO, // years,
                BigInteger.ZERO, // months
                BigInteger.ZERO, // days
                BigInteger.ZERO, // hours
                BigInteger.ZERO, // minutes
                new BigDecimal(61), // seconds
                61000L, // durationInMilliSeconds,
                "-P0Y0M0DT0H0M61S", // lexicalRepresentation
        };

        final Object[] NORM_VALUES = {
                // test 61 seconds -> 1 minute, 1 second
                true, // normalized isPositive,
                BigInteger.ZERO, // normalized years,
                BigInteger.ZERO, // normalized months
                BigInteger.ZERO, // normalized days
                BigInteger.ZERO, // normalized hours
                BigInteger.ONE, // normalized minutes
                BigDecimal.ONE, // normalized seconds
                61000L, // normalized durationInMilliSeconds,
                "P0Y0M0DT0H1M1.000S", // normalized lexicalRepresentation

                // test - 61 seconds -> - 1 minute, 1 second
                false, // normalized isPositive,
                BigInteger.ZERO, // normalized years,
                BigInteger.ZERO, // normalized months
                BigInteger.ZERO, // normalized days
                BigInteger.ZERO, // normalized hours
                BigInteger.ONE, // normalized minutes
                BigDecimal.ONE, // normalized seconds
                61000L, // normalized durationInMilliSeconds,
                "-P0Y0M0DT0H1M1.000S" // normalized lexicalRepresentation
        };

        for (int onValue = 0; onValue < TEST_VALUES.length; onValue += 9) {
            newDurationTester(((Boolean) TEST_VALUES[onValue]).booleanValue(), // isPositive,
                    ((Boolean) NORM_VALUES[onValue]).booleanValue(), // normalized
                                                                     // isPositive,
                    (BigInteger) TEST_VALUES[onValue + 1], // years,
                    (BigInteger) NORM_VALUES[onValue + 1], // normalized years,
                    (BigInteger) TEST_VALUES[onValue + 2], // months
                    (BigInteger) NORM_VALUES[onValue + 2], // normalized months
                    (BigInteger) TEST_VALUES[onValue + 3], // days
                    (BigInteger) NORM_VALUES[onValue + 3], // normalized days
                    (BigInteger) TEST_VALUES[onValue + 4], // hours
                    (BigInteger) NORM_VALUES[onValue + 4], // normalized hours
                    (BigInteger) TEST_VALUES[onValue + 5], // minutes
                    (BigInteger) NORM_VALUES[onValue + 5], // normalized minutes
                    (BigDecimal) TEST_VALUES[onValue + 6], // seconds
                    (BigDecimal) NORM_VALUES[onValue + 6], // normalized seconds
                    ((Long) TEST_VALUES[onValue + 7]).longValue(), // durationInMilliSeconds,
                    ((Long) NORM_VALUES[onValue + 7]).longValue(), // normalized
                                                                   // durationInMilliSeconds,
                    (String) TEST_VALUES[onValue + 8], // lexicalRepresentation
                    (String) NORM_VALUES[onValue + 8]); // normalized
                                                        // lexicalRepresentation

            newDurationDayTimeTester(((Boolean) TEST_VALUES[onValue]).booleanValue(), // isPositive,
                    ((Boolean) NORM_VALUES[onValue]).booleanValue(), // normalized
                                                                     // isPositive,
                    BigInteger.ZERO, // years,
                    BigInteger.ZERO, // normalized years,
                    BigInteger.ZERO, // months
                    BigInteger.ZERO, // normalized months
                    (BigInteger) TEST_VALUES[onValue + 3], // days
                    (BigInteger) NORM_VALUES[onValue + 3], // normalized days
                    (BigInteger) TEST_VALUES[onValue + 4], // hours
                    (BigInteger) NORM_VALUES[onValue + 4], // normalized hours
                    (BigInteger) TEST_VALUES[onValue + 5], // minutes
                    (BigInteger) NORM_VALUES[onValue + 5], // normalized minutes
                    (BigDecimal) TEST_VALUES[onValue + 6], // seconds
                    (BigDecimal) NORM_VALUES[onValue + 6], // normalized seconds
                    ((Long) TEST_VALUES[onValue + 7]).longValue(), // durationInMilliSeconds,
                    ((Long) NORM_VALUES[onValue + 7]).longValue(), // normalized
                                                                   // durationInMilliSeconds,
                    (String) TEST_VALUES[onValue + 8], // lexicalRepresentation
                    (String) NORM_VALUES[onValue + 8]); // normalized
                                                        // lexicalRepresentation
        }
    }

    private void newDurationTester(boolean isPositive, boolean normalizedIsPositive, BigInteger years, BigInteger normalizedYears, BigInteger months,
            BigInteger normalizedMonths, BigInteger days, BigInteger normalizedDays, BigInteger hours, BigInteger normalizedHours, BigInteger minutes,
            BigInteger normalizedMinutes, BigDecimal seconds, BigDecimal normalizedSeconds, long durationInMilliSeconds, long normalizedDurationInMilliSeconds,
            String lexicalRepresentation, String normalizedLexicalRepresentation) {

        DatatypeFactory datatypeFactory = null;
        try {
            datatypeFactory = DatatypeFactory.newInstance();
        } catch (DatatypeConfigurationException ex) {
            ex.printStackTrace();
            Assert.fail(ex.toString());
        }

        // create 4 Durations using the 4 different constructors

        Duration durationBigInteger = datatypeFactory.newDuration(isPositive, years, months, days, hours, minutes, seconds);
        durationAssertEquals(durationBigInteger, DatatypeConstants.DURATION, normalizedIsPositive, normalizedYears.intValue(), normalizedMonths.intValue(),
                normalizedDays.intValue(), normalizedHours.intValue(), normalizedMinutes.intValue(), normalizedSeconds.intValue(),
                normalizedDurationInMilliSeconds, normalizedLexicalRepresentation);

        Duration durationInt = datatypeFactory.newDuration(isPositive, years.intValue(), months.intValue(), days.intValue(), hours.intValue(),
                minutes.intValue(), seconds.intValue());
        durationAssertEquals(durationInt, DatatypeConstants.DURATION, normalizedIsPositive, normalizedYears.intValue(), normalizedMonths.intValue(),
                normalizedDays.intValue(), normalizedHours.intValue(), normalizedMinutes.intValue(), normalizedSeconds.intValue(),
                normalizedDurationInMilliSeconds, normalizedLexicalRepresentation);

        Duration durationMilliseconds = datatypeFactory.newDuration(durationInMilliSeconds);
        durationAssertEquals(durationMilliseconds, DatatypeConstants.DURATION, normalizedIsPositive, normalizedYears.intValue(), normalizedMonths.intValue(),
                normalizedDays.intValue(), normalizedHours.intValue(), normalizedMinutes.intValue(), normalizedSeconds.intValue(),
                normalizedDurationInMilliSeconds, normalizedLexicalRepresentation);

        Duration durationLexical = datatypeFactory.newDuration(lexicalRepresentation);
        durationAssertEquals(durationLexical, DatatypeConstants.DURATION, normalizedIsPositive, normalizedYears.intValue(), normalizedMonths.intValue(),
                normalizedDays.intValue(), normalizedHours.intValue(), normalizedMinutes.intValue(), normalizedSeconds.intValue(),
                normalizedDurationInMilliSeconds, normalizedLexicalRepresentation);
    }

    private void newDurationDayTimeTester(boolean isPositive, boolean normalizedIsPositive, BigInteger years, BigInteger normalizedYears, BigInteger months,
            BigInteger normalizedMonths, BigInteger days, BigInteger normalizedDays, BigInteger hours, BigInteger normalizedHours, BigInteger minutes,
            BigInteger normalizedMinutes, BigDecimal seconds, BigDecimal normalizedSeconds, long durationInMilliSeconds, long normalizedDurationInMilliSeconds,
            String lexicalRepresentation, String normalizedLexicalRepresentation) {

        DatatypeFactory datatypeFactory = null;
        try {
            datatypeFactory = DatatypeFactory.newInstance();
        } catch (DatatypeConfigurationException ex) {
            ex.printStackTrace();
            Assert.fail(ex.toString());
        }

        // create 4 dayTime Durations using the 4 different constructors

        Duration durationDayTimeBigInteger = datatypeFactory.newDurationDayTime(isPositive, days, hours, minutes, seconds.toBigInteger());
        durationAssertEquals(durationDayTimeBigInteger, DatatypeConstants.DURATION_DAYTIME, normalizedIsPositive, normalizedYears.intValue(),
                normalizedMonths.intValue(), normalizedDays.intValue(), normalizedHours.intValue(), normalizedMinutes.intValue(), normalizedSeconds.intValue(),
                normalizedDurationInMilliSeconds, normalizedLexicalRepresentation);

        /*
         * Duration durationDayTimeInt = datatypeFactory.newDurationDayTime(
         * isPositive, days.intValue(), hours.intValue(), minutes.intValue(),
         * seconds.intValue()); Duration durationDayTimeMilliseconds =
         * datatypeFactory.newDurationDayTime( durationInMilliSeconds); Duration
         * durationDayTimeLexical = datatypeFactory.newDurationDayTime(
         * lexicalRepresentation);
         * Duration durationYearMonthBigInteger =
         * datatypeFactory.newDurationYearMonth( isPositive, years, months);
         * Duration durationYearMonthInt = datatypeFactory.newDurationYearMonth(
         * isPositive, years.intValue(), months.intValue()); Duration
         * durationYearMonthMilliseconds = datatypeFactory.newDurationYearMonth(
         * durationInMilliSeconds); Duration durationYearMonthLexical =
         * datatypeFactory.newDurationYearMonth( lexicalRepresentation) ;
         */

    }

    private void durationAssertEquals(Duration duration, QName xmlSchemaType, boolean isPositive, int years, int months, int days, int hours, int minutes,
            int seconds, long milliseconds, String lexical) {

        final TimeZone GMT = TimeZone.getTimeZone("GMT");
        final GregorianCalendar EPOCH = new GregorianCalendar(GMT);
        EPOCH.clear();

        if (DEBUG) {
            System.out.println("Testing Duration: " + duration.toString());
        }

        // sign
        if (DEBUG) {
            boolean actual = (duration.getSign() == 1) ? true : false;
            System.out.println("sign:");
            System.out.println("    expected: \"" + isPositive + "\"");
            System.out.println("    actual:   \"" + actual + "\"");
        }

        if (DEBUG) {
            System.out.println("years:");
            System.out.println("    expected: \"" + years + "\"");
            System.out.println("    actual:   \"" + duration.getYears() + "\"");
        }

        if (DEBUG) {
            System.out.println("months:");
            System.out.println("    expected: \"" + months + "\"");
            System.out.println("    actual:   \"" + duration.getMonths() + "\"");
        }

        if (DEBUG) {
            System.out.println("days:");
            System.out.println("    expected: \"" + days + "\"");
            System.out.println("    actual:   \"" + duration.getDays() + "\"");
        }

        if (DEBUG) {
            System.out.println("hours:");
            System.out.println("    expected: \"" + hours + "\"");
            System.out.println("    actual:   \"" + duration.getHours() + "\"");
        }

        if (DEBUG) {
            System.out.println("minutes:");
            System.out.println("    expected: \"" + minutes + "\"");
            System.out.println("    actual:   \"" + duration.getMinutes() + "\"");
        }

        if (DEBUG) {
            System.out.println("seconds:");
            System.out.println("    expected: \"" + seconds + "\"");
            System.out.println("    actual:   \"" + duration.getSeconds() + "\"");
        }

        if (DEBUG) {
            System.out.println("milliseconds:");
            System.out.println("    expected: \"" + milliseconds + "\"");
            System.out.println("    actual:   \"" + duration.getTimeInMillis(EPOCH) + "\"");
        }

        if (DEBUG) {
            System.out.println("lexical:");
            System.out.println("    expected: \"" + lexical + "\"");
            System.out.println("    actual:   \"" + duration.toString() + "\"");
        }

    }
}
