/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * 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.teiid.maven;

import java.io.File;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.Collections;

import org.apache.commons.lang3.SerializationUtils;
import org.apache.maven.plugin.logging.Log;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.metadata.Column;
import org.teiid.metadata.Database;
import org.teiid.metadata.MetadataFactory;
import org.teiid.metadata.Schema;
import org.teiid.metadata.Server;
import org.teiid.metadata.Table;

public class MaterializationEnhancer {
    static final String CACHE_STORE = "cacheStore";

    private String type;
    private Log log;
    private String version;

    public MaterializationEnhancer(String type, Log log, String version) {
        this.type = type;
        this.log = log;
        this.version = version;
    }

    public boolean isMaterializationRequired(PluginDatabaseStore databaseStore) {
        Database database = databaseStore.db();
        for (Schema schema : database.getSchemas()) {
            if (schema.isPhysical()) {
                continue;
            }

            for (Table table : schema.getTables().values()) {
                if (table.isMaterialized() && table.getMaterializedTable() == null) {
                    return true;
                }
            }
        }
        return false;
    }

    public void instrumentForMaterialization(PluginDatabaseStore databaseStore, File resourcesDir) throws Exception {
        Database database = databaseStore.db();
        String schemaName = "materialized"+"_"+this.version;
        Server server = new Server(CACHE_STORE);
        server.setDataWrapper(this.type);

        database.addServer(server);

        MetadataFactory factory = new MetadataFactory(database.getName(), database.getVersion(), schemaName,
                database.getMetadataStore().getDatatypes(), null, null);

        Schema matSchema = factory.getSchema();
        matSchema.setVisible(false);
        matSchema.setPhysical(true);
        matSchema.addServer(server);
        database.addSchema(matSchema);

        // if not infinispan based materialization add table directly
        Table statusTable = buildStatusTable(factory);

        for (Schema schema : database.getSchemas()) {
            if (schema.isPhysical()) {
                continue;
            }

            for (Table table : schema.getTables().values()) {
                if (table.isMaterialized() && table.getMaterializedTable() == null) {
                    Table matTable = cloneTable(factory, table);
                    matSchema.addTable(matTable);

                    // set auto-management
                    table.setProperty("MATERIALIZED_TABLE", matTable.getFullName());
                    table.setProperty("teiid_rel:ALLOW_MATVIEW_MANAGEMENT", "true");
                    table.setProperty("teiid_rel:MATVIEW_LOADNUMBER_COLUMN","LoadNumber");
                    table.setProperty("teiid_rel:MATVIEW_STATUS_TABLE", statusTable.getFullName());
                }
            }
        }

        // Write the DDL out, however for the Infinispan we need to generate the
        databaseStore.importSchema(schemaName, null, CACHE_STORE, null, Collections.emptyList(),
                Collections.emptyList(), Collections.emptyMap());
        ImportSchemaAwareDDLStringVisitor visitor = new ImportSchemaAwareDDLStringVisitor(databaseStore, null, null);
        visitor.visit(databaseStore.db());
        String vdbDDL = visitor.toString();
        this.log.info("Materialization based VDB: " + vdbDDL);

        // write the materialized database file, this should be the one runtime should deploy
        File file = new File(resourcesDir, "materialized.ddl");
        this.log.info("Materialization Written to : " + file.getAbsolutePath());
        FileWriter fw = new FileWriter(file);
        fw.write(vdbDDL);
        fw.close();
    }

    private Table buildStatusTable(MetadataFactory factory) {
        Table tbl = factory.addTable("status");
        tbl.setName(sanitize(factory.getVdbName() + "_" + tbl.getFullName()));
        Column c = factory.addColumn("VDBName","string", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(50);

        c = factory.addColumn("VDBVersion", "string", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(50);

        c = factory.addColumn("SchemaName", "string", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(50);

        c = factory.addColumn("Name", "string", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(256);

        c = factory.addColumn("TargetSchemaName", "string", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(50);

        c = factory.addColumn("TargetName", "string", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(256);

        c = factory.addColumn("Valid", "boolean", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(50);

        c = factory.addColumn("LoadState", "string", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(25);

        c = factory.addColumn("Cardinality", "long", tbl);

        c = factory.addColumn("Updated", "timestamp", tbl);
        c.setNullType(NullType.No_Nulls);

        c = factory.addColumn("LoadNumber", "long", tbl);
        c.setNullType(NullType.No_Nulls);

        c = factory.addColumn("NodeName", "string", tbl);
        c.setNullType(NullType.No_Nulls);
        c.setLength(25);

        c = factory.addColumn("StaleCount", "long", tbl);

        factory.addPrimaryKey("PK", Arrays.asList(new String[] {"VDBName", "VDBVersion", "SchemaName", "Name"}), tbl);
        tbl.setSupportsUpdate(true);

        for (Column col : tbl.getColumns()) {
            col.setUpdatable(true);
        }

        // if this is going to Infinispan assign the cache name
        if (this.type.equalsIgnoreCase(VdbCodeGeneratorMojo.ISPN)) {
            tbl.setProperty("teiid_ispn:cache", tbl.getName());
        }
        return tbl;
    }

    private String sanitize(String str) {
        return str.replace('.', '_').replace('-', '_');
    }

    private Table cloneTable(MetadataFactory factory, Table table) throws Exception {
        Table matTable = SerializationUtils.clone(table);
        matTable.setVirtual(false);
        matTable.setMaterialized(false);
        matTable.setSupportsUpdate(true);
        matTable.setName(sanitize(factory.getVdbName() + "_" + table.getFullName()));
        factory.addColumn("LoadNumber", "long", matTable);
        //matTable.setUUID(UUID.randomUUID().toString());

        for (Column c : matTable.getColumns()) {
            c.setUpdatable(true);
        }

        // remove any matview specific properties that carried over from the original table
        for (String key : matTable.getProperties().keySet()) {
            if (key.startsWith("teiid_rel:MATVIEW")) {
                matTable.setProperty(key, null);
            }
        }
        // if this is going to Infinispan assign the cache name
        if (this.type.equalsIgnoreCase(VdbCodeGeneratorMojo.ISPN)) {
            matTable.setProperty("teiid_ispn:cache", matTable.getName());
        }
        return matTable;
    }
}