/*************************************************************************
 * OLE Automation
 * SafeArray Implementation
 *
 * This file contains the implementation of the SafeArray interface.
 *
 * Copyright 1999 Sylvain St-Germain
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#ifdef __WIN32OS2__
#define HAVE_FLOAT_H
#define WINE_LARGE_INTEGER
#include "oleaut32.h"
#endif

#include <stdio.h>
#include <string.h>
#include "windef.h"
#include "winerror.h"
#include "winbase.h"
#include "oleauto.h"
#include "wine/obj_base.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(ole);

#define SYSDUPSTRING(str) SysAllocStringLen((str), SysStringLen(str))

/* Locally used methods */
static INT
endOfDim(LONG *coor, SAFEARRAYBOUND *mat, LONG dim, LONG realDim);

static ULONG
calcDisplacement(LONG *coor, SAFEARRAYBOUND *mat, LONG dim);

static BOOL
isPointer(USHORT feature);

static INT
getFeatures(VARTYPE vt);

static BOOL
validCoordinate(LONG *coor, SAFEARRAY *psa);

static BOOL
resizeSafeArray(SAFEARRAY *psa, LONG lDelta);

static BOOL
validArg(SAFEARRAY *psa);

static ULONG
getArraySize(SAFEARRAY *psa);

static HRESULT
duplicateData(SAFEARRAY *psa, SAFEARRAY **ppsaOut);

/* Association between VARTYPE and their size.
   A size of zero is defined for the unsupported types.  */

#define VARTYPE_NOT_SUPPORTED 0
static const ULONG VARTYPE_SIZE[] =
{
  /* this is taken from wtypes.h.  Only [S]es are supported by the SafeArray */
VARTYPE_NOT_SUPPORTED,  /* VT_EMPTY    [V]   [P]    nothing			*/
VARTYPE_NOT_SUPPORTED,  /* VT_NULL     [V]   [P]    SQL style Nul	*/
2,                      /* VT_I2       [V][T][P][S] 2 byte signed int */
4,                      /* VT_I4       [V][T][P][S] 4 byte signed int */
4,                      /* VT_R4       [V][T][P][S] 4 byte real	*/
8,                      /* VT_R8       [V][T][P][S] 8 byte real	*/
8,                      /* VT_CY       [V][T][P][S] currency */
8,                      /* VT_DATE     [V][T][P][S] date */
sizeof(BSTR),           /* VT_BSTR     [V][T][P][S] OLE Automation string*/
sizeof(LPDISPATCH),     /* VT_DISPATCH [V][T][P][S] IDispatch *	*/
4,                      /* VT_ERROR    [V][T]   [S] SCODE	*/
4,                      /* VT_BOOL     [V][T][P][S] True=-1, False=0*/
sizeof(VARIANT),        /* VT_VARIANT  [V][T][P][S] VARIANT *	*/
sizeof(LPUNKNOWN),      /* VT_UNKNOWN  [V][T]   [S] IUnknown * */
sizeof(DECIMAL),        /* VT_DECIMAL  [V][T]   [S] 16 byte fixed point	*/
VARTYPE_NOT_SUPPORTED,                         /* no VARTYPE here..... */
VARTYPE_NOT_SUPPORTED,	/* VT_I1          [T]       signed char	*/
1,                      /* VT_UI1      [V][T][P][S] unsigned char			*/
VARTYPE_NOT_SUPPORTED,	/* VT_UI2         [T][P]    unsigned short	*/
VARTYPE_NOT_SUPPORTED,	/* VT_UI4         [T][P]    unsigned short	*/
VARTYPE_NOT_SUPPORTED,	/* VT_I8          [T][P]    signed 64-bit int			*/
VARTYPE_NOT_SUPPORTED,	/* VT_UI8         [T][P]    unsigned 64-bit int		*/
VARTYPE_NOT_SUPPORTED,	/* VT_INT         [T]       signed machine int		*/
VARTYPE_NOT_SUPPORTED,	/* VT_UINT        [T]       unsigned machine int	*/
VARTYPE_NOT_SUPPORTED,	/* VT_VOID        [T]       C style void			*/
VARTYPE_NOT_SUPPORTED,	/* VT_HRESULT     [T]       Standard return type	*/
VARTYPE_NOT_SUPPORTED,	/* VT_PTR         [T]       pointer type			*/
VARTYPE_NOT_SUPPORTED,	/* VT_SAFEARRAY   [T]       (use VT_ARRAY in VARIANT)*/
VARTYPE_NOT_SUPPORTED,	/* VT_CARRAY      [T]       C style array			*/
VARTYPE_NOT_SUPPORTED,	/* VT_USERDEFINED [T]       user defined type			*/
VARTYPE_NOT_SUPPORTED,	/* VT_LPSTR       [T][P]    null terminated string	*/
VARTYPE_NOT_SUPPORTED,	/* VT_LPWSTR      [T][P]    wide null term string		*/
VARTYPE_NOT_SUPPORTED,	/* VT_FILETIME       [P]    FILETIME			*/
VARTYPE_NOT_SUPPORTED,	/* VT_BLOB           [P]    Length prefixed bytes */
VARTYPE_NOT_SUPPORTED,	/* VT_STREAM         [P]    Name of stream follows		*/
VARTYPE_NOT_SUPPORTED,	/* VT_STORAGE        [P]    Name of storage follows	*/
VARTYPE_NOT_SUPPORTED,	/* VT_STREAMED_OBJECT[P]    Stream contains an object*/
VARTYPE_NOT_SUPPORTED,	/* VT_STORED_OBJECT  [P]    Storage contains object*/
VARTYPE_NOT_SUPPORTED,	/* VT_BLOB_OBJECT    [P]    Blob contains an object*/
VARTYPE_NOT_SUPPORTED,	/* VT_CF             [P]    Clipboard format			*/
VARTYPE_NOT_SUPPORTED,	/* VT_CLSID          [P]    A Class ID			*/
VARTYPE_NOT_SUPPORTED,	/* VT_VECTOR         [P]    simple counted array		*/
VARTYPE_NOT_SUPPORTED,	/* VT_ARRAY    [V]          SAFEARRAY*			*/
VARTYPE_NOT_SUPPORTED 	/* VT_BYREF    [V]          void* for local use	*/
};

static const int LAST_VARTYPE = sizeof(VARTYPE_SIZE)/sizeof(VARTYPE_SIZE[0]);


/***********************************************************************
 *		SafeArrayCreateEx (OLEAUT32.42)
*
 * Create a new SafeArray.
 *
 * PARAMS
 *  vt        [I] Type to store in the safe array
 *  cDims     [I] Number of array dimensions
 *  rgsabound [I] Bounds of the array dimensions
 *  pvExtra   [I] Extra data
 *
 * RETURNS
 *  Success: A pointer to a new array object.
 *  Failure: NULL, if any parameter is invalid or memory allocation fails.
 *
 * NOTES
 * See SafeArray.
 */
SAFEARRAY* WINAPI SafeArrayCreateEx(VARTYPE vt, UINT cDims, SAFEARRAYBOUND *rgsabound, LPVOID pvExtra)
#ifdef __WIN32OS2__
{
  FIXME("%s: Not implemented! (returning E_NOTIMPL)", __FUNCTION__);
  return NULL;
}
#else
{
  ULONG ulSize = 0;
  IRecordInfo* iRecInfo = (IRecordInfo*)pvExtra;
  SAFEARRAY* psa;
 
  TRACE("(%d->%s,%d,%p,%p)\n", vt, debugstr_vt(vt), cDims, rgsabound, pvExtra);
 
  if (vt == VT_RECORD)
  {
    if  (!iRecInfo)
      return NULL;
    IRecordInfo_GetSize(iRecInfo, &ulSize);
  }
  psa = SAFEARRAY_Create(vt, cDims, rgsabound, ulSize);

  if (pvExtra)
  {
    switch(vt)
    {
      case VT_RECORD:
        SafeArraySetRecordInfo(psa, pvExtra);
        break;
      case VT_UNKNOWN:
      case VT_DISPATCH:
        SafeArraySetIID(psa, pvExtra);
        break;
    }
  }
  return psa;
}
#endif

/***********************************************************************
 *		SafeArraySetIID (OLEAUT32.57)
*
 * Set the IID for a SafeArray.
 *
 * PARAMS
 *  psa  [I] Array to set the IID from
 *  guid [I] IID
 *
 * RETURNS
 *  Success: S_OK. The IID is stored with the array
 *  Failure: An HRESULT error code indicating the error.
 *
 * NOTES
 * See SafeArray.
 */
HRESULT WINAPI SafeArraySetIID(SAFEARRAY *psa, REFGUID guid)
{
  GUID* dest = (GUID*)psa;

  TRACE("(%p,%s)\n", psa, debugstr_guid(guid));

  if (!psa || !guid || !(psa->fFeatures & FADF_HAVEIID))
    return E_INVALIDARG;

  dest[-1] = *guid;
  return S_OK;
}


/***********************************************************************
 *		SafeArrayGetIID (OLEAUT32.67)
 *
 * Get the IID from a SafeArray.
 *
 * PARAMS
 *  psa   [I] Array to get the ID from
 *  pGuid [O] Destination for the IID
 *
 * RETURNS
 *  Success: S_OK. pRinfo contains the IID, or NULL if there was none.
 *  Failure: An HRESULT error code indicating the error.
 *
 * NOTES
 * See SafeArray.
 */
HRESULT WINAPI SafeArrayGetIID(SAFEARRAY *psa, GUID *pGuid)
{
  GUID* src = (GUID*)psa;

  TRACE("(%p,%p)\n", psa, pGuid);

  if (!psa || !pGuid || !(psa->fFeatures & FADF_HAVEIID))
    return E_INVALIDARG;

  *pGuid = src[-1];
  return S_OK;
}


/***********************************************************************
 *		SafeArraySetRecordInfo (OLEAUT32.45)
 */
#ifdef __WIN32OS2__
HRESULT WINAPI SafeArraySetRecordInfo(void* pFixMe, void* pFixMe2)
#else
HRESULT WINAPI SafeArraySetRecordInfo(SAFEARRAY* psa,  IRecordInfo *  psaboundNew)
#endif
{
  FIXME("%s: Not implemented! (returning E_NOTIMPL)", __FUNCTION__);
  return E_NOTIMPL;
}


/***********************************************************************
 *		SafeArrayGetRecordInfo (OLEAUT32.45)
 */
/* IRecordInfo helpers */
#ifdef __WIN32OS2__
HRESULT WINAPI SafeArrayGetRecordInfo(void* pFixMe, void** ppFixMe)
#else
HRESULT WINAPI SafeArrayGetRecordInfo(SAFEARRAY* psa,IRecordInfo** prinfo)
#endif
{
  FIXME("%s: Not implemented! (returning E_NOTIMPL)", __FUNCTION__);
  return E_NOTIMPL;
}


/*************************************************************************
 *		SafeArrayAllocDescriptor (OLEAUT32.36)
 * Allocate the appropriate amount of memory for the SafeArray descriptor
 */
HRESULT WINAPI SafeArrayAllocDescriptor(
  UINT    cDims,
  SAFEARRAY **ppsaOut)
{
  SAFEARRAYBOUND *sab;
  LONG allocSize = 0;

  /* SAFEARRAY + SAFEARRAYBOUND * (cDims -1) ( -1 because there is already one
                                             ( in SAFEARRAY struct */
  allocSize = sizeof(**ppsaOut) + (sizeof(*sab) * (cDims-1));

  /* Allocate memory for SAFEARRAY struc */
  if(( (*ppsaOut)=HeapAlloc(
        GetProcessHeap(), HEAP_ZERO_MEMORY, allocSize)) == NULL){
    return(E_UNEXPECTED);
  }
  TRACE("SafeArray: %lu bytes allocated for descriptor.\n", allocSize);

  return(S_OK);
}

/*************************************************************************
 *		SafeArrayAllocDescriptorEx (OLEAUT32.41)
 * Allocate the appropriate amount of memory for the SafeArray descriptor
 *
 * This is a minimal implementation just to get things moving.
 *
 * The MSDN documentation on this doesn't tell us much.
 */
HRESULT WINAPI SafeArrayAllocDescriptorEx(
  VARTYPE vt,
  UINT    cDims,
  SAFEARRAY **ppsaOut)
{
  if ( (vt >= LAST_VARTYPE) ||
       ( VARTYPE_SIZE[vt] == VARTYPE_NOT_SUPPORTED ) )
    return E_UNEXPECTED;

  return SafeArrayAllocDescriptor (cDims, ppsaOut);
}

/*************************************************************************
 *		SafeArrayAllocData (OLEAUT32.37)
 * Allocate the appropriate amount of data for the SafeArray data
 */
HRESULT WINAPI SafeArrayAllocData(
  SAFEARRAY *psa)
{
  ULONG  ulWholeArraySize;   /* to store the size of the whole thing */

  if(! validArg(psa))
    return E_INVALIDARG;

  ulWholeArraySize = getArraySize(psa);

  /* Allocate memory for the data itself */
  if((psa->pvData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
        psa->cbElements*ulWholeArraySize)) == NULL)
    return(E_UNEXPECTED);

  TRACE("SafeArray: %lu bytes allocated for data at %p (%lu objects).\n",
    psa->cbElements*ulWholeArraySize, psa->pvData, ulWholeArraySize);

  return(S_OK);
}

/*************************************************************************
 *		SafeArrayCreate (OLEAUT32.15)
 * Create a SafeArray object by encapsulating AllocDescriptor and AllocData
 */
SAFEARRAY* WINAPI SafeArrayCreate(
  VARTYPE        vt,
  UINT         cDims,
  SAFEARRAYBOUND *rgsabound)
{
  SAFEARRAY *psa;
  HRESULT   hRes;
  USHORT    cDim;

  /* Validate supported VARTYPE */
  if ( (vt >= LAST_VARTYPE) ||
       ( VARTYPE_SIZE[vt] == VARTYPE_NOT_SUPPORTED ) )
    return NULL;

  /* Allocate memory for the array descriptor */
  if( FAILED( hRes = SafeArrayAllocDescriptor(cDims, &psa)))
    return NULL;

  /* setup data members... */
  psa->cDims     = cDims;
  psa->fFeatures = getFeatures(vt);
  psa->cLocks    = 0;
  psa->pvData    = NULL;
  psa->cbElements= VARTYPE_SIZE[vt];

  /* Invert the bounds ... */
  for(cDim=0; cDim < psa->cDims; cDim++) {
    psa->rgsabound[cDim].cElements = rgsabound[psa->cDims-cDim-1].cElements;
    psa->rgsabound[cDim].lLbound   = rgsabound[psa->cDims-cDim-1].lLbound;
  }

  /* allocate memory for the data... */
  if( FAILED( hRes = SafeArrayAllocData(psa))) {
    SafeArrayDestroyDescriptor(psa);
    ERR("() : Failed to allocate the Safe Array data\n");
    return NULL;
  }

  return(psa);
}

/*************************************************************************
 *		SafeArrayDestroyDescriptor (OLEAUT32.38)
 * Frees the memory associated with the descriptor.
 */
HRESULT WINAPI SafeArrayDestroyDescriptor(
  SAFEARRAY *psa)
{
  /* Check for lockness before to free... */
  if(psa->cLocks > 0)
    return DISP_E_ARRAYISLOCKED;

  /* The array is unlocked, then, deallocate memory */
  if(HeapFree( GetProcessHeap(), 0, psa) == FALSE)
    return E_UNEXPECTED;

  return(S_OK);
}


/*************************************************************************
 *		SafeArrayLock (OLEAUT32.21)
 * Increment the lock counter
 *
 * Doc says (MSDN Library ) that psa->pvData should be made available (!= NULL)
 * only when psa->cLocks is > 0... I don't get it since pvData is allocated
 * before the array is locked, therefore
 */
HRESULT WINAPI SafeArrayLock(
  SAFEARRAY *psa)
{
  if(! validArg(psa))
    return E_INVALIDARG;

  psa->cLocks++;

  return(S_OK);
}

/*************************************************************************
 *		SafeArrayUnlock (OLEAUT32.22)
 * Decrement the lock counter
 */
HRESULT WINAPI SafeArrayUnlock(
  SAFEARRAY *psa)
{
  if(! validArg(psa))
    return E_INVALIDARG;

  if (psa->cLocks > 0)
    psa->cLocks--;

  return(S_OK);
}


/*************************************************************************
 *		SafeArrayPutElement (OLEAUT32.26)
 * Set the data at the given coordinate
 */
HRESULT WINAPI SafeArrayPutElement(
  SAFEARRAY *psa,
  LONG      *rgIndices,
  void      *pv)
{
  ULONG stepCountInSAData     = 0;    /* Number of array item to skip to get to
                                         the desired one... */
  PVOID elementStorageAddress = NULL; /* Adress to store the data */

  /* Validate the index given */
  if(! validCoordinate(rgIndices, psa))
    return DISP_E_BADINDEX;
  if(! validArg(psa))
    return E_INVALIDARG;

  if( SafeArrayLock(psa) == S_OK) {

    /* Figure out the number of items to skip */
    stepCountInSAData = calcDisplacement(rgIndices, psa->rgsabound, psa->cDims);

    /* Figure out the number of byte to skip ... */
    elementStorageAddress = (char *) psa->pvData+(stepCountInSAData*psa->cbElements);

    if(isPointer(psa->fFeatures)) { /* increment ref count for this pointer */

      *((PVOID*)elementStorageAddress) = *(PVOID*)pv;
      IUnknown_AddRef( *(IUnknown**)pv);

    } else {

      if(psa->fFeatures == FADF_BSTR) { /* Create a new object */
        BSTR pbstrReAllocStr = NULL;
        if(pv &&
           ((pbstrReAllocStr = SYSDUPSTRING( (OLECHAR*)pv )) == NULL)) {
          SafeArrayUnlock(psa);
          return E_OUTOFMEMORY;
        } else
          *((BSTR*)elementStorageAddress) = pbstrReAllocStr;
      }
      else if(psa->fFeatures == FADF_VARIANT) {
        HRESULT hr = VariantCopy(elementStorageAddress, pv);
        if (FAILED(hr)) {
          SafeArrayUnlock(psa);
          return hr;
        }
      }
      else /* duplicate the memory */
        memcpy(elementStorageAddress, pv, SafeArrayGetElemsize(psa) );
    }

  } else {
    ERR("SafeArray: Cannot lock array....\n");
    return E_UNEXPECTED; /* UNDOC error condition */
  }

  TRACE("SafeArray: item put at adress %p.\n",elementStorageAddress);
  return SafeArrayUnlock(psa);
}


/*************************************************************************
 *		SafeArrayGetElement (OLEAUT32.25)
 * Return the data element corresponding the the given coordinate
 */
HRESULT WINAPI SafeArrayGetElement(
  SAFEARRAY *psa,
  LONG      *rgIndices,
  void      *pv)
{
  ULONG stepCountInSAData     = 0;    /* Number of array item to skip to get to
                                         the desired one... */
  PVOID elementStorageAddress = NULL; /* Adress to store the data */

  if(! validArg(psa))
    return E_INVALIDARG;

  if(! validCoordinate(rgIndices, psa)) /* Validate the index given */
    return(DISP_E_BADINDEX);

  if( SafeArrayLock(psa) == S_OK) {

    /* Figure out the number of items to skip */
    stepCountInSAData = calcDisplacement(rgIndices, psa->rgsabound, psa->cDims);

    /* Figure out the number of byte to skip ... */
    elementStorageAddress = (char *) psa->pvData+(stepCountInSAData*psa->cbElements);

    if( psa->fFeatures == FADF_BSTR) {           /* reallocate the obj */
      BSTR pbstrStoredStr = *(OLECHAR**)elementStorageAddress;
      BSTR pbstrReturnedStr = NULL;
      if( pbstrStoredStr &&
          ((pbstrReturnedStr = SYSDUPSTRING( pbstrStoredStr )) == NULL) ) {
        SafeArrayUnlock(psa);
        return E_OUTOFMEMORY;
      } else
        *((BSTR*)pv) = pbstrReturnedStr;
    }
    else if( psa->fFeatures == FADF_VARIANT) {
      HRESULT hr;
      VariantInit(pv);
      hr = VariantCopy(pv, elementStorageAddress);
      if (FAILED(hr)) {
        SafeArrayUnlock(psa);
        return hr;
      }
    }
    else if( isPointer(psa->fFeatures) )         /* simply copy the pointer */
      *(PVOID*)pv = *((PVOID*)elementStorageAddress);
    else                                         /* copy the bytes */
      memcpy(pv, elementStorageAddress, psa->cbElements );

  } else {
    ERR("SafeArray: Cannot lock array....\n");
    return E_UNEXPECTED; /* UNDOC error condition */
  }

  return( SafeArrayUnlock(psa) );
}

/*************************************************************************
 *		SafeArrayGetUBound (OLEAUT32.19)
 * return the UP bound for a given array dimension
 */
HRESULT WINAPI SafeArrayGetUBound(
  SAFEARRAY *psa,
  UINT    nDim,
  LONG      *plUbound)
{
  if(! validArg(psa))
    return E_INVALIDARG;

  if(nDim > psa->cDims)
    return DISP_E_BADINDEX;

  if(0 == nDim)
    return DISP_E_BADINDEX;

  *plUbound = psa->rgsabound[nDim-1].lLbound +
              psa->rgsabound[nDim-1].cElements - 1;

  return S_OK;
}

/*************************************************************************
 *		SafeArrayGetLBound (OLEAUT32.20)
 * Return the LO bound for a given array dimension
 */
HRESULT WINAPI SafeArrayGetLBound(
  SAFEARRAY *psa,
  UINT    nDim,
  LONG      *plLbound)
{
  if(! validArg(psa))
    return E_INVALIDARG;

  if(nDim > psa->cDims)
    return DISP_E_BADINDEX;

  if(0 == nDim)
    return DISP_E_BADINDEX;

  *plLbound = psa->rgsabound[nDim-1].lLbound;
  return S_OK;
}

/*************************************************************************
 *		SafeArrayGetDim (OLEAUT32.17)
 * returns the number of dimension in the array
 */
UINT WINAPI SafeArrayGetDim(
  SAFEARRAY * psa)
{
  /*
   * A quick test in Windows shows that the behavior here for an invalid
   * pointer is to return 0.
   */
  if(! validArg(psa))
    return 0;

  return psa->cDims;
}

/*************************************************************************
 *		SafeArrayGetElemsize (OLEAUT32.18)
 * Return the size of the element in the array
 */
UINT WINAPI SafeArrayGetElemsize(
  SAFEARRAY * psa)
{
  /*
   * A quick test in Windows shows that the behavior here for an invalid
   * pointer is to return 0.
   */
  if(! validArg(psa))
    return 0;

  return psa->cbElements;
}

/*************************************************************************
 *		SafeArrayAccessData (OLEAUT32.23)
 * increment the access count and return the data
 */
HRESULT WINAPI SafeArrayAccessData(
  SAFEARRAY *psa,
  void      **ppvData)
{
  HRESULT hRes;

  if(! validArg(psa))
    return E_INVALIDARG;

  hRes = SafeArrayLock(psa);

  switch (hRes) {
    case S_OK:
      (*ppvData) = psa->pvData;
      break;
    case E_INVALIDARG:
      (*ppvData) = NULL;
      return E_INVALIDARG;
  }

  return S_OK;
}


/*************************************************************************
 *		SafeArrayUnaccessData (OLEAUT32.24)
 * Decrement the access count
 */
HRESULT WINAPI SafeArrayUnaccessData(
  SAFEARRAY * psa)
{
  if(! validArg(psa))
    return E_INVALIDARG;

  return(SafeArrayUnlock(psa));
}

/************************************************************************
 *		SafeArrayPtrOfIndex (OLEAUT32.148)
 * Return a pointer to the element at rgIndices
 */
HRESULT WINAPI SafeArrayPtrOfIndex(
  SAFEARRAY *psa,
  LONG      *rgIndices,
  void      **ppvData)
{
  ULONG stepCountInSAData     = 0;    /* Number of array item to skip to get to
                                         the desired one... */

  if(! validArg(psa))
    return E_INVALIDARG;

  if(! validCoordinate(rgIndices, psa))
    return DISP_E_BADINDEX;

  /* Although it is dangerous to do this without having a lock, it is not
   * illegal.  Microsoft do warn of the danger.
   */

  /* Figure out the number of items to skip */
  stepCountInSAData = calcDisplacement(rgIndices, psa->rgsabound, psa->cDims);

  *ppvData = (char *) psa->pvData+(stepCountInSAData*psa->cbElements);

  return S_OK;
}

/************************************************************************
 *		SafeArrayDestroyData (OLEAUT32.39)
 * Frees the memory data bloc
 */
HRESULT WINAPI SafeArrayDestroyData(
  SAFEARRAY *psa)
{
  HRESULT  hRes;
  ULONG    ulWholeArraySize; /* count spot in array  */
  ULONG    ulDataIter;       /* to iterate the data space */

  if(! validArg(psa))
    return E_INVALIDARG;

  if(psa->cLocks > 0)
    return DISP_E_ARRAYISLOCKED;

  ulWholeArraySize = getArraySize(psa);

  if(isPointer(psa->fFeatures)) {           /* release the pointers */
    IUnknown *punk;

    for(ulDataIter=0; ulDataIter < ulWholeArraySize; ulDataIter++) {
      punk = *(IUnknown**)((char *) psa->pvData+(ulDataIter*(psa->cbElements)));

      if( punk != NULL)
        IUnknown_Release(punk);
    }

  }
  else if(psa->fFeatures & FADF_BSTR) {  /* deallocate the obj */
    BSTR bstr;

    for(ulDataIter=0; ulDataIter < ulWholeArraySize; ulDataIter++) {
      bstr = *(BSTR*)((char *) psa->pvData+(ulDataIter*(psa->cbElements)));

      if( bstr != NULL)
        SysFreeString( bstr );
    }
  }
  else if(psa->fFeatures & FADF_VARIANT) {  /* deallocate the obj */

    for(ulDataIter=0; ulDataIter < ulWholeArraySize; ulDataIter++) {
      VariantClear((VARIANT*)((char *) psa->pvData+(ulDataIter*(psa->cbElements))));
    }
  }

  /* check if this array is a Vector, in which case do not free the data
     block since it has been allocated by AllocDescriptor and therefore
     deserve to be freed by DestroyDescriptor */
  if(!(psa->fFeatures & FADF_CREATEVECTOR)) { /* Set when we do CreateVector */

    /* free the whole chunk */
    if((hRes = HeapFree( GetProcessHeap(), 0, psa->pvData)) == 0) /*falied*/
      return E_UNEXPECTED; /* UNDOC error condition */

    psa->pvData = NULL;
  }

  return S_OK;
}

/************************************************************************
 *		SafeArrayCopyData (OLEAUT32.412)
 * Copy the psaSource's data block into psaTarget if dimension and size
 * permits it.
 */
HRESULT WINAPI SafeArrayCopyData(
  SAFEARRAY *psaSource,
  SAFEARRAY **psaTarget)
{
  USHORT   cDimCount;        /* looper */
  LONG     lDelta;           /* looper */
  IUnknown *punk;
  ULONG    ulWholeArraySize; /* Number of item in SA */
  BSTR   bstr;

  if(! (validArg(psaSource) && validArg(*psaTarget)) )
    return E_INVALIDARG;

  if(SafeArrayGetDim(psaSource) != SafeArrayGetDim(*psaTarget))
    return E_INVALIDARG;

  ulWholeArraySize = getArraySize(psaSource);

  /* The two arrays boundaries must be of same lenght */
  for(cDimCount=0;cDimCount < psaSource->cDims; cDimCount++)
    if( psaSource->rgsabound[cDimCount].cElements !=
      (*psaTarget)->rgsabound[cDimCount].cElements)
      return E_INVALIDARG;

  if( isPointer((*psaTarget)->fFeatures) ) {         /* the target contains ptr
                                                        that must be released */
    for(lDelta=0;lDelta < ulWholeArraySize; lDelta++) {
      punk = *(IUnknown**)
        ((char *) (*psaTarget)->pvData + (lDelta * (*psaTarget)->cbElements));

      if( punk != NULL)
        IUnknown_Release(punk);
    }

  }
  else if( (*psaTarget)->fFeatures & FADF_BSTR) {    /* the target contain BSTR
                                                        that must be freed */
    for(lDelta=0;lDelta < ulWholeArraySize; lDelta++) {
      bstr =
        *(BSTR*)((char *) (*psaTarget)->pvData + (lDelta * (*psaTarget)->cbElements));

      if( bstr != NULL)
        SysFreeString( bstr );
    }
  }
  else if( (*psaTarget)->fFeatures & FADF_VARIANT) {

    for(lDelta=0;lDelta < ulWholeArraySize; lDelta++) {
      VariantClear((VARIANT*)((char *) (*psaTarget)->pvData + (lDelta * (*psaTarget)->cbElements)));
    }
  }

  return duplicateData(psaSource, psaTarget);
}

/************************************************************************
 *		SafeArrayDestroy (OLEAUT32.16)
 * Deallocates all memory reserved for the SafeArray
 */
HRESULT WINAPI SafeArrayDestroy(
  SAFEARRAY * psa)
{
  HRESULT hRes;

  if(! validArg(psa))
    return E_INVALIDARG;

  if(psa->cLocks > 0)
    return DISP_E_ARRAYISLOCKED;

  if((hRes = SafeArrayDestroyData( psa )) == S_OK)
    if((hRes = SafeArrayDestroyDescriptor( psa )) == S_OK)
      return S_OK;

  return E_UNEXPECTED; /* UNDOC error condition */
}

/************************************************************************
 *		SafeArrayCopy (OLEAUT32.27)
 * Make a dupplicate of a SafeArray
 */
HRESULT WINAPI SafeArrayCopy(
  SAFEARRAY *psa,
  SAFEARRAY **ppsaOut)
{
  HRESULT hRes;
  DWORD   dAllocSize;
  ULONG   ulWholeArraySize; /* size of the thing */

  if(! validArg(psa))
    return E_INVALIDARG;

  if((hRes=SafeArrayAllocDescriptor(psa->cDims, ppsaOut)) == S_OK){

    /* Duplicate the SAFEARRAY struc */
    memcpy(*ppsaOut, psa,
            sizeof(*psa)+(sizeof(*(psa->rgsabound))*(psa->cDims-1)));

    (*ppsaOut)->pvData = NULL; /* do not point to the same data area */

    /* make sure the new safe array doesn't have the FADF_CREATEVECTOR flag,
       because the data has not been allocated with the descriptor. */
    (*ppsaOut)->fFeatures &= ~FADF_CREATEVECTOR;

    /* Get the allocated memory size for source and allocate it for target */
    ulWholeArraySize = getArraySize(psa); /* Number of item in SA */
    dAllocSize = ulWholeArraySize*psa->cbElements;

    (*ppsaOut)->pvData =
      HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dAllocSize);
    if( (*ppsaOut)->pvData != NULL) {   /* HeapAlloc succeed */

      if( (hRes=duplicateData(psa, ppsaOut)) != S_OK) { /* E_OUTOFMEMORY */
        HeapFree(GetProcessHeap(), 0, (*ppsaOut)->pvData);
        (*ppsaOut)->pvData = NULL;
        SafeArrayDestroyDescriptor(*ppsaOut);
        return hRes;
      }

    } else { /* failed to allocate or dupplicate... */
      SafeArrayDestroyDescriptor(*ppsaOut);
      return E_UNEXPECTED; /* UNDOC error condition */
    }
  } else { /* failed to allocate mem for descriptor */
    return E_OUTOFMEMORY; /* UNDOC error condiftion */
  }

  return S_OK;
}

/************************************************************************
 *		SafeArrayCreateVector (OLEAUT32.411)
 * Creates a one dimension safearray where the data is next to the
 * SAFEARRAY structure.
 */
SAFEARRAY* WINAPI SafeArrayCreateVector(
  VARTYPE vt,
  LONG    lLbound,
  ULONG   cElements)
{
  SAFEARRAY *psa;

  /* Validate supported VARTYPE */
  if ( (vt >= LAST_VARTYPE) ||
       ( VARTYPE_SIZE[vt] == VARTYPE_NOT_SUPPORTED ) )
    return NULL;

  /* Allocate memory for the array descriptor and data contiguously  */
  if( FAILED( psa = HeapAlloc( GetProcessHeap(),
                      HEAP_ZERO_MEMORY,
                      (sizeof(*psa) + (VARTYPE_SIZE[vt] * cElements))))) {
    return NULL;
  }

  /* setup data members... */
  psa->cDims      = 1; /* always and forever */
  psa->fFeatures  = getFeatures(vt) | FADF_CREATEVECTOR;  /* undocumented flag used by Microsoft */
  psa->cLocks     = 0;
  psa->pvData     = (BYTE*)psa + sizeof(*psa);
  psa->cbElements = VARTYPE_SIZE[vt];

  psa->rgsabound[0].cElements = cElements;
  psa->rgsabound[0].lLbound   = lLbound;

  return(psa);
}

/************************************************************************
 *		SafeArrayRedim (OLEAUT32.40)
 * Changes the caracteristics of the last dimension of the SafeArray
 */
HRESULT WINAPI SafeArrayRedim(
  SAFEARRAY      *psa,
  SAFEARRAYBOUND *psaboundNew)
{
  LONG   lDelta;  /* hold difference in size */
  USHORT cDims=1; /* dims counter */

  if( !validArg(psa) )
    return E_INVALIDARG;

  if( psa->cLocks > 0 )
    return DISP_E_ARRAYISLOCKED;

  if( psa->fFeatures & FADF_FIXEDSIZE )
    return E_INVALIDARG;

  if( SafeArrayLock(psa)==E_UNEXPECTED )
    return E_UNEXPECTED;/* UNDOC error condition */

  /* find the delta in number of array spot to apply to the new array */
  lDelta = psaboundNew->cElements - psa->rgsabound[0].cElements;
  for(; cDims < psa->cDims; cDims++)
    /* delta in number of spot implied by modifying the last dimension */
    lDelta *= psa->rgsabound[cDims].cElements;

  TRACE("elements=%ld, Lbound=%ld (delta=%ld)\n", psaboundNew->cElements, psaboundNew->lLbound, lDelta);

  if (lDelta == 0) { ;/* same size, maybe a change of lLbound, just set it */

  } else /* need to enlarge (lDelta +) reduce (lDelta -) */
    if(! resizeSafeArray(psa, lDelta))
      return E_UNEXPECTED; /* UNDOC error condition */

  /* the only modifyable dimension sits in [0] as the dimensions were reversed
     at array creation time... */
  psa->rgsabound[0].cElements = psaboundNew->cElements;
  psa->rgsabound[0].lLbound   = psaboundNew->lLbound;

  return SafeArrayUnlock(psa);
}

/************************************************************************
 * NOT WINDOWS API - SafeArray* Utility functions
 ************************************************************************/

/************************************************************************
 * Used to validate the SAFEARRAY type of arg
 */
static BOOL validArg(
  SAFEARRAY *psa)
{
  SAFEARRAYBOUND *sab;
  LONG psaSize  = 0;
  LONG descSize = 0;
  LONG fullSize = 0;

  /*
   * Let's check for the null pointer just in case.
   */
  if (psa == NULL)
    return FALSE;

  /* Check whether the size of the chunk makes sense... That's the only thing
     I can think of now... */

  psaSize = HeapSize(GetProcessHeap(), 0, psa);
  if (psaSize == -1)
    /* uh, foreign heap. Better don't mess with it ! */
    return TRUE;

  /* size of the descriptor when the SA is not created with CreateVector */
  descSize = sizeof(*psa) + (sizeof(*sab) * (psa->cDims-1));

  /* size of the descriptor + data when created with CreateVector */
  fullSize = sizeof(*psa) + (psa->cbElements * psa->rgsabound[0].cElements);

  return((psaSize >= descSize) || (psaSize >= fullSize));
}

/************************************************************************
 * Used to reallocate memory
 */
static BOOL resizeSafeArray(
  SAFEARRAY *psa,
  LONG lDelta)
{
  ULONG    ulWholeArraySize;  /* use as multiplicator */
  PVOID    pvNewBlock = NULL;
  IUnknown *punk;
  BSTR   bstr;

  ulWholeArraySize = getArraySize(psa);

  if(lDelta < 0) {                    /* array needs to be shorthen  */
    if( isPointer(psa->fFeatures))    /* ptr that need to be released */
      for(;lDelta < 0; lDelta++) {
	      punk = *(IUnknown**)
          ((char *) psa->pvData+((ulWholeArraySize+lDelta)*psa->cbElements));

        if( punk != NULL )
          IUnknown_Release(punk);
	    }

    else if(psa->fFeatures & FADF_BSTR)  /* BSTR that need to be freed */
      for(;lDelta < 0; lDelta++) {
        bstr = *(BSTR*)
          ((char *) psa->pvData+((ulWholeArraySize+lDelta)*psa->cbElements));

        if( bstr != NULL )
          SysFreeString( bstr );
      }
    else if(psa->fFeatures & FADF_VARIANT)
      for(;lDelta < 0; lDelta++) {
        VariantClear((VARIANT*)((char *) psa->pvData+((ulWholeArraySize+lDelta)*psa->cbElements)));
      }
  }

  if (!(psa->fFeatures & FADF_CREATEVECTOR))
  {
    /* Ok now, if we are enlarging the array, we *MUST* move the whole block
       pointed to by pvData.   If we are shorthening the array, this move is
       optional but we do it anyway becuase the benefit is that we are
       releasing to the system the unused memory */

    if((pvNewBlock = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, psa->pvData,
       (ulWholeArraySize + lDelta) * psa->cbElements)) == NULL)
        return FALSE; /* TODO If we get here it means:
                         SHRINK situation :  we've deleted the undesired
                                             data and did not release the memory
                         GROWING situation:  we've been unable to grow the array
                      */
  }
  else
  {
    /* Allocate a new block, because the previous data has been allocated with
       the descriptor in SafeArrayCreateVector function. */

    if((pvNewBlock = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
       ulWholeArraySize * psa->cbElements)) == NULL)
        return FALSE;

    psa->fFeatures &= ~FADF_CREATEVECTOR;
  }
  /* reassign to the new block of data */
  psa->pvData = pvNewBlock;
  return TRUE;
}

/************************************************************************
 * Used to set the fFeatures data member of the SAFEARRAY structure.
 */
static INT getFeatures(
  VARTYPE vt)
{
  switch(vt) {
    case VT_BSTR:      return FADF_BSTR;
    case VT_UNKNOWN:   return FADF_UNKNOWN;
    case VT_DISPATCH:  return FADF_DISPATCH;
    case VT_VARIANT:   return FADF_VARIANT;
  }
  return 0;
}

/************************************************************************
 * Used to figure out if the fFeatures data member of the SAFEARRAY
 * structure contain any information about the type of data stored...
 */
static BOOL isPointer(
  USHORT feature)
{
  switch(feature) {
    case FADF_UNKNOWN:  return TRUE; /* those are pointers */
    case FADF_DISPATCH: return TRUE;
  }
  return FALSE;
}

/************************************************************************
 * Used to calculate the displacement when accessing or modifying
 * safearray data set.
 *
 *  Parameters: - LONG *coor is the desired location in the multidimension
 *              table.  Ex for a 3 dim table: coor[] = {1,2,3};
 *              - ULONG *mat is the format of the table.  Ex for a 3 dim
 *              table mat[] = {4,4,4};
 *              - USHORT dim is the number of dimension of the SafeArray
 */
static ULONG calcDisplacement(
  LONG           *coor,
  SAFEARRAYBOUND *mat,
  LONG           dim)
{
  ULONG res = 0;
  LONG  iterDim;

  for(iterDim=0; iterDim<dim; iterDim++)
    /* the -mat[dim] bring coor[dim] relative to 0 for calculation */
    res += ((coor[iterDim]-mat[iterDim].lLbound) *
            endOfDim(coor, mat, iterDim+1, dim));

  TRACE("SafeArray: calculated displacement is %lu.\n", res);
  return(res);
}

/************************************************************************
 * Recursivity agent for calcDisplacement method.  Used within Put and
 * Get methods.
 */
static INT endOfDim(
  LONG           *coor,
  SAFEARRAYBOUND *mat,
  LONG           dim,
  LONG           realDim)
{
  if(dim==realDim)
    return 1;
  else
    return (endOfDim(coor, mat, dim+1, realDim) * mat[dim].cElements);
}


/************************************************************************
 * Method used to validate the coordinate received in Put and Get
 * methods.
 */
static BOOL validCoordinate(
  LONG      *coor,
  SAFEARRAY *psa)
{
  INT   iter=0;
  LONG    lUBound;
  LONG    lLBound;
  HRESULT hRes;

  if (!psa->cDims) return FALSE;
  for(; iter<psa->cDims; iter++) {
    TRACE("coor[%d]=%ld\n", iter, coor[iter]);
    if((hRes = SafeArrayGetLBound(psa, (iter+1), &lLBound)) != S_OK)
      return FALSE;
    if((hRes = SafeArrayGetUBound(psa, (iter+1), &lUBound)) != S_OK)
      return FALSE;

    if(lLBound > lUBound)
      return FALSE;

    if((coor[iter] < lLBound) || (coor[iter] > lUBound))
      return FALSE;
  }
  return TRUE;
}

/************************************************************************
 * Method used to calculate the number of cells of the SA
 */
static ULONG getArraySize(
  SAFEARRAY *psa)
{
  USHORT cCount;
  ULONG  ulWholeArraySize = 1;

  for(cCount=0; cCount < psa->cDims; cCount++) /* foreach dimensions... */
    ulWholeArraySize *= psa->rgsabound[cCount].cElements;

  return ulWholeArraySize;
}


/************************************************************************
 * Method used to handle data space dupplication for Copy32 and CopyData32
 */
static HRESULT duplicateData(
  SAFEARRAY *psa,
  SAFEARRAY **ppsaOut)
{
  ULONG    ulWholeArraySize; /* size of the thing */
  LONG     lDelta;

  ulWholeArraySize = getArraySize(psa); /* Number of item in SA */

  SafeArrayLock(*ppsaOut);

  if( isPointer(psa->fFeatures) ) {  /* If datatype is object increment
                                        object's reference count */
    IUnknown *punk;

    for(lDelta=0; lDelta < ulWholeArraySize; lDelta++) {
      punk = *(IUnknown**)((char *) psa->pvData+(lDelta * psa->cbElements));

      if( punk != NULL)
        IUnknown_AddRef(punk);
    }

    /* Copy the source array data into target array */
    memcpy((*ppsaOut)->pvData, psa->pvData,
      ulWholeArraySize*psa->cbElements);

  }
  else if( psa->fFeatures & FADF_BSTR ) { /* if datatype is BSTR allocate
                                             the BSTR in the new array */
    BSTR   pbstrReAllocStr = NULL;

    for(lDelta=0; lDelta < ulWholeArraySize; lDelta++) {
      if(( pbstrReAllocStr = SYSDUPSTRING(
            *(BSTR*)((char *) psa->pvData+(lDelta * psa->cbElements)))) == NULL) {

        SafeArrayUnlock(*ppsaOut);
        return E_OUTOFMEMORY;
      }

      *((BSTR*)((char *) (*ppsaOut)->pvData+(lDelta * psa->cbElements))) =
        pbstrReAllocStr;
    }

  }
  else if( psa->fFeatures & FADF_VARIANT ) {

    for(lDelta=0; lDelta < ulWholeArraySize; lDelta++) {
      VariantCopy((VARIANT*)((char *) (*ppsaOut)->pvData+(lDelta * psa->cbElements)),
                  (VARIANT*)((char *) psa->pvData+(lDelta * psa->cbElements)));
    }

  }
  else { /* Simply copy the source array data into target array */

    memcpy((*ppsaOut)->pvData, psa->pvData,
      ulWholeArraySize*psa->cbElements);
  }

  SafeArrayUnlock(*ppsaOut);

  return S_OK;
}


/************************************************************************
 *		SafeArrayGetVartype (OLEAUT32.77)
 * Returns the VARTYPE stored in the given safearray
 */
HRESULT WINAPI SafeArrayGetVartype(
  SAFEARRAY* psa,
  VARTYPE*   pvt)
{
  HRESULT hr = E_INVALIDARG;
  VARTYPE vt = VT_EMPTY;

  /* const short VARTYPE_OFFSET = -4; */

  if (psa->fFeatures & FADF_HAVEVARTYPE)
  {
    /* VT tag @ negative offset 4 in the array descriptor */
    FIXME("Returning VT_BSTR instead of VT_...\n");
    vt = VT_BSTR;
  }
  else if (psa->fFeatures & FADF_RECORD)
  {
    vt = VT_RECORD;
  }
  else if (psa->fFeatures & FADF_BSTR)
  {
    vt = VT_BSTR;
  }
  else if (psa->fFeatures & FADF_UNKNOWN)
  {
    vt = VT_UNKNOWN;
  }
  else if (psa->fFeatures & FADF_DISPATCH)
  {
    vt = VT_DISPATCH;
  }
  else if (psa->fFeatures & FADF_VARIANT)
  {
    vt = VT_VARIANT;
  }

  if (vt != VT_EMPTY)
  {
    *pvt = vt;
    hr = S_OK;
  }

  TRACE("HRESULT = %08lx\n", hr);
  return hr;
}
