/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

#include "nsISupportsArray.h"

static NS_DEFINE_IID(kISupportsArrayIID, NS_ISUPPORTSARRAY_IID);

static const PRInt32 kGrowArrayBy = 8;
static const PRInt32 kAutoArraySize = 4;

class SupportsArrayImpl : public nsISupportsArray {
public:
  SupportsArrayImpl(void);
  ~SupportsArrayImpl(void);

  NS_DECL_ISUPPORTS

  NS_IMETHOD_(nsISupportsArray&) operator=(const nsISupportsArray& aOther);
  NS_IMETHOD_(PRBool) operator==(const nsISupportsArray& aOther) const { return Equals(&aOther); }
  NS_IMETHOD_(PRBool) Equals(const nsISupportsArray* aOther) const;

  NS_IMETHOD_(PRInt32) Count(void) const {  return mCount;  }

  NS_IMETHOD_(nsISupports*) ElementAt(PRInt32 aIndex) const;
  NS_IMETHOD_(nsISupports*) operator[](PRInt32 aIndex) const { return ElementAt(aIndex); }

  NS_IMETHOD_(PRInt32) IndexOf(const nsISupports* aPossibleElement, PRInt32 aStartIndex = 0) const;
  NS_IMETHOD_(PRInt32) LastIndexOf(const nsISupports* aPossibleElement) const;

  NS_IMETHOD_(PRBool) InsertElementAt(nsISupports* aElement, PRInt32 aIndex);

  NS_IMETHOD_(PRBool) ReplaceElementAt(nsISupports* aElement, PRInt32 aIndex);

  NS_IMETHOD_(PRBool) AppendElement(nsISupports* aElement) {
    return InsertElementAt(aElement, mCount);
  }

  NS_IMETHOD_(PRBool) RemoveElementAt(PRInt32 aIndex);
  NS_IMETHOD_(PRBool) RemoveElement(const nsISupports* aElement, PRInt32 aStartIndex = 0);
  NS_IMETHOD_(PRBool) RemoveLastElement(const nsISupports* aElement);

  NS_IMETHOD_(PRBool) AppendElements(nsISupportsArray* aElements);
  
  NS_IMETHOD_(void)   Clear(void);

  NS_IMETHOD_(void)   Compact(void);

  NS_IMETHOD_(PRBool) EnumerateForwards(nsISupportsArrayEnumFunc aFunc, void* aData) const;
  NS_IMETHOD_(PRBool) EnumerateBackwards(nsISupportsArrayEnumFunc aFunc, void* aData) const;

protected:
  void DeleteArray(void);

  nsISupports** mArray;
  PRInt32 mArraySize;
  PRInt32 mCount;
  nsISupports*  mAutoArray[kAutoArraySize];

private:
  // Copy constructors are not allowed
  SupportsArrayImpl(const nsISupportsArray& other);
};


SupportsArrayImpl::SupportsArrayImpl()
{
  NS_INIT_REFCNT();
  mArray = &(mAutoArray[0]);
  mArraySize = kAutoArraySize;
  mCount = 0;
}

SupportsArrayImpl::~SupportsArrayImpl()
{
  DeleteArray();
}

NS_IMPL_ISUPPORTS(SupportsArrayImpl, kISupportsArrayIID);

void SupportsArrayImpl::DeleteArray(void)
{
  Clear();
  if (mArray != &(mAutoArray[0])) {
    delete[] mArray;
    mArray = &(mAutoArray[0]);
    mArraySize = kAutoArraySize;
  }
}

nsISupportsArray& SupportsArrayImpl::operator=(const nsISupportsArray& aOther)
{
  PRInt32 otherCount = aOther.Count();

  if (otherCount > mArraySize) {
    DeleteArray();
    mArraySize = otherCount;
    mArray = new nsISupports*[mArraySize];
  }
  else {
    Clear();
  }
  mCount = otherCount;
  while (0 <= --otherCount) {
    mArray[otherCount] = aOther.ElementAt(otherCount);
  }
  return *this;
}

PRBool SupportsArrayImpl::Equals(const nsISupportsArray* aOther) const
{
  if (0 != aOther) {
    const SupportsArrayImpl* other = (const SupportsArrayImpl*)aOther;
    if (mCount == other->mCount) {
      PRInt32 index = mCount;
      while (0 <= --index) {
        if (mArray[index] != other->mArray[index]) {
          return PR_FALSE;
        }
      }
      return PR_TRUE;
    }
  }
  return PR_FALSE;
}

nsISupports* SupportsArrayImpl::ElementAt(PRInt32 aIndex) const
{
  if ((0 <= aIndex) && (aIndex < mCount)) {
    nsISupports*  element = mArray[aIndex];
    NS_ADDREF(element);
    return element;
  }
  return 0;
}

PRInt32 SupportsArrayImpl::IndexOf(const nsISupports* aPossibleElement, PRInt32 aStartIndex) const
{
  if ((0 <= aStartIndex) && (aStartIndex < mCount)) {
    const nsISupports** start = (const nsISupports**)mArray;  // work around goofy compiler behavior
    const nsISupports** ep = (start + aStartIndex);
    const nsISupports** end = (start + mCount);
    while (ep < end) {
      if (aPossibleElement == *ep) {
        return (ep - start);
      }
      ep++;
    }
  }
  return -1;
}

PRInt32 SupportsArrayImpl::LastIndexOf(const nsISupports* aPossibleElement) const
{
  if (0 < mCount) {
    const nsISupports** start = (const nsISupports**)mArray;  // work around goofy compiler behavior
    const nsISupports** ep = (start + mCount);
    while (start <= --ep) {
      if (aPossibleElement == *ep) {
        return (ep - start);
      }
    }
  }
  return -1;
}

PRBool SupportsArrayImpl::InsertElementAt(nsISupports* aElement, PRInt32 aIndex)
{
  if ((0 <= aIndex) && (aIndex <= mCount)) {
    if (mArraySize < (mCount + 1)) {  // need to grow the array
      mArraySize += kGrowArrayBy;
      nsISupports** oldArray = mArray;
      mArray = new nsISupports*[mArraySize];
      if (0 == mArray) { // ran out of memory
        mArray = oldArray;
        mArraySize -= kGrowArrayBy;
        return PR_FALSE;
      }
      if (0 != oldArray) { // need to move old data
        if (0 < aIndex) {
          ::memcpy(mArray, oldArray, aIndex * sizeof(nsISupports*));
        }
        PRInt32 slide = (mCount - aIndex);
        if (0 < slide) {
          ::memcpy(mArray + aIndex + 1, oldArray + aIndex, slide * sizeof(nsISupports*));
        }
        if (oldArray != &(mAutoArray[0])) {
          delete[] oldArray;
        }
      }
    }
    else {
      PRInt32 slide = (mCount - aIndex);
      if (0 < slide) {
        ::memmove(mArray + aIndex + 1, mArray + aIndex, slide * sizeof(nsISupports*));
      }
    }

    mArray[aIndex] = aElement;
    NS_ADDREF(aElement);
    mCount++;
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool SupportsArrayImpl::ReplaceElementAt(nsISupports* aElement, PRInt32 aIndex)
{
  if ((0 <= aIndex) && (aIndex < mCount)) {
    NS_ADDREF(aElement);  // addref first in case it's the same object!
    NS_RELEASE(mArray[aIndex]);
    mArray[aIndex] = aElement;
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool SupportsArrayImpl::RemoveElementAt(PRInt32 aIndex)
{
  if ((0 <= aIndex) && (aIndex < mCount)) {
    NS_RELEASE(mArray[aIndex]);
    mCount--;
    PRInt32 slide = (mCount - aIndex);
    if (0 < slide) {
      ::memmove(mArray + aIndex, mArray + aIndex + 1,
                slide * sizeof(nsISupports*));
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

PRBool SupportsArrayImpl::RemoveElement(const nsISupports* aElement, PRInt32 aStartIndex)
{
  if ((0 <= aStartIndex) && (aStartIndex < mCount)) {
    nsISupports** ep = mArray;
    nsISupports** end = ep + mCount;
    while (ep < end) {
      if (*ep == aElement) {
        return RemoveElementAt(PRInt32(ep - mArray));
      }
      ep++;
    }
  }
  return PR_FALSE;
}

PRBool SupportsArrayImpl::RemoveLastElement(const nsISupports* aElement)
{
  if (0 < mCount) {
    nsISupports** ep = (mArray + mCount);
    while (mArray <= --ep) {
      if (*ep == aElement) {
        return RemoveElementAt(PRInt32(ep - mArray));
      }
    }
  }
  return PR_FALSE;
}

PRBool SupportsArrayImpl::AppendElements(nsISupportsArray* aElements)
{
  SupportsArrayImpl*  elements = (SupportsArrayImpl*)aElements;

  if (elements && (0 < elements->mCount)) {
    if (mArraySize < (mCount + elements->mCount)) {  // need to grow the array
      PRInt32 count = mCount + elements->mCount;
      PRInt32 oldSize = mArraySize;
      while (mArraySize < count) {  // ick
        mArraySize += kGrowArrayBy;
      }
      nsISupports** oldArray = mArray;
      mArray = new nsISupports*[mArraySize];
      if (0 == mArray) { // ran out of memory
        mArray = oldArray;
        mArraySize = oldSize;
        return PR_FALSE;
      }
      if (0 != oldArray) { // need to move old data
        if (0 < mCount) {
          ::memcpy(mArray, oldArray, mCount);
        }
        if (oldArray != &(mAutoArray[0])) {
          delete[] oldArray;
        }
      }
    }

    PRInt32 index;
    while (index < elements->mCount) {
      NS_ADDREF(elements->mArray[index]);
      mArray[mCount++] = elements->mArray[index++];
    }
    return PR_TRUE;
  }
  return PR_FALSE;
}

void SupportsArrayImpl::Clear(void)
{
  while (0 <= --mCount) {
    NS_RELEASE(mArray[mCount]);
  }
  mCount = 0;
}

void SupportsArrayImpl::Compact(void)
{
  if ((mArraySize != mCount) && (kAutoArraySize < mArraySize)) {
    nsISupports** oldArray = mArray;
    PRInt32 oldArraySize = mArraySize;
    if (mCount <= kAutoArraySize) {
      mArray = &(mAutoArray[0]);
      mArraySize = kAutoArraySize;
    }
    else {
      mArray = new nsISupports*[mCount];
      mArraySize = mCount;
    }
    if (0 == mArray) {
      mArray = oldArray;
      mArraySize = oldArraySize;
      return;
    }
    ::memcpy(mArray, oldArray, mCount * sizeof(nsISupports*));
    delete[] oldArray;
  }
}

PRBool SupportsArrayImpl::EnumerateForwards(nsISupportsArrayEnumFunc aFunc, void* aData) const
{
  PRInt32 index = -1;
  PRBool  running = PR_TRUE;

  while (running && (++index < mCount)) {
    running = (*aFunc)(mArray[index], aData);
  }
  return running;
}

PRBool SupportsArrayImpl::EnumerateBackwards(nsISupportsArrayEnumFunc aFunc, void* aData) const
{
  PRInt32 index = mCount;
  PRBool  running = PR_TRUE;

  while (running && (0 <= --index)) {
    running = (*aFunc)(mArray[index], aData);
  }
  return running;
}


NS_COM nsresult
  NS_NewISupportsArray(nsISupportsArray** aInstancePtrResult)
{
  if (aInstancePtrResult == 0) {
    return NS_ERROR_NULL_POINTER;
  }

  SupportsArrayImpl *it = new SupportsArrayImpl();

  if (0 == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  return it->QueryInterface(kISupportsArrayIID, (void **) aInstancePtrResult);
}

