/*
 * ModeShape (http://www.modeshape.org)
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * See the AUTHORS.txt file in the distribution for a full listing of 
 * individual contributors.
 *
 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
 * is licensed to you under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
 * ModeShape 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.modeshape.jdbc;

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.ImportUUIDBehavior;
import javax.jcr.query.QueryResult;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.modeshape.jcr.MultiUseAbstractTest;
import org.modeshape.jdbc.delegate.ConnectionInfo;
import org.modeshape.jdbc.delegate.RepositoryDelegate;

public class JcrMetaDataTest extends MultiUseAbstractTest {

    private JcrMetaData metadata;
    private DriverInfo driverInfo;
    @Mock
    private JcrConnection connection;
    @Mock
    private RepositoryDelegate delegate;
    @Mock
    private ConnectionInfo connInfo;
    @Mock
    private QueryResult queryResult;

    @BeforeClass
    public static void beforeAll() throws Exception {
        MultiUseAbstractTest.beforeAll();

        registerNodeTypes("cars.cnd");
        importContent("/", "cars-system-view-with-uuids.xml", ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
    }

    @Override
    @Before
    public void beforeEach() throws Exception {
        super.beforeEach();

        MockitoAnnotations.initMocks(this);

        driverInfo = new DriverInfo(JdbcLocalI18n.driverName.text(), JdbcLocalI18n.driverVendor.text(),
                                    JdbcLocalI18n.driverVendorUrl.text(), JdbcLocalI18n.driverVersion.text());
        print = false;

        when(connection.getRepositoryDelegate()).thenReturn(delegate);
        Set<String> names = new HashSet<String>();
        names.add("repo");

        when(connection.getCatalog()).thenReturn("repo");
        when(connection.driverInfo()).thenReturn(driverInfo);

        metadata = new JcrMetaData(connection);

        when(delegate.getConnectionInfo()).thenReturn(connInfo);
        when(delegate.execute(anyString(), anyString())).thenReturn(queryResult);

        when(connInfo.getRepositoryName()).thenReturn("repoName");

        when(queryResult.getColumnNames()).thenReturn(TestUtil.COLUMN_NAMES);
    }

    // ----------------------------------------------------------------------------------------------------------------
    // Test methods
    // ----------------------------------------------------------------------------------------------------------------

    @Test
    public void shouldHaveSession() {
        assertThat(session, is(notNullValue()));
    }

    @Test
    public void shouldHaveMetaData() {
        assertThat(metadata, is(notNullValue()));
    }

    @Test
    public void shouldImplementGetTables() throws Exception {
        ResultSet result = metadata.getTables(null, null, null, null);
        assertThat(result, is(notNullValue()));
    }

    @Test
    public void shouldImplementGetColumns() throws Exception {
        ResultSet result = metadata.getColumns(null, null, null, null);
        assertThat(result, is(notNullValue()));
    }

    @Test
    public void shouldImplementGetProcedures() throws Exception {
        ResultSet result = metadata.getProcedures(null, null, null);
        assertThat(result, is(notNullValue()));
    }

    @Test
    public void shouldImplementGetImportedKeys() throws Exception {
        ResultSet result = metadata.getImportedKeys(null, null, null);
        assertThat(result, is(notNullValue()));
    }

    @Test
    public void shouldImplementGetExportedKeys() throws Exception {
        ResultSet result = metadata.getExportedKeys(null, null, null);
        assertThat(result, is(notNullValue()));
    }

    @Test
    public void shouldImplementGetUniqueIndexes() throws Exception {
        ResultSet result = metadata.getIndexInfo(null, null, null, true, false);
        assertThat(result, is(notNullValue()));
    }

    @Test
    public void shouldImplementGetNonUniqueIndexes() throws Exception {
        ResultSet result = metadata.getIndexInfo(null, null, null, false, false);
        assertThat(result, is(notNullValue()));
    }

    /**
     * Test all the non-query methods
     * 
     * @throws Exception
     */
    @SuppressWarnings( "unchecked" )
    @Test
    public void testMethodsWithoutParams() throws Exception {
        Class<?> dbmdClass = metadata.getClass();
        // non-query Methods return String, boolean or int
        Method[] methods = dbmdClass.getDeclaredMethods();
        Map<String, Object> expectedMap = new HashMap<String, Object>();

        List<String> failedMessages = new ArrayList<String>();
        expectedMap = getExpected();
        // SYS.out.println(" -- total method == " + methods.length + ", non-query == " + expectedMap.size());
        for (int i = 0; i < methods.length; i++) {
            if (expectedMap.containsKey(methods[i].getName())) {

                Object actualValue = null;
                Object expectedValue = null;
                Object expectedReturn = expectedMap.get(methods[i].getName());
                Object[] params = null;

                if (expectedReturn instanceof List<?>) {
                    // has input parameters
                    List<Object[]> returned = (List<Object[]>)expectedReturn;
                    params = returned.get(1);
                    // SYS.out.println(" params == " + params[0]);
                    expectedValue = returned.get(0);
                    actualValue = methods[i].invoke(metadata, params);
                } else {
                    // without params
                    expectedValue = expectedReturn;
                    actualValue = methods[i].invoke(metadata, new Object[0]);
                }

                if (expectedValue == null || actualValue == null) {
                    if (expectedValue == null && actualValue != null) {
                        failedMessages.add(" Expected doesn't match with actual for method - " + methods[i].getName()
                                           + " expected: <" + expectedValue + "> but was: < " + actualValue + "> ");

                    } else if (expectedValue != null && actualValue == null) {
                        failedMessages.add(" Expected doesn't match with actual for method - " + methods[i].getName()
                                           + " expected: <" + expectedValue + "> but was: < " + actualValue + "> ");

                    }
                } else if (!expectedValue.equals(actualValue)) {
                    failedMessages.add(" Expected doesn't match with actual for method - " + methods[i].getName()
                                       + " expected: <" + expectedValue + "> but was: < " + actualValue + "> ");
                }
            }
        }

        assertThat(failedMessages.toString().trim(), is("[]"));

    }

    /**
     * Test all the methods that throw exception
     * 
     * @throws Exception
     */
    @Test
    public void testMethodsWithExceptions() throws Exception {
        Class<?> metadataClass = metadata.getClass();
        Method[] methods = metadataClass.getDeclaredMethods();

        Map<String, Object> expectedMap = new HashMap<String, Object>(); // none expected
        // SYS.out.println(" -- total method == " + methods.length + ", non-query == " + expectedMap.size());
        for (int i = 0; i < methods.length; i++) {
            if (expectedMap.containsKey(methods[i].getName())) {
                methods[i].invoke(metadata, new Object[0]);
            }
        }
    }

    // ////////////////////Expected Result//////////////////

    // constant
    private static final int NO_LIMIT = JcrMetaData.NO_LIMIT;

    private Map<String, Object> getExpected() {
        Map<String, Object> expected = new HashMap<String, Object>();
        // return type -- boolean
        expected.put("allProceduresAreCallable", Boolean.FALSE);
        expected.put("allTablesAreSelectable", Boolean.FALSE);
        expected.put("doesMaxRowSizeIncludeBlobs", Boolean.FALSE);
        expected.put("isCatalogAtStart", Boolean.TRUE);
        expected.put("isReadOnly", Boolean.TRUE);
        expected.put("locatorsUpdateCopy", Boolean.FALSE);
        expected.put("nullPlusNonNullIsNull", Boolean.FALSE);
        expected.put("nullsAreSortedAtEnd", Boolean.FALSE);
        expected.put("nullsAreSortedAtStart", Boolean.FALSE);
        expected.put("nullsAreSortedHigh", Boolean.FALSE);
        expected.put("nullsAreSortedLow", Boolean.TRUE);
        expected.put("storesLowerCaseIdentifiers", Boolean.FALSE);
        expected.put("storesLowerCaseQuotedIdentifiers", Boolean.FALSE);
        expected.put("storesMixedCaseIdentifiers", Boolean.FALSE);
        expected.put("storesMixedCaseQuotedIdentifiers", Boolean.FALSE);
        expected.put("storesUpperCaseIdentifiers", Boolean.FALSE);
        expected.put("storesUpperCaseQuotedIdentifiers", Boolean.FALSE);
        expected.put("supportsAlterTableWithAddColumn", Boolean.FALSE);
        expected.put("supportsAlterTableWithDropColumn", Boolean.FALSE);
        expected.put("supportsANSI92EntryLevelSQL", Boolean.FALSE);
        expected.put("supportsANSI92FullSQL", Boolean.FALSE);
        expected.put("supportsANSI92IntermediateSQL", Boolean.FALSE);
        expected.put("supportsBatchUpdates", Boolean.FALSE);
        expected.put("supportsCatalogsInDataManipulation", Boolean.FALSE);
        expected.put("supportsCatalogsInIndexDefinitions", Boolean.FALSE);
        expected.put("supportsCatalogsInPrivilegeDefinitions", Boolean.FALSE);
        expected.put("supportsCatalogsInProcedureCalls", Boolean.FALSE);
        expected.put("supportsCatalogsInTableDefinitions", Boolean.FALSE);
        expected.put("supportsColumnAliasing", Boolean.FALSE);
        expected.put("supportsCorrelatedSubqueries", Boolean.FALSE);
        expected.put("supportsCoreSQLGrammar", Boolean.FALSE);
        expected.put("supportsDataDefinitionAndDataManipulationTransactions", Boolean.FALSE);
        expected.put("supportsDataManipulationTransactionsOnly", Boolean.FALSE);
        expected.put("supportsDifferentTableCorrelationNames", Boolean.FALSE);
        expected.put("supportsExpressionsInOrderBy", Boolean.FALSE);
        expected.put("supportsExtendedSQLGrammar", Boolean.FALSE);
        expected.put("supportsFullOuterJoins", Boolean.FALSE);
        expected.put("supportsGetGeneratedKeys", Boolean.FALSE);
        expected.put("supportsGroupBy", Boolean.FALSE);
        expected.put("supportsGroupByBeyondSelect", Boolean.FALSE);
        expected.put("supportsGroupByUnrelated", Boolean.FALSE);
        expected.put("supportsIntegrityEnhancementFacility", Boolean.FALSE);
        expected.put("supportsLikeEscapeClause", Boolean.FALSE);
        expected.put("supportsLimitedOuterJoins", Boolean.FALSE);
        expected.put("supportsMinimumSQLGrammar", Boolean.FALSE);
        expected.put("supportsMixedCaseIdentifiers", Boolean.FALSE);
        expected.put("supportsMixedCaseQuotedIdentifiers", Boolean.FALSE);
        expected.put("supportsOpenCursorsAcrossCommit", Boolean.FALSE);
        expected.put("supportsMultipleResultSets", Boolean.FALSE);
        expected.put("supportsMultipleOpenResults", Boolean.FALSE);
        expected.put("supportsMultipleTransactions", Boolean.FALSE);
        expected.put("supportsNamedParameters", Boolean.FALSE);
        expected.put("supportsNonNullableColumns", Boolean.FALSE);
        expected.put("supportsOpenCursorsAcrossRollback", Boolean.FALSE);
        expected.put("supportsOpenStatementsAcrossCommit", Boolean.FALSE);
        expected.put("supportsOpenStatementsAcrossRollback", Boolean.FALSE);
        expected.put("supportsOrderByUnrelated", Boolean.FALSE);
        expected.put("supportsOuterJoins", Boolean.TRUE);
        expected.put("supportsPositionedDelete", Boolean.FALSE);
        expected.put("supportsPositionedUpdate", Boolean.FALSE);
        expected.put("supportsSavepoints", Boolean.FALSE);
        expected.put("supportsSchemasInDataManipulation", Boolean.FALSE);
        expected.put("supportsSchemasInIndexDefinitions", Boolean.FALSE);
        expected.put("supportsSchemasInPrivilegeDefinitions", Boolean.FALSE);
        expected.put("supportsSchemasInProcedureCalls", Boolean.FALSE);
        expected.put("supportsSchemasInTableDefinitions", Boolean.FALSE);
        expected.put("supportsSelectForUpdate", Boolean.FALSE);
        expected.put("supportsStatementPooling", Boolean.FALSE);
        expected.put("supportsStoredProcedures", Boolean.FALSE);
        expected.put("supportsSubqueriesInComparisons", Boolean.FALSE);
        expected.put("supportsSubqueriesInExists", Boolean.FALSE);
        expected.put("supportsSubqueriesInIns", Boolean.FALSE);
        expected.put("supportsSubqueriesInQuantifieds", Boolean.FALSE);
        expected.put("supportsTableCorrelationNames", Boolean.TRUE);
        expected.put("supportsTransactions", Boolean.FALSE);
        expected.put("supportsUnion", Boolean.FALSE);
        expected.put("supportsUnionAll", Boolean.FALSE);
        expected.put("usesLocalFilePerTable", Boolean.FALSE);
        expected.put("usesLocalFiles", Boolean.FALSE);
        expected.put("usesLocalFilePerTable", Boolean.FALSE);

        // return type -- int
        // expected.put("getDatabaseMinorVersion", new Integer(ApplicationInfo.getInstance().getMinorReleaseVersion()));
        // expected.put("getDatabaseMajorVersion", new Integer(ApplicationInfo.getInstance().getMajorReleaseVersion()));
        expected.put("getJDBCMajorVersion", new Integer(2));
        expected.put("getJDBCMinorVersion", new Integer(0));
        expected.put("getDefaultTransactionIsolation", Connection.TRANSACTION_NONE);
        expected.put("getDriverMajorVersion", TestUtil.majorVersion(JdbcLocalI18n.driverVersion.text()));
        expected.put("getDriverMinorVersion", TestUtil.minorVersion(JdbcLocalI18n.driverVersion.text()));
        expected.put("getMaxBinaryLiteralLength", new Integer(NO_LIMIT));
        expected.put("getMaxCatalogNameLength", new Integer(NO_LIMIT));
        expected.put("getMaxCharLiteralLength", new Integer(NO_LIMIT));
        expected.put("getMaxColumnNameLength", new Integer(NO_LIMIT));
        expected.put("getMaxColumnsInGroupBy", new Integer(NO_LIMIT));
        expected.put("getMaxColumnsInIndex", new Integer(NO_LIMIT));
        expected.put("getMaxColumnsInOrderBy", new Integer(NO_LIMIT));
        expected.put("getMaxColumnsInSelect", new Integer(NO_LIMIT));
        expected.put("getMaxColumnsInTable", new Integer(NO_LIMIT));
        expected.put("getMaxConnections", new Integer(NO_LIMIT));
        expected.put("getMaxCursorNameLength", new Integer(NO_LIMIT));
        expected.put("getMaxIndexLength", new Integer(NO_LIMIT));
        expected.put("getMaxProcedureNameLength", new Integer(0));
        expected.put("getMaxRowSize", new Integer(NO_LIMIT));
        expected.put("getMaxStatementLength", new Integer(NO_LIMIT));
        expected.put("getMaxStatements", new Integer(NO_LIMIT));
        expected.put("getMaxTableNameLength", new Integer(NO_LIMIT));
        expected.put("getMaxTablesInSelect", new Integer(NO_LIMIT));
        expected.put("getMaxUserNameLength", new Integer(NO_LIMIT));
        expected.put("getSQLStateType", new Integer(0));

        // return type -- String
        expected.put("getCatalogSeparator", null);
        expected.put("getCatalogTerm", "Repository");
        // expected.put("getDatabaseProductName", "Teiid Embedded");
        // expected.put("getDatabaseProductVersion", "7.1");
        expected.put("getDriverName", JdbcLocalI18n.driverName.text());
        expected.put("getDriverVersion", JdbcLocalI18n.driverVersion.text());
        // expected.put("getExtraNameCharacters", ".@");
        expected.put("getIdentifierQuoteString", "\"");
        // expected.put("getNumericFunctions", DatabaseMetaDataImpl.NUMERIC_FUNCTIONS);
        // expected.put("getSearchStringEscape", "\\");
        // expected.put("getSQLKeywords", DatabaseMetaDataImpl.KEY_WORDS);
        // expected.put("getStringFunctions", DatabaseMetaDataImpl.STRING_FUNCTIONS);
        // expected.put("getSystemFunctions", DatabaseMetaDataImpl.SYSTEM_FUNCTIONS);
        // expected.put("getTimeDateFunctions", DatabaseMetaDataImpl.DATE_FUNCTIONS);
        // expected.put("getUrl", primaryUrl + serverUrl);
        // expected.put("getUserName", CoreConstants.DEFAULT_ANON_USERNAME);

        // ========== NOT SUPPORTED ======//
        // expected.put("getProcedureTerm", "StoredProcedure");
        // expected.put("getSchemaTerm", "Schema");
        // expected.put("getMaxSchemaNameLength", new Integer(NO_LIMIT));

        return expected;
    }

}
