/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
==================================================================== */

package org.apache.poi.ss.formula.functions;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.Calendar;
import java.util.Date;

import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.StringEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FormulaError;
import org.apache.poi.util.LocaleUtil;
import org.junit.Test;

public class TestEOMonth {

    private static final double BAD_DATE = -1.0;

    private static final double DATE_1900_01_01 = 1.0;
    private static final double DATE_1900_01_31 = 31.0;
    private static final double DATE_1900_02_28 = 59.0;
    private static final double DATE_1902_09_26 = 1000.0;
    private static final double DATE_1902_09_30 = 1004.0;
    private static final double DATE_2034_06_09 = 49104.0;
    private static final double DATE_2034_06_30 = 49125.0;
    private static final double DATE_2034_07_31 = 49156.0;

    private final FreeRefFunction eOMonth = EOMonth.instance;
    private final OperationEvaluationContext ec = new OperationEvaluationContext(null, null, 0, 0, 0, null);

    @Test
    public void testEOMonthProperValues() {
        // verify some border-case combinations of startDate and month-increase
        checkValue(DATE_1900_01_01, 0, DATE_1900_01_31);
        checkValue(DATE_1900_01_01, 1, DATE_1900_02_28);
        checkValue(DATE_1902_09_26, 0, DATE_1902_09_30);
        checkValue(DATE_2034_06_09, 0, DATE_2034_06_30);
        checkValue(DATE_2034_06_09, 1, DATE_2034_07_31);
    }

    @Test
    public void testEOMonthBadDateValues() {
        checkValue(0.0, -2, BAD_DATE);
        checkValue(0.0, -3, BAD_DATE);
        checkValue(DATE_1900_01_31, -1, BAD_DATE);
    }

    private void checkValue(double startDate, int monthInc, double expectedResult) {
        ValueEval ve[] = {new NumberEval(startDate), new NumberEval(monthInc)};
        NumberEval result = (NumberEval) eOMonth.evaluate(ve, ec);
        assertEquals(expectedResult, result.getNumberValue(), 0);
    }

    @Test
    public void testEOMonthZeroDate() {
        NumberEval result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new NumberEval(0), new NumberEval(0)}, ec);
        assertEquals("0 startDate is 1900-01-00", DATE_1900_01_31, result.getNumberValue(), 0);

        result = (NumberEval) eOMonth.evaluate(new ValueEval[] {new NumberEval(0), new NumberEval(1)}, ec);
        assertEquals("0 startDate is 1900-01-00", DATE_1900_02_28, result.getNumberValue(), 0);
    }

    @Test
    public void testEOMonthInvalidArguments() {
        ValueEval result = eOMonth.evaluate(new ValueEval[] {new NumberEval(DATE_1902_09_26)}, ec);
        assertTrue(result instanceof ErrorEval);
        assertEquals(FormulaError.VALUE.getCode(), ((ErrorEval) result).getErrorCode(), 0);

        result = eOMonth.evaluate(new ValueEval[] {new StringEval("a"), new StringEval("b")}, ec);
        assertTrue(result instanceof ErrorEval);
        assertEquals(FormulaError.VALUE.getCode(), ((ErrorEval) result).getErrorCode(), 0);
    }

    @Test
    public void checkOffset() {
        for (int offset=-12; offset<=12; offset++) {
            Calendar cal = LocaleUtil.getLocaleCalendar();
            Date startDate = cal.getTime();

            cal.add(Calendar.MONTH, offset);
            cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
            cal.clear(Calendar.HOUR);
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.clear(Calendar.MINUTE);
            cal.clear(Calendar.SECOND);
            cal.clear(Calendar.MILLISECOND);
            Date expDate = cal.getTime();
            
            ValueEval ve[] = {
                new NumberEval(DateUtil.getExcelDate(startDate)),
                new NumberEval(offset)
            };
            NumberEval result = (NumberEval) eOMonth.evaluate(ve, ec);
            Date actDate = DateUtil.getJavaDate(result.getNumberValue());
            
            assertEquals(expDate, actDate);
        }
    }

    @Test
    public void testBug56688() {
        ValueEval ve[] = {new NumberEval(DATE_1902_09_26), new RefEvalImplementation(new NumberEval(0))};
        NumberEval result = (NumberEval) eOMonth.evaluate(ve, ec);
        assertEquals(DATE_1902_09_30, result.getNumberValue(), 0);
    }

    @Test
    public void testRefEvalStartDate() {
        ValueEval ve[] = {new RefEvalImplementation(new NumberEval(DATE_1902_09_26)), new NumberEval(0)};
        NumberEval result = (NumberEval) eOMonth.evaluate(ve, ec);
        assertEquals(DATE_1902_09_30, result.getNumberValue(), 0);
    }

    @Test
    public void testEOMonthBlankValueEval() {
        NumberEval evaluate = (NumberEval) eOMonth.evaluate(new ValueEval[] {BlankEval.instance, new NumberEval(0)}, ec);
        assertEquals("Blank is handled as 0", DATE_1900_01_31, evaluate.getNumberValue(), 0);
    }

    @Test
    public void testEOMonthBlankRefValueEval() {
        ValueEval[] ve1 = {new RefEvalImplementation(BlankEval.instance), new NumberEval(1)};
        NumberEval result = (NumberEval) eOMonth.evaluate(ve1, ec);
        assertEquals("Blank is handled as 0", DATE_1900_02_28, result.getNumberValue(), 0);

        ValueEval[] ve2 = {new NumberEval(1), new RefEvalImplementation(BlankEval.instance)};
        result = (NumberEval) eOMonth.evaluate(ve2, ec);
        assertEquals("Blank is handled as 0", DATE_1900_01_31, result.getNumberValue(), 0);
    }
}
