/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2013 Pentaho Corporation..  All rights reserved.
*/

package mondrian.xmla;

import mondrian.olap.Result;
import mondrian.rolap.RolapCube;
import mondrian.spi.Dialect;
import mondrian.test.*;

import org.olap4j.CellSet;

import java.math.BigInteger;
import java.sql.SQLException;
import java.util.*;

/**
 * Unit test to validate expected marshalling of Java objects
 * to their respective XML Schema types
 * {@link mondrian.xmla}).
 *
 * @author mcampbell
 */
public class XmlaHandlerTypeTest extends FoodMartTestCase {

    TestVal[] typeTests = {
        TestVal.having("StringValue", "xsd:string", "String"),
        TestVal.having(new Double(0), "xsd:double", "Numeric"),
        TestVal.having(new Integer(0), "xsd:int", "Integer"),
        TestVal.having(Long.MAX_VALUE, "xsd:long", "Integer"),
        TestVal.having(new Float(0), "xsd:float", "Numeric"),
        TestVal.having(Byte.MAX_VALUE, "xsd:byte", "Integer"),
        TestVal.having(Short.MAX_VALUE, "xsd:short", "Integer"),
        TestVal.having(new Boolean(true), "xsd:boolean",  null),
        TestVal.having(
            BigInteger.valueOf(Long.MAX_VALUE)
            .add(BigInteger.valueOf(1)), "xsd:integer", "Integer")
    };


    public void testMarshalledValueType() {
        // run through the tests once with no hint, then again with
        // the hint value.
        for (TestVal val : typeTests) {
            assertEquals(
                val.expectedXsdType,
                new XmlaHandler.ValueInfo(null, val.value).valueType);
        }

        for (TestVal val : typeTests) {
            assertEquals(
                val.expectedXsdType,
                new XmlaHandler.ValueInfo(val.hint, val.value).valueType);
        }
    }

    /**
     * Checks whether Cell.getValue() returns a consistent datatype whether
     * retrieved from Olap4jXmla, Olap4j, or native Mondrian.
     * @throws SQLException
     */
    public void testDatatypeConsistency() throws SQLException {
        TestContext context = getTestContext();

        // MDX cast expressions
        String[] castedTypes = {
            "Cast(1 as String)",
            "Cast(1 as Numeric)",
            "Cast(1 as Boolean)",
            "Cast(1 as Integer)",
        };

        for (String castedType : castedTypes) {
            String mdx = "with member measures.type as '"
            + castedType + "' "
            + "select measures.type on 0 from sales";
            CellSet olap4jXmlaCellset = context.executeOlap4jXmlaQuery(mdx);
            CellSet olap4jCellset = context.executeOlap4jQuery(mdx);
            Result nativeMondrianResult = context.executeQuery(mdx);
            assertEquals(
                "Checking olap4jXmla datatype against native Mondrian. \n"
                + "Unexpected datatype when running mdx " + mdx + "\n",
                nativeMondrianResult.getCell(new int[]{0})
                    .getValue().getClass(),
                olap4jXmlaCellset.getCell(0).getValue().getClass());
            assertEquals(
                "Checking olap4jXmla datatype against native Mondrian. \n"
                + "Unexpected datatype when running mdx " + mdx + "\n",
                olap4jXmlaCellset.getCell(0).getValue().getClass(),
                olap4jCellset.getCell(0).getValue().getClass());
        }

        RolapCube cube =
            (RolapCube)context.executeQuery("select from sales")
                .getQuery().getCube();
        Dialect dialect = cube.getStar().getSqlQueryDialect();

        if (!dialect.getDatabaseProduct()
            .equals(Dialect.DatabaseProduct.MYSQL)
            && !dialect.getDatabaseProduct()
                .equals(Dialect.DatabaseProduct.ORACLE))
        {
            return;
        }

        // map of sql expressions to the corresponding (optional) datatype
        // attribute (RolapBaseCubeMeasure.Datatype)
        Map<String, String> expressionTypeMap = new HashMap<String, String>();
        expressionTypeMap.put("'StringValue'", "String");
        expressionTypeMap.put("cast(1.0001 as decimal)", null);
        expressionTypeMap.put("cast(1.0001 as decimal)", "Numeric");
        expressionTypeMap.put("cast(10.101 as decimal(10,8))", null);
        expressionTypeMap.put("cast(10.101 as decimal(10,8))", "Numeric");


        for (String expression : expressionTypeMap.keySet()) {
            String query = "Select measures.typeMeasure on 0 from Sales";
            context = getContextWithMeasureExpression(
                expression, expressionTypeMap.get(expression));
            CellSet olap4jXmlaCellset = context.executeOlap4jXmlaQuery(query);
            CellSet olap4jCellset = context.executeOlap4jQuery(query);
            Result nativeMondrianResult = context.executeQuery(query);

            assertEquals(
                "Checking olap4jXmla datatype against native Mondrian. \n"
                + "Unexpected datatype for measure expression " + expression
                + " with datatype attribute "
                + expressionTypeMap.get(expression) + "\n",
                nativeMondrianResult.getCell(new int[]{0})
                    .getValue().getClass(),
                olap4jXmlaCellset.getCell(0).getValue().getClass());
            assertEquals(
                "Checking olap4jXmla datatype against olap4j in process. \n"
                + "Unexpected datatype for expression " + expression
                + " with datatype attribute "
                + expressionTypeMap.get(expression) + "\n",
                olap4jXmlaCellset.getCell(0).getValue().getClass(),
                olap4jCellset.getCell(0).getValue().getClass());
        }
    }

    private TestContext getContextWithMeasureExpression(
        String expression, String type)
    {
        String datatype = "";
        String aggregator = " aggregator='sum' ";
        if (type != null) {
            datatype = " datatype='" + type + "' ";
            if (type.equals("String")) {
                aggregator = " aggregator='max'  ";
            }
        }
        return getTestContext().createSubstitutingCube(
            "Sales",
            null,
            "<Measure name='typeMeasure' " + aggregator + datatype + ">\n"
            + "  <MeasureExpression>\n"
            + "  <SQL dialect='generic'>\n"
            + expression
            + "  </SQL></MeasureExpression></Measure>",
            null, null);
    }

    static class TestVal {
        Object value;
        String expectedXsdType;
        String hint;

        static TestVal having(Object val, String xsd, String hint) {
            TestVal typeTest = new TestVal();
            typeTest.value = val;
            typeTest.expectedXsdType = xsd;
            typeTest.hint = hint;
            return typeTest;
        }
    }
}

// End XmlaHandlerTypeTest.java

