// Copyright (c) 2012 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/history/android/android_provider_backend.h"

#include "base/i18n/case_conversion.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/favicon/favicon_changed_details.h"
#include "chrome/browser/history/android/android_time.h"
#include "chrome/browser/history/android/android_urls_sql_handler.h"
#include "chrome/browser/history/android/bookmark_model_sql_handler.h"
#include "chrome/browser/history/android/favicon_sql_handler.h"
#include "chrome/browser/history/android/urls_sql_handler.h"
#include "chrome/browser/history/android/visit_sql_handler.h"
#include "chrome/browser/history/history_backend.h"
#include "chrome/browser/history/history_database.h"
#include "chrome/browser/history/thumbnail_database.h"
#include "components/bookmarks/core/browser/bookmark_service.h"
#include "content/public/common/page_transition_types.h"
#include "sql/connection.h"


namespace history {


// Helpers --------------------------------------------------------------------

namespace {

const char kVirtualHistoryAndBookmarkTable[] =
    "SELECT android_urls.id AS _id, "
        "android_cache_db.bookmark_cache.created_time AS created, "
        "urls.title AS title, android_urls.raw_url AS url, "
        "urls.visit_count AS visits, "
        "android_cache_db.bookmark_cache.last_visit_time AS date, "
        "android_cache_db.bookmark_cache.bookmark AS bookmark, "
        "android_cache_db.bookmark_cache.favicon_id AS favicon, "
        "urls.id AS url_id, urls.url AS urls_url, "
    // TODO (michaelbai) : Remove folder column once we remove it from Android
    // framework.
    // Android framework assumes 'folder' column exist in the table, the row is
    // the bookmark once folder is 0, though it is not part of public API, it
    // has to be added and set as 0 when the row is bookmark.
        "(CASE WHEN android_cache_db.bookmark_cache.bookmark IS 0 "
        "THEN 1 ELSE 0 END) as folder "
    "FROM (android_urls JOIN urls on (android_urls.url_id = urls.id) "
        "LEFT JOIN android_cache_db.bookmark_cache "
        "on (android_urls.url_id = android_cache_db.bookmark_cache.url_id))";

const char kURLUpdateClause[] =
    "SELECT urls.id, urls.last_visit_time, created_time, urls.url "
    "FROM urls LEFT JOIN "
        "(SELECT url as visit_url, min(visit_time) as created_time"
        " FROM visits GROUP BY url) ON (visit_url = urls.id) ";

const char kSearchTermUpdateClause[] =
    "SELECT keyword_search_terms.term, max(urls.last_visit_time) "
    "FROM keyword_search_terms JOIN urls ON "
        "(keyword_search_terms.url_id = urls.id) "
    "GROUP BY keyword_search_terms.term";

void BindStatement(const std::vector<base::string16>& selection_args,
                   sql::Statement* statement,
                   int* col_index) {
  for (std::vector<base::string16>::const_iterator i = selection_args.begin();
       i != selection_args.end(); ++i) {
    // Using the same method as Android, binding all argument as String.
    statement->BindString16(*col_index, *i);
    ++(*col_index);
  }
}

bool IsHistoryAndBookmarkRowValid(const HistoryAndBookmarkRow& row) {
  // The caller should make sure both/neither Raw URL and/nor URL should be set.
  DCHECK(row.is_value_set_explicitly(HistoryAndBookmarkRow::RAW_URL) ==
         row.is_value_set_explicitly(HistoryAndBookmarkRow::URL));

  // The following cases are checked:
  // a. Last visit time or created time is large than now.
  // b. Last visit time is less than created time.
  // c. Created time and last visit time is different, but visit count is less
  //    than 2.
  // d. The difference between created and last visit time is less than
  //    visit_count.
  // e. Visit count is 0 or 1 and both last visit time and created time are set
  //    explicitly, but the time is different or created time is not UnixEpoch.
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME) &&
      row.last_visit_time() > base::Time::Now())
    return false;

  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) &&
      row.created() > base::Time::Now())
    return false;

  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME) &&
      row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED)) {
    if (row.created() > row.last_visit_time())
      return false;

    if (row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT) &&
        row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED) &&
        row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) {
      if (row.created() != row.last_visit_time() &&
          row.created() != base::Time::UnixEpoch() &&
          (row.visit_count() == 0 || row.visit_count() == 1))
        return false;

      if (row.last_visit_time().ToInternalValue() -
          row.created().ToInternalValue() < row.visit_count())
        return false;
    }
  }
  return true;
}

}  // namespace


// AndroidProviderBackend::HistoryNotifications -------------------------------

AndroidProviderBackend::HistoryNotifications::HistoryNotifications() {
}

AndroidProviderBackend::HistoryNotifications::~HistoryNotifications() {
}

void AndroidProviderBackend::HistoryNotifications::PushBack(
    int type,
    scoped_ptr<HistoryDetails> detail) {
  DCHECK_EQ(types_.size(), details_.size());
  types_.push_back(type);
  details_.push_back(detail.release());
}

int AndroidProviderBackend::HistoryNotifications::PopBackType() {
  DCHECK(!empty());
  int type = types_.back();
  types_.pop_back();
  return type;
}

scoped_ptr<HistoryDetails>
    AndroidProviderBackend::HistoryNotifications::PopBackDetails() {
  DCHECK(!details_.empty());
  scoped_ptr<HistoryDetails> detail(details_.back());
  details_.weak_erase(details_.end() - 1);
  return detail.Pass();
}


// AndroidProviderBackend::ScopedTransaction ----------------------------------

AndroidProviderBackend::ScopedTransaction::ScopedTransaction(
    HistoryDatabase* history_db,
    ThumbnailDatabase* thumbnail_db)
    : history_db_(history_db),
      thumbnail_db_(thumbnail_db),
      committed_(false),
      history_transaction_nesting_(history_db_->transaction_nesting()),
      thumbnail_transaction_nesting_(
          thumbnail_db_ ? thumbnail_db_->transaction_nesting() : 0) {
  // Commit all existing transactions since the AndroidProviderBackend's
  // transaction is very like to be rolled back when compared with the others.
  // The existing transactions have been scheduled to commit by
  // ScheduleCommit in HistoryBackend and the same number of transaction
  // will be created after this scoped transaction ends, there should have no
  // issue to directly commit all transactions here.
  int count = history_transaction_nesting_;
  while (count--)
    history_db_->CommitTransaction();
  history_db_->BeginTransaction();

  if (thumbnail_db_) {
    count = thumbnail_transaction_nesting_;
    while (count--)
      thumbnail_db_->CommitTransaction();
    thumbnail_db_->BeginTransaction();
  }
}

AndroidProviderBackend::ScopedTransaction::~ScopedTransaction() {
  if (!committed_) {
    history_db_->RollbackTransaction();
    if (thumbnail_db_)
      thumbnail_db_->RollbackTransaction();
  }
  // There is no transaction now.
  DCHECK_EQ(0, history_db_->transaction_nesting());
  DCHECK(!thumbnail_db_ || 0 == thumbnail_db_->transaction_nesting());

  int count = history_transaction_nesting_;
  while (count--)
    history_db_->BeginTransaction();

  if (thumbnail_db_) {
    count = thumbnail_transaction_nesting_;
    while (count--)
      thumbnail_db_->BeginTransaction();
  }
}

void AndroidProviderBackend::ScopedTransaction::Commit() {
  DCHECK(!committed_);
  history_db_->CommitTransaction();
  if (thumbnail_db_)
    thumbnail_db_->CommitTransaction();
  committed_ = true;
}


// AndroidProviderBackend -----------------------------------------------------

AndroidProviderBackend::AndroidProviderBackend(
    const base::FilePath& db_name,
    HistoryDatabase* history_db,
    ThumbnailDatabase* thumbnail_db,
    BookmarkService* bookmark_service,
    HistoryBackend::Delegate* delegate)
    : android_cache_db_filename_(db_name),
      db_(&history_db->GetDB()),
      history_db_(history_db),
      thumbnail_db_(thumbnail_db),
      bookmark_service_(bookmark_service),
      initialized_(false),
      delegate_(delegate) {
  DCHECK(delegate_);
}

AndroidProviderBackend::~AndroidProviderBackend() {
}

AndroidStatement* AndroidProviderBackend::QueryHistoryAndBookmarks(
    const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    const std::string& sort_order) {
  if (projections.empty())
    return NULL;

  ScopedTransaction transaction(history_db_, thumbnail_db_);

  if (!EnsureInitializedAndUpdated())
    return NULL;

  transaction.Commit();

  return QueryHistoryAndBookmarksInternal(projections, selection,
                                          selection_args, sort_order);
}

bool AndroidProviderBackend::UpdateHistoryAndBookmarks(
    const HistoryAndBookmarkRow& row,
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    int* updated_count) {
  HistoryNotifications notifications;

  ScopedTransaction transaction(history_db_, thumbnail_db_);

  if (!UpdateHistoryAndBookmarks(row, selection, selection_args, updated_count,
                                 &notifications))
    return false;

  transaction.Commit();
  BroadcastNotifications(&notifications);
  return true;
}

AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark(
    const HistoryAndBookmarkRow& values) {
  HistoryNotifications notifications;

  ScopedTransaction transaction(history_db_, thumbnail_db_);

  AndroidURLID id = InsertHistoryAndBookmark(values, true, &notifications);
  if (!id)
    return 0;

  transaction.Commit();
  BroadcastNotifications(&notifications);
  return id;
}

bool AndroidProviderBackend::DeleteHistoryAndBookmarks(
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    int* deleted_count) {
  HistoryNotifications notifications;

  ScopedTransaction transaction(history_db_, thumbnail_db_);

  if (!DeleteHistoryAndBookmarks(selection, selection_args, deleted_count,
                                 &notifications))
    return false;

  transaction.Commit();
  BroadcastNotifications(&notifications);
  return true;
}

bool AndroidProviderBackend::DeleteHistory(
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    int* deleted_count) {
  HistoryNotifications notifications;

  ScopedTransaction transaction(history_db_, thumbnail_db_);

  if (!DeleteHistory(selection, selection_args, deleted_count, &notifications))
    return false;

  transaction.Commit();
  BroadcastNotifications(&notifications);
  return true;
}

bool AndroidProviderBackend::UpdateHistoryAndBookmarks(
    const HistoryAndBookmarkRow& row,
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    int* updated_count,
    HistoryNotifications* notifications) {
  if (!IsHistoryAndBookmarkRowValid(row))
    return false;

  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::ID))
    return false;

  if (!EnsureInitializedAndUpdated())
    return false;

  TableIDRows ids_set;
  if (!GetSelectedURLs(selection, selection_args, &ids_set))
    return false;

  if (ids_set.empty()) {
    *updated_count = 0;
    return true;
  }

  // URL can not be updated, we simulate the update.
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::URL)) {
    // Only one row's URL can be updated at a time as we can't have multiple
    // rows have the same URL.
    if (ids_set.size() != 1)
      return false;

    HistoryAndBookmarkRow new_row = row;
    if (!SimulateUpdateURL(new_row, ids_set, notifications))
      return false;
    *updated_count = 1;
    return true;
  }

  for (std::vector<SQLHandler*>::iterator i =
       sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
    if ((*i)->HasColumnIn(row)) {
      if (!(*i)->Update(row, ids_set))
        return false;
    }
  }
  *updated_count = ids_set.size();

  scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
  scoped_ptr<FaviconChangedDetails> favicon(new FaviconChangedDetails);

  for (TableIDRows::const_iterator i = ids_set.begin(); i != ids_set.end();
       ++i) {
    if (row.is_value_set_explicitly(HistoryAndBookmarkRow::TITLE) ||
        row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT) ||
        row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME)) {
      URLRow url_row;
      if (!history_db_->GetURLRow(i->url_id, &url_row))
        return false;
      modified->changed_urls.push_back(url_row);
    }
    if (thumbnail_db_ &&
        row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON))
      favicon->urls.insert(i->url);
  }

  if (!modified->changed_urls.empty()) {
    notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
                            modified.PassAs<HistoryDetails>());
  }

  if (!favicon->urls.empty()) {
    notifications->PushBack(chrome::NOTIFICATION_FAVICON_CHANGED,
                            favicon.PassAs<HistoryDetails>());
  }

  return true;
}

AndroidURLID AndroidProviderBackend::InsertHistoryAndBookmark(
    const HistoryAndBookmarkRow& values,
    bool ensure_initialized_and_updated,
    HistoryNotifications* notifications) {
  if (!IsHistoryAndBookmarkRowValid(values))
    return false;

  if (ensure_initialized_and_updated && !EnsureInitializedAndUpdated())
    return 0;

  DCHECK(values.is_value_set_explicitly(HistoryAndBookmarkRow::URL));
  // Make a copy of values as we need change it during insert.
  HistoryAndBookmarkRow row = values;
  for (std::vector<SQLHandler*>::iterator i =
       sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
    if (!(*i)->Insert(&row))
      return 0;
  }

  URLRow url_row;
  if (!history_db_->GetURLRow(row.url_id(), &url_row))
    return false;

  scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
  if (!modified.get())
    return false;
  modified->changed_urls.push_back(url_row);

  scoped_ptr<FaviconChangedDetails> favicon;
  // No favicon should be changed if the thumbnail_db_ is not available.
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON) &&
      row.favicon_valid() && thumbnail_db_) {
    favicon.reset(new FaviconChangedDetails);
    if (!favicon.get())
      return false;
    favicon->urls.insert(url_row.url());
  }

  notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
                          modified.PassAs<HistoryDetails>());
  if (favicon) {
    notifications->PushBack(chrome::NOTIFICATION_FAVICON_CHANGED,
                            favicon.PassAs<HistoryDetails>());
  }

  return row.id();
}

bool AndroidProviderBackend::DeleteHistoryAndBookmarks(
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    int * deleted_count,
    HistoryNotifications* notifications) {
  if (!EnsureInitializedAndUpdated())
    return false;

  TableIDRows ids_set;
  if (!GetSelectedURLs(selection, selection_args, &ids_set))
    return false;

  if (ids_set.empty()) {
    *deleted_count = 0;
    return true;
  }

  if (!DeleteHistoryInternal(ids_set, true, notifications))
    return false;

  *deleted_count = ids_set.size();

  return true;
}

bool AndroidProviderBackend::DeleteHistory(
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    int* deleted_count,
    HistoryNotifications* notifications) {
  if (!EnsureInitializedAndUpdated())
    return false;

  TableIDRows ids_set;
  if (!GetSelectedURLs(selection, selection_args, &ids_set))
    return false;

  if (ids_set.empty()) {
    *deleted_count = 0;
    return true;
  }

  *deleted_count = ids_set.size();

  // Get the bookmarked rows.
  std::vector<HistoryAndBookmarkRow> bookmarks;
  for (TableIDRows::const_iterator i = ids_set.begin(); i != ids_set.end();
       ++i) {
    if (i->bookmarked) {
      AndroidURLRow android_url_row;
      if (!history_db_->GetAndroidURLRow(i->url_id, &android_url_row))
        return false;
      HistoryAndBookmarkRow row;
      row.set_raw_url(android_url_row.raw_url);
      row.set_url(i->url);
      // Set the visit time to the UnixEpoch since that's when the Android
      // system time starts. The Android have a CTS testcase for this.
      row.set_last_visit_time(base::Time::UnixEpoch());
      row.set_visit_count(0);
      // We don't want to change the bookmark model, so set_is_bookmark() is
      // not called.
      bookmarks.push_back(row);
    }
  }

  // Don't delete the bookmark from bookmark model when deleting the history.
  if (!DeleteHistoryInternal(ids_set, false, notifications))
    return false;

  for (std::vector<HistoryAndBookmarkRow>::const_iterator i = bookmarks.begin();
       i != bookmarks.end(); ++i) {
    // Don't update the tables, otherwise, the bookmarks will be added to
    // database during UpdateBookmark(), then the insertion will fail.
    // We can't rely on UpdateBookmark() to insert the bookmarks into history
    // database as the raw_url will be lost.
    if (!InsertHistoryAndBookmark(*i, false, notifications))
      return false;
  }
  return true;
}

AndroidStatement* AndroidProviderBackend::QuerySearchTerms(
    const std::vector<SearchRow::ColumnID>& projections,
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    const std::string& sort_order) {
  if (projections.empty())
    return NULL;

  if (!EnsureInitializedAndUpdated())
    return NULL;

  std::string sql;
  sql.append("SELECT ");
  AppendSearchResultColumn(projections, &sql);
  sql.append(" FROM android_cache_db.search_terms ");

  if (!selection.empty()) {
    sql.append(" WHERE ");
    sql.append(selection);
  }

  if (!sort_order.empty()) {
    sql.append(" ORDER BY ");
    sql.append(sort_order);
  }

  scoped_ptr<sql::Statement> statement(new sql::Statement(
      db_->GetUniqueStatement(sql.c_str())));
  int count = 0;
  BindStatement(selection_args, statement.get(), &count);
  if (!statement->is_valid()) {
    LOG(ERROR) << db_->GetErrorMessage();
    return NULL;
  }
  sql::Statement* result = statement.release();
  return new AndroidStatement(result, -1);
}

bool AndroidProviderBackend::UpdateSearchTerms(
    const SearchRow& row,
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    int* update_count) {
  if (!EnsureInitializedAndUpdated())
    return false;

  SearchTerms search_terms;
  if (!GetSelectedSearchTerms(selection, selection_args, &search_terms))
    return false;

  // We can not update search term if multiple row selected.
  if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM) &&
      search_terms.size() > 1)
    return false;

  *update_count = search_terms.size();

  if (search_terms.empty())
    return true;

  if (row.is_value_set_explicitly(SearchRow::SEARCH_TERM)) {
    SearchTermRow search_term_row;
    SearchRow search_row = row;
    if (!history_db_->GetSearchTerm(search_terms[0], &search_term_row))
      return false;

    search_term_row.term = search_row.search_term();
    if (!search_row.is_value_set_explicitly(SearchRow::SEARCH_TIME))
      search_row.set_search_time(search_term_row.last_visit_time);
    else
      search_term_row.last_visit_time = search_row.search_time();

    // Delete the original search term.
    if (!history_db_->DeleteKeywordSearchTerm(search_terms[0]))
      return false;

    // Add the new one.
    if (!AddSearchTerm(search_row))
      return false;

    // Update the cache table so the id will not be changed.
    if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row))
      return false;

     return true;
  }

  for (SearchTerms::const_iterator i = search_terms.begin();
       i != search_terms.end(); ++i) {
    SearchTermRow search_term_row;
    if (!history_db_->GetSearchTerm(*i, &search_term_row))
      return false;

    // Check whether the given search time less than the existing one.
    if (search_term_row.last_visit_time > row.search_time())
      return false;

    std::vector<KeywordSearchTermRow> search_term_rows;
    if (!history_db_->GetKeywordSearchTermRows(*i, &search_term_rows) ||
        search_term_rows.empty())
      return false;

    // Actually only search_time update. As there might multiple URLs
    // asocciated with the keyword, Just update the first one's last_visit_time.
    URLRow url_row;
    if (!history_db_->GetURLRow(search_term_rows[0].url_id, &url_row))
      return false;

    HistoryAndBookmarkRow bookmark_row;
    bookmark_row.set_last_visit_time(row.search_time());
    TableIDRow table_id_row;
    table_id_row.url_id = url_row.id();
    TableIDRows table_id_rows;
    table_id_rows.push_back(table_id_row);
    if (!urls_handler_->Update(bookmark_row, table_id_rows))
      return false;

    if (!visit_handler_->Update(bookmark_row, table_id_rows))
      return false;
  }
  return true;
}

SearchTermID AndroidProviderBackend::InsertSearchTerm(
    const SearchRow& values) {
  if (!EnsureInitializedAndUpdated())
    return 0;

  if (!AddSearchTerm(values))
    return 0;

  SearchTermID id = history_db_->GetSearchTerm(values.search_term(), NULL);
  if (!id)
    // Note the passed in Time() will be changed in UpdateSearchTermTable().
    id = history_db_->AddSearchTerm(values.search_term(), base::Time());
  return id;
}

bool AndroidProviderBackend::DeleteSearchTerms(
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    int * deleted_count) {
  if (!EnsureInitializedAndUpdated())
    return false;

  SearchTerms rows;
  if (!GetSelectedSearchTerms(selection, selection_args, &rows))
    return false;

  *deleted_count = rows.size();
  if (rows.empty())
    return true;

  for (SearchTerms::const_iterator i = rows.begin(); i != rows.end(); ++i)
    if (!history_db_->DeleteKeywordSearchTerm(*i))
      return false;
  // We don't delete the rows in search_terms table, as once the
  // search_terms table is updated with keyword_search_terms, all
  // keyword cache not found in the keyword_search_terms will be removed.
  return true;
}

bool AndroidProviderBackend::EnsureInitializedAndUpdated() {
  if (!initialized_) {
    if (!Init())
      return false;
  }
  return UpdateTables();
}

bool AndroidProviderBackend::Init() {
  urls_handler_.reset(new UrlsSQLHandler(history_db_));
  visit_handler_.reset(new VisitSQLHandler(history_db_));
  android_urls_handler_.reset(new AndroidURLsSQLHandler(history_db_));
  if (thumbnail_db_)
    favicon_handler_.reset(new FaviconSQLHandler(thumbnail_db_));
  bookmark_model_handler_.reset(new BookmarkModelSQLHandler(history_db_));
  // The urls_handler must be pushed first, because the subsequent handlers
  // depend on its output.
  sql_handlers_.push_back(urls_handler_.get());
  sql_handlers_.push_back(visit_handler_.get());
  sql_handlers_.push_back(android_urls_handler_.get());
  if (favicon_handler_.get())
    sql_handlers_.push_back(favicon_handler_.get());
  sql_handlers_.push_back(bookmark_model_handler_.get());

  if (!history_db_->CreateAndroidURLsTable())
    return false;
  if (sql::INIT_OK != history_db_->InitAndroidCacheDatabase(
          android_cache_db_filename_))
    return false;
  initialized_ = true;
  return true;
}

bool AndroidProviderBackend::UpdateTables() {
  if (!UpdateVisitedURLs()) {
    LOG(ERROR) << "Update of the visisted URLS failed";
    return false;
  }

  if (!UpdateRemovedURLs()) {
    LOG(ERROR) << "Update of the removed URLS failed";
    return false;
  }

  if (!UpdateBookmarks()) {
    LOG(ERROR) << "Update of the bookmarks failed";
    return false;
  }

  if (!UpdateFavicon()) {
    LOG(ERROR) << "Update of the icons failed";
    return false;
  }

  if (!UpdateSearchTermTable()) {
    LOG(ERROR) << "Update of the search_terms failed";
    return false;
  }
  return true;
}

bool AndroidProviderBackend::UpdateVisitedURLs() {
  std::string sql(kURLUpdateClause);
  sql.append("WHERE urls.id NOT IN (SELECT url_id FROM android_urls)");
  sql::Statement urls_statement(db_->GetCachedStatement(SQL_FROM_HERE,
                                                        sql.c_str()));
  if (!urls_statement.is_valid()) {
    LOG(ERROR) << db_->GetErrorMessage();
    return false;
  }

  while (urls_statement.Step()) {
    if (history_db_->GetAndroidURLRow(urls_statement.ColumnInt64(0), NULL))
      continue;
    if (!history_db_->AddAndroidURLRow(urls_statement.ColumnString(3),
                                       urls_statement.ColumnInt64(0)))
      return false;
  }

  if (!history_db_->ClearAllBookmarkCache())
    return false;

  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
                                                   kURLUpdateClause));
  while (statement.Step()) {
    // The last_visit_time and the created time should be same when the visit
    // count is 0, this behavior is also required by the Android CTS.
    // The created_time could be set to the last_visit_time only when the type
    // of the 'created' column is NULL because the left join is used in query
    // and there is no row in the visit table when the visit count is 0.
    base::Time last_visit_time =
        base::Time::FromInternalValue(statement.ColumnInt64(1));
    base::Time created_time = last_visit_time;

    if (statement.ColumnType(2) != sql::COLUMN_TYPE_NULL)
      created_time = base::Time::FromInternalValue(statement.ColumnInt64(2));

    if (!history_db_->AddBookmarkCacheRow(created_time, last_visit_time,
                                          statement.ColumnInt64(0)))
      return false;
  }
  return true;
}

bool AndroidProviderBackend::UpdateRemovedURLs() {
  return history_db_->DeleteUnusedAndroidURLs();
}

bool AndroidProviderBackend::UpdateBookmarks() {
  if (bookmark_service_ == NULL) {
    LOG(ERROR) << "Bookmark service is not available";
    return false;
  }

  bookmark_service_->BlockTillLoaded();
  std::vector<BookmarkService::URLAndTitle> bookmarks;
  bookmark_service_->GetBookmarks(&bookmarks);

  if (bookmarks.empty())
    return true;

  std::vector<URLID> url_ids;
  for (std::vector<BookmarkService::URLAndTitle>::const_iterator i =
           bookmarks.begin(); i != bookmarks.end(); ++i) {
    URLID url_id = history_db_->GetRowForURL(i->url, NULL);
    if (url_id == 0) {
      URLRow url_row(i->url);
      url_row.set_title(i->title);
      // Set the visit time to the UnixEpoch since that's when the Android
      // system time starts. The Android have a CTS testcase for this.
      url_row.set_last_visit(base::Time::UnixEpoch());
      url_row.set_hidden(true);
      url_id = history_db_->AddURL(url_row);
      if (url_id == 0) {
        LOG(ERROR) << "Can not add url for the new bookmark";
        return false;
      }
      if (!history_db_->AddAndroidURLRow(i->url.spec(), url_id))
        return false;
      if (!history_db_->AddBookmarkCacheRow(base::Time::UnixEpoch(),
                                            base::Time::UnixEpoch(), url_id))
        return false;
    }
    url_ids.push_back(url_id);
  }

  return history_db_->MarkURLsAsBookmarked(url_ids);
}

bool AndroidProviderBackend::UpdateFavicon() {
  ThumbnailDatabase::IconMappingEnumerator enumerator;

  // We want the AndroidProviderBackend run without thumbnail_db_
  if (!thumbnail_db_)
    return true;

  if (!thumbnail_db_->InitIconMappingEnumerator(favicon_base::FAVICON,
                                                &enumerator))
    return false;

  IconMapping icon_mapping;
  while (enumerator.GetNextIconMapping(&icon_mapping)) {
    URLID url_id = history_db_->GetRowForURL(icon_mapping.page_url, NULL);
    if (url_id == 0) {
      LOG(ERROR) << "Can not find favicon's page url";
      continue;
    }
    history_db_->SetFaviconID(url_id, icon_mapping.icon_id);
  }
  return true;
}

bool AndroidProviderBackend::UpdateSearchTermTable() {
  sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
                                                   kSearchTermUpdateClause));
  while (statement.Step()) {
    base::string16 term = statement.ColumnString16(0);
    base::Time last_visit_time =
        base::Time::FromInternalValue(statement.ColumnInt64(1));
    SearchTermRow search_term_row;
    if (history_db_->GetSearchTerm(term, &search_term_row)) {
      if (search_term_row.last_visit_time != last_visit_time) {
        search_term_row.last_visit_time = last_visit_time;
        if (!history_db_->UpdateSearchTerm(search_term_row.id, search_term_row))
          return false;
      }
    } else {
      if (!history_db_->AddSearchTerm(term, last_visit_time))
        return false;
    }
  }
  if (!history_db_->DeleteUnusedSearchTerms())
    return false;

  return true;
}

int AndroidProviderBackend::AppendBookmarkResultColumn(
    const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
    std::string* result_column) {
  int replaced_index = -1;
  // Attach the projections
  bool first = true;
  int index = 0;
  for (std::vector<HistoryAndBookmarkRow::ColumnID>::const_iterator i =
           projections.begin(); i != projections.end(); ++i) {
    if (first)
      first = false;
    else
      result_column->append(", ");

    if (*i == HistoryAndBookmarkRow::FAVICON)
      replaced_index = index;

    result_column->append(HistoryAndBookmarkRow::GetAndroidName(*i));
    index++;
  }
  return replaced_index;
}

bool AndroidProviderBackend::GetSelectedURLs(
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    TableIDRows* rows) {
  std::string sql("SELECT url_id, urls_url, bookmark FROM (");
  sql.append(kVirtualHistoryAndBookmarkTable);
  sql.append(" )");

  if (!selection.empty()) {
    sql.append(" WHERE ");
    sql.append(selection);
  }

  sql::Statement statement(db_->GetUniqueStatement(sql.c_str()));
  int count = 0;
  BindStatement(selection_args, &statement, &count);
  if (!statement.is_valid()) {
    LOG(ERROR) << db_->GetErrorMessage();
    return false;
  }
  while (statement.Step()) {
    TableIDRow row;
    row.url_id = statement.ColumnInt64(0);
    row.url = GURL(statement.ColumnString(1));
    row.bookmarked = statement.ColumnBool(2);
    rows->push_back(row);
  }
  return true;
}

bool AndroidProviderBackend::GetSelectedSearchTerms(
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    SearchTerms* rows) {
  std::string sql("SELECT search "
                  "FROM android_cache_db.search_terms ");
  if (!selection.empty()) {
    sql.append(" WHERE ");
    sql.append(selection);
  }
  sql::Statement statement(db_->GetUniqueStatement(sql.c_str()));
  int count = 0;
  BindStatement(selection_args, &statement, &count);
  if (!statement.is_valid()) {
    LOG(ERROR) << db_->GetErrorMessage();
    return false;
  }
  while (statement.Step()) {
    rows->push_back(statement.ColumnString16(0));
  }
  return true;
}

void AndroidProviderBackend::AppendSearchResultColumn(
    const std::vector<SearchRow::ColumnID>& projections,
    std::string* result_column) {
  bool first = true;
  int index = 0;
  for (std::vector<SearchRow::ColumnID>::const_iterator i =
           projections.begin(); i != projections.end(); ++i) {
    if (first)
      first = false;
    else
      result_column->append(", ");

    result_column->append(SearchRow::GetAndroidName(*i));
    index++;
  }
}

bool AndroidProviderBackend::SimulateUpdateURL(
    const HistoryAndBookmarkRow& row,
    const TableIDRows& ids,
    HistoryNotifications* notifications) {
  DCHECK(ids.size() == 1);
  // URL can not be updated, we simulate the update by deleting the old URL
  // and inserting the new one; We do update the android_urls table as the id
  // need to keep same.

  // Find all columns value of the current URL.
  std::vector<HistoryAndBookmarkRow::ColumnID> projections;
  projections.push_back(HistoryAndBookmarkRow::LAST_VISIT_TIME);
  projections.push_back(HistoryAndBookmarkRow::CREATED);
  projections.push_back(HistoryAndBookmarkRow::VISIT_COUNT);
  projections.push_back(HistoryAndBookmarkRow::TITLE);
  projections.push_back(HistoryAndBookmarkRow::FAVICON);
  projections.push_back(HistoryAndBookmarkRow::BOOKMARK);

  std::ostringstream oss;
  oss << "url_id = " << ids[0].url_id;

  scoped_ptr<AndroidStatement> statement;
  statement.reset(QueryHistoryAndBookmarksInternal(projections, oss.str(),
      std::vector<base::string16>(), std::string()));
  if (!statement.get() || !statement->statement()->Step())
    return false;

  HistoryAndBookmarkRow new_row;
  new_row.set_last_visit_time(FromDatabaseTime(
      statement->statement()->ColumnInt64(0)));
  new_row.set_created(FromDatabaseTime(
      statement->statement()->ColumnInt64(1)));
  new_row.set_visit_count(statement->statement()->ColumnInt(2));
  new_row.set_title(statement->statement()->ColumnString16(3));

  scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails);
  scoped_ptr<FaviconChangedDetails> favicon_details(new FaviconChangedDetails);
  scoped_ptr<URLsModifiedDetails> modified(new URLsModifiedDetails);
  URLRow old_url_row;
  if (!history_db_->GetURLRow(ids[0].url_id, &old_url_row))
    return false;
  deleted_details->rows.push_back(old_url_row);

  favicon_base::FaviconID favicon_id = statement->statement()->ColumnInt64(4);
  if (favicon_id) {
    std::vector<FaviconBitmap> favicon_bitmaps;
    if (!thumbnail_db_ ||
        !thumbnail_db_->GetFaviconBitmaps(favicon_id, &favicon_bitmaps))
      return false;
   scoped_refptr<base::RefCountedMemory> bitmap_data =
       favicon_bitmaps[0].bitmap_data;
   if (bitmap_data.get() && bitmap_data->size())
      new_row.set_favicon(bitmap_data);
    favicon_details->urls.insert(old_url_row.url());
    favicon_details->urls.insert(row.url());
  }
  new_row.set_is_bookmark(statement->statement()->ColumnBool(5));

  // The SQLHandler vector is not used here because the row in android_url
  // shouldn't be deleted, we need keep the AndroidUIID unchanged, so it
  // appears update to the client.
  if (!urls_handler_->Delete(ids))
    return false;

  if (!visit_handler_->Delete(ids))
    return false;

  if (favicon_handler_ && !favicon_handler_->Delete(ids))
    return false;

  if (!bookmark_model_handler_->Delete(ids))
    return false;

  new_row.set_url(row.url());
  new_row.set_raw_url(row.raw_url());
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::LAST_VISIT_TIME))
    new_row.set_last_visit_time(row.last_visit_time());
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::CREATED))
    new_row.set_created(row.created());
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::VISIT_COUNT))
    new_row.set_visit_count(row.visit_count());
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::TITLE))
    new_row.set_title(row.title());
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::FAVICON)) {
    new_row.set_favicon(row.favicon());
    favicon_details->urls.insert(new_row.url());
  }
  if (row.is_value_set_explicitly(HistoryAndBookmarkRow::BOOKMARK))
    new_row.set_is_bookmark(row.is_bookmark());

  if (!urls_handler_->Insert(&new_row))
    return false;

  if (!visit_handler_->Insert(&new_row))
    return false;

  // Update the current row instead of inserting a new row in android urls
  // table. We need keep the AndroidUIID unchanged, so it appears update
  // to the client.
  if (!android_urls_handler_->Update(new_row, ids))
    return false;

  if (favicon_handler_ && !favicon_handler_->Insert(&new_row))
    return false;

  if (!bookmark_model_handler_->Insert(&new_row))
    return false;

  URLRow new_url_row;
  if (!history_db_->GetURLRow(new_row.url_id(), &new_url_row))
    return false;

  modified->changed_urls.push_back(new_url_row);

  notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
                          deleted_details.PassAs<HistoryDetails>());
  if (favicon_details && !favicon_details->urls.empty()) {
    notifications->PushBack(chrome::NOTIFICATION_FAVICON_CHANGED,
                            favicon_details.PassAs<HistoryDetails>());
  }
  notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
                          modified.PassAs<HistoryDetails>());

  return true;
}

AndroidStatement* AndroidProviderBackend::QueryHistoryAndBookmarksInternal(
    const std::vector<HistoryAndBookmarkRow::ColumnID>& projections,
    const std::string& selection,
    const std::vector<base::string16>& selection_args,
    const std::string& sort_order) {
  std::string sql;
  sql.append("SELECT ");
  int replaced_index = AppendBookmarkResultColumn(projections, &sql);
  sql.append(" FROM (");
  sql.append(kVirtualHistoryAndBookmarkTable);
  sql.append(")");

  if (!selection.empty()) {
    sql.append(" WHERE ");
    sql.append(selection);
  }

  if (!sort_order.empty()) {
    sql.append(" ORDER BY ");
    sql.append(sort_order);
  }

  scoped_ptr<sql::Statement> statement(new sql::Statement(
      db_->GetUniqueStatement(sql.c_str())));
  int count = 0;
  BindStatement(selection_args, statement.get(), &count);
  if (!statement->is_valid()) {
    LOG(ERROR) << db_->GetErrorMessage();
    return NULL;
  }
  sql::Statement* result = statement.release();
  return new AndroidStatement(result, replaced_index);
}

bool AndroidProviderBackend::DeleteHistoryInternal(
    const TableIDRows& urls,
    bool delete_bookmarks,
    HistoryNotifications* notifications) {
  scoped_ptr<URLsDeletedDetails> deleted_details(new URLsDeletedDetails);
  scoped_ptr<FaviconChangedDetails> favicon(new FaviconChangedDetails);
  for (TableIDRows::const_iterator i = urls.begin(); i != urls.end(); ++i) {
    URLRow url_row;
    if (!history_db_->GetURLRow(i->url_id, &url_row))
      return false;
    deleted_details->rows.push_back(url_row);
    if (thumbnail_db_ &&
        thumbnail_db_->GetIconMappingsForPageURL(url_row.url(), NULL))
      favicon->urls.insert(url_row.url());
  }

  // Only invoke Delete on the BookmarkModelHandler if we need
  // to delete bookmarks.
  for (std::vector<SQLHandler*>::iterator i =
       sql_handlers_.begin(); i != sql_handlers_.end(); ++i) {
    if ((*i) != bookmark_model_handler_.get() || delete_bookmarks)
      if (!(*i)->Delete(urls))
        return false;
  }

  notifications->PushBack(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
                          deleted_details.PassAs<HistoryDetails>());
  if (favicon && !favicon->urls.empty()) {
    notifications->PushBack(chrome::NOTIFICATION_FAVICON_CHANGED,
                            favicon.PassAs<HistoryDetails>());
  }
  return true;
}

void AndroidProviderBackend::BroadcastNotifications(
    HistoryNotifications* notifications) {
  while (!notifications->empty()) {
    delegate_->BroadcastNotifications(notifications->PopBackType(),
                                      notifications->PopBackDetails());
  }
}

bool AndroidProviderBackend::AddSearchTerm(const SearchRow& values) {
  DCHECK(values.is_value_set_explicitly(SearchRow::SEARCH_TERM));
  DCHECK(values.is_value_set_explicitly(SearchRow::TEMPLATE_URL));
  DCHECK(values.is_value_set_explicitly(SearchRow::URL));

  URLRow url_row;
  HistoryAndBookmarkRow bookmark_row;
  // Android CTS test BrowserTest.testAccessSearches allows insert the same
  // seach term multiple times, and just search time need updated.
  if (history_db_->GetRowForURL(values.url(), &url_row)) {
    // Already exist, Add a visit.
    if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME))
      bookmark_row.set_last_visit_time(values.search_time());
    else
      bookmark_row.set_visit_count(url_row.visit_count() + 1);
    TableIDRows table_id_rows;
    TableIDRow table_id_row;
    table_id_row.url = values.url();
    table_id_row.url_id = url_row.id();
    table_id_rows.push_back(table_id_row);
    if (!urls_handler_->Update(bookmark_row, table_id_rows))
      return false;
    if (!visit_handler_->Update(bookmark_row, table_id_rows))
      return false;

    if (!history_db_->GetKeywordSearchTermRow(url_row.id(), NULL))
      if (!history_db_->SetKeywordSearchTermsForURL(url_row.id(),
               values.template_url_id(), values.search_term()))
        return false;
  } else {
    bookmark_row.set_raw_url(values.url().spec());
    bookmark_row.set_url(values.url());
    if (values.is_value_set_explicitly(SearchRow::SEARCH_TIME))
      bookmark_row.set_last_visit_time(values.search_time());

    if (!urls_handler_->Insert(&bookmark_row))
      return false;

    if (!visit_handler_->Insert(&bookmark_row))
      return false;

    if (!android_urls_handler_->Insert(&bookmark_row))
      return false;

    if (!history_db_->SetKeywordSearchTermsForURL(bookmark_row.url_id(),
                          values.template_url_id(), values.search_term()))
      return false;
  }
  return true;
}

}  // namespace history