package liquibase.sqlgenerator.core;

import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.exception.DatabaseException;
import liquibase.exception.ValidationErrors;
import liquibase.sql.Sql;
import liquibase.sql.UnparsedSql;
import liquibase.sqlgenerator.SqlGeneratorChain;
import liquibase.statement.core.DropPrimaryKeyStatement;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Table;
import liquibase.structure.core.Schema;

public class DropPrimaryKeyGenerator extends AbstractSqlGenerator<DropPrimaryKeyStatement> {

    @Override
    public boolean supports(DropPrimaryKeyStatement statement, Database database) {
        return (!(database instanceof SQLiteDatabase));
    }

    @Override
    public ValidationErrors validate(DropPrimaryKeyStatement dropPrimaryKeyStatement, Database database, SqlGeneratorChain sqlGeneratorChain) {
        ValidationErrors validationErrors = new ValidationErrors();
        validationErrors.checkRequiredField("tableName", dropPrimaryKeyStatement.getTableName());

        if (database instanceof FirebirdDatabase || database instanceof InformixDatabase) {
            validationErrors.checkRequiredField("constraintName", dropPrimaryKeyStatement.getConstraintName());
        }

        return validationErrors;
    }

    @Override
    public Sql[] generateSql(DropPrimaryKeyStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
        String sql;
        if (database instanceof MSSQLDatabase) {
            String escapedTableName = database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName());
            if (statement.getConstraintName() == null) {
                boolean sql2005OrLater = true;
                try {
                    sql2005OrLater = database.getDatabaseMajorVersion() >= 9;
                } catch (DatabaseException e) {
                    // Assume SQL Server 2005 or later
                }
                if (sql2005OrLater) {
                    // SQL Server 2005 or later
                    sql =
                            "DECLARE @sql [nvarchar](MAX)\r\n" +
                            "SELECT @sql = N'ALTER TABLE " + database.escapeStringForDatabase(escapedTableName) + " DROP CONSTRAINT ' + QUOTENAME([kc].[name]) " +
                            "FROM [sys].[key_constraints] AS [kc] " +
                            "WHERE [kc].[parent_object_id] = OBJECT_ID(N'" + database.escapeStringForDatabase(escapedTableName) +  "') " +
                            "AND [kc].[type] = 'PK'\r\n" +
                            "EXEC sp_executesql @sql";
                } else {
                    // SQL Server 2000
                    sql =
                            "DECLARE @sql [nvarchar](4000)\r\n" +
                            "SELECT @sql = N'ALTER TABLE " + database.escapeStringForDatabase(escapedTableName) + " DROP CONSTRAINT ' + QUOTENAME([kc].[name]) " +
                            "FROM [dbo].[sysobjects] AS [kc] " +
                            "WHERE [kc].[parent_obj] = OBJECT_ID(N'" + database.escapeStringForDatabase(escapedTableName) +  "') " +
                            "AND [kc].[xtype] = 'PK'\r\n" +
                            "EXEC sp_executesql @sql";
                }
            } else {
                sql = "ALTER TABLE " + escapedTableName + " DROP CONSTRAINT " + database.escapeConstraintName(statement.getConstraintName());
            }
        } else if (database instanceof PostgresDatabase) {
			if (statement.getConstraintName() == null) {
				String schemaName = statement.getSchemaName() != null ? statement.getSchemaName() : database.getDefaultSchemaName();
				schemaName = database.correctObjectName(schemaName, Schema.class);
				String tableName = database.correctObjectName(statement.getTableName(), Table.class);

				sql = String.format(""
						+ "DO $$ DECLARE constraint_name varchar;\n"
						+ "BEGIN\n"
						+ "  SELECT tc.CONSTRAINT_NAME into strict constraint_name\n"
						+ "    FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc\n"
						+ "    WHERE CONSTRAINT_TYPE = 'PRIMARY KEY'\n"
						+ "      AND TABLE_NAME = '%2$s' AND TABLE_SCHEMA = '%1$s';\n"
						+ "    EXECUTE 'alter table %1$s.%2$s drop constraint ' || constraint_name;\n"
						+ "END $$;"
						, schemaName, tableName);
			} else {
				sql = "ALTER TABLE " + database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName()) + " DROP CONSTRAINT " + database.escapeConstraintName(statement.getConstraintName());
			}
        } else if (database instanceof FirebirdDatabase) {
            sql = "ALTER TABLE " + database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName()) + " DROP CONSTRAINT "+database.escapeConstraintName(statement.getConstraintName());
        } else if (database instanceof OracleDatabase) {
            sql = "ALTER TABLE " + database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName()) + " DROP PRIMARY KEY";
            if (statement.getDropIndex() == null || statement.getDropIndex()) {
                sql += " DROP INDEX";
            } else {
                sql += " KEEP INDEX";
            }
        } else if (database instanceof InformixDatabase) {
            sql = "ALTER TABLE " + database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName()) + " DROP CONSTRAINT " + database.escapeConstraintName(statement.getConstraintName());
        } else if (database instanceof SybaseDatabase) {
            String escapedTableName = database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName());
            String escapedConstraintName = database.escapeConstraintName(statement.getConstraintName());
            sql = "ALTER TABLE " + escapedTableName + " DROP CONSTRAINT " + escapedConstraintName;
        } else {
            sql = "ALTER TABLE " + database.escapeTableName(statement.getCatalogName(), statement.getSchemaName(), statement.getTableName()) + " DROP PRIMARY KEY";
        }

        return new Sql[] {
                new UnparsedSql(sql, getAffectedPrimaryKey(statement))
        };
    }

    protected PrimaryKey getAffectedPrimaryKey(DropPrimaryKeyStatement statement) {
        return new PrimaryKey().setName(statement.getConstraintName()).setTable((Table) new Table().setName(statement.getTableName()).setSchema(statement.getCatalogName(), statement.getSchemaName()));
    }
}
