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

#include <stdint.h>
#include <string.h>

#include "nsArrayEnumerator.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsSupportsArray.h"
#include "nsSupportsArrayEnumerator.h"

// Disable deprecation warnings generated by nsISupportsArray and associated
// classes.
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined(_MSC_VER)
#pragma warning (push)
#pragma warning (disable : 4996)
#endif

nsresult
nsQueryElementAt::operator()(const nsIID& aIID, void** aResult) const
{
  nsresult status =
    mCollection ? mCollection->QueryElementAt(mIndex, aIID, aResult) :
                  NS_ERROR_NULL_POINTER;

  if (mErrorPtr) {
    *mErrorPtr = status;
  }

  return status;
}

nsSupportsArray::nsSupportsArray()
{
}

nsSupportsArray::~nsSupportsArray()
{
  Clear();
}

nsresult
nsSupportsArray::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
{
  if (aOuter) {
    return NS_ERROR_NO_AGGREGATION;
  }

  nsCOMPtr<nsISupportsArray> it = new nsSupportsArray();

  return it->QueryInterface(aIID, aResult);
}

NS_IMPL_ISUPPORTS(nsSupportsArray, nsIArray, nsISupportsArray, nsICollection,
                  nsISerializable)

NS_IMETHODIMP
nsSupportsArray::Read(nsIObjectInputStream* aStream)
{
  nsresult rv;

  uint32_t newArraySize;
  rv = aStream->Read32(&newArraySize);
  if (NS_FAILED(rv)) {
    return rv;
  }

  uint32_t count;
  rv = aStream->Read32(&count);
  if (NS_FAILED(rv)) {
    return rv;
  }

  NS_ASSERTION(count <= newArraySize, "overlarge mCount!");
  if (count > newArraySize) {
    count = newArraySize;
  }

  // Don't clear out our array until we know we have enough space for the new
  // one and have successfully copied everything out of the stream.
  nsCOMArray<nsISupports> tmp;
  tmp.SetCapacity(newArraySize);
  tmp.SetCount(count);

  auto elems = tmp.Elements();
  for (uint32_t i = 0; i < count; i++) {
    rv = aStream->ReadObject(true, &elems[i]);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  // Now clear out existing refs and replace with the new array.
  mArray.Clear();
  mArray.SwapElements(tmp);

  return NS_OK;
}

NS_IMETHODIMP
nsSupportsArray::Write(nsIObjectOutputStream* aStream)
{
  nsresult rv;

  rv = aStream->Write32(mArray.Capacity());
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = aStream->Write32(mArray.Length());
  if (NS_FAILED(rv)) {
    return rv;
  }

  for (size_t i = 0; i < mArray.Length(); i++) {
    rv = aStream->WriteObject(mArray[i], true);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSupportsArray::GetElementAt(uint32_t aIndex, nsISupports** aOutPtr)
{
  nsCOMPtr<nsISupports> elm = mArray.SafeElementAt(aIndex);
  elm.forget(aOutPtr);
  return NS_OK;
}

NS_IMETHODIMP_(int32_t)
nsSupportsArray::IndexOf(const nsISupports* aPossibleElement)
{
  // nsCOMArray takes a non-const param, but it just passes through to
  // nsTArray which takes a const param.
  return mArray.IndexOf(const_cast<nsISupports*>(aPossibleElement));
}

NS_IMETHODIMP_(bool)
nsSupportsArray::InsertElementAt(nsISupports* aElement, uint32_t aIndex)
{
  return mArray.InsertObjectAt(aElement, aIndex);
}

NS_IMETHODIMP_(bool)
nsSupportsArray::ReplaceElementAt(nsISupports* aElement, uint32_t aIndex)
{
  // nsCOMArray::ReplaceObjectAt will grow the array if necessary. Instead
  // we do the bounds check and only replace if it's in range.
  if (aIndex < mArray.Length()) {
    mArray.ReplaceElementAt(aIndex, aElement);
    return true;
  }
  return false;
}

NS_IMETHODIMP_(bool)
nsSupportsArray::RemoveElementAt(uint32_t aIndex)
{
  return mArray.RemoveObjectAt(aIndex);
}

NS_IMETHODIMP
nsSupportsArray::RemoveElement(nsISupports* aElement)
{
  return mArray.RemoveObject(aElement) ? NS_OK : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSupportsArray::Clear(void)
{
  mArray.Clear();
  return NS_OK;
}

NS_IMETHODIMP
nsSupportsArray::DeprecatedEnumerate(nsIEnumerator** aResult)
{
  RefPtr<nsSupportsArrayEnumerator> e = new nsSupportsArrayEnumerator(this);
  e.forget(aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSupportsArray::Clone(nsISupportsArray** aResult)
{
  nsCOMPtr<nsISupportsArray> newArray;
  nsresult rv = NS_NewISupportsArray(getter_AddRefs(newArray));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  for (size_t i = 0; i < mArray.Length(); i++) {
    // AppendElement does an odd cast of bool to nsresult, we just cast back
    // here.
    if (!(bool)newArray->AppendElement(mArray[i])) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
  }

  newArray.forget(aResult);
  return NS_OK;
}

nsresult
NS_NewISupportsArray(nsISupportsArray** aInstancePtrResult)
{
  nsresult rv;
  rv = nsSupportsArray::Create(nullptr, NS_GET_IID(nsISupportsArray),
                               (void**)aInstancePtrResult);
  return rv;
}

/**
 * nsIArray adapters.
 */
NS_IMETHODIMP
nsSupportsArray::GetLength(uint32_t* aLength) {
  return Count(aLength);
}

NS_IMETHODIMP
nsSupportsArray::QueryElementAt(uint32_t aIndex, const nsIID& aIID, void** aResult)
{
  nsISupports* element = mArray.SafeElementAt(aIndex);
  if (element) {
    return element->QueryInterface(aIID, aResult);
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSupportsArray::IndexOf(uint32_t aStartIndex, nsISupports* aElement, uint32_t* aResult)
{
  int32_t idx = mArray.IndexOf(aElement, aStartIndex);
  if (idx < 0) {
    return NS_ERROR_FAILURE;
  }

  *aResult = static_cast<uint32_t>(idx);
  return NS_OK;
}

NS_IMETHODIMP
nsSupportsArray::Enumerate(nsISimpleEnumerator** aResult)
{
  return NS_NewArrayEnumerator(aResult, this);
}

#if defined(__GNUC__)
#pragma GCC diagnostic pop
#elif defined(_MSC_VER)
#pragma warning (pop)
#endif