/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the IDispatch implementation for XPConnect.
 *
 * The Initial Developer of the Original Code is
 * David Bradley.
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * \file XPCDispTypeInfo.cpp
 * Is the implementation for XPCDispTypeInfo and supporting classes
 */

/**
 * \file XPCDispTypeInfo.cpp 
 * \brief implementation of XPCDispTypeInfo
 * This file contains the implementations for XPCDispTypeInfo class and
 * associated helper classes and functions
 */

#include "xpcprivate.h"

// There's no CPP for XPCDispNameArray so it's just plopped here
const nsString XPCDispNameArray::sEmpty;

/**
 * Helper function that initializes the element description
 * @param vt The type of variant
 * @param paramFlags flats for the type of parameter, in, out, etc.
 * @param elemDesc The element being initialized
 */
inline
void FillOutElemDesc(VARTYPE vt, PRUint16 paramFlags, ELEMDESC & elemDesc)
{
    elemDesc.tdesc.vt = vt;
    elemDesc.paramdesc.wParamFlags = paramFlags;
    elemDesc.paramdesc.pparamdescex = 0;
    elemDesc.tdesc.lptdesc = 0;
    elemDesc.tdesc.lpadesc = 0;
    elemDesc.tdesc.hreftype = 0;
}

XPCDispTypeInfo::~XPCDispTypeInfo()
{
    delete mIDArray;
}

void XPCDispJSPropertyInfo::GetReturnType(XPCCallContext& ccx, ELEMDESC & elemDesc)
{
    VARTYPE vt;
    if(IsSetter())      // if this is a setter
    {
        vt = VT_EMPTY;
    }
    else if(IsProperty())   // If this is a property
    {
        vt = XPCDispConvert::JSTypeToCOMType(ccx, mProperty);
    }
    else                    // if this is a function
    {
        vt = VT_VARIANT;
    }
    FillOutElemDesc(vt, PARAMFLAG_FRETVAL, elemDesc);
}

ELEMDESC* XPCDispJSPropertyInfo::GetParamInfo()
{
    PRUint32 paramCount = GetParamCount();
    ELEMDESC* elemDesc;
    if(paramCount != 0)
    {
        elemDesc = new ELEMDESC[paramCount];
        if(elemDesc)
        {
            for(PRUint32 index = 0; index < paramCount; ++index)
            {
                FillOutElemDesc(VT_VARIANT, PARAMFLAG_FIN, elemDesc[index]);
            }
        }
    }
    else
        elemDesc = 0;
    // Caller becomes owner
    return elemDesc;
}

XPCDispJSPropertyInfo::XPCDispJSPropertyInfo(JSContext* cx, PRUint32 memid, 
                                             JSObject* obj, jsval val) : 
    mPropertyType(INVALID), mMemID(memid)
{
    PRUint32 len;
    jschar * chars = xpc_JSString2String(cx, val, &len);
    if(!chars)
        return;

    mName = nsString(NS_REINTERPRET_CAST(const PRUnichar *, chars), len);
    JSBool found;
    uintN attr;
    // Get the property's attributes, and make sure it's found and enumerable 
    if(!JS_GetUCPropertyAttributes(cx, obj, chars, len, &attr, &found) || 
        !found || (attr & JSPROP_ENUMERATE) == 0)
        return;

    // Retrieve the property 
    if(!chars || !JS_GetUCProperty(cx, obj, chars, len, &mProperty) ||
        JSVAL_IS_NULL(mProperty))
        return;

    // If this is a function
    if(JSVAL_IS_OBJECT(mProperty) && 
        JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(mProperty)))
    {
        mPropertyType = FUNCTION;
        JSObject * funcObj = JSVAL_TO_OBJECT(mProperty);
        JSIdArray * funcObjArray = JS_Enumerate(cx, funcObj);
        if(funcObjArray)
        {
            mParamCount = funcObjArray->length;
        }
    }
    else // It's a property
    {
        mParamCount = 0;
        if((attr & JSPROP_READONLY) != 0)
        {
            mPropertyType = READONLY_PROPERTY;
        }
        else
        {
            mPropertyType = PROPERTY;
        }
    }
}

PRBool XPCDispTypeInfo::FuncDescArray::BuildFuncDesc(XPCCallContext& ccx, JSObject* obj, 
                                     XPCDispJSPropertyInfo & propInfo)
{
    // While this memory is passed out, we're ultimately responsible to free it
    FUNCDESC* funcDesc = new FUNCDESC;
    if(!funcDesc)
        return PR_FALSE;
    mArray.AppendElement(funcDesc);
    // zero is reserved
    funcDesc->memid = propInfo.GetMemID();
    funcDesc->lprgscode = 0; // Not used (for 16 bit systems)
    funcDesc->funckind = FUNC_DISPATCH;
    funcDesc->invkind = propInfo.GetInvokeKind();
    funcDesc->callconv = CC_STDCALL;
    funcDesc->cParams = propInfo.GetParamCount();
    funcDesc->lprgelemdescParam = propInfo.GetParamInfo();
    // We might want to make all parameters optional since JS can handle that
    funcDesc->cParamsOpt = 0; // Optional parameters
    // This could be a problem, supposed to be used for functions of
    // type FUNC_VIRTUAL which we aren't, so zero should be ok
    funcDesc->oVft = 0;         // Specifies offset in VTBL for FUNC_VIRTUAL
    funcDesc->cScodes = 0;      // Counts the permitted return values.
    funcDesc->wFuncFlags = 0;   // Indicates the FUNCFLAGS of a function.
    propInfo.GetReturnType(ccx, funcDesc->elemdescFunc);
    return PR_TRUE;
}

XPCDispTypeInfo::FuncDescArray::FuncDescArray(XPCCallContext& ccx, JSObject* obj, const XPCDispIDArray& array, XPCDispNameArray & names)
{
    PRUint32 size = array.Length();
    names.SetSize(size);
    PRUint32 memid = 0;
    JSContext* cx = ccx;
    // Initialize each function description in the array
    for(PRUint32 index = 0; index < size; ++index)
    {
        XPCDispJSPropertyInfo propInfo(cx, ++memid, obj, array.Item(cx, index));
        names.SetName(index + 1, propInfo.GetName());
        if(!BuildFuncDesc(ccx, obj, propInfo))
            return;
        // non-readonly Properties get two function descriptions
        if(propInfo.IsProperty() && !propInfo.IsReadOnly())
        {
            propInfo.SetSetter();
            if(!BuildFuncDesc(ccx, obj, propInfo))
                return;
        }
    }
}

XPCDispTypeInfo::FuncDescArray::~FuncDescArray()
{
    PRUint32 size = mArray.Count();
    for(PRUint32 index = 0; index < size; ++index)
    {
        FUNCDESC* funcDesc = NS_REINTERPRET_CAST(FUNCDESC*,mArray.ElementAt(index));
        delete [] funcDesc->lprgelemdescParam;
        delete funcDesc;
    }
}

XPCDispTypeInfo::XPCDispTypeInfo(XPCCallContext& ccx, JSObject* obj, 
                               XPCDispIDArray* array) :
    mRefCnt(0), mJSObject(obj), mIDArray(array), 
    mFuncDescArray(ccx, obj, *array, mNameArray)
{
}

XPCDispTypeInfo * XPCDispTypeInfo::New(XPCCallContext& ccx, JSObject* obj)
{
    XPCDispTypeInfo * pTypeInfo = 0;
    JSIdArray * jsArray = JS_Enumerate(ccx, obj);
    if(!jsArray)
        return nsnull;
    XPCDispIDArray* array = new XPCDispIDArray(ccx, jsArray);
    if(!array)
        return nsnull;
    pTypeInfo = new XPCDispTypeInfo(ccx, obj, array);
    if(!pTypeInfo)
    {
        delete array;
        return nsnull;
    }
    NS_ADDREF(pTypeInfo);
    return pTypeInfo;
}

NS_COM_MAP_BEGIN(XPCDispTypeInfo)
    NS_COM_MAP_ENTRY(ITypeInfo)
    NS_COM_MAP_ENTRY(IUnknown)
NS_COM_MAP_END

NS_IMPL_THREADSAFE_ADDREF(XPCDispTypeInfo)
NS_IMPL_THREADSAFE_RELEASE(XPCDispTypeInfo)


STDMETHODIMP XPCDispTypeInfo::GetTypeAttr( 
    /* [out] */ TYPEATTR __RPC_FAR *__RPC_FAR *ppTypeAttr)
{
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::GetTypeComp(
    /* [out] */ ITypeComp __RPC_FAR *__RPC_FAR *ppTComp)
{
    return E_NOTIMPL;
}

STDMETHODIMP XPCDispTypeInfo::GetFuncDesc(
        /* [in] */ UINT index,
        /* [out] */ FUNCDESC __RPC_FAR *__RPC_FAR *ppFuncDesc)
{
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::GetVarDesc(
        /* [in] */ UINT index,
        /* [out] */ VARDESC __RPC_FAR *__RPC_FAR *ppVarDesc)
{
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::GetNames(
        /* [in] */ MEMBERID memid,
        /* [length_is][size_is][out] */ BSTR __RPC_FAR *rgBstrNames,
        /* [in] */ UINT cMaxNames,
        /* [out] */ UINT __RPC_FAR *pcNames)
{
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::GetRefTypeOfImplType(
        /* [in] */ UINT index,
        /* [out] */ HREFTYPE __RPC_FAR *pRefType)
{
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::GetImplTypeFlags(
        /* [in] */ UINT index,
        /* [out] */ INT __RPC_FAR *pImplTypeFlags)
{
    return E_NOTIMPL;
}

STDMETHODIMP XPCDispTypeInfo::GetIDsOfNames(
        /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
        /* [in] */ UINT cNames,
        /* [size_is][out] */ MEMBERID __RPC_FAR *pMemId)
{
    // lookup each name
    for(UINT index = 0; index < cNames; ++index)
    {
        nsDependentString buffer(NS_STATIC_CAST(const PRUnichar *,
                                 rgszNames[index]),
                                 wcslen(rgszNames[index]));
        pMemId[index] = mNameArray.Find(buffer);

    }
    return S_OK;
}

STDMETHODIMP XPCDispTypeInfo::Invoke(
        /* [in] */ PVOID pvInstance,
        /* [in] */ MEMBERID memid,
        /* [in] */ WORD wFlags,
        /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
        /* [out] */ VARIANT __RPC_FAR *pVarResult,
        /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
        /* [out] */ UINT __RPC_FAR *puArgErr)
{
    CComQIPtr<IDispatch> pDisp(NS_REINTERPRET_CAST(IUnknown*,pvInstance));
    if(pDisp)
    {
        return pDisp->Invoke(memid, IID_NULL, LOCALE_SYSTEM_DEFAULT, wFlags, 
                               pDispParams, pVarResult, pExcepInfo, puArgErr);
    }
    return E_NOINTERFACE;
}
    
STDMETHODIMP XPCDispTypeInfo::GetDocumentation(
        /* [in] */ MEMBERID memid,
        /* [out] */ BSTR __RPC_FAR *pBstrName,
        /* [out] */ BSTR __RPC_FAR *pBstrDocString,
        /* [out] */ DWORD __RPC_FAR *pdwHelpContext,
        /* [out] */ BSTR __RPC_FAR *pBstrHelpFile)
{
    PRUint32 index = memid;
    if(index < mIDArray->Length())
        return E_FAIL;

    XPCCallContext ccx(NATIVE_CALLER);
    PRUnichar * chars = xpc_JSString2PRUnichar(ccx, mIDArray->Item(ccx, index));
    if(!chars)
    {
        return E_FAIL;
    }
    CComBSTR name(chars);
    *pBstrName = name.Detach();
    pBstrDocString = 0;
    pdwHelpContext = 0;
    pBstrHelpFile = 0;
    return S_OK;
}
    
STDMETHODIMP XPCDispTypeInfo::GetDllEntry(
        /* [in] */ MEMBERID memid,
        /* [in] */ INVOKEKIND invKind,
        /* [out] */ BSTR __RPC_FAR *pBstrDllName,
        /* [out] */ BSTR __RPC_FAR *pBstrName,
        /* [out] */ WORD __RPC_FAR *pwOrdinal)
{
    // We are not supporting this till a need arrises
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::GetRefTypeInfo(
        /* [in] */ HREFTYPE hRefType,
        /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo)
{
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::AddressOfMember(
        /* [in] */ MEMBERID memid,
        /* [in] */ INVOKEKIND invKind,
        /* [out] */ PVOID __RPC_FAR *ppv)
{
    // We are not supporting this till a need arrises
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::CreateInstance(
        /* [in] */ IUnknown __RPC_FAR *pUnkOuter,
        /* [in] */ REFIID riid,
        /* [iid_is][out] */ PVOID __RPC_FAR *ppvObj)
{
    // We are not supporting this till a need arrises
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::GetMops(
        /* [in] */ MEMBERID memid,
        /* [out] */ BSTR __RPC_FAR *pBstrMops)
{
    return E_NOTIMPL;
}
    
STDMETHODIMP XPCDispTypeInfo::GetContainingTypeLib(
        /* [out] */ ITypeLib __RPC_FAR *__RPC_FAR *ppTLib,
        /* [out] */ UINT __RPC_FAR *pIndex)
{
    // We are not supporting this till a need arrises
    return E_NOTIMPL;
}
    
void STDMETHODCALLTYPE XPCDispTypeInfo::ReleaseTypeAttr( 
    /* [in] */ TYPEATTR __RPC_FAR *pTypeAttr)
{
    // Nothing for us to do
}

void STDMETHODCALLTYPE XPCDispTypeInfo::ReleaseFuncDesc( 
    /* [in] */ FUNCDESC __RPC_FAR *pFuncDesc)
{
    // Nothing for us to do
}

void STDMETHODCALLTYPE XPCDispTypeInfo::ReleaseVarDesc( 
    /* [in] */ VARDESC __RPC_FAR *pVarDesc)
{
    // Nothing for us to do
}

XPCDispIDArray::XPCDispIDArray(XPCCallContext& ccx, JSIdArray* array) : 
    mMarked(JS_FALSE), mIDArray(array->length)
{
    // copy the JS ID Array to our internal array
    for(jsint index = 0; index < array->length; ++index)
    {
        mIDArray.ReplaceElementAt(NS_REINTERPRET_CAST(void*,
                                                      array->vector[index]), 
                                  index);
    }   
}

void XPCDispIDArray::Mark()
{
    // If already marked nothing to do
    if(IsMarked())
        return;
    mMarked = JS_TRUE;
    XPCCallContext ccx(NATIVE_CALLER);
    // Bail if our call context is bad
    if(!ccx.IsValid())
        return;

    PRInt32 count = Length();
    jsval val;
    JSContext* cx = ccx;
    // Iterate each of the ID's and mark them
    for(PRInt32 index = 0; index < count; ++index)
    {
        if(JS_IdToValue(cx,
                        NS_REINTERPRET_CAST(jsid,
                                            mIDArray.ElementAt(index)),
                        &val) &&
            JSVAL_IS_GCTHING(val))
        {
            JS_MarkGCThing(cx, NS_REINTERPRET_CAST(void*,val),
                           nsnull, nsnull);
        }
    }
}

