// Copyright 2019 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 BASE_WIN_SCOPED_SAFEARRAY_H_
#define BASE_WIN_SCOPED_SAFEARRAY_H_

#include <objbase.h>

#include "base/base_export.h"
#include "base/check_op.h"
#include "base/win/variant_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace base {
namespace win {

// Manages a Windows SAFEARRAY. This is a minimal wrapper that simply provides
// RAII semantics and does not duplicate the extensive functionality that
// CComSafeArray offers.
class BASE_EXPORT ScopedSafearray {
 public:
  // LockScope<VARTYPE> class for automatically managing the lifetime of a
  // SAFEARRAY lock, and granting easy access to the underlying data either
  // through random access or as an iterator.
  // It is undefined behavior if the underlying SAFEARRAY is destroyed
  // before the LockScope.
  // LockScope implements std::iterator_traits as a random access iterator, so
  // that LockScope is compatible with STL methods that require these traits.
  template <VARTYPE ElementVartype>
  class BASE_EXPORT LockScope final {
   public:
    // Type declarations to support std::iterator_traits
    using iterator_category = std::random_access_iterator_tag;
    using value_type = typename internal::VariantUtil<ElementVartype>::Type;
    using difference_type = ptrdiff_t;
    using reference = value_type&;
    using const_reference = const value_type&;
    using pointer = value_type*;
    using const_pointer = const value_type*;

    LockScope() = default;

    LockScope(LockScope<ElementVartype>&& other)
        : safearray_(std::exchange(other.safearray_, nullptr)),
          vartype_(std::exchange(other.vartype_, VT_EMPTY)),
          array_(std::exchange(other.array_, nullptr)),
          array_size_(std::exchange(other.array_size_, 0U)) {}

    LockScope<ElementVartype>& operator=(LockScope<ElementVartype>&& other) {
      DCHECK_NE(this, &other);
      Reset();
      safearray_ = std::exchange(other.safearray_, nullptr);
      vartype_ = std::exchange(other.vartype_, VT_EMPTY);
      array_ = std::exchange(other.array_, nullptr);
      array_size_ = std::exchange(other.array_size_, 0U);
      return *this;
    }

    LockScope(const LockScope&) = delete;
    LockScope& operator=(const LockScope&) = delete;

    ~LockScope() { Reset(); }

    VARTYPE Type() const { return vartype_; }

    size_t size() const { return array_size_; }

    pointer begin() { return array_; }
    pointer end() { return array_ + array_size_; }
    const_pointer begin() const { return array_; }
    const_pointer end() const { return array_ + array_size_; }

    pointer data() { return array_; }
    const_pointer data() const { return array_; }

    reference operator[](int index) { return at(index); }
    const_reference operator[](int index) const { return at(index); }

    reference at(size_t index) {
      DCHECK_NE(array_, nullptr);
      DCHECK_LT(index, array_size_);
      return array_[index];
    }
    const_reference at(size_t index) const {
      return const_cast<LockScope<ElementVartype>*>(this)->at(index);
    }

   private:
    LockScope(SAFEARRAY* safearray,
              VARTYPE vartype,
              pointer array,
              size_t array_size)
        : safearray_(safearray),
          vartype_(vartype),
          array_(array),
          array_size_(array_size) {}

    void Reset() {
      if (safearray_)
        SafeArrayUnaccessData(safearray_);
      safearray_ = nullptr;
      vartype_ = VT_EMPTY;
      array_ = nullptr;
      array_size_ = 0U;
    }

    SAFEARRAY* safearray_ = nullptr;
    VARTYPE vartype_ = VT_EMPTY;
    pointer array_ = nullptr;
    size_t array_size_ = 0U;

    friend class ScopedSafearray;
  };

  explicit ScopedSafearray(SAFEARRAY* safearray = nullptr)
      : safearray_(safearray) {}

  ScopedSafearray(const ScopedSafearray&) = delete;
  ScopedSafearray& operator=(const ScopedSafearray&) = delete;

  // Move constructor
  ScopedSafearray(ScopedSafearray&& r) noexcept : safearray_(r.safearray_) {
    r.safearray_ = nullptr;
  }

  // Move operator=. Allows assignment from a ScopedSafearray rvalue.
  ScopedSafearray& operator=(ScopedSafearray&& rvalue) {
    Reset(rvalue.Release());
    return *this;
  }

  ~ScopedSafearray() { Destroy(); }

  // Creates a LockScope for accessing the contents of a
  // single-dimensional SAFEARRAYs.
  template <VARTYPE ElementVartype>
  absl::optional<LockScope<ElementVartype>> CreateLockScope() const {
    if (!safearray_ || SafeArrayGetDim(safearray_) != 1)
      return absl::nullopt;

    VARTYPE vartype;
    HRESULT hr = SafeArrayGetVartype(safearray_, &vartype);
    if (FAILED(hr) ||
        !internal::VariantUtil<ElementVartype>::IsConvertibleTo(vartype)) {
      return absl::nullopt;
    }

    typename LockScope<ElementVartype>::pointer array = nullptr;
    hr = SafeArrayAccessData(safearray_, reinterpret_cast<void**>(&array));
    if (FAILED(hr))
      return absl::nullopt;

    const size_t array_size = GetCount();
    return LockScope<ElementVartype>(safearray_, vartype, array, array_size);
  }

  void Destroy() {
    if (safearray_) {
      HRESULT hr = SafeArrayDestroy(safearray_);
      DCHECK_EQ(S_OK, hr);
      safearray_ = nullptr;
    }
  }

  // Give ScopedSafearray ownership over an already allocated SAFEARRAY or
  // nullptr.
  void Reset(SAFEARRAY* safearray = nullptr) {
    if (safearray != safearray_) {
      Destroy();
      safearray_ = safearray;
    }
  }

  // Releases ownership of the SAFEARRAY to the caller.
  SAFEARRAY* Release() {
    SAFEARRAY* safearray = safearray_;
    safearray_ = nullptr;
    return safearray;
  }

  // Retrieves the pointer address.
  // Used to receive SAFEARRAYs as out arguments (and take ownership).
  // This function releases any existing references because it will leak
  // the existing ref otherwise.
  // Usage: GetSafearray(safearray.Receive());
  SAFEARRAY** Receive() {
    Destroy();
    return &safearray_;
  }

  // Returns the number of elements in a dimension of the array.
  size_t GetCount(UINT dimension = 0) const {
    DCHECK(safearray_);
    // Initialize |lower| and |upper| so this method will return zero if either
    // SafeArrayGetLBound or SafeArrayGetUBound returns failure because they
    // only write to the output parameter when successful.
    LONG lower = 0;
    LONG upper = -1;
    DCHECK_LT(dimension, SafeArrayGetDim(safearray_));
    HRESULT hr = SafeArrayGetLBound(safearray_, dimension + 1, &lower);
    DCHECK(SUCCEEDED(hr));
    hr = SafeArrayGetUBound(safearray_, dimension + 1, &upper);
    DCHECK(SUCCEEDED(hr));
    return (upper - lower + 1);
  }

  // Returns the internal pointer.
  SAFEARRAY* Get() const { return safearray_; }

  // Forbid comparison of ScopedSafearray types.  You should never have the same
  // SAFEARRAY owned by two different scoped_ptrs.
  bool operator==(const ScopedSafearray& safearray2) const = delete;
  bool operator!=(const ScopedSafearray& safearray2) const = delete;

 private:
  SAFEARRAY* safearray_;
};

}  // namespace win
}  // namespace base

#endif  // BASE_WIN_SCOPED_SAFEARRAY_H_
