package liquibase.integration.ant;

import liquibase.CatalogAndSchema;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.diff.DiffResult;
import liquibase.diff.compare.CompareControl;
import liquibase.exception.LiquibaseException;
import liquibase.integration.ant.type.DatabaseType;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.SnapshotControl;
import liquibase.snapshot.SnapshotGeneratorFactory;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Reference;

public abstract class AbstractDatabaseDiffTask extends BaseLiquibaseTask {
    private DatabaseType referenceDatabaseType;
    private String diffTypes;

    protected DiffResult getDiffResult() {
        Liquibase liquibase = getLiquibase();
        Database targetDatabase = liquibase.getDatabase();
        Database referenceDatabase = createDatabaseFromType(referenceDatabaseType);

        CatalogAndSchema targetCatalogAndSchema = buildCatalogAndSchema(targetDatabase);
        CatalogAndSchema referenceCatalogAndSchema = buildCatalogAndSchema(referenceDatabase);
        CompareControl.SchemaComparison[] schemaComparisons = {
                new CompareControl.SchemaComparison(referenceCatalogAndSchema, targetCatalogAndSchema)
        };

        SnapshotGeneratorFactory snapshotGeneratorFactory = SnapshotGeneratorFactory.getInstance();
        DatabaseSnapshot referenceSnapshot;
        try {
            referenceSnapshot = snapshotGeneratorFactory.createSnapshot(referenceDatabase.getDefaultSchema(),
                    referenceDatabase, new SnapshotControl(referenceDatabase, diffTypes));
        } catch (LiquibaseException e) {
            throw new BuildException("Unable to create a DatabaseSnapshot. " + e.toString(), e);
        }

        CompareControl compareControl = new CompareControl(schemaComparisons, referenceSnapshot.getSnapshotControl().getTypesToInclude());

        try {
            return liquibase.diff(referenceDatabase, targetDatabase, compareControl);
        } catch (LiquibaseException e) {
            throw new BuildException("Unable to diff databases. " + e.toString(), e);
        }
    }

    @Override
    protected void validateParameters() {
        super.validateParameters();

        if(referenceDatabaseType == null) {
            throw new BuildException("Reference database element or reference required.");
        }
    }

    private CatalogAndSchema buildCatalogAndSchema(Database database) {
        return new CatalogAndSchema(database.getDefaultCatalogName(), database.getDefaultSchemaName());
    }

    public void addReferenceDatabase(DatabaseType referenceDatabase) {
        if(this.referenceDatabaseType != null) {
            throw new BuildException("Only one <referenceDatabase> element is allowed.");
        }
        this.referenceDatabaseType = referenceDatabase;
    }

    public void setReferenceDatabaseRef(Reference referenceDatabaseRef) {
        referenceDatabaseType = new DatabaseType(getProject());
        referenceDatabaseType.setRefid(referenceDatabaseRef);
    }

    public String getDiffTypes() {
        return diffTypes;
    }

    public void setDiffTypes(String diffTypes) {
        this.diffTypes = diffTypes;
    }

    /*************************
     * Deprecated attributes *
     *************************/

    /**
     * Helper method for deprecated ant attributes. This method will be removed when the deprecated methods are removed.
     * Do not rely on this method.
     */
    private DatabaseType getReferenceDatabaseType() {
        if(referenceDatabaseType == null) {
            referenceDatabaseType = new DatabaseType(getProject());
        }
        return referenceDatabaseType;
    }

    @Deprecated
    public String getReferenceDriver() {
        return getReferenceDatabaseType().getDriver();
    }

    @Deprecated
    public void setReferenceDriver(String referenceDriver) {
        log("The referenceDriver attribute is deprecated. Use a nested <referenceDatabase> element or set the referenceDatabaseRef attribute instead.", Project.MSG_WARN);
        getReferenceDatabaseType().setDriver(referenceDriver);
    }

    @Deprecated
    public String getReferenceUrl() {
        return getReferenceDatabaseType().getUrl();
    }

    @Deprecated
    public void setReferenceUrl(String referenceUrl) {
        log("The referenceUrl attribute is deprecated. Use a nested <referenceDatabase> element or set the referenceDatabaseRef attribute instead.", Project.MSG_WARN);
        getReferenceDatabaseType().setUrl(referenceUrl);
    }

    @Deprecated
    public String getReferenceUsername() {
        return getReferenceDatabaseType().getUser();
    }

    @Deprecated
    public void setReferenceUsername(String referenceUsername) {
        log("The referenceUsername attribute is deprecated. Use a nested <referenceDatabase> element or set the referenceDatabaseRef attribute instead.", Project.MSG_WARN);
        getReferenceDatabaseType().setUser(referenceUsername);
    }

    @Deprecated
    public String getReferencePassword() {
        return getReferenceDatabaseType().getPassword();
    }

    @Deprecated
    public void setReferencePassword(String referencePassword) {
        log("The referencePassword attribute is deprecated. Use a nested <referenceDatabase> element or set the referenceDatabaseRef attribute instead.", Project.MSG_WARN);
        getReferenceDatabaseType().setPassword(referencePassword);
    }

    @Deprecated
    public String getReferenceDefaultCatalogName() {
        return getReferenceDatabaseType().getDefaultCatalogName();
    }

    @Deprecated
    public void setReferenceDefaultCatalogName(String referenceDefaultCatalogName) {
        log("The referenceDefaultCatalogName attribute is deprecated. Use a nested <referenceDatabase> element or set the referenceDatabaseRef attribute instead.", Project.MSG_WARN);
        getReferenceDatabaseType().setDefaultCatalogName(referenceDefaultCatalogName);
    }

    @Deprecated
    public String getReferenceDefaultSchemaName() {
        return getReferenceDatabaseType().getDefaultSchemaName();
    }

    @Deprecated
    public void setReferenceDefaultSchemaName(String referenceDefaultSchemaName) {
        log("The referenceDefaultSchemaName attribute is deprecated. Use a nested <referenceDatabase> element or set the referenceDatabaseRef attribute instead.", Project.MSG_WARN);
        getReferenceDatabaseType().setDefaultSchemaName(referenceDefaultSchemaName);
    }

    @Deprecated
    public String getDataDir() {
        return null;
    }

    @Deprecated
    public void setDataDir(String dataDir) {
        log("The dataDir attribute is deprecated. It is no longer needed and will be removed in the future.", Project.MSG_WARN);
    }
}
