// 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/autocomplete/network_action_predictor_database.h"

#include "base/bind.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/guid.h"
#include "content/public/browser/browser_thread.h"
#include "sql/statement.h"

namespace {

const char kNetworkActionPredictorTableName[] = "network_action_predictor";
const FilePath::CharType kNetworkActionPredictorDatabaseName[] =
    FILE_PATH_LITERAL("Network Action Predictor");

// The maximum length allowed for strings in the database.
const size_t kMaxDataLength = 2048;

void BindRowToStatement(const NetworkActionPredictorDatabase::Row& row,
                        sql::Statement* statement) {
  DCHECK(guid::IsValidGUID(row.id));
  statement->BindString(0, row.id);
  statement->BindString16(1, row.user_text.substr(0, kMaxDataLength));
  statement->BindString(2, row.url.spec().substr(0, kMaxDataLength));
  statement->BindInt(3, row.number_of_hits);
  statement->BindInt(4, row.number_of_misses);
}

bool StepAndInitializeRow(sql::Statement* statement,
                          NetworkActionPredictorDatabase::Row* row) {
  if (!statement->Step())
    return false;

  row->id = statement->ColumnString(0);
  row->user_text = statement->ColumnString16(1);
  row->url = GURL(statement->ColumnString(2));
  row->number_of_hits = statement->ColumnInt(3);
  row->number_of_misses = statement->ColumnInt(4);
  return true;
}

void LogDatabaseStats(const FilePath& db_path, sql::Connection* db) {
  int64 db_size;
  bool success = file_util::GetFileSize(db_path, &db_size);
  DCHECK(success) << "Failed to get file size for " << db_path.value();
  UMA_HISTOGRAM_MEMORY_KB("NetworkActionPredictor.DatabaseSizeKB",
                          static_cast<int>(db_size / 1024));

  sql::Statement count_statement(db->GetUniqueStatement(
      base::StringPrintf("SELECT count(id) FROM %s",
                         kNetworkActionPredictorTableName).c_str()));
  if (!count_statement || !count_statement.Step())
    return;
  UMA_HISTOGRAM_COUNTS("NetworkActionPredictor.DatabaseRowCount",
                       count_statement.ColumnInt(0));
}

}

NetworkActionPredictorDatabase::Row::Row() {
}

NetworkActionPredictorDatabase::Row::Row(const Row::Id& id,
                                         const string16& user_text,
                                         const GURL& url,
                                         int number_of_hits,
                                         int number_of_misses)
    : id(id),
      user_text(user_text),
      url(url),
      number_of_hits(number_of_hits),
      number_of_misses(number_of_misses) {
}

NetworkActionPredictorDatabase::NetworkActionPredictorDatabase(Profile* profile)
    : db_path_(profile->GetPath().Append(kNetworkActionPredictorDatabaseName)) {
}

NetworkActionPredictorDatabase::~NetworkActionPredictorDatabase() {
}

void NetworkActionPredictorDatabase::Initialize() {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  db_.set_exclusive_locking();
  if (!db_.Open(db_path_)) {
    canceled_.Set();
    return;
  }

  if (!db_.DoesTableExist(kNetworkActionPredictorTableName))
    CreateTable();

  LogDatabaseStats(db_path_, &db_);
}

void NetworkActionPredictorDatabase::GetRow(const Row::Id& id, Row* row) {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      base::StringPrintf(
          "SELECT * FROM %s WHERE id=?",
          kNetworkActionPredictorTableName).c_str()));
  DCHECK(statement);

  statement.BindString(0, id);

  bool success = StepAndInitializeRow(&statement, row);
  DCHECK(success) << "Failed to get row " << id << " from "
                  << kNetworkActionPredictorTableName;
}

void NetworkActionPredictorDatabase::GetAllRows(
    std::vector<NetworkActionPredictorDatabase::Row>* row_buffer) {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));
  CHECK(row_buffer);
  row_buffer->clear();

  if (canceled_.IsSet())
    return;

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      base::StringPrintf(
          "SELECT * FROM %s", kNetworkActionPredictorTableName).c_str()));
  DCHECK(statement);

  Row row;
  while (StepAndInitializeRow(&statement, &row))
    row_buffer->push_back(row);
}

void NetworkActionPredictorDatabase::AddRow(
    const NetworkActionPredictorDatabase::Row& row) {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      base::StringPrintf(
          "INSERT INTO %s "
          "(id, user_text, url, number_of_hits, number_of_misses) "
          "VALUES (?,?,?,?,?)", kNetworkActionPredictorTableName).c_str()));
  DCHECK(statement);

  BindRowToStatement(row, &statement);

  bool success = statement.Run();
  DCHECK(success) << "Failed to insert row " << row.id << " into "
                  << kNetworkActionPredictorTableName;
}

void NetworkActionPredictorDatabase::UpdateRow(
    const NetworkActionPredictorDatabase::Row& row) {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      base::StringPrintf(
          "UPDATE %s "
          "SET id=?, user_text=?, url=?, number_of_hits=?, number_of_misses=? "
          "WHERE id=?1", kNetworkActionPredictorTableName).c_str()));
  DCHECK(statement);

  BindRowToStatement(row, &statement);

  statement.Run();
  DCHECK_GT(db_.GetLastChangeCount(), 0);
}

void NetworkActionPredictorDatabase::DeleteRow(const Row::Id& id) {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  DeleteRows(std::vector<Row::Id>(1, id));
}

void NetworkActionPredictorDatabase::DeleteRows(
    const std::vector<Row::Id>& id_list) {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  sql::Statement statement(db_.GetUniqueStatement(base::StringPrintf(
          "DELETE FROM %s WHERE id=?",
          kNetworkActionPredictorTableName).c_str()));
  DCHECK(statement);

  db_.BeginTransaction();
  for (std::vector<Row::Id>::const_iterator it = id_list.begin();
       it != id_list.end(); ++it) {
    statement.BindString(0, *it);
    statement.Run();
    statement.Reset();
  }
  db_.CommitTransaction();
}

void NetworkActionPredictorDatabase::DeleteAllRows() {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      base::StringPrintf("DELETE FROM %s",
                         kNetworkActionPredictorTableName).c_str()));
  DCHECK(statement);

  statement.Run();
}

void NetworkActionPredictorDatabase::BeginTransaction() {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  db_.BeginTransaction();
}

void NetworkActionPredictorDatabase::CommitTransaction() {
  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::DB));

  if (canceled_.IsSet())
    return;

  db_.CommitTransaction();
}

void NetworkActionPredictorDatabase::OnPredictorDestroyed() {
  canceled_.Set();
}

void NetworkActionPredictorDatabase::CreateTable() {
  bool success = db_.Execute(base::StringPrintf(
      "CREATE TABLE %s ( "
      "id TEXT PRIMARY KEY, "
      "user_text TEXT, "
      "url TEXT, "
      "number_of_hits INTEGER, "
      "number_of_misses INTEGER)", kNetworkActionPredictorTableName).c_str());
  DCHECK(success) << "Failed to create " << kNetworkActionPredictorTableName
                  << " table.";
}