/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set ts=4 sw=4 sts=4 ci et: */
/*
 *
 * ***** 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 mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of 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 *****
 *
 * This Original Code has been modified by IBM Corporation.
 * Modifications made by IBM described herein are
 * Copyright (c) International Business Machines
 * Corporation, 2000
 *
 * Modifications to Mozilla code or documentation
 * identified per MPL Section 3.3
 *
 * Date             Modified by     Description of modification
 * 04/20/2000       IBM Corp.      Added PR_CALLBACK for Optlink use in OS2
 */

#include "nsProxyEventPrivate.h"
#include "nsProxyRelease.h"
#include "nsIProxyObjectManager.h"
#include "nsCRT.h"

#include "pratom.h"
#include "prmem.h"
#include "xptcall.h"

#include "nsAutoLock.h"
#include "nsXPCOMCID.h"
#include "nsServiceManagerUtils.h"
#include "nsIComponentManager.h"
#include "nsThreadUtils.h"
#include "nsEventQueue.h"
#include "nsMemory.h"

/**
 * Map the nsAUTF8String, nsUTF8String classes to the nsACString and
 * nsCString classes respectively for now.  These defines need to be removed
 * once Jag lands his nsUTF8String implementation.
 */
#define nsAUTF8String nsACString
#define nsUTF8String nsCString

class nsProxyCallCompletedEvent : public nsRunnable
{
public:
    nsProxyCallCompletedEvent(nsProxyObjectCallInfo *info)
        : mInfo(info)
    {}

    NS_DECL_NSIRUNNABLE

    NS_IMETHOD QueryInterface(REFNSIID aIID, void **aResult);

private:
    nsProxyObjectCallInfo *mInfo;
};

NS_IMETHODIMP
nsProxyCallCompletedEvent::Run()
{
    NS_ASSERTION(mInfo, "no info");
    mInfo->SetCompleted();
    return NS_OK;
}

NS_DEFINE_IID(kFilterIID, NS_PROXYEVENT_FILTER_IID);

NS_IMETHODIMP
nsProxyCallCompletedEvent::QueryInterface(REFNSIID aIID, void **aResult)
{
    // We are explicitly breaking XPCOM rules here by returning a different
    // object from QueryInterface. We do this so that
    // nsProxyThreadFilter::AcceptEvent can know whether we are an event that
    // needs to be allowed through during a synchronous proxy call.
    if (aIID.Equals(kFilterIID)) {
        *aResult = mInfo;
        mInfo->AddRef();
        return NS_OK;
    }
    return nsRunnable::QueryInterface(aIID, aResult);
}

//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsProxyObject::nsProxyObjectDestructorEvent::Run()
{
    delete mDoomed;
    return NS_OK;
}

//-----------------------------------------------------------------------------

nsProxyObjectCallInfo::nsProxyObjectCallInfo(nsProxyEventObject* owner,
                                             const XPTMethodDescriptor *methodInfo,
                                             PRUint32 methodIndex, 
                                             nsXPTCVariant* parameterList, 
                                             PRUint32 parameterCount) :
    mResult(NS_ERROR_FAILURE),
    mMethodInfo(methodInfo),
    mMethodIndex(methodIndex),
    mParameterList(parameterList),
    mParameterCount(parameterCount),
    mCompleted(0),
    mOwner(owner)
{
    NS_ASSERTION(owner, "No nsProxyObject!");
    NS_ASSERTION(methodInfo, "No nsXPTMethodInfo!");

    RefCountInInterfacePointers(PR_TRUE);
    if (mOwner->GetProxyType() & NS_PROXY_ASYNC)
        CopyStrings(PR_TRUE);
}

nsProxyObjectCallInfo::~nsProxyObjectCallInfo()
{
    RefCountInInterfacePointers(PR_FALSE);
    if (mOwner->GetProxyType() & NS_PROXY_ASYNC)
        CopyStrings(PR_FALSE);

    mOwner = nsnull;
    
    if (mParameterList)  
        free(mParameterList);
}

NS_IMETHODIMP
nsProxyObjectCallInfo::QueryInterface(REFNSIID aIID, void **aResult)
{
    if (aIID.Equals(kFilterIID)) {
        *aResult = this;
        AddRef();
        return NS_OK;
    }
    return nsRunnable::QueryInterface(aIID, aResult);
}

NS_IMETHODIMP
nsProxyObjectCallInfo::Run()
{
    PROXY_LOG(("PROXY(%p): Run\n", this));

    mResult = NS_InvokeByIndex(mOwner->GetProxiedInterface(),
                               mMethodIndex,
                               mParameterCount,
                               mParameterList);

    if (IsSync()) {
        PostCompleted();
    }

    return NS_OK;
}

void
nsProxyObjectCallInfo::RefCountInInterfacePointers(PRBool addRef)
{
    for (PRUint32 i = 0; i < mParameterCount; i++)
    {
        nsXPTParamInfo paramInfo = mMethodInfo->params[i];

        if (paramInfo.GetType().IsInterfacePointer() )
        {
            nsISupports* anInterface = nsnull;

            if (paramInfo.IsIn())
            {
                anInterface = ((nsISupports*)mParameterList[i].val.p);
                
                if (anInterface)
                {
                    if (addRef)
                        anInterface->AddRef();
                    else
                        anInterface->Release();
            
                }
            }
        }
    }
}

void
nsProxyObjectCallInfo::CopyStrings(PRBool copy)
{
    for (PRUint32 i = 0; i < mParameterCount; i++)
    {
        const nsXPTParamInfo paramInfo = mMethodInfo->params[i];

        if (paramInfo.IsIn())
        {
            const nsXPTType& type = paramInfo.GetType();
            uint8 type_tag = type.TagPart();
            void *ptr = mParameterList[i].val.p;

            if (!ptr)
                continue;

            if (copy)
            {                
                switch (type_tag) 
                {
                    case nsXPTType::T_CHAR_STR:                                
                        mParameterList[i].val.p =
                            PL_strdup((const char *)ptr);
                        break;
                    case nsXPTType::T_WCHAR_STR:
                        mParameterList[i].val.p =
                            nsCRT::strdup((const PRUnichar *)ptr);
                        break;
                    case nsXPTType::T_DOMSTRING:
                    case nsXPTType::T_ASTRING:
                        mParameterList[i].val.p = 
                            new nsString(*((nsAString*) ptr));
                        break;
                    case nsXPTType::T_CSTRING:
                        mParameterList[i].val.p = 
                            new nsCString(*((nsACString*) ptr));
                        break;
                    case nsXPTType::T_UTF8STRING:                        
                        mParameterList[i].val.p = 
                            new nsUTF8String(*((nsAUTF8String*) ptr));
                        break;
                    default:
                        // Other types are ignored
                        break;                    
                }
            }
            else
            {
                switch (type_tag) 
                {
                    case nsXPTType::T_CHAR_STR:
                        PL_strfree((char*) ptr);
                        break;
                    case nsXPTType::T_WCHAR_STR:
                        nsCRT::free((PRUnichar*)ptr);
                        break;
                    case nsXPTType::T_DOMSTRING:
                    case nsXPTType::T_ASTRING:
                        delete (nsString*) ptr;
                        break;
                    case nsXPTType::T_CSTRING:
                        delete (nsCString*) ptr;
                        break;
                    case nsXPTType::T_UTF8STRING:
                        delete (nsUTF8String*) ptr;
                        break;
                    default:
                        // Other types are ignored
                        break;
                }
            }
        }
    }
}

PRBool                
nsProxyObjectCallInfo::GetCompleted()
{
    return !!mCompleted;
}

void
nsProxyObjectCallInfo::SetCompleted()
{
    PROXY_LOG(("PROXY(%p): SetCompleted\n", this));
    PR_AtomicSet(&mCompleted, 1);
}

void                
nsProxyObjectCallInfo::PostCompleted()
{
    PROXY_LOG(("PROXY(%p): PostCompleted\n", this));

    if (mCallersTarget) {
        nsCOMPtr<nsIRunnable> event =
                new nsProxyCallCompletedEvent(this);
        if (event &&
            NS_SUCCEEDED(mCallersTarget->Dispatch(event, NS_DISPATCH_NORMAL)))
            return;
    }

    // OOM?  caller does not have a target?  This is an error!
    NS_WARNING("Failed to dispatch nsProxyCallCompletedEvent");
    SetCompleted();
}
  
nsIEventTarget*      
nsProxyObjectCallInfo::GetCallersTarget() 
{ 
    return mCallersTarget;
}

void
nsProxyObjectCallInfo::SetCallersTarget(nsIEventTarget* target)
{
    mCallersTarget = target;
}   

nsProxyObject::nsProxyObject(nsIEventTarget *target, PRInt32 proxyType,
                             nsISupports *realObject) :
  mProxyType(proxyType),
  mTarget(target),
  mRealObject(realObject),
  mFirst(nsnull)
{
    MOZ_COUNT_CTOR(nsProxyObject);

#ifdef DEBUG
    nsCOMPtr<nsISupports> canonicalTarget = do_QueryInterface(target);
    NS_ASSERTION(target == canonicalTarget,
                 "Non-canonical nsISupports passed to nsProxyObject constructor");
#endif

    nsProxyObjectManager *pom = nsProxyObjectManager::GetInstance();
    NS_ASSERTION(pom, "Creating a proxy without a global proxy-object-manager.");
    pom->AddRef();
}

nsProxyObject::~nsProxyObject()
{
    // Proxy the release of mRealObject to protect against it being deleted on
    // the wrong thread.
    nsISupports *doomed = nsnull;
    mRealObject.swap(doomed);
    NS_ProxyRelease(mTarget, doomed);

    MOZ_COUNT_DTOR(nsProxyObject);
}

NS_IMETHODIMP_(nsrefcnt)
nsProxyObject::AddRef()
{
    nsAutoLock lock(nsProxyObjectManager::GetInstance()->GetLock());
    return LockedAddRef();
}

NS_IMETHODIMP_(nsrefcnt)
nsProxyObject::Release()
{
    nsAutoLock lock(nsProxyObjectManager::GetInstance()->GetLock());
    return LockedRelease();
}

nsrefcnt
nsProxyObject::LockedAddRef()
{
    ++mRefCnt;
    NS_LOG_ADDREF(this, mRefCnt, "nsProxyObject", sizeof(nsProxyObject));
    return mRefCnt;
}

nsrefcnt
nsProxyObject::LockedRelease()
{
    NS_PRECONDITION(0 != mRefCnt, "dup release");
    --mRefCnt;
    NS_LOG_RELEASE(this, mRefCnt, "nsProxyObject");
    if (mRefCnt)
        return mRefCnt;

    nsProxyObjectManager *pom = nsProxyObjectManager::GetInstance();
    pom->LockedRemove(this);

    nsAutoUnlock unlock(pom->GetLock());
    delete this;
    pom->Release();

    return 0;
}

NS_IMETHODIMP
nsProxyObject::QueryInterface(REFNSIID aIID, void **aResult)
{
    if (aIID.Equals(GetIID())) {
        *aResult = this;
        AddRef();
        return NS_OK;
    }

    if (aIID.Equals(NS_GET_IID(nsISupports))) {
        *aResult = static_cast<nsISupports*>(this);
        AddRef();
        return NS_OK;
    }

    nsProxyObjectManager *pom = nsProxyObjectManager::GetInstance();
    NS_ASSERTION(pom, "Deleting a proxy without a global proxy-object-manager.");

    nsAutoLock lock(pom->GetLock());
    return LockedFind(aIID, aResult);
}

nsresult
nsProxyObject::LockedFind(REFNSIID aIID, void **aResult)
{
    // This method is only called when the global lock is held.
    // XXX assert this

    nsProxyEventObject *peo;

    for (peo = mFirst; peo; peo = peo->mNext) {
        if (peo->GetClass()->GetProxiedIID().Equals(aIID)) {
            *aResult = static_cast<nsISupports*>(peo->mXPTCStub);
            peo->LockedAddRef();
            return NS_OK;
        }
    }

    nsProxyEventObject *newpeo;

    // Both GetClass and QueryInterface call out to XPCOM, so we unlock for them
    {
        nsProxyObjectManager* pom = nsProxyObjectManager::GetInstance();
        nsAutoUnlock unlock(pom->GetLock());

        nsProxyEventClass *pec;
        nsresult rv = pom->GetClass(aIID, &pec);
        if (NS_FAILED(rv))
            return rv;

        nsISomeInterface* newInterface;
        rv = mRealObject->QueryInterface(aIID, (void**) &newInterface);
        if (NS_FAILED(rv))
            return rv;

        newpeo = new nsProxyEventObject(this, pec, 
                       already_AddRefed<nsISomeInterface>(newInterface), &rv);
        if (!newpeo) {
            NS_RELEASE(newInterface);
            return NS_ERROR_OUT_OF_MEMORY;
        }

        if (NS_FAILED(rv)) {
            delete newpeo;
            return rv;
        }
    }

    // Now that we're locked again, check for races by repeating the
    // linked-list check.
    for (peo = mFirst; peo; peo = peo->mNext) {
        if (peo->GetClass()->GetProxiedIID().Equals(aIID)) {
            delete newpeo;
            *aResult = static_cast<nsISupports*>(peo->mXPTCStub);
            peo->LockedAddRef();
            return NS_OK;
        }
    }

    newpeo->mNext = mFirst;
    mFirst = newpeo;

    newpeo->LockedAddRef();

    *aResult = static_cast<nsISupports*>(newpeo->mXPTCStub);
    return NS_OK;
}

void
nsProxyObject::LockedRemove(nsProxyEventObject *peo)
{
    nsProxyEventObject **i;
    for (i = &mFirst; *i; i = &((*i)->mNext)) {
        if (*i == peo) {
            *i = peo->mNext;
            return;
        }
    }
    NS_ERROR("Didn't find nsProxyEventObject in nsProxyObject chain!");
}
