/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef DOM_FS_PARENT_RESULTSTATEMENT_H_
#define DOM_FS_PARENT_RESULTSTATEMENT_H_

#include "FileSystemParentTypes.h"
#include "mozIStorageStatement.h"
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "nsCOMPtr.h"
#include "nsString.h"

class mozIStorageConnection;

namespace mozilla::dom::fs {

using Column = uint32_t;

using ResultConnection = nsCOMPtr<mozIStorageConnection>;

/**
 * @brief ResultStatement
 * - provides error monad Result<T, E> compatible interface to the lower level
 * error code-based statement implementation in order to enable remote
 * debugging with error stack traces
 * - converts between OPFS internal data types and the generic data types of
 * the lower level implementation
 * - provides a customization point for requests aimed at the lower level
 * implementation allowing for example to remap errors or implement mocks
 */
class ResultStatement {
 public:
  using underlying_t = nsCOMPtr<mozIStorageStatement>;

  explicit ResultStatement(underlying_t aStmt) : mStmt(std::move(aStmt)) {}

  ResultStatement(const ResultStatement& aOther)
      : ResultStatement(aOther.mStmt) {}

  ResultStatement(ResultStatement&& aOther) noexcept
      : ResultStatement(std::move(aOther.mStmt)) {}

  ResultStatement& operator=(const ResultStatement& aOther) = default;

  ResultStatement& operator=(ResultStatement&& aOther) noexcept {
    mStmt = std::move(aOther.mStmt);
    return *this;
  }

  static Result<ResultStatement, QMResult> Create(
      const ResultConnection& aConnection, const nsACString& aSQLStatement);

  // XXX Consider moving all these "inline" methods into a separate file
  // called ResultStatementInlines.h. ResultStatement.h wouldn't have to then
  // include ResultExtensions.h, QuotaCommon.h and mozIStorageStatement.h
  // which are quite large and should be preferable only included from cpp
  // files or special headers like ResultStatementInlines.h. So in the end,
  // other headers would include ResultStatement.h only and other cpp files
  // would include ResultStatementInlines.h. See also IndedexDababase.h and
  // IndexedDatabaseInlines.h to see how it's done.

  inline nsresult BindEntryIdByName(const nsACString& aField,
                                    const EntryId& aValue) {
    return mStmt->BindUTF8StringAsBlobByName(aField, aValue);
  }

  inline nsresult BindFileIdByName(const nsACString& aField,
                                   const FileId& aValue) {
    return mStmt->BindUTF8StringAsBlobByName(aField, aValue.Value());
  }

  inline nsresult BindContentTypeByName(const nsACString& aField,
                                        const ContentType& aValue) {
    if (aValue.IsVoid()) {
      return mStmt->BindNullByName(aField);
    }

    return mStmt->BindUTF8StringByName(aField, aValue);
  }

  inline nsresult BindNameByName(const nsACString& aField, const Name& aValue) {
    return mStmt->BindStringAsBlobByName(aField, aValue);
  }

  inline nsresult BindPageNumberByName(const nsACString& aField,
                                       PageNumber aValue) {
    return mStmt->BindInt32ByName(aField, aValue);
  }

  inline nsresult BindUsageByName(const nsACString& aField, Usage aValue) {
    return mStmt->BindInt64ByName(aField, aValue);
  }

  inline nsresult BindBooleanByName(const nsACString& aField, bool aValue) {
    return mStmt->BindInt32ByName(aField, aValue ? 1 : 0);
  }

  inline Result<bool, QMResult> GetBooleanByColumn(Column aColumn) {
    int32_t value = 0;
    QM_TRY(QM_TO_RESULT(mStmt->GetInt32(aColumn, &value)));

    return 0 != value;
  }

  inline Result<ContentType, QMResult> GetContentTypeByColumn(Column aColumn) {
    ContentType value;
    QM_TRY(QM_TO_RESULT(mStmt->GetUTF8String(aColumn, value)));

    return value;
  }

  inline Result<EntryId, QMResult> GetEntryIdByColumn(Column aColumn) {
    EntryId value;
    QM_TRY(QM_TO_RESULT(mStmt->GetBlobAsUTF8String(aColumn, value)));

    return value;
  }

  inline Result<FileId, QMResult> GetFileIdByColumn(Column aColumn) {
    nsCString value;
    QM_TRY(QM_TO_RESULT(mStmt->GetBlobAsUTF8String(aColumn, value)));

    return FileId(std::move(value));
  }

  inline Result<Name, QMResult> GetNameByColumn(Column aColumn) {
    Name value;
    QM_TRY(QM_TO_RESULT(mStmt->GetBlobAsString(aColumn, value)));

    return value;
  }

  inline Result<Usage, QMResult> GetUsageByColumn(Column aColumn) {
    Usage value = 0;
    QM_TRY(QM_TO_RESULT(mStmt->GetInt64(aColumn, &value)));

    return value;
  }

  inline bool IsNullByColumn(Column aColumn) const {
    bool value = mStmt->IsNull(aColumn);

    return value;
  }

  inline nsresult Execute() { return mStmt->Execute(); }

  inline Result<bool, QMResult> ExecuteStep() {
    bool hasEntries = false;
    QM_TRY(QM_TO_RESULT(mStmt->ExecuteStep(&hasEntries)));

    return hasEntries;
  }

  inline Result<bool, QMResult> YesOrNoQuery() {
    bool hasEntries = false;
    QM_TRY(QM_TO_RESULT(mStmt->ExecuteStep(&hasEntries)));
    MOZ_ALWAYS_TRUE(hasEntries);
    return GetBooleanByColumn(0u);
  }

 private:
  underlying_t mStmt;
};

}  // namespace mozilla::dom::fs

#endif  // DOM_FS_PARENT_RESULTSTATEMENT_H_
