// Copyright 2008 Google Inc.
//
// 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 com.google.gwtorm.jdbc;

import com.google.common.base.Preconditions;
import com.google.gwtorm.schema.ColumnModel;
import com.google.gwtorm.schema.RelationModel;
import com.google.gwtorm.schema.SchemaModel;
import com.google.gwtorm.schema.SequenceModel;
import com.google.gwtorm.schema.sql.SqlDialect;
import com.google.gwtorm.server.AbstractSchema;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.Schema;
import com.google.gwtorm.server.StatementExecutor;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;

/** Internal base class for implementations of {@link Schema}. */
public abstract class JdbcSchema extends AbstractSchema {
  private final Database<?> dbDef;
  private Connection conn;

  protected JdbcSchema(final Database<?> d) throws OrmException {
    dbDef = d;
    conn = dbDef.newConnection();
  }

  public final Connection getConnection() {
    return conn;
  }

  public final SqlDialect getDialect() {
    return dbDef.getDialect();
  }

  public void updateSchema(final StatementExecutor e) throws OrmException {
    try {
      createSequences(e);
      createRelations(e);

      for (final RelationModel rel : dbDef.getSchemaModel().getRelations()) {
        addColumns(e, rel);
      }
    } catch (SQLException err) {
      throw new OrmException("Cannot update schema", err);
    }
  }

  private void createSequences(final StatementExecutor e) throws OrmException,
      SQLException {
    final SqlDialect dialect = dbDef.getDialect();
    final SchemaModel model = dbDef.getSchemaModel();

    Set<String> have = dialect.listSequences(getConnection());
    for (final SequenceModel s : model.getSequences()) {
      if (!have.contains(s.getSequenceName().toLowerCase())) {
        e.execute(s.getCreateSequenceSql(dialect));
      }
    }
  }

  private void createRelations(final StatementExecutor e) throws SQLException,
      OrmException {
    final SqlDialect dialect = dbDef.getDialect();
    final SchemaModel model = dbDef.getSchemaModel();
    Set<String> have = dialect.listTables(getConnection());
    for (final RelationModel r : model.getRelations()) {
      if (!have.contains(r.getRelationName().toLowerCase())) {
        e.execute(r.getCreateTableSql(dialect));
      }
    }
  }

  private void addColumns(final StatementExecutor e, final RelationModel rel)
      throws SQLException, OrmException {
    final SqlDialect dialect = dbDef.getDialect();
    Set<String> have = dialect.listColumns( //
        getConnection(), rel.getRelationName().toLowerCase());
    for (final ColumnModel c : rel.getColumns()) {
      if (!have.contains(c.getColumnName().toLowerCase())) {
        dialect.addColumn(e, rel.getRelationName(), c);
      }
    }
  }

  public void renameTable(final StatementExecutor e, String from, String to)
      throws OrmException {
    Preconditions.checkNotNull(e);
    Preconditions.checkNotNull(from);
    Preconditions.checkNotNull(to);
    getDialect().renameTable(e, from, to);
  }

  public void renameField(final StatementExecutor e, String table, String from,
      String to) throws OrmException {
    final RelationModel rel = findRelationModel(table);
    if (rel == null) {
      throw new OrmException("Relation " + table + " not defined");
    }
    final ColumnModel col = rel.getField(to);
    if (col == null) {
      throw new OrmException("Relation " + table + " does not have " + to);
    }
    getDialect().renameColumn(e, table, from, col);
  }

  public void renameColumn(final StatementExecutor e, String table, String from,
      String to) throws OrmException {
    final RelationModel rel = findRelationModel(table);
    if (rel == null) {
      throw new OrmException("Relation " + table + " not defined");
    }
    final ColumnModel col = rel.getColumn(to);
    if (col == null) {
      throw new OrmException("Relation " + table + " does not have " + to);
    }
    getDialect().renameColumn(e, table, from, col);
  }

  private RelationModel findRelationModel(String table) {
    for (final RelationModel rel : dbDef.getSchemaModel().getRelations()) {
      if (table.equalsIgnoreCase(rel.getRelationName())) {
        return rel;
      }
    }
    return null;
  }

  public void pruneSchema(final StatementExecutor e) throws OrmException {
    try {
      pruneSequences(e);
      pruneRelations(e);

      for (final RelationModel rel : dbDef.getSchemaModel().getRelations()) {
        pruneColumns(e, rel);
      }
    } catch (SQLException err) {
      throw new OrmException("Schema prune failure", err);
    }
  }

  private void pruneSequences(final StatementExecutor e) throws SQLException,
      OrmException {
    final SqlDialect dialect = dbDef.getDialect();
    final SchemaModel model = dbDef.getSchemaModel();
    HashSet<String> want = new HashSet<String>();
    for (final SequenceModel s : model.getSequences()) {
      want.add(s.getSequenceName().toLowerCase());
    }
    for (final String sequence : dialect.listSequences(getConnection())) {
      if (!want.contains(sequence)) {
        e.execute(dialect.getDropSequenceSql(sequence));
      }
    }
  }

  private void pruneRelations(final StatementExecutor e) throws SQLException,
      OrmException {
    final SqlDialect dialect = dbDef.getDialect();
    final SchemaModel model = dbDef.getSchemaModel();
    HashSet<String> want = new HashSet<String>();
    for (final RelationModel r : model.getRelations()) {
      want.add(r.getRelationName().toLowerCase());
    }
    for (final String table : dialect.listTables(getConnection())) {
      if (!want.contains(table)) {
        e.execute("DROP TABLE " + table);
      }
    }
  }

  private void pruneColumns(final StatementExecutor e, final RelationModel rel)
      throws SQLException, OrmException {
    final SqlDialect dialect = dbDef.getDialect();
    HashSet<String> want = new HashSet<String>();
    for (final ColumnModel c : rel.getColumns()) {
      want.add(c.getColumnName().toLowerCase());
    }
    for (String column : dialect.listColumns( //
        getConnection(), rel.getRelationName().toLowerCase())) {
      if (!want.contains(column)) {
        dialect.dropColumn(e, rel.getRelationName(), column);
      }
    }
  }

  @Override
  protected long nextLong(final String poolName) throws OrmException {
    return getDialect().nextLong(getConnection(), poolName);
  }

  public void close() {
    if (conn != null) {
      try {
        conn.close();
      } catch (SQLException err) {
        // TODO Handle an exception while closing a connection
      }
      conn = null;
    }
  }
}

@@ -1136,35 +1136,8 @@
 on;%0A
-import java.sql.Statement;%0A
 impo

Remove unused import