// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/webdata/web_database.h"

#include <algorithm>

#include "app/sql/statement.h"
#include "app/sql/transaction.h"
#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
#include "content/common/notification_service.h"

namespace {

// Current version number.  Note: when changing the current version number,
// corresponding changes must happen in the unit tests, and new migration test
// added.  See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|.
const int kCurrentVersionNumber = 37;
const int kCompatibleVersionNumber = 37;

// Change the version number and possibly the compatibility version of
// |meta_table_|.
void ChangeVersion(sql::MetaTable* meta_table,
                   int version_num,
                   bool update_compatible_version_num) {
  meta_table->SetVersionNumber(version_num);
  if (update_compatible_version_num) {
    meta_table->SetCompatibleVersionNumber(
          std::min(version_num, kCompatibleVersionNumber));
  }
}

// Outputs the failed version number as a warning and always returns
// |sql::INIT_FAILURE|.
sql::InitStatus FailedMigrationTo(int version_num) {
  LOG(WARNING) << "Unable to update web database to version "
               << version_num << ".";
  NOTREACHED();
  return sql::INIT_FAILURE;
}

}  // namespace

WebDatabase::WebDatabase() {}

WebDatabase::~WebDatabase() {}

void WebDatabase::BeginTransaction() {
  db_.BeginTransaction();
}

void WebDatabase::CommitTransaction() {
  db_.CommitTransaction();
}

AutofillTable* WebDatabase::GetAutofillTable() {
  return autofill_table_.get();
}

KeywordTable* WebDatabase::GetKeywordTable() {
  return keyword_table_.get();
}

LoginsTable* WebDatabase::GetLoginsTable() {
  return logins_table_.get();
}

TokenServiceTable* WebDatabase::GetTokenServiceTable() {
  return token_service_table_.get();
}

WebAppsTable* WebDatabase::GetWebAppsTable() {
  return web_apps_table_.get();
}

sql::Connection* WebDatabase::GetSQLConnection() {
  return &db_;
}

sql::InitStatus WebDatabase::Init(const FilePath& db_name) {
  // When running in unit tests, there is already a NotificationService object.
  // Since only one can exist at a time per thread, check first.
  if (!NotificationService::current())
    notification_service_.reset(new NotificationService);

  // Set the exceptional sqlite error handler.
  db_.set_error_delegate(GetErrorHandlerForWebDb());

  // We don't store that much data in the tables so use a small page size.
  // This provides a large benefit for empty tables (which is very likely with
  // the tables we create).
  db_.set_page_size(2048);

  // We shouldn't have much data and what access we currently have is quite
  // infrequent. So we go with a small cache size.
  db_.set_cache_size(32);

  // Run the database in exclusive mode. Nobody else should be accessing the
  // database while we're running, and this will give somewhat improved perf.
  db_.set_exclusive_locking();

  if (!db_.Open(db_name))
    return sql::INIT_FAILURE;

  // Initialize various tables
  sql::Transaction transaction(&db_);
  if (!transaction.Begin())
    return sql::INIT_FAILURE;

  // Version check.
  if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber))
    return sql::INIT_FAILURE;
  if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
    LOG(WARNING) << "Web database is too new.";
    return sql::INIT_TOO_NEW;
  }

  // Create the tables.
  autofill_table_.reset(new AutofillTable(&db_, &meta_table_));
  keyword_table_.reset(new KeywordTable(&db_, &meta_table_));
  logins_table_.reset(new LoginsTable(&db_, &meta_table_));
  token_service_table_.reset(new TokenServiceTable(&db_, &meta_table_));
  web_apps_table_.reset(new WebAppsTable(&db_, &meta_table_));

  // Initialize the tables.
  if (!keyword_table_->Init() || !autofill_table_->Init() ||
      !logins_table_->Init() || !web_apps_table_->Init() ||
      !token_service_table_->Init()) {
    LOG(WARNING) << "Unable to initialize the web database.";
    return sql::INIT_FAILURE;
  }

  // If the file on disk is an older database version, bring it up to date.
  // If the migration fails we return an error to caller and do not commit
  // the migration.
  sql::InitStatus migration_status = MigrateOldVersionsAsNeeded();
  if (migration_status != sql::INIT_OK)
    return migration_status;

  return transaction.Commit() ? sql::INIT_OK : sql::INIT_FAILURE;
}

sql::InitStatus WebDatabase::MigrateOldVersionsAsNeeded() {
  // Migrate if necessary.
  int current_version = meta_table_.GetVersionNumber();
  switch (current_version) {
    // Versions 1 - 19 are unhandled.  Version numbers greater than
    // kCurrentVersionNumber should have already been weeded out by the caller.
    default:
      // When the version is too old, we return failure error code.  The schema
      // is too out of date to migrate.
      // There should not be a released product that makes a database too old to
      // migrate. If we do encounter such a legacy database, we will need a
      // better solution to handle it (i.e., pop up a dialog to tell the user,
      // erase all their prefs and start over, etc.).
      LOG(WARNING) << "Web database version " << current_version <<
          " is too old to handle.";
      NOTREACHED();
      return sql::INIT_FAILURE;

    case 20:
      if (!keyword_table_->MigrateToVersion21AutoGenerateKeywordColumn())
        return FailedMigrationTo(21);

      ChangeVersion(&meta_table_, 21, true);
      // FALL THROUGH

    case 21:
      if (!autofill_table_->ClearAutofillEmptyValueElements())
        return FailedMigrationTo(22);

      ChangeVersion(&meta_table_, 22, false);
      // FALL THROUGH

    case 22:
      if (!autofill_table_->MigrateToVersion23AddCardNumberEncryptedColumn())
        return FailedMigrationTo(23);

      ChangeVersion(&meta_table_, 23, false);
      // FALL THROUGH

    case 23:
      if (!autofill_table_->MigrateToVersion24CleanupOversizedStringFields())
        return FailedMigrationTo(24);

      ChangeVersion(&meta_table_, 24, false);
      // FALL THROUGH

    case 24:
      if (!keyword_table_->MigrateToVersion25AddLogoIDColumn())
        return FailedMigrationTo(25);

      ChangeVersion(&meta_table_, 25, true);
      // FALL THROUGH

    case 25:
      if (!keyword_table_->MigrateToVersion26AddCreatedByPolicyColumn())
        return FailedMigrationTo(26);

      ChangeVersion(&meta_table_, 26, true);
      // FALL THROUGH

    case 26:
      if (!autofill_table_->MigrateToVersion27UpdateLegacyCreditCards())
        return FailedMigrationTo(27);

      ChangeVersion(&meta_table_, 27, true);
      // FALL THROUGH

    case 27:
      if (!keyword_table_->MigrateToVersion28SupportsInstantColumn())
        return FailedMigrationTo(28);

      ChangeVersion(&meta_table_, 28, true);
      // FALL THROUGH

    case 28:
      if (!keyword_table_->MigrateToVersion29InstantUrlToSupportsInstant())
        return FailedMigrationTo(29);

      ChangeVersion(&meta_table_, 29, true);
      // FALL THROUGH

    case 29:
      if (!autofill_table_->MigrateToVersion30AddDateModifed())
        return FailedMigrationTo(30);

      ChangeVersion(&meta_table_, 30, true);
      // FALL THROUGH

    case 30:
      if (!autofill_table_->MigrateToVersion31AddGUIDToCreditCardsAndProfiles())
        return FailedMigrationTo(31);

      ChangeVersion(&meta_table_, 31, true);
      // FALL THROUGH

    case 31:
      if (!autofill_table_->MigrateToVersion32UpdateProfilesAndCreditCards())
        return FailedMigrationTo(32);

      ChangeVersion(&meta_table_, 32, true);
      // FALL THROUGH

    case 32:
      if (!autofill_table_->MigrateToVersion33ProfilesBasedOnFirstName())
        return FailedMigrationTo(33);

      ChangeVersion(&meta_table_, 33, true);
      // FALL THROUGH

    case 33:
      if (!autofill_table_->MigrateToVersion34ProfilesBasedOnCountryCode())
        return FailedMigrationTo(34);

      ChangeVersion(&meta_table_, 34, true);
      // FALL THROUGH

    case 34:
      if (!autofill_table_->MigrateToVersion35GreatBritainCountryCodes())
        return FailedMigrationTo(35);

      ChangeVersion(&meta_table_, 35, true);
      // FALL THROUGH

    // Combine migrations 35 and 36.  This is due to enhancements to the merge
    // step when migrating profiles.  The original migration from 35 to 36 did
    // not merge profiles with identical addresses, but the migration from 36 to
    // 37 does.  The step from 35 to 36 should only happen on the Chrome 12 dev
    // channel.  Chrome 12 beta and release users will jump from 35 to 37
    // directly getting the full benefits of the multi-valued merge as well as
    // the culling of bad data.
    case 35:
    case 36:
      if (!autofill_table_->MigrateToVersion37MergeAndCullOlderProfiles())
        return FailedMigrationTo(37);

      ChangeVersion(&meta_table_, 37, true);
      // FALL THROUGH

    // Add successive versions here.  Each should set the version number and
    // compatible version number as appropriate, then fall through to the next
    // case.

    case kCurrentVersionNumber:
      // No migration needed.
      return sql::INIT_OK;
  }
}