// 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.

#ifndef WEBKIT_BROWSER_APPCACHE_APPCACHE_DATABASE_H_
#define WEBKIT_BROWSER_APPCACHE_APPCACHE_DATABASE_H_

#include <map>
#include <set>
#include <vector>

#include "base/basictypes.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/time/time.h"
#include "url/gurl.h"
#include "webkit/browser/webkit_storage_browser_export.h"
#include "webkit/common/appcache/appcache_interfaces.h"

namespace sql {
class Connection;
class MetaTable;
class Statement;
class StatementID;
}

namespace appcache {

class WEBKIT_STORAGE_BROWSER_EXPORT AppCacheDatabase {
 public:
  struct WEBKIT_STORAGE_BROWSER_EXPORT GroupRecord {
    GroupRecord();
    ~GroupRecord();

    int64 group_id;
    GURL origin;
    GURL manifest_url;
    base::Time creation_time;
    base::Time last_access_time;
  };

  struct WEBKIT_STORAGE_BROWSER_EXPORT CacheRecord {
    CacheRecord()
        : cache_id(0), group_id(0), online_wildcard(false), cache_size(0) {}

    int64 cache_id;
    int64 group_id;
    bool online_wildcard;
    base::Time update_time;
    int64 cache_size;  // the sum of all response sizes in this cache
  };

  struct EntryRecord {
    EntryRecord() : cache_id(0), flags(0), response_id(0), response_size(0) {}

    int64 cache_id;
    GURL url;
    int flags;
    int64 response_id;
    int64 response_size;
  };

  struct WEBKIT_STORAGE_BROWSER_EXPORT NamespaceRecord {
    NamespaceRecord();
    ~NamespaceRecord();

    int64 cache_id;
    GURL origin;
    Namespace namespace_;
  };

  typedef std::vector<NamespaceRecord> NamespaceRecordVector;

  struct OnlineWhiteListRecord {
    OnlineWhiteListRecord() : cache_id(0), is_pattern(false) {}

    int64 cache_id;
    GURL namespace_url;
    bool is_pattern;
  };

  explicit AppCacheDatabase(const base::FilePath& path);
  ~AppCacheDatabase();

  void CloseConnection();
  void Disable();
  bool is_disabled() const { return is_disabled_; }

  int64 GetOriginUsage(const GURL& origin);
  bool GetAllOriginUsage(std::map<GURL, int64>* usage_map);

  bool FindOriginsWithGroups(std::set<GURL>* origins);
  bool FindLastStorageIds(
      int64* last_group_id, int64* last_cache_id, int64* last_response_id,
      int64* last_deletable_response_rowid);

  bool FindGroup(int64 group_id, GroupRecord* record);
  bool FindGroupForManifestUrl(const GURL& manifest_url, GroupRecord* record);
  bool FindGroupsForOrigin(
      const GURL& origin, std::vector<GroupRecord>* records);
  bool FindGroupForCache(int64 cache_id, GroupRecord* record);
  bool UpdateGroupLastAccessTime(
      int64 group_id, base::Time last_access_time);
  bool InsertGroup(const GroupRecord* record);
  bool DeleteGroup(int64 group_id);

  bool FindCache(int64 cache_id, CacheRecord* record);
  bool FindCacheForGroup(int64 group_id, CacheRecord* record);
  bool FindCachesForOrigin(
      const GURL& origin, std::vector<CacheRecord>* records);
  bool InsertCache(const CacheRecord* record);
  bool DeleteCache(int64 cache_id);

  bool FindEntriesForCache(
      int64 cache_id, std::vector<EntryRecord>* records);
  bool FindEntriesForUrl(
      const GURL& url, std::vector<EntryRecord>* records);
  bool FindEntry(int64 cache_id, const GURL& url, EntryRecord* record);
  bool InsertEntry(const EntryRecord* record);
  bool InsertEntryRecords(
      const std::vector<EntryRecord>& records);
  bool DeleteEntriesForCache(int64 cache_id);
  bool AddEntryFlags(const GURL& entry_url, int64 cache_id,
                     int additional_flags);
  bool FindResponseIdsForCacheAsVector(
      int64 cache_id, std::vector<int64>* response_ids) {
    return FindResponseIdsForCacheHelper(cache_id, response_ids, NULL);
  }
  bool FindResponseIdsForCacheAsSet(
      int64 cache_id, std::set<int64>* response_ids) {
    return FindResponseIdsForCacheHelper(cache_id, NULL, response_ids);
  }

  bool FindNamespacesForOrigin(
      const GURL& origin,
      NamespaceRecordVector* intercepts,
      NamespaceRecordVector* fallbacks);
  bool FindNamespacesForCache(
      int64 cache_id,
      NamespaceRecordVector* intercepts,
      std::vector<NamespaceRecord>* fallbacks);
  bool InsertNamespaceRecords(
      const NamespaceRecordVector& records);
  bool InsertNamespace(const NamespaceRecord* record);
  bool DeleteNamespacesForCache(int64 cache_id);

  bool FindOnlineWhiteListForCache(
      int64 cache_id, std::vector<OnlineWhiteListRecord>* records);
  bool InsertOnlineWhiteList(const OnlineWhiteListRecord* record);
  bool InsertOnlineWhiteListRecords(
      const std::vector<OnlineWhiteListRecord>& records);
  bool DeleteOnlineWhiteListForCache(int64 cache_id);

  bool GetDeletableResponseIds(std::vector<int64>* response_ids,
                               int64 max_rowid, int limit);
  bool InsertDeletableResponseIds(const std::vector<int64>& response_ids);
  bool DeleteDeletableResponseIds(const std::vector<int64>& response_ids);

  // So our callers can wrap operations in transactions.
  sql::Connection* db_connection() {
    LazyOpen(true);
    return db_.get();
  }

 private:
  bool RunCachedStatementWithIds(
      const sql::StatementID& statement_id, const char* sql,
      const std::vector<int64>& ids);
  bool RunUniqueStatementWithInt64Result(const char* sql, int64* result);

  bool FindResponseIdsForCacheHelper(
      int64 cache_id, std::vector<int64>* ids_vector,
      std::set<int64>* ids_set);

  // Record retrieval helpers
  void ReadGroupRecord(const sql::Statement& statement, GroupRecord* record);
  void ReadCacheRecord(const sql::Statement& statement, CacheRecord* record);
  void ReadEntryRecord(const sql::Statement& statement, EntryRecord* record);
  void ReadNamespaceRecords(
      sql::Statement* statement,
      NamespaceRecordVector* intercepts,
      NamespaceRecordVector* fallbacks);
  void ReadNamespaceRecord(
      const sql::Statement* statement, NamespaceRecord* record);
  void ReadOnlineWhiteListRecord(
      const sql::Statement& statement, OnlineWhiteListRecord* record);

  // Database creation
  bool LazyOpen(bool create_if_needed);
  bool EnsureDatabaseVersion();
  bool CreateSchema();
  bool UpgradeSchema();

  void ResetConnectionAndTables();

  // Deletes the existing database file and the entire directory containing
  // the database file including the disk cache in which response headers
  // and bodies are stored, and then creates a new database file.
  bool DeleteExistingAndCreateNewDatabase();

  base::FilePath db_file_path_;
  scoped_ptr<sql::Connection> db_;
  scoped_ptr<sql::MetaTable> meta_table_;
  bool is_disabled_;
  bool is_recreating_;

  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, CacheRecords);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, EntryRecords);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, QuickIntegrityCheck);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, NamespaceRecords);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, GroupRecords);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, LazyOpen);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, ExperimentalFlags);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, OnlineWhiteListRecords);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, ReCreate);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, DeletableResponseIds);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, OriginUsage);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, UpgradeSchema3to5);
  FRIEND_TEST_ALL_PREFIXES(AppCacheDatabaseTest, UpgradeSchema4to5);

  DISALLOW_COPY_AND_ASSIGN(AppCacheDatabase);
};

}  // namespace appcache

#endif  // WEBKIT_BROWSER_APPCACHE_APPCACHE_DATABASE_H_