/*
 * ModeShape (http://www.modeshape.org)
 *
 * Licensed 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.modeshape.connector.meta.jdbc;

import java.sql.Connection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.infinispan.schematic.document.Document;
import org.modeshape.jcr.JcrNtLexicon;
import org.modeshape.jcr.spi.federation.DocumentWriter;

/**
 * Class which converts schema metadata to connector documents.
 *
 * @author Horia Chiorean (hchiorea@redhat.com)
 */
public class SchemaRetriever extends AbstractMetadataRetriever {

    private static final Pattern SCHEMA_PATH_PATTERN = Pattern.compile("/([^/]+)/([^/]+)/([^/]+)");
    private static final Pattern SCHEMA_ID_PATTERN = Pattern.compile("([^@]+)@([^@]+)@([^@]+)");

    static final Pattern TABLES_PATH_PATTERN = Pattern.compile(SCHEMA_PATH_PATTERN.pattern() + "/tables");
    static final String TABLES_CONTAINER = "tables";
    private static final Pattern TABLES_ID_PATTERN = Pattern.compile(SCHEMA_ID_PATTERN.pattern() + "@" + TABLES_CONTAINER);

    static final Pattern PROCEDURES_PATH_PATTERN = Pattern.compile(SCHEMA_PATH_PATTERN.pattern() + "/procedures");
    static final String PROCEDURES_CONTAINER = "procedures";
    private static final Pattern PROCEDURES_ID_PATTERN = Pattern.compile(SCHEMA_ID_PATTERN + "@" + PROCEDURES_CONTAINER);

    protected SchemaRetriever( JdbcMetadataConnector connector ) {
        super(connector);
    }

    @Override
    protected Document getDocumentById( String id,
                                        DocumentWriter writer,
                                        Connection connection ) {
        //the request is for a named/default schema
        String schemaId = schemaIdFrom(id, SCHEMA_ID_PATTERN);
        if (schemaId != null) {
            String catalogId = catalogIdFrom(id, SCHEMA_ID_PATTERN);
            return createDocumentForSchema(writer, catalogId, schemaId);
        }

        //the request is for all the tables of a schema
        String schemaIdFromTable = schemaIdFrom(id, TABLES_ID_PATTERN);
        if (schemaIdFromTable != null) {
            String catalogId = catalogIdFrom(id, TABLES_ID_PATTERN);
            return createDocumentForAllTables(writer, connection, catalogId, schemaIdFromTable);
        }

        //the request is for all the procedures of a schema
        String schemaIdFromProcedures = schemaIdFrom(id, PROCEDURES_ID_PATTERN);
        if (schemaIdFromProcedures != null) {
            String catalogId = catalogIdFrom(id, PROCEDURES_ID_PATTERN);
            return createDocumentForAllProcedures(writer, connection, catalogId, schemaIdFromProcedures);
        }

        return null;
    }

    private Document createDocumentForAllProcedures( DocumentWriter writer,
                                                     Connection connection,
                                                     String catalogId,
                                                     String schemaId ) {
        writer.setPrimaryType(JcrNtLexicon.UNSTRUCTURED);
        writer.addMixinType(JdbcMetadataLexicon.PROCEDURES);
        writer.setParent(SchemaRetriever.documentId(DatabaseRetriever.ID, catalogId, schemaId, false, false));
        String catalog = catalogId;
        if (catalog.equalsIgnoreCase(connector.getDefaultCatalogName())) {
            catalog = null;
        }
        String schema = schemaId;
        if (schema.equalsIgnoreCase(connector.getDefaultSchemaName())) {
            schema = null;
        }

        List<ProcedureMetadata> procedures = connector.getMetadataCollector().getProcedures(connection, catalog, schema, null);
        if (!procedures.isEmpty()) {
            for (ProcedureMetadata procedureMetadata : procedures) {
                String procedureId = ProcedureRetriever.documentId(DatabaseRetriever.ID, catalogId, schemaId,
                                                                   procedureMetadata.getName());
                writer.addChild(procedureId, procedureMetadata.getName());
            }
        }
        return writer.document();
    }

    private Document createDocumentForAllTables( DocumentWriter writer,
                                                 Connection connection,
                                                 String catalogId,
                                                 String schemaId ) {
        writer.setPrimaryType(JcrNtLexicon.UNSTRUCTURED);
        writer.addMixinType(JdbcMetadataLexicon.TABLES);
        writer.setParent(SchemaRetriever.documentId(DatabaseRetriever.ID, catalogId, schemaId, false, false));
        String catalog = catalogId;
        if (catalog.equalsIgnoreCase(connector.getDefaultCatalogName())) {
            catalog = null;
        }
        String schema = schemaId;
        if (schema.equalsIgnoreCase(connector.getDefaultSchemaName())) {
            schema = null;
        }

        List<TableMetadata> tables = connector.getMetadataCollector().getTables(connection, catalog, schema, null);
        if (!tables.isEmpty()) {
            for (TableMetadata tableMetadata : tables) {
                String tableId = TableRetriever.documentId(DatabaseRetriever.ID, catalogId, schemaId,
                                                           tableMetadata.getName(),
                                                           false);
                writer.addChild(tableId, tableMetadata.getName());
            }
        }
        return writer.document();
    }

    private Document createDocumentForSchema( DocumentWriter writer,
                                              String catalogId,
                                              String schemaId ) {
        writer.setPrimaryType(JcrNtLexicon.UNSTRUCTURED);
        writer.addMixinType(JdbcMetadataLexicon.SCHEMA);
        writer.setParent(CatalogRetriever.documentId(DatabaseRetriever.ID, catalogId));
        writer.addChild(documentId(DatabaseRetriever.ID, catalogId, schemaId, true, false), TABLES_CONTAINER);
        writer.addChild(documentId(DatabaseRetriever.ID, catalogId, schemaId, false, true), PROCEDURES_CONTAINER);
        return writer.document();
    }

    static String catalogIdFrom( String id,
                                 Pattern pattern ) {
        Matcher matcher = pattern.matcher(id);
        return matcher.matches() ? matcher.group(2) : null;
    }

    static String schemaIdFrom( String id,
                                Pattern pattern ) {
        Matcher matcher = pattern.matcher(id);
        return matcher.matches() ? matcher.group(3) : null;
    }

    @Override
    protected String idFrom( String path ) {
        Matcher namedSchemaMatcher = SCHEMA_PATH_PATTERN.matcher(path);
        if (namedSchemaMatcher.matches()) {
            return documentId(namedSchemaMatcher.group(1), namedSchemaMatcher.group(2), namedSchemaMatcher.group(3), false,
                              false);
        }
        Matcher tablesMatcher = TABLES_PATH_PATTERN.matcher(path);
        if (tablesMatcher.matches()) {
            return documentId(tablesMatcher.group(1), tablesMatcher.group(2), tablesMatcher.group(3), true, false);
        }
        Matcher proceduresMatcher = PROCEDURES_PATH_PATTERN.matcher(path);
        if (proceduresMatcher.matches()) {
            return documentId(proceduresMatcher.group(1), proceduresMatcher.group(2), proceduresMatcher.group(3), false, true);
        }
        return null;
    }

    @Override
    protected boolean canHandle( String id ) {
        return SCHEMA_ID_PATTERN.matcher(id).matches() ||
                TABLES_ID_PATTERN.matcher(id).matches() ||
                PROCEDURES_ID_PATTERN.matcher(id).matches();
    }

    static String documentId( String databaseId,
                              String catalogId,
                              String schemaId,
                              boolean onlyTables,
                              boolean onlyProcedures ) {
        String baseId = generateId(databaseId, catalogId, schemaId);
        if (onlyTables) {
            return generateId(baseId, "tables");
        } else if (onlyProcedures) {
            return generateId(baseId, "procedures");
        } else {
            return baseId;
        }
    }
}
