/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: nsHXPlayer.cpp,v 1.5.2.6 2004/11/23 00:24:25 rggammon Exp $
 * 
 * * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. 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
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

/* XXXRGG: Undefine DEBUG so we don't pull in nsDebug stuff */
#ifdef DEBUG
#  undef DEBUG
#endif

#include "nsHXPlayer.h"
#include "hxbackend.h"
#include "tokenizer.h"
#include "asprintf.h"

#include <stdlib.h>
#include <stdio.h>

nsHXPlayer::nsHXPlayer(CHXPlayerBackend *pBackend)
    : nsPluginInstanceBase(),
      m_iPlayerID(-1),
      m_instance(NULL),
      m_szName(NULL),
      m_bIsFirstNewStream(PR_TRUE),
      m_bReceivedXEmbedQuery(PR_FALSE),
      m_bIsInitialized(PR_FALSE),
      m_bReceivedScriptableInstanceQuery(PR_FALSE),
      m_bSentBrowserInfo(PR_FALSE),
      m_pBackend(pBackend)
{    
}

nsHXPlayer::~nsHXPlayer()
{
}

NS_IMPL_ISUPPORTS2(nsHXPlayer,
                   nsIHXPlayer,
                   nsIClassInfo)

NS_IMETHODIMP nsHXPlayer::DoCommand(const char *szCommand, PRBool *_retval)
{
    nsresult result;
    char *pMsg;
    int nLen;

    *_retval = PR_FALSE;

    if(!m_bIsInitialized)
    {
        return NS_ERROR_FAILURE;
    }        

    nLen = asprintf(&pMsg, "%s %d\n", szCommand, m_iPlayerID);
    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(result != NS_OK)
    {
        return result;
    }
    
    return m_pBackend->ReadGenericResponse(_retval);
}

/* SetPosition is really a command that takes a position argument. As it's
   the only command that does this, we'll special case it here rather than
   expanding our IPC to handle this sort of thing.*/
NS_IMETHODIMP nsHXPlayer::SetPosition(PRInt32 nPosition, PRBool *_retval)
{
    nsresult result;
    char *pMsg;
    int nLen;

    *_retval = PR_FALSE;

    if(!m_bIsInitialized)
    {
        return NS_ERROR_FAILURE;
    }        

    nLen = asprintf(&pMsg, "Seek %d, %d\n",  m_iPlayerID, nPosition);
    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }
    
    return m_pBackend->ReadGenericResponse(_retval);
}

NS_IMETHODIMP nsHXPlayer::GetDRMInfo(const char *szIdentifier, char **_retval)
{
    *_retval = (char*) NPN_MemAlloc(1);
    *_retval[0] = '\0';

    return NS_OK;
}

char *nsHXPlayer::GetQuotedString(const char *szString)
{
    int nLen = 0;
    const char *pPos = szString;
    char *pQuotedPos = NULL;
    char *szQuotedString = NULL;
    
    while(*pPos)
    {
        if(*pPos == '\'')
        {
            nLen += 4; // '\''
        }
        else
        {
            nLen++;
        }
        
        pPos++;
    }

    nLen += 3; // two ' and a \0
    
    szQuotedString = (char*)malloc(nLen);
    pQuotedPos = szQuotedString; 

    *pQuotedPos++ = '\'';
    
    pPos = szString;
    while(*pPos)
    {
        if(*pPos == '\'')
        {
            *pQuotedPos++ = '\'';
            *pQuotedPos++ = '\\';
            *pQuotedPos++ = '\'';
            *pQuotedPos++ = '\'';
        }
        else
        {
            *pQuotedPos++ = *pPos;
        }
        pPos++;
    }    

    *pQuotedPos++ = '\'';
    *pQuotedPos = '\0';
    
    return szQuotedString;
}

nsresult nsHXPlayer::GetPlayerUINT32Prop(const char *szPropName, PRInt32 *pValue)
{
    char *pMsg;
    int nLen;
    nsresult result;
    PRInt32 iReturnCode;

    *pValue = 0;

    if(!m_bIsInitialized)
    {
        return NS_ERROR_FAILURE;
    }        

    nLen = asprintf(&pMsg, "GetPlayerUINT32Prop %d %s\n", m_iPlayerID, szPropName);

    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    result = m_pBackend->ReceiveMessage(&pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    if(sscanf(pMsg, "%d, %d", &iReturnCode, pValue) != 2)
    {
        result = NS_ERROR_FAILURE;
    }
    else if(iReturnCode != NS_OK)
    {
        *pValue = 0;
    }
    
    free(pMsg);
    
    return result;
}


nsresult nsHXPlayer::GetPlayerStringProp(const char *szPropName, char **pszValue)
{
    char *pMsg;
    int nLen;
    nsresult result;
    PRUint32 iReturnCode;

    *pszValue = 0;

    if(!m_bIsInitialized)
    {
        return NS_ERROR_FAILURE;
    }        

    nLen = asprintf(&pMsg, "GetPlayerStringProp %d %s\n", m_iPlayerID, szPropName);

    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    result = m_pBackend->ReceiveMessage(&pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    CStringTokenizer tokenizer(pMsg);

    char *szReturnCode = tokenizer.NextToken();
    iReturnCode = atol(szReturnCode);
    free(szReturnCode);

    if(iReturnCode == NS_OK)
    {
        char *szValue = tokenizer.NextToken();
        char *szNPNAllocedValue;

        // The browser will free this memory with the expectation that it was
        // allocated with NPN_MemAlloc. Copy over the result to a buffer allocated
        // this way.

        szNPNAllocedValue = (char*)NPN_MemAlloc(strlen(szValue) + 1);
        strcpy(szNPNAllocedValue, szValue);
        free(szValue);
        
        *pszValue = szNPNAllocedValue;        
    }
    
    free(pMsg);
    
    return result;
}

nsresult nsHXPlayer::GetEntryStringProp(const char *szPropName, PRInt32 uIndex, char **pszValue)
{
    char *pMsg;
    int nLen;
    nsresult result;
    PRInt32 iReturnCode;

    *pszValue = NULL;

    if(!m_bIsInitialized)
    {
        return NS_ERROR_FAILURE;
    }        

    nLen = asprintf(&pMsg, "GetEntryStringProp %d '%s' %d\n", m_iPlayerID, szPropName, uIndex);

    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    result = m_pBackend->ReceiveMessage(&pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    CStringTokenizer tokenizer(pMsg);

    char *szReturnCode = tokenizer.NextToken();
    iReturnCode = atol(szReturnCode);
    free(szReturnCode);

    if(iReturnCode == NS_OK)
    {
        char *szValue = tokenizer.NextToken();
        char *szNPNAllocedValue;

        // The browser will free this memory with the expectation that it was
        // allocated with NPN_MemAlloc. Copy over the result to a buffer allocated
        // this way.

        szNPNAllocedValue = (char*)NPN_MemAlloc(strlen(szValue) + 1);
        strcpy(szNPNAllocedValue, szValue);
        free(szValue);
        
        *pszValue = szNPNAllocedValue;        
    }
    
    free(pMsg);
    
    return result;
}

nsresult nsHXPlayer::SetPlayerUINT32Prop(const char *szPropName, PRInt32 uiPropValue, PRBool *_retval)
{
    char *pMsg;
    int nLen;
    nsresult result;

    *_retval = PR_FALSE;

    if(!m_bIsInitialized)
    {
        return NS_ERROR_FAILURE;
    }        

    nLen = asprintf(&pMsg, "SetPlayerUINT32Prop %d '%s' %d\n", m_iPlayerID, szPropName, uiPropValue);

    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    return m_pBackend->ReadGenericResponse(_retval);
}

nsresult nsHXPlayer::SetPlayerStringProp(const char *szPropName, const char *szValue, PRBool *retval)
{
    char *pMsg;
    int nLen;
    nsresult result;
    char *szQuotedValue;

    *retval = PR_FALSE;
    
    if(!m_bIsInitialized)
    {
        return NS_ERROR_FAILURE;
    }        

    szQuotedValue = GetQuotedString(szValue);    
    nLen = asprintf(&pMsg, "SetPlayerStringProp %d '%s' %s\n", m_iPlayerID, szPropName, szQuotedValue);
    free(szQuotedValue);
    
    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    return m_pBackend->ReadGenericResponse(retval);
}

nsresult nsHXPlayer::SendBrowserInfo(void)
{
    /* Send the browser's User Agent and JavaScript support level */
    int nLen = 0;
    char *pMsg;
    nsresult result;
    PRBool retval;
    const char* szUserAgent = NPN_UserAgent(m_instance);
    PRBool bHasCallbacks = m_pBackend->AreCallbacksSupported();
    char* szQuotedUserAgent = GetQuotedString(szUserAgent);
    
    nLen = asprintf(&pMsg, "Browser %d %s %d %d\n",
                    m_iPlayerID,
                    szQuotedUserAgent,
                    bHasCallbacks,
                    m_bReceivedXEmbedQuery);

    free(szQuotedUserAgent);
    
    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return NPERR_GENERIC_ERROR;
    }

    result = m_pBackend->ReadGenericResponse(&retval);
    if(NS_FAILED(result))
    {
        return NPERR_GENERIC_ERROR;
    }

    return NPERR_NO_ERROR;    
}    

    
// integration APIs
nsresult nsHXPlayer::Init(nsPluginCreateData * aCreateDataStruct)
{
    int i;
    int nLen = 0;
    char *szEmbedCommand;
    char *pPos;
    char* szEmbed = "Embed ";
    nsresult result;
    char *pMsg;
    PRBool bSrcFound = PR_FALSE;

    int argc = aCreateDataStruct->argc;
    char **argv = aCreateDataStruct->argv;
    char **argn = aCreateDataStruct->argn;

    m_instance = aCreateDataStruct->instance;
    m_bIsInitialized = PR_TRUE;
    
    // See if we were passed a "src" attribute -- if we weren't,
    // we aren't going to get an inital stream
    for(i = 0; i < aCreateDataStruct->argc; i++)
    {
        if(strcasecmp(aCreateDataStruct->argn[i], "src") == 0)
        {
            bSrcFound = PR_TRUE;
        }
    }
    
    if(!bSrcFound)
    {
        m_bIsFirstNewStream = PR_FALSE;
    }
    
    for(i = 0; i < argc; i++)
    {
        // Calculate space for the Embed command
        nLen += strlen(argn[i]) + strlen(argv[i]) + 4; // quotes, equals, and space

        if(strcasecmp(argn[i], "name") == 0)
        {
            if(m_szName)
            {
                free(m_szName);
            }
            m_szName = strdup(argv[i]);
        }
    }

    nLen += strlen(szEmbed) + 2; // '\0', '\n'

    szEmbedCommand = (char*)malloc(nLen);
    pPos = szEmbedCommand; 
    
    pPos += sprintf(pPos, "%s", szEmbed);
    
    for(i = 0; i < argc; i++)
    {
        pPos += sprintf(pPos, "%s='%s' ", argn[i], argv[i]);
    }

    pPos += sprintf(pPos, "\n");
    
    result = m_pBackend->SendMessage(szEmbedCommand, nLen - 1);
    free(szEmbedCommand);
    if(NS_FAILED(result))
    {
        return result;
    }
    
    result = m_pBackend->ReceiveMessage(&pMsg);
    if(NS_FAILED(result))
    {
        return result;
    }

    if(sscanf(pMsg, "%d", &m_iPlayerID) != 1)
    {
        result = NS_ERROR_FAILURE;
    }

    free(pMsg);

    m_pBackend->AddHXPlayer(this);

    return result;
}

nsresult nsHXPlayer::Destroy(void)
{
    SetWindow(NULL);
    m_bIsInitialized = PR_FALSE;
    m_pBackend->RemoveHXPlayer(this);
    
    return NS_OK;
}


NPError nsHXPlayer::SetWindow(NPWindow *aWindow)
{
    char *pMsg;
    PRBool retval;
    int nLen;
    nsresult result;

    if(!m_bSentBrowserInfo)
    {
        /* At this point, XEmbed and Scriptability info should be set */
        SendBrowserInfo();
        m_bSentBrowserInfo = TRUE;
    }
    
    if(aWindow)
    {
        const char *szCommand = (m_bReceivedXEmbedQuery)? "SetWindow": "SetXID";
        
        NPSetWindowCallbackStruct *ws_info = (NPSetWindowCallbackStruct *)aWindow->ws_info;

        // RGG: This XFlush is required by NS4.x, or the display created by
        // hxplay will not see aWindow->window as existing.
        XFlush(ws_info->display);
        
        nLen = asprintf(&pMsg, "%s %d %d %d %d %d %d %d %d %d %d %d\n",
                        szCommand,
                        m_iPlayerID,
                        (int)aWindow->window,
                        aWindow->x,
                        aWindow->y,
                        aWindow->width, 
                        aWindow->height, 
                        aWindow->clipRect.left,
                        aWindow->clipRect.top,
                        aWindow->clipRect.bottom,
                        aWindow->clipRect.right,
                        (int)aWindow->type);
    }
    else
    {
        nLen = asprintf(&pMsg, "UnsetWindow %d\n", m_iPlayerID);
    }

    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return NPERR_GENERIC_ERROR;
    }

    result = m_pBackend->ReadGenericResponse(&retval);
    if(NS_FAILED(result))
    {
        return NPERR_GENERIC_ERROR;
    }

    return NPERR_NO_ERROR;
}

NPError nsHXPlayer::NewStream(NPMIMEType type, NPStream* stream, NPBool /* seekable */, uint16* stype)
{
    char *pMsg;
    int nLen;
    nsresult result;
    PRBool retval;
    unsigned int nStreamLength;
    int nStreamId = 0;

    nStreamLength = ( stream->end > 0 ) ? stream->end : 0; 
    if(nStreamLength > 0)
    {
        const uint32 kMinimumStreamSize = 3; // Enough for the old RA3 header ( ".ra" ).
                
        if( nStreamLength < kMinimumStreamSize )
        {
            return NPERR_NO_DATA;
        }
    }
    
    *stype = NP_NORMAL;

    if(m_bIsFirstNewStream)
    {
        nStreamId = 0;    
    }
    else
    {
        nStreamId = (int)stream;
    }
    
    nLen = asprintf(&pMsg, "NewStream %d %d %s %s %d\n",
                    m_iPlayerID,
                    nStreamId,
                    stream->url,
                    type,
                    nStreamLength);
    
    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return NPERR_GENERIC_ERROR;
    }

    result = m_pBackend->ReadGenericResponse(&retval);
    if(NS_FAILED(result))
    {
        return NPERR_GENERIC_ERROR;
    }

    if(m_bIsFirstNewStream)
    {
        // We return NPERR_NO_DATA so that mozilla doesn't start streaming
        // us the data. We will request this data when the player wishes to start
        // playback. This makes us consistant with the behavior of the Windows
        // plugin, which doesn't start buffering until playback is requested.

        m_bIsFirstNewStream = PR_FALSE;
        
        return NPERR_NO_DATA; // Kevin Foreman wrote this line of code
    }

    return NPERR_NO_ERROR;
}


int32 nsHXPlayer::Write(NPStream *stream, int32 offset, int32 nDataLen, void *pData)
{
    char *pMsg;
    int nLen;
    nsresult result;
    PRBool retval;
    const void *streamID = (void*)stream;
    (void)offset;

    nLen = asprintf(&pMsg, "StreamData %d %d %d\n", m_iPlayerID, (int)streamID, nDataLen);
    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return 0;
    }

    // this is one of the very few commands that is not a single-line
    // the actual stream data follows here...
    result = m_pBackend->SendMessage((const char*)pData, nDataLen);
    if(NS_FAILED(result))
    {
        return 0;
    }

    result = m_pBackend->ReadGenericResponse(&retval);
    if(NS_FAILED(result))
    {
        return 0;
    }

    return nDataLen;
}

NPError nsHXPlayer::DestroyStream(NPStream *stream, NPError /* reason */)
{
    char *pMsg;
    int nLen;
    nsresult result;
    PRBool retval;
    const void *streamID = (void*)stream;
    
    nLen = asprintf(&pMsg, "StreamDone %d %d\n", m_iPlayerID, (int)streamID);
    result = m_pBackend->SendMessage(pMsg, nLen);
    free(pMsg);
    if(NS_FAILED(result))
    {
        return NPERR_GENERIC_ERROR;
    }

    result = m_pBackend->ReadGenericResponse(&retval);
    if(NS_FAILED(result))
    {
        return NPERR_GENERIC_ERROR;
    }

    return NPERR_NO_ERROR;
}

NPError	nsHXPlayer::GetValue(NPPVariable aVariable, void *aValue)
{
    NPError rv = NPERR_NO_ERROR;
    static nsIID scriptableIID = NS_IHXPLAYER_IID;
    nsIID* ptr = NULL;
    
    switch(aVariable)
    {
        case NPPVpluginScriptableInstance:
            // add reference for the caller requesting the object
            m_bReceivedScriptableInstanceQuery = PR_TRUE;
            
            NS_ADDREF(this);
            QueryInterface(NS_GET_IID(nsISupports), (void**)aValue);
            break;
            
        case NPPVpluginScriptableIID:
            ptr = (nsIID *)NPN_MemAlloc(sizeof(nsIID));
            if (ptr)
            {
                *ptr = scriptableIID;
                *(nsIID **)aValue = ptr;
            }
            else
            {
                rv = NPERR_OUT_OF_MEMORY_ERROR;
            }
            break;

        case NPPVpluginNeedsXEmbed:
            *((PRBool *)aValue) = PR_TRUE;
            m_bReceivedXEmbedQuery = PR_TRUE;
            break;

        default:
            break;
    }
    
    return rv;
}

void nsHXPlayer::URLNotify(const char *szUrl, NPReason reason, void * /* pData */ )
{
    switch(reason)
    {            
        case NPRES_DONE:
            printf("Got url %s\n", szUrl);
            break;

        case NPRES_USER_BREAK:
        case NPRES_NETWORK_ERR:
        default:
            printf("Error getting url %s\n", szUrl);
            break;
    }
}

    
nsresult nsHXPlayer::OnGetURL(const char *szUrl, const char *szTarget)
{
    NPError result;
    
    // NULL for the target parameter will tell mozilla to
    // send us the stream starting with NEWStream...
    if(szTarget && *szTarget == '\0')
    {
        szTarget = NULL;
    }

    result = NPN_GetURLNotify(m_instance, szUrl, szTarget, NULL);

    if(result != NPERR_NO_ERROR)
    {
        printf("Error %d invoking GetURL: %d!\n", result, result);
        return NS_ERROR_FAILURE;
    }

    return NS_OK;
}
