/* ***** 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 Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Adam Lock <adamlock@netscape.com>
 *
 * 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 ***** */
#include "stdafx.h"

#include <process.h>

#include "Pluginhostctrl.h"
#include "nsPluginHostCtrl.h"
#include "nsURLDataCallback.h"


/////////////////////////////////////////////////////////////////////////////
// nsURLDataCallback

nsURLDataCallback::nsURLDataCallback() :
    m_pOwner(NULL),
    m_pNotifyData(NULL),
    m_szContentType(NULL),
    m_szURL(NULL),
    m_nDataPos(0),
    m_nDataMax(0),
    m_hPostData(NULL),
    m_bSaveToTempFile(FALSE),
    m_bNotifyOnWrite(TRUE),
    m_szTempFileName(NULL),
    m_pTempFile(NULL)
{
    memset(&m_NPStream, 0, sizeof(m_NPStream));
    m_NPStream.ndata = this;
}

nsURLDataCallback::~nsURLDataCallback()
{
    SetPostData(NULL, 0);
    SetURL(NULL);
    SetContentType(NULL);
    if (m_pTempFile)
    {
        fclose(m_pTempFile);
        remove(m_szTempFileName);
    }
    if (m_szTempFileName)
    {
        free(m_szTempFileName);
    }
}

void nsURLDataCallback::SetPostData(const void *pData, unsigned long nSize)
{
    // Copy the post data into an HGLOBAL so it can be given to the 
    // bind object during the call to GetBindInfo
    if (m_hPostData)
    {
        GlobalFree(m_hPostData);
        m_hPostData = NULL;
    }
    if (pData)
    {
        m_hPostData = GlobalAlloc(GHND, nSize);
        if (m_hPostData)
        {
            void *pPostData = GlobalLock(m_hPostData);
            ATLASSERT(pPostData);
            memcpy(pPostData, pData, nSize);
            GlobalUnlock(m_hPostData);
        }
    }
}

HRESULT nsURLDataCallback::OpenURL(nsPluginHostCtrl *pOwner, const TCHAR *szURL, void *pNotifyData, const void *pPostData, unsigned long nPostDataSize)
{
    // Create the callback object
    CComObject<nsURLDataCallback> *pCallback = NULL;
    CComObject<nsURLDataCallback>::CreateInstance(&pCallback);
    if (!pCallback)
    {
        return E_OUTOFMEMORY;
    }
    pCallback->AddRef();

    // Initialise it
    pCallback->SetOwner(pOwner);
    pCallback->SetNotifyData(pNotifyData);
    if (pPostData && nPostDataSize > 0)
    {
        pCallback->SetPostData(pPostData, nPostDataSize);
    }

    USES_CONVERSION;
    pCallback->SetURL(T2CA(szURL));
    
    // Create an object window on this thread that will be sent messages when
    // something happens on the worker thread.
    RECT rcPos = {0, 0, 10, 10};
    pCallback->Create(HWND_DESKTOP, rcPos);    

    // Start the worker thread
    _beginthread(StreamThread, 0, pCallback);

    return S_OK;
}

void __cdecl nsURLDataCallback::StreamThread(void *pData)
{
    HRESULT hr = CoInitialize(NULL);
    ATLASSERT(SUCCEEDED(hr));

    CComObject<nsURLDataCallback> *pThis = (CComObject<nsURLDataCallback> *) pData;

    // Open the URL
    hr = URLOpenStream(NULL, pThis->m_szURL, 0, static_cast<IBindStatusCallback*>(pThis));
    ATLASSERT(SUCCEEDED(hr));

    // Pump messages until WM_QUIT arrives
    BOOL bQuit = FALSE;
    while (!bQuit)
    {
        MSG msg;
        if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
        {
            if (GetMessage(&msg, NULL, 0, 0))
            {
                DispatchMessage(&msg);
            }
            else
            {
                bQuit = TRUE;
            }
        }
    }

    CoUninitialize();
    _endthread();
}

///////////////////////////////////////////////////////////////////////////////
// Windows message handlers

LRESULT nsURLDataCallback::OnNPPNewStream(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    _NewStreamData *pNewStreamData = (_NewStreamData *) lParam;

    // Notify the plugin that a new stream has been created
    if (m_pOwner->m_NPPFuncs.newstream)
    {
        NPError npres = m_pOwner->m_NPPFuncs.newstream(
            pNewStreamData->npp,
            pNewStreamData->contenttype,
            pNewStreamData->stream,
            pNewStreamData->seekable,
            pNewStreamData->stype);

        // How does the plugin want its data?
        switch (*(pNewStreamData->stype))
        {
        case NP_NORMAL:
            m_bSaveToTempFile = FALSE;
            m_bNotifyOnWrite = TRUE;
            break;
        case NP_ASFILEONLY:
            m_bNotifyOnWrite = FALSE;
            m_bSaveToTempFile = TRUE;
            break;
        case NP_ASFILE:
            m_bNotifyOnWrite = TRUE;
            m_bSaveToTempFile = TRUE;
            break;
        case NP_SEEK:
            // TODO!!!
            ATLASSERT(0);
            break;
        }
    }
    return 0;
}

LRESULT nsURLDataCallback::OnNPPDestroyStream(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    _DestroyStreamData *pDestroyStreamData = (_DestroyStreamData *) lParam;

    // Tell the plugin the name of the temporary file containing the data
    if (m_bSaveToTempFile)
    {
        // Close the file
        if (m_pTempFile)
        {
            fclose(m_pTempFile);
        }

        // Determine whether the plugin should be told the name of the temp
        // file depending on whether it completed properly or not.
        char *szTempFileName = NULL;
        if (pDestroyStreamData->reason == NPRES_DONE &&
            m_pTempFile)
        {
            szTempFileName = m_szTempFileName;
        }
        
        // Notify the plugin
        if (m_pOwner->m_NPPFuncs.asfile)
        {
            m_pOwner->m_NPPFuncs.asfile(
                pDestroyStreamData->npp,
                pDestroyStreamData->stream,
                szTempFileName);
        }

        // Remove the file if it wasn't passed into the plugin
        if (!szTempFileName ||
            !m_pOwner->m_NPPFuncs.asfile)
        {
            remove(szTempFileName);
        }

        // Cleanup strings & pointers
        if (m_szTempFileName)
        {
            free(m_szTempFileName);
            m_szTempFileName = NULL;
        }
        m_pTempFile = NULL;
    }

    // Notify the plugin that the stream has been closed
    if (m_pOwner->m_NPPFuncs.destroystream)
    {
        m_pOwner->m_NPPFuncs.destroystream(
            pDestroyStreamData->npp,
            pDestroyStreamData->stream,
            pDestroyStreamData->reason);
    }

    return 0;
}

LRESULT nsURLDataCallback::OnNPPURLNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    _UrlNotifyData *pUrlNotifyData = (_UrlNotifyData *) lParam;

    // Notify the plugin that the url has loaded
    if (m_pNotifyData && m_pOwner->m_NPPFuncs.urlnotify)
    {
        m_pOwner->m_NPPFuncs.urlnotify(
            pUrlNotifyData->npp,
            pUrlNotifyData->url,
            pUrlNotifyData->reason,
            pUrlNotifyData->notifydata);
    }
    return 0;
}

LRESULT nsURLDataCallback::OnNPPWriteReady(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    _WriteReadyData *pWriteReadyData = (_WriteReadyData *) lParam;
    if (m_pOwner->m_NPPFuncs.writeready &&
        m_bNotifyOnWrite)
    {
        pWriteReadyData->result = m_pOwner->m_NPPFuncs.writeready(
            pWriteReadyData->npp,
            pWriteReadyData->stream);
    }

    return 0;
}

LRESULT nsURLDataCallback::OnNPPWrite(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    _WriteData *pWriteData = (_WriteData *) lParam;

#ifdef DUMP_STREAM_DATA_AS_HEX
    // Dumps out the data to the display so you can see it's correct as its
    // fed into the plugin.
    const int kLineBreakAfter = 16;
    const int kSpaceBreakAfter = 8;
    ATLTRACE(_T("nsURLDataCallback::OnNPPWrite()\n"));
    for (int i = 0; i < pWriteData->len; i++)
    {
        TCHAR szLine[100];
        TCHAR szTmp[20];
        if (i % kLineBreakAfter == 0)
        {
            _stprintf(szLine, _T("%04x  "), i);
        }
        unsigned char b = ((unsigned char *) pWriteData->buffer)[i];
        _stprintf(szTmp, _T("%02X "), (unsigned int) b);
        _tcscat(szLine, szTmp);
        
        if (i == pWriteData->len - 1 ||
            i % kLineBreakAfter == kLineBreakAfter - 1)
        {
            _tcscat(szLine, _T("\n"));
            ATLTRACE(szLine);
        }
        else if (i % kSpaceBreakAfter == kSpaceBreakAfter - 1)
        {
            _tcscat(szLine, _T(" "));
        }
    }
    ATLTRACE(_T("--\n"));
#endif

    if (m_bSaveToTempFile)
    {
        if (!m_szTempFileName)
        {
            m_szTempFileName = strdup(_tempnam(NULL, "moz"));
        }
        if (!m_pTempFile)
        {
            m_pTempFile = fopen(m_szTempFileName, "wb");
        }
        ATLASSERT(m_pTempFile);
        if (m_pTempFile)
        {
            fwrite(pWriteData->buffer, 1, pWriteData->len, m_pTempFile);
        }
    }

    if (m_pOwner->m_NPPFuncs.write &&
        m_bNotifyOnWrite)
    {
        int32 nConsumed = m_pOwner->m_NPPFuncs.write(
            pWriteData->npp,
            pWriteData->stream,
            pWriteData->offset,
            pWriteData->len,
            pWriteData->buffer);
        if (nConsumed < 0)
        {
            // TODO destroy the stream!
        }
        ATLASSERT(nConsumed == pWriteData->len);
    }
    return 0;
}

LRESULT nsURLDataCallback::OnClassCreatePluginInstance(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    // Test whether the plugin for this content type exists or not and if not,
    // create it right now.
    if (!m_pOwner->m_bPluginIsAlive &&
        m_pOwner->m_bCreatePluginFromStreamData)
    {
        if (FAILED(m_pOwner->LoadPluginByContentType(A2CT(m_szContentType))) ||
            FAILED(m_pOwner->CreatePluginInstance()))
        {
            return 1;
        }
    }
    return 0;
}

LRESULT nsURLDataCallback::OnClassCleanup(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    DestroyWindow();
    Release();
    return 0;
}

///////////////////////////////////////////////////////////////////////////////
// IBindStatusCallback implementation

HRESULT STDMETHODCALLTYPE nsURLDataCallback::OnStartBinding( 
    /* [in] */ DWORD dwReserved,
    /* [in] */ IBinding __RPC_FAR *pib)
{
    ATLTRACE(_T("nsURLDataCallback::OnStartBinding()\n"));
    m_cpBinding = pib;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE nsURLDataCallback::GetPriority( 
    /* [out] */ LONG __RPC_FAR *pnPriority)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE nsURLDataCallback::OnLowResource( 
    /* [in] */ DWORD reserved)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE nsURLDataCallback::OnProgress( 
    /* [in] */ ULONG ulProgress,
    /* [in] */ ULONG ulProgressMax,
    /* [in] */ ULONG ulStatusCode,
    /* [in] */ LPCWSTR szStatusText)
{
    switch (ulStatusCode)
    {
    case BINDSTATUS_BEGINDOWNLOADDATA:
    case BINDSTATUS_REDIRECTING:
        {
            USES_CONVERSION;
            SetURL(W2A(szStatusText));
        }
        break;

    case BINDSTATUS_MIMETYPEAVAILABLE:
        {
            USES_CONVERSION;
            SetContentType(W2A(szStatusText));
        }
        break;
    }

    m_nDataMax = ulProgressMax;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE nsURLDataCallback::OnStopBinding( 
    /* [in] */ HRESULT hresult,
    /* [unique][in] */ LPCWSTR szError)
{
    ATLTRACE(_T("nsURLDataCallback::OnStopBinding()\n"));
    if (m_pOwner && m_pOwner->m_bPluginIsAlive)
    {
        // TODO check for aborted ops and send NPRES_USER_BREAK
        NPReason reason = SUCCEEDED(hresult) ? NPRES_DONE : NPRES_NETWORK_ERR;

        // Notify the plugin that the stream has been closed
        _DestroyStreamData destroyStreamData;
        destroyStreamData.npp = &m_pOwner->m_NPP;
        destroyStreamData.stream = &m_NPStream;
        destroyStreamData.reason = reason;
        SendMessage(WM_NPP_DESTROYSTREAM, 0, (LPARAM) &destroyStreamData);

        // Notify the plugin that the url has loaded
        _UrlNotifyData urlNotifyData;
        urlNotifyData.npp = &m_pOwner->m_NPP;
        urlNotifyData.url = m_szURL;
        urlNotifyData.reason = reason;
        urlNotifyData.notifydata = m_pNotifyData;
        SendMessage(WM_NPP_URLNOTIFY, 0, (LPARAM) &urlNotifyData);
    }

    m_cpBinding.Release();

    SendMessage(WM_CLASS_CLEANUP);
    PostQuitMessage(0);

    return S_OK;
}

/* [local] */ HRESULT STDMETHODCALLTYPE nsURLDataCallback::GetBindInfo( 
    /* [out] */ DWORD __RPC_FAR *grfBINDF,
    /* [unique][out][in] */ BINDINFO __RPC_FAR *pbindinfo)
{
    *grfBINDF = BINDF_ASYNCHRONOUS |  BINDF_ASYNCSTORAGE |
        BINDF_GETNEWESTVERSION;
    
    ULONG cbSize = pbindinfo->cbSize;
    memset(pbindinfo, 0, cbSize); // zero out structure
    pbindinfo->cbSize = cbSize;
    if (m_hPostData)
    {
        pbindinfo->dwBindVerb = BINDVERB_POST;
        pbindinfo->stgmedData.tymed = TYMED_HGLOBAL;
        pbindinfo->stgmedData.hGlobal = m_hPostData;
    }
    else
    {
        pbindinfo->dwBindVerb = BINDVERB_GET;
    }

    return S_OK ;
}

/* [local] */ HRESULT STDMETHODCALLTYPE nsURLDataCallback::OnDataAvailable( 
    /* [in] */ DWORD grfBSCF,
    /* [in] */ DWORD dwSize,
    /* [in] */ FORMATETC __RPC_FAR *pformatetc,
    /* [in] */ STGMEDIUM __RPC_FAR *pstgmed)
{
    ATLTRACE(_T("nsURLDataCallback::OnDataAvailable()\n"));
    if (pstgmed->tymed != TYMED_ISTREAM ||
        !pstgmed->pstm)
    {
        return S_OK;
    }
    if (!m_pOwner)
    {
        return S_OK;
    }

    // Notify the plugin that a stream has been opened
    if (grfBSCF & BSCF_FIRSTDATANOTIFICATION)
    {
        USES_CONVERSION;

        // Test if there is a plugin yet. If not try and create one for this
        // kind of content.
        if (SendMessage(WM_CLASS_CREATEPLUGININSTANCE))
        {
            m_cpBinding->Abort();
            return S_OK;
        }

        // Tell the plugin that there is a new stream of data
        m_NPStream.url = m_szURL;
        m_NPStream.end = 0;
        m_NPStream.lastmodified = 0;
        m_NPStream.notifyData = m_pNotifyData;

        uint16 stype = NP_NORMAL;
        _NewStreamData newStreamData;
        newStreamData.npp = &m_pOwner->m_NPP;
        newStreamData.contenttype = m_szContentType;
        newStreamData.stream = &m_NPStream;
        newStreamData.seekable = FALSE;
        newStreamData.stype = &stype;
        SendMessage(WM_NPP_NEWSTREAM, 0, (LPARAM) &newStreamData);
    }
    if (!m_pOwner->m_bPluginIsAlive)
    {
        return S_OK;
    }

    m_NPStream.end = m_nDataMax;

    ATLTRACE(_T("Data for stream %s (%d of %d bytes are available)\n"), m_szURL, dwSize, m_NPStream.end);

    // Feed the stream data into the plugin
    HRESULT hr;
    char bData[16384];
    while (m_nDataPos < dwSize)
    {
        ULONG nBytesToRead = dwSize - m_nDataPos;
        ULONG nBytesRead = 0;

        if (nBytesToRead > sizeof(bData))
        {
            nBytesToRead = sizeof(bData);
        }

        // How many bytes can the plugin cope with?
        _WriteReadyData writeReadyData;
        writeReadyData.npp = &m_pOwner->m_NPP;
        writeReadyData.stream = &m_NPStream;
        writeReadyData.result = nBytesToRead;
        SendMessage(WM_NPP_WRITEREADY, 0, (LPARAM) &writeReadyData);
        if (nBytesToRead > writeReadyData.result)
        {
            nBytesToRead = writeReadyData.result;
        }

        // Read 'n' feed
        ATLTRACE(_T("  Reading %d bytes\n"), (int) nBytesToRead);
        hr = pstgmed->pstm->Read(&bData, nBytesToRead, &nBytesRead);

        _WriteData writeData;
        writeData.npp = &m_pOwner->m_NPP;
        writeData.stream = &m_NPStream;
        writeData.offset = m_nDataPos;
        writeData.len = nBytesRead;
        writeData.buffer = bData;
        SendMessage(WM_NPP_WRITE, 0, (LPARAM) &writeData);

        m_nDataPos += nBytesRead;
    }

    return S_OK;
}

HRESULT STDMETHODCALLTYPE nsURLDataCallback::OnObjectAvailable( 
    /* [in] */ REFIID riid,
    /* [iid_is][in] */ IUnknown __RPC_FAR *punk)
{
    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// IAuthenticate implementation

HRESULT STDMETHODCALLTYPE nsURLDataCallback::Authenticate( 
    /* [out] */ HWND __RPC_FAR *phwnd,
    /* [out] */ LPWSTR __RPC_FAR *pszUsername,
    /* [out] */ LPWSTR __RPC_FAR *pszPassword)
{
    ATLTRACE(_T("nsURLDataCallback::Authenticate()\n"));

    // Caller wants to pose a password dialog so get a parent HWND for it.
    *phwnd = HWND_DESKTOP;
    if (m_pOwner)
    {
        *phwnd = m_pOwner->m_hWnd;
        CComPtr<IWebBrowserApp> cpBrowser;
        m_pOwner->GetWebBrowserApp(&cpBrowser);
        if (cpBrowser)
        {
            long hwnd = NULL;
            cpBrowser->get_HWND(&hwnd);
            if (hwnd)
            {
                *phwnd = (HWND) hwnd;
            }
        }
    }

    // Caller will figure these out for itself
    *pszUsername = NULL;
    *pszPassword = NULL;
    
    return S_OK;
}
