/*
 * 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
 *
 *  https://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.
 *
 * Other licenses:
 * -----------------------------------------------------------------------------
 * Commercial licenses for this work are available. These replace the above
 * Apache-2.0 license and offer limited warranties, support, maintenance, and
 * commercial database integrations.
 *
 * For more information, please visit: https://www.jooq.org/legal/licensing
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package org.jooq.codegen;

import static org.jooq.impl.QOM.GenerationOption.STORED;
import static org.jooq.impl.QOM.GenerationOption.VIRTUAL;
import static org.jooq.tools.StringUtils.isBlank;
import static org.jooq.util.xml.XmlUtils.foreignKeyRule;
import static org.jooq.util.xml.jaxb.TableConstraintType.CHECK;
import static org.jooq.util.xml.jaxb.TableConstraintType.FOREIGN_KEY;
import static org.jooq.util.xml.jaxb.TableConstraintType.PRIMARY_KEY;
import static org.jooq.util.xml.jaxb.TableConstraintType.UNIQUE;

import java.io.StringWriter;
import java.math.BigInteger;
import java.util.List;

import org.jooq.SortOrder;
// ...
// ...
import org.jooq.meta.CatalogDefinition;
import org.jooq.meta.CheckConstraintDefinition;
import org.jooq.meta.ColumnDefinition;
import org.jooq.meta.DataTypeDefinition;
import org.jooq.meta.Database;
import org.jooq.meta.ForeignKeyDefinition;
import org.jooq.meta.IndexColumnDefinition;
import org.jooq.meta.IndexDefinition;
import org.jooq.meta.PackageDefinition;
import org.jooq.meta.ParameterDefinition;
import org.jooq.meta.RoutineDefinition;
import org.jooq.meta.SchemaDefinition;
import org.jooq.meta.SequenceDefinition;
// ...
import org.jooq.meta.TableDefinition;
// ...
import org.jooq.meta.UniqueKeyDefinition;
import org.jooq.tools.Convert;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.util.jaxb.tools.MiniJAXB;
import org.jooq.util.xml.jaxb.Catalog;
import org.jooq.util.xml.jaxb.CheckConstraint;
import org.jooq.util.xml.jaxb.Column;
import org.jooq.util.xml.jaxb.Index;
import org.jooq.util.xml.jaxb.IndexColumnUsage;
import org.jooq.util.xml.jaxb.InformationSchema;
import org.jooq.util.xml.jaxb.KeyColumnUsage;
import org.jooq.util.xml.jaxb.Parameter;
import org.jooq.util.xml.jaxb.ParameterMode;
import org.jooq.util.xml.jaxb.ReferentialConstraint;
import org.jooq.util.xml.jaxb.Routine;
import org.jooq.util.xml.jaxb.RoutineType;
import org.jooq.util.xml.jaxb.Schema;
import org.jooq.util.xml.jaxb.Sequence;
import org.jooq.util.xml.jaxb.Synonym;
import org.jooq.util.xml.jaxb.Table;
import org.jooq.util.xml.jaxb.TableConstraint;
import org.jooq.util.xml.jaxb.TableType;
import org.jooq.util.xml.jaxb.Trigger;
import org.jooq.util.xml.jaxb.TriggerActionOrientation;
import org.jooq.util.xml.jaxb.TriggerActionTiming;
import org.jooq.util.xml.jaxb.TriggerEventManipulation;
import org.jooq.util.xml.jaxb.View;

/**
 * @author Lukas Eder
 */
public class XMLGenerator extends AbstractGenerator {

    private static final JooqLogger log = JooqLogger.getLogger(XMLGenerator.class);

    public XMLGenerator() {
        super(Language.XML);
    }

    @Override
    public void generate0(Database db) {
        logDatabaseParameters(db);
        log.info("");
        logGenerationRemarks(db);

        log.info("");
        log.info("----------------------------------------------------------");

        TextWriter out = new TextWriter(getStrategy().getFile("information_schema.xml"), targetEncoding);
        log.info("");
        log.info("Generating XML", out.file().getName());
        log.info("==========================================================");

        InformationSchema is = new InformationSchema();

        boolean hasNonDefaultCatalogs = false;
        for (CatalogDefinition c : db.getCatalogs()) {
            if (!StringUtils.isBlank(c.getName())) {
                hasNonDefaultCatalogs = true;
                break;
            }
        }

        for (CatalogDefinition c : db.getCatalogs()) {
            String catalogName = c.getOutputName();

            if (hasNonDefaultCatalogs)
                is.getCatalogs().add(new Catalog()
                    .withCatalogName(catalogName)
                    .withComment(generateCommentsOnCatalogs() ? c.getComment() : null));

            for (SchemaDefinition s : c.getSchemata()) {
                String schemaName = s.getOutputName();

                Schema schema = new Schema();
                schema.setCatalogName(catalogName);
                schema.setSchemaName(schemaName);

                if (generateCommentsOnSchemas())
                    schema.setComment(s.getComment());

                is.getSchemata().add(schema);

                for (TableDefinition t : s.getTables()) {
                    String tableName = t.getOutputName();

                    Table table = new Table();
                    table.setTableCatalog(catalogName);
                    table.setTableSchema(schemaName);
                    table.setTableName(tableName);
                    table.setTableType(
                        t.isView()
                      ? TableType.VIEW
                      : t.isMaterializedView()
                      ? TableType.MATERIALIZED_VIEW
                      : t.isTemporary()
                      ? TableType.GLOBAL_TEMPORARY
                      : TableType.BASE_TABLE
                    );

                    if (generateCommentsOnTables())
                        table.setComment(t.getComment());

                    is.getTables().add(table);

                    if (t.isView() || t.isMaterializedView()) {
                        View view = new View();

                        view.setTableCatalog(catalogName);
                        view.setTableSchema(schemaName);
                        view.setTableName(tableName);
                        if (generateSourcesOnViews())
                            view.setViewDefinition(t.getSource());

                        is.getViews().add(view);
                    }

                    for (ColumnDefinition co : t.getColumns()) {
                        String columnName = co.getOutputName();
                        DataTypeDefinition type = co.getType();

                        Column column = new Column();
                        column.setTableCatalog(catalogName);
                        column.setTableSchema(schemaName);
                        column.setTableName(tableName);
                        column.setColumnName(columnName);

                        if (generateCommentsOnColumns())
                            column.setComment(co.getComment());

                        column.setCharacterMaximumLength(type.getLength());
                        column.setColumnDefault(type.getDefaultValue());
                        column.setDataType(type.getType());
                        if (co.isIdentity())
                            column.setIdentityGeneration("YES");
                        column.setIsNullable(type.isNullable());
                        column.setNumericPrecision(type.getPrecision());
                        column.setNumericScale(type.getScale());
                        column.setOrdinalPosition(co.getPosition());
                        column.setHidden(type.isHidden());
                        column.setReadonly(co.isReadonly());

                        if (type.isComputed()) {
                            column.setIsGenerated(type.isComputed());
                            column.setGenerationExpression(type.getGeneratedAlwaysAs());
                            column.setGenerationOption(type.getGenerationOption() == VIRTUAL ? "VIRTUAL" : type.getGenerationOption() == STORED ? "STORED" : null);
                        }

                        is.getColumns().add(column);
                    }
                }

                for (IndexDefinition i : db.getIndexes(s)) {
                    String indexName = i.getOutputName();
                    TableDefinition table = i.getTable();
                    List<IndexColumnDefinition> columns = i.getIndexColumns();

                    Index index = new Index();
                    index.setIndexCatalog(catalogName);
                    index.setIndexSchema(schemaName);
                    index.setIndexName(indexName);

                    if (generateCommentsOnKeys())
                        index.setComment(i.getComment());

                    index.setTableCatalog(table.getCatalog().getOutputName());
                    index.setTableSchema(table.getSchema().getOutputName());
                    index.setTableName(table.getOutputName());
                    index.setIsUnique(i.isUnique());

                    is.getIndexes().add(index);

                    for (int j = 0; j < columns.size(); j++) {
                        IndexColumnDefinition indexColumn = columns.get(j);
                        ColumnDefinition column = indexColumn.getColumn();

                        IndexColumnUsage ic = new IndexColumnUsage();
                        ic.setIndexCatalog(catalogName);
                        ic.setIndexSchema(schemaName);
                        ic.setIndexName(indexName);
                        ic.setColumnName(column.getOutputName());
                        ic.setOrdinalPosition(j + 1);
                        ic.setIsDescending(indexColumn.getSortOrder() == SortOrder.DESC);
                        ic.setTableCatalog(table.getCatalog().getOutputName());
                        ic.setTableSchema(table.getSchema().getOutputName());
                        ic.setTableName(table.getOutputName());

                        is.getIndexColumnUsages().add(ic);
                    }
                }

                for (UniqueKeyDefinition u : db.getKeys(s)) {
                    String constraintName = u.getOutputName();
                    TableDefinition table = u.getTable();
                    List<ColumnDefinition> columns = u.getKeyColumns();

                    TableConstraint tc = new TableConstraint();
                    tc.setConstraintCatalog(catalogName);
                    tc.setConstraintSchema(schemaName);
                    tc.setConstraintName(constraintName);
                    tc.setConstraintType(u.isPrimaryKey() ? PRIMARY_KEY : UNIQUE);

                    if (generateCommentsOnKeys())
                        tc.setComment(u.getComment());

                    tc.setTableCatalog(table.getCatalog().getOutputName());
                    tc.setTableSchema(table.getSchema().getOutputName());
                    tc.setTableName(table.getOutputName());
                    tc.setEnforced(u.enforced());

                    is.getTableConstraints().add(tc);

                    for (int i = 0; i < columns.size(); i++) {
                        ColumnDefinition column = columns.get(i);

                        KeyColumnUsage kc = new KeyColumnUsage();

                        kc.setConstraintCatalog(catalogName);
                        kc.setConstraintSchema(schemaName);
                        kc.setConstraintName(constraintName);
                        kc.setColumnName(column.getOutputName());
                        kc.setOrdinalPosition(i + 1);
                        kc.setTableCatalog(table.getCatalog().getOutputName());
                        kc.setTableSchema(table.getSchema().getOutputName());
                        kc.setTableName(table.getOutputName());

                        is.getKeyColumnUsages().add(kc);
                    }
                }

                for (ForeignKeyDefinition f : db.getForeignKeys(s)) {
                    String constraintName = f.getOutputName();
                    UniqueKeyDefinition referenced = f.getReferencedKey();
                    TableDefinition table = f.getKeyTable();
                    List<ColumnDefinition> columns = f.getKeyColumns();

                    TableConstraint tc = new TableConstraint();
                    tc.setConstraintCatalog(catalogName);
                    tc.setConstraintSchema(schemaName);
                    tc.setConstraintName(constraintName);
                    tc.setConstraintType(FOREIGN_KEY);

                    if (generateCommentsOnKeys())
                        tc.setComment(f.getComment());

                    tc.setTableCatalog(table.getCatalog().getOutputName());
                    tc.setTableSchema(table.getSchema().getOutputName());
                    tc.setTableName(table.getOutputName());
                    tc.setEnforced(f.enforced());

                    ReferentialConstraint rc = new ReferentialConstraint();
                    rc.setConstraintCatalog(catalogName);
                    rc.setConstraintSchema(schemaName);
                    rc.setConstraintName(constraintName);
                    rc.setUniqueConstraintCatalog(referenced.getCatalog().getOutputName());
                    rc.setUniqueConstraintSchema(referenced.getSchema().getOutputName());
                    rc.setUniqueConstraintName(referenced.getOutputName());
                    rc.setDeleteRule(foreignKeyRule(f.getDeleteRule()));
                    rc.setUpdateRule(foreignKeyRule(f.getUpdateRule()));

                    is.getTableConstraints().add(tc);
                    is.getReferentialConstraints().add(rc);

                    for (int i = 0; i < columns.size(); i++) {
                        ColumnDefinition column = columns.get(i);

                        KeyColumnUsage kc = new KeyColumnUsage();

                        kc.setConstraintCatalog(catalogName);
                        kc.setConstraintSchema(schemaName);
                        kc.setConstraintName(constraintName);
                        kc.setColumnName(column.getOutputName());
                        kc.setOrdinalPosition(i + 1);
                        kc.setTableCatalog(table.getCatalog().getOutputName());
                        kc.setTableSchema(table.getSchema().getOutputName());
                        kc.setTableName(table.getOutputName());

                        is.getKeyColumnUsages().add(kc);
                    }
                }

                for (CheckConstraintDefinition ch : db.getCheckConstraints(s)) {
                    String constraintName = ch.getOutputName();
                    TableDefinition table = ch.getTable();

                    TableConstraint tc = new TableConstraint();
                    tc.setConstraintCatalog(catalogName);
                    tc.setConstraintSchema(schemaName);
                    tc.setConstraintName(constraintName);
                    tc.setConstraintType(CHECK);

                    if (generateCommentsOnKeys())
                        tc.setComment(ch.getComment());

                    tc.setTableCatalog(table.getCatalog().getOutputName());
                    tc.setTableSchema(table.getSchema().getOutputName());
                    tc.setTableName(table.getOutputName());
                    tc.setEnforced(ch.enforced());

                    is.getTableConstraints().add(tc);

                    CheckConstraint cc = new CheckConstraint();
                    cc.setConstraintCatalog(catalogName);
                    cc.setConstraintSchema(schemaName);
                    cc.setConstraintName(constraintName);
                    cc.setCheckClause(ch.getCheckClause());

                    is.getCheckConstraints().add(cc);
                }

                for (SequenceDefinition se : db.getSequences(s)) {
                    String sequenceName = se.getOutputName();
                    DataTypeDefinition type = se.getType();

                    Sequence sequence = new Sequence();
                    sequence.setSequenceCatalog(catalogName);
                    sequence.setSequenceSchema(schemaName);
                    sequence.setSequenceName(sequenceName);

                    if (generateCommentsOnSequences())
                        sequence.setComment(se.getComment());

                    sequence.setCharacterMaximumLength(type.getLength());
                    sequence.setDataType(type.getType());
                    sequence.setNumericPrecision(type.getPrecision());
                    sequence.setNumericScale(type.getScale());

                    sequence.setStartValue(Convert.convert(se.getStartWith(), BigInteger.class));
                    sequence.setIncrement(Convert.convert(se.getIncrementBy(), BigInteger.class));
                    sequence.setMinimumValue(Convert.convert(se.getMinvalue(), BigInteger.class));
                    sequence.setMaximumValue(Convert.convert(se.getMaxvalue(), BigInteger.class));
                    sequence.setCycleOption(se.getCycle());
                    sequence.setCache(Convert.convert(se.getCache(), BigInteger.class));

                    is.getSequences().add(sequence);
                }

                for (PackageDefinition pkg : db.getPackages(s))
                    for (RoutineDefinition r : pkg.getRoutines())
                        exportRoutine(is, r, catalogName, schemaName);

                for (RoutineDefinition r : db.getRoutines(s))
                    exportRoutine(is, r, catalogName, schemaName);










































            }
        }

        StringWriter writer = new StringWriter();
        MiniJAXB.marshal(is, writer);
        out.print(writer.toString());
        out.close();
    }

    private void exportRoutine(InformationSchema is, RoutineDefinition r, String catalogName, String schemaName) {
        String specificName = r.getName() + (isBlank(r.getOverload()) ? "" : "_" + r.getOverload());

        Routine routine = new Routine();
        routine.setRoutineCatalog(catalogName);
        routine.setSpecificCatalog(catalogName);
        routine.setRoutineSchema(schemaName);
        routine.setSpecificSchema(schemaName);

        if (r.getPackage() != null) {
            routine.setRoutinePackage(r.getPackage().getName());
            routine.setSpecificPackage(r.getPackage().getName());
        }

        routine.setRoutineName(r.getName());
        routine.setSpecificName(specificName);

        if (generateCommentsOnRoutines())
            routine.setComment(r.getComment());

        if (r.getReturnValue() == null) {
            routine.setRoutineType(RoutineType.PROCEDURE);
        }
        else {
            routine.setRoutineType(RoutineType.FUNCTION);
            routine.setDataType(r.getReturnType().getType());
            routine.setCharacterMaximumLength(r.getReturnType().getLength());
            routine.setNumericPrecision(r.getReturnType().getPrecision());
            routine.setNumericScale(r.getReturnType().getScale());
        }

        is.getRoutines().add(routine);

        int i = 1;
        for (ParameterDefinition p : r.getAllParameters()) {
            if (p != r.getReturnValue()) {
                Parameter parameter = new Parameter();

                parameter.setSpecificCatalog(catalogName);
                parameter.setSpecificSchema(schemaName);

                if (r.getPackage() != null)
                    parameter.setSpecificPackage(r.getPackage().getName());

                parameter.setSpecificName(specificName);
                parameter.setOrdinalPosition(i++);
                parameter.setParameterName(p.getName());

                if (generateCommentsOnParameters())
                    parameter.setComment(p.getComment());

                boolean in = r.getInParameters().contains(p);
                boolean out = r.getOutParameters().contains(p);

                if (in && out)
                    parameter.setParameterMode(ParameterMode.INOUT);
                else if (in)
                    parameter.setParameterMode(ParameterMode.IN);
                else if (out)
                    parameter.setParameterMode(ParameterMode.OUT);

                parameter.setDataType(p.getType().getType());
                parameter.setCharacterMaximumLength(p.getType().getLength());
                parameter.setNumericPrecision(p.getType().getPrecision());
                parameter.setNumericScale(p.getType().getScale());
                parameter.setParameterDefault(p.getType().getDefaultValue());

                is.getParameters().add(parameter);
            }
        }
    }
}
