// Copyright (c) 2013 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.

#ifndef THIRD_PARTY_LEVELDATABASE_ENV_CHROMIUM_H_
#define THIRD_PARTY_LEVELDATABASE_ENV_CHROMIUM_H_

#include <deque>
#include <map>

#include "base/metrics/histogram.h"
#include "base/platform_file.h"
#include "base/synchronization/condition_variable.h"
#include "leveldb/env.h"
#include "leveldb/slice.h"
#include "leveldb/status.h"

namespace leveldb_env {

enum MethodID {
  kSequentialFileRead,
  kSequentialFileSkip,
  kRandomAccessFileRead,
  kWritableFileAppend,
  kWritableFileClose,
  kWritableFileFlush,
  kWritableFileSync,
  kNewSequentialFile,
  kNewRandomAccessFile,
  kNewWritableFile,
  kDeleteFile,
  kCreateDir,
  kDeleteDir,
  kGetFileSize,
  kRenameFile,
  kLockFile,
  kUnlockFile,
  kGetTestDirectory,
  kNewLogger,
  kSyncParent,
  kNumEntries
};

const char* MethodIDToString(MethodID method);

leveldb::Status MakeIOError(leveldb::Slice filename,
                            const char* message,
                            MethodID method,
                            int saved_errno);
leveldb::Status MakeIOError(leveldb::Slice filename,
                            const char* message,
                            MethodID method,
                            base::PlatformFileError error);
leveldb::Status MakeIOError(leveldb::Slice filename,
                            const char* message,
                            MethodID method);

enum ErrorParsingResult {
  METHOD_ONLY,
  METHOD_AND_PFE,
  METHOD_AND_ERRNO,
  NONE,
};

ErrorParsingResult ParseMethodAndError(const char* string,
                                       MethodID* method,
                                       int* error);
bool IndicatesDiskFull(leveldb::Status status);
std::string FilePathToString(const base::FilePath& file_path);

class UMALogger {
 public:
  virtual void RecordErrorAt(MethodID method) const = 0;
  virtual void RecordOSError(MethodID method, int saved_errno) const = 0;
  virtual void RecordOSError(MethodID method,
                             base::PlatformFileError error) const = 0;
};

class RetrierProvider {
 public:
  virtual int MaxRetryTimeMillis() const = 0;
  virtual base::HistogramBase* GetRetryTimeHistogram(MethodID method) const = 0;
  virtual base::HistogramBase* GetRecoveredFromErrorHistogram(
      MethodID method) const = 0;
};

class WriteTracker {
 public:
  virtual void DidCreateNewFile(const std::string& fname) = 0;
  virtual bool DoesDirNeedSync(const std::string& fname) = 0;
  virtual void DidSyncDir(const std::string& fname) = 0;
};

class ChromiumWritableFile : public leveldb::WritableFile {
 public:
  ChromiumWritableFile(const std::string& fname,
                       FILE* f,
                       const UMALogger* uma_logger,
                       WriteTracker* tracker);
  virtual ~ChromiumWritableFile();
  virtual leveldb::Status Append(const leveldb::Slice& data);
  virtual leveldb::Status Close();
  virtual leveldb::Status Flush();
  virtual leveldb::Status Sync();

 private:
  leveldb::Status SyncParent();

  std::string filename_;
  FILE* file_;
  const UMALogger* uma_logger_;
  WriteTracker* tracker_;
  bool is_manifest_;
  std::string parent_dir_;
};

class ChromiumEnv : public leveldb::Env,
                    public UMALogger,
                    public RetrierProvider,
                    public WriteTracker {
 public:
  ChromiumEnv();
  virtual ~ChromiumEnv();

  virtual leveldb::Status NewSequentialFile(const std::string& fname,
                                            leveldb::SequentialFile** result);
  virtual leveldb::Status NewRandomAccessFile(
      const std::string& fname,
      leveldb::RandomAccessFile** result);
  virtual leveldb::Status NewWritableFile(const std::string& fname,
                                          leveldb::WritableFile** result);
  virtual bool FileExists(const std::string& fname);
  virtual leveldb::Status GetChildren(const std::string& dir,
                                      std::vector<std::string>* result);
  virtual leveldb::Status DeleteFile(const std::string& fname);
  virtual leveldb::Status CreateDir(const std::string& name);
  virtual leveldb::Status DeleteDir(const std::string& name);
  virtual leveldb::Status GetFileSize(const std::string& fname, uint64_t* size);
  virtual leveldb::Status RenameFile(const std::string& src,
                                     const std::string& dst);
  virtual leveldb::Status LockFile(const std::string& fname,
                                   leveldb::FileLock** lock);
  virtual leveldb::Status UnlockFile(leveldb::FileLock* lock);
  virtual void Schedule(void (*function)(void*), void* arg);
  virtual void StartThread(void (*function)(void* arg), void* arg);
  virtual leveldb::Status GetTestDirectory(std::string* path);
  virtual leveldb::Status NewLogger(const std::string& fname,
                                    leveldb::Logger** result);
  virtual uint64_t NowMicros();
  virtual void SleepForMicroseconds(int micros);

 protected:
  virtual void DidCreateNewFile(const std::string& fname);
  virtual bool DoesDirNeedSync(const std::string& fname);
  virtual void DidSyncDir(const std::string& fname);

  std::string name_;

 private:
  std::map<std::string, bool> needs_sync_map_;
  base::Lock map_lock_;

  const int kMaxRetryTimeMillis;
  // BGThread() is the body of the background thread
  void BGThread();
  static void BGThreadWrapper(void* arg) {
    reinterpret_cast<ChromiumEnv*>(arg)->BGThread();
  }

  virtual void RecordErrorAt(MethodID method) const;
  virtual void RecordOSError(MethodID method, int saved_errno) const;
  virtual void RecordOSError(MethodID method,
                             base::PlatformFileError error) const;
  void RecordOpenFilesLimit(const std::string& type);
  void RecordLockFileAncestors(int num_missing_ancestors) const;
  base::HistogramBase* GetOSErrorHistogram(MethodID method, int limit) const;
  base::HistogramBase* GetMethodIOErrorHistogram() const;
  base::HistogramBase* GetMaxFDHistogram(const std::string& type) const;
  base::HistogramBase* GetLockFileAncestorHistogram() const;

  // RetrierProvider implementation.
  virtual int MaxRetryTimeMillis() const { return kMaxRetryTimeMillis; }
  virtual base::HistogramBase* GetRetryTimeHistogram(MethodID method) const;
  virtual base::HistogramBase* GetRecoveredFromErrorHistogram(
      MethodID method) const;

  base::FilePath test_directory_;

  ::base::Lock mu_;
  ::base::ConditionVariable bgsignal_;
  bool started_bgthread_;

  // Entry per Schedule() call
  struct BGItem {
    void* arg;
    void (*function)(void*);
  };
  typedef std::deque<BGItem> BGQueue;
  BGQueue queue_;
};

}  // namespace leveldb_env

#endif
