/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_idbobjectstore_h__
#define mozilla_dom_idbobjectstore_h__

#include "js/RootingAPI.h"
#include "mozilla/dom/IDBCursorBinding.h"
#include "mozilla/dom/IDBIndexBinding.h"
#include "nsAutoPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupports.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsWrapperCache.h"

struct JSClass;
class nsPIDOMWindowInner;

namespace mozilla {

class ErrorResult;

namespace dom {

class DOMStringList;
class IDBCursor;
class IDBRequest;
class IDBTransaction;
class StringOrStringSequence;
template <typename> class Sequence;

namespace indexedDB {
class Key;
class KeyPath;
class IndexUpdateInfo;
class ObjectStoreSpec;
struct StructuredCloneReadInfo;
} // namespace indexedDB

class IDBObjectStore final
  : public nsISupports
  , public nsWrapperCache
{
  typedef indexedDB::IndexUpdateInfo IndexUpdateInfo;
  typedef indexedDB::Key Key;
  typedef indexedDB::KeyPath KeyPath;
  typedef indexedDB::ObjectStoreSpec ObjectStoreSpec;
  typedef indexedDB::StructuredCloneReadInfo StructuredCloneReadInfo;

  // For AddOrPut() and DeleteInternal().
  friend class IDBCursor;

  static const JSClass sDummyPropJSClass;

  RefPtr<IDBTransaction> mTransaction;
  JS::Heap<JS::Value> mCachedKeyPath;

  // This normally points to the ObjectStoreSpec owned by the parent IDBDatabase
  // object. However, if this objectStore is part of a versionchange transaction
  // and it gets deleted then the spec is copied into mDeletedSpec and mSpec is
  // set to point at mDeletedSpec.
  const ObjectStoreSpec* mSpec;
  nsAutoPtr<ObjectStoreSpec> mDeletedSpec;

  nsTArray<RefPtr<IDBIndex>> mIndexes;
  nsTArray<RefPtr<IDBIndex>> mDeletedIndexes;

  const int64_t mId;
  bool mRooted;

public:
  struct StructuredCloneWriteInfo;

  static already_AddRefed<IDBObjectStore>
  Create(IDBTransaction* aTransaction, const ObjectStoreSpec& aSpec);

  static nsresult
  AppendIndexUpdateInfo(int64_t aIndexID,
                        const KeyPath& aKeyPath,
                        bool aUnique,
                        bool aMultiEntry,
                        const nsCString& aLocale,
                        JSContext* aCx,
                        JS::Handle<JS::Value> aObject,
                        nsTArray<IndexUpdateInfo>& aUpdateInfoArray);

  static void
  ClearCloneReadInfo(StructuredCloneReadInfo& aReadInfo);

  static bool
  DeserializeValue(JSContext* aCx,
                   StructuredCloneReadInfo& aCloneReadInfo,
                   JS::MutableHandle<JS::Value> aValue);

  static bool
  DeserializeIndexValue(JSContext* aCx,
                        StructuredCloneReadInfo& aCloneReadInfo,
                        JS::MutableHandle<JS::Value> aValue);

  static bool
  DeserializeUpgradeValue(JSContext* aCx,
                          StructuredCloneReadInfo& aCloneReadInfo,
                          JS::MutableHandle<JS::Value> aValue);

  static const JSClass*
  DummyPropClass()
  {
    return &sDummyPropJSClass;
  }

  void
  AssertIsOnOwningThread() const
#ifdef DEBUG
  ;
#else
  { }
#endif

  int64_t
  Id() const
  {
    AssertIsOnOwningThread();

    return mId;
  }

  const nsString&
  Name() const;

  bool
  AutoIncrement() const;

  const KeyPath&
  GetKeyPath() const;

  bool
  HasValidKeyPath() const;

  nsPIDOMWindowInner*
  GetParentObject() const;

  void
  GetName(nsString& aName) const
  {
    AssertIsOnOwningThread();

    aName = Name();
  }

  void
  SetName(const nsAString& aName, ErrorResult& aRv);

  void
  GetKeyPath(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
             ErrorResult& aRv);

  already_AddRefed<DOMStringList>
  IndexNames();

  IDBTransaction*
  Transaction() const
  {
    AssertIsOnOwningThread();

    return mTransaction;
  }

  already_AddRefed<IDBRequest>
  Add(JSContext* aCx,
      JS::Handle<JS::Value> aValue,
      JS::Handle<JS::Value> aKey,
      ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return AddOrPut(aCx, aValue, aKey, false, /* aFromCursor */ false, aRv);
  }

  already_AddRefed<IDBRequest>
  Put(JSContext* aCx,
      JS::Handle<JS::Value> aValue,
      JS::Handle<JS::Value> aKey,
      ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return AddOrPut(aCx, aValue, aKey, true, /* aFromCursor */ false, aRv);
  }

  already_AddRefed<IDBRequest>
  Delete(JSContext* aCx,
         JS::Handle<JS::Value> aKey,
         ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return DeleteInternal(aCx, aKey, /* aFromCursor */ false, aRv);
  }

  already_AddRefed<IDBRequest>
  Get(JSContext* aCx,
      JS::Handle<JS::Value> aKey,
      ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return GetInternal(/* aKeyOnly */ false, aCx, aKey, aRv);
  }

  already_AddRefed<IDBRequest>
  GetKey(JSContext* aCx,
         JS::Handle<JS::Value> aKey,
         ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return GetInternal(/* aKeyOnly */ true, aCx, aKey, aRv);
  }

  already_AddRefed<IDBRequest>
  Clear(JSContext* aCx, ErrorResult& aRv);

  already_AddRefed<IDBIndex>
  CreateIndex(const nsAString& aName,
              const StringOrStringSequence& aKeyPath,
              const IDBIndexParameters& aOptionalParameters,
              ErrorResult& aRv);

  already_AddRefed<IDBIndex>
  Index(const nsAString& aName, ErrorResult &aRv);

  void
  DeleteIndex(const nsAString& aIndexName, ErrorResult& aRv);

  already_AddRefed<IDBRequest>
  Count(JSContext* aCx,
        JS::Handle<JS::Value> aKey,
        ErrorResult& aRv);

  already_AddRefed<IDBRequest>
  GetAll(JSContext* aCx,
         JS::Handle<JS::Value> aKey,
         const Optional<uint32_t>& aLimit,
         ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return GetAllInternal(/* aKeysOnly */ false, aCx, aKey, aLimit, aRv);
  }

  already_AddRefed<IDBRequest>
  GetAllKeys(JSContext* aCx,
             JS::Handle<JS::Value> aKey,
             const Optional<uint32_t>& aLimit,
             ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return GetAllInternal(/* aKeysOnly */ true, aCx, aKey, aLimit, aRv);
  }

  already_AddRefed<IDBRequest>
  OpenCursor(JSContext* aCx,
             JS::Handle<JS::Value> aRange,
             IDBCursorDirection aDirection,
             ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return OpenCursorInternal(/* aKeysOnly */ false, aCx, aRange, aDirection,
                              aRv);
  }

  already_AddRefed<IDBRequest>
  OpenCursor(JSContext* aCx,
             IDBCursorDirection aDirection,
             ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return OpenCursorInternal(/* aKeysOnly */ false, aCx,
                              JS::UndefinedHandleValue, aDirection, aRv);
  }

  already_AddRefed<IDBRequest>
  OpenKeyCursor(JSContext* aCx,
                JS::Handle<JS::Value> aRange,
                IDBCursorDirection aDirection,
                ErrorResult& aRv)
  {
    AssertIsOnOwningThread();

    return OpenCursorInternal(/* aKeysOnly */ true, aCx, aRange, aDirection,
                              aRv);
  }

  void
  RefreshSpec(bool aMayDelete);

  const ObjectStoreSpec&
  Spec() const;

  void
  NoteDeletion();

  bool
  IsDeleted() const
  {
    AssertIsOnOwningThread();

    return !!mDeletedSpec;
  }

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IDBObjectStore)

  // nsWrapperCache
  virtual JSObject*
  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;

private:
  IDBObjectStore(IDBTransaction* aTransaction, const ObjectStoreSpec* aSpec);

  ~IDBObjectStore();

  nsresult
  GetAddInfo(JSContext* aCx,
             JS::Handle<JS::Value> aValue,
             JS::Handle<JS::Value> aKeyVal,
             StructuredCloneWriteInfo& aCloneWriteInfo,
             Key& aKey,
             nsTArray<IndexUpdateInfo>& aUpdateInfoArray);

  already_AddRefed<IDBRequest>
  AddOrPut(JSContext* aCx,
           JS::Handle<JS::Value> aValue,
           JS::Handle<JS::Value> aKey,
           bool aOverwrite,
           bool aFromCursor,
           ErrorResult& aRv);

  already_AddRefed<IDBRequest>
  DeleteInternal(JSContext* aCx,
                 JS::Handle<JS::Value> aKey,
                 bool aFromCursor,
                 ErrorResult& aRv);

  already_AddRefed<IDBRequest>
  GetInternal(bool aKeyOnly,
              JSContext* aCx,
              JS::Handle<JS::Value> aKey,
              ErrorResult& aRv);

  already_AddRefed<IDBRequest>
  GetAllInternal(bool aKeysOnly,
                 JSContext* aCx,
                 JS::Handle<JS::Value> aKey,
                 const Optional<uint32_t>& aLimit,
                 ErrorResult& aRv);

  already_AddRefed<IDBRequest>
  OpenCursorInternal(bool aKeysOnly,
                     JSContext* aCx,
                     JS::Handle<JS::Value> aRange,
                     IDBCursorDirection aDirection,
                     ErrorResult& aRv);
};

} // namespace dom
} // namespace mozilla

#endif // mozilla_dom_idbobjectstore_h__