// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/predictors/autocomplete_action_predictor_table.h"

#include <cstddef>
#include <utility>

#include "base/check_op.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/uuid.h"
#include "content/public/browser/browser_thread.h"
#include "sql/statement.h"
#include "sql/transaction.h"

namespace {

// TODO(shishir): Rename the table for consistency.
#define AUTOCOMPLETE_PREDICTOR_TABLE_NAME "network_action_predictor"

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

void BindRowToStatement(
    const predictors::AutocompleteActionPredictorTable::Row& row,
    sql::Statement* statement) {
  DCHECK(base::Uuid::ParseCaseInsensitive(row.id).is_valid());
  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,
    predictors::AutocompleteActionPredictorTable::Row* row) {
  if (!statement->Step())
    return false;

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

}  // namespace

namespace predictors {

AutocompleteActionPredictorTable::Row::Row()
    : number_of_hits(0),
      number_of_misses(0) {
}

AutocompleteActionPredictorTable::Row::Row(const Row::Id& id,
                                           const std::u16string& 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) {}

AutocompleteActionPredictorTable::Row::Row(const Row& row)
    : id(row.id),
      user_text(row.user_text),
      url(row.url),
      number_of_hits(row.number_of_hits),
      number_of_misses(row.number_of_misses) {
}


void AutocompleteActionPredictorTable::GetRow(const Row::Id& id, Row* row) {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  sql::Statement statement(DB()->GetCachedStatement(
      SQL_FROM_HERE,
      "SELECT * FROM " AUTOCOMPLETE_PREDICTOR_TABLE_NAME " WHERE id=?"));
  statement.BindString(0, id);

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

void AutocompleteActionPredictorTable::GetAllRows(Rows* row_buffer) {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  row_buffer->clear();

  sql::Statement statement(DB()->GetCachedStatement(
      SQL_FROM_HERE, "SELECT * FROM " AUTOCOMPLETE_PREDICTOR_TABLE_NAME));
  if (!statement.is_valid())
    return;

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

void AutocompleteActionPredictorTable::AddRow(
    const AutocompleteActionPredictorTable::Row& row) {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  AddAndUpdateRows(Rows(1, row), Rows());
}

void AutocompleteActionPredictorTable::UpdateRow(
    const AutocompleteActionPredictorTable::Row& row) {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  AddAndUpdateRows(Rows(), Rows(1, row));
}

void AutocompleteActionPredictorTable::AddAndUpdateRows(
    const Rows& rows_to_add,
    const Rows& rows_to_update) {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  sql::Transaction transaction(DB());
  if (!transaction.Begin())
    return;
  for (auto it = rows_to_add.begin(); it != rows_to_add.end(); ++it) {
    sql::Statement statement(DB()->GetCachedStatement(
        SQL_FROM_HERE, "INSERT INTO " AUTOCOMPLETE_PREDICTOR_TABLE_NAME
                       "(id, user_text, url, number_of_hits, number_of_misses) "
                       "VALUES (?,?,?,?,?)"));
    if (!statement.is_valid())
      return;

    BindRowToStatement(*it, &statement);
    if (!statement.Run())
      return;
  }
  for (auto it = rows_to_update.begin(); it != rows_to_update.end(); ++it) {
    sql::Statement statement(DB()->GetCachedStatement(
        SQL_FROM_HERE,
        "UPDATE " AUTOCOMPLETE_PREDICTOR_TABLE_NAME
        " SET id=?, user_text=?, url=?, number_of_hits=?, number_of_misses=?"
        " WHERE id=?1"));
    if (!statement.is_valid())
      return;

    BindRowToStatement(*it, &statement);
    if (!statement.Run())
      return;
    DCHECK_GT(DB()->GetLastChangeCount(), 0);
  }
  transaction.Commit();
}

void AutocompleteActionPredictorTable::DeleteRows(
    const std::vector<Row::Id>& id_list) {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  sql::Transaction transaction(DB());
  if (!transaction.Begin())
    return;
  for (auto it = id_list.begin(); it != id_list.end(); ++it) {
    sql::Statement statement(DB()->GetCachedStatement(
        SQL_FROM_HERE,
        "DELETE FROM " AUTOCOMPLETE_PREDICTOR_TABLE_NAME " WHERE id=?"));
    if (!statement.is_valid())
      return;

    statement.BindString(0, *it);
    if (!statement.Run())
      return;
  }
  transaction.Commit();
}

void AutocompleteActionPredictorTable::DeleteAllRows() {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  sql::Statement statement(DB()->GetCachedStatement(
      SQL_FROM_HERE, "DELETE FROM " AUTOCOMPLETE_PREDICTOR_TABLE_NAME));
  if (!statement.is_valid())
    return;

  statement.Run();
}

AutocompleteActionPredictorTable::AutocompleteActionPredictorTable(
    scoped_refptr<base::SequencedTaskRunner> db_task_runner)
    : sqlite_proto::TableManager(std::move(db_task_runner)) {}

AutocompleteActionPredictorTable::~AutocompleteActionPredictorTable() = default;

void AutocompleteActionPredictorTable::CreateOrClearTablesIfNecessary() {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  if (DB()->DoesTableExist(AUTOCOMPLETE_PREDICTOR_TABLE_NAME)) {
    return;
  }

  bool success = DB()->Execute("CREATE TABLE " AUTOCOMPLETE_PREDICTOR_TABLE_NAME
                               "(id TEXT PRIMARY KEY, "
                               "user_text TEXT, "
                               "url TEXT, "
                               "number_of_hits INTEGER, "
                               "number_of_misses INTEGER)");
  if (!success)
    ResetDB();
}

void AutocompleteActionPredictorTable::LogDatabaseStats()  {
  DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
  if (CantAccessDatabase())
    return;

  sql::Statement count_statement(DB()->GetUniqueStatement(
      "SELECT count(id) FROM " AUTOCOMPLETE_PREDICTOR_TABLE_NAME));
  if (!count_statement.is_valid() || !count_statement.Step())
    return;
}

}  // namespace predictors
