/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape 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/NPL/
 *
 * 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 Communicator client 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):
 *   Dan Rosen <dr@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 NPL, 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 NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsCOMPtr.h"
#include "nscore.h"
#include "nsCRT.h"
#include "nsIContentViewer.h"
#include "nsIPluginHost.h"
#include "nsIPluginInstance.h"
#include "nsIStreamListener.h"
#include "nsIURL.h"
#include "nsIChannel.h"
#include "nsNetUtil.h"
#include "nsIComponentManager.h"
#include "nsWidgetsCID.h"
#include "nsILinkHandler.h"
#include "nsIWebShell.h"
#include "nsIContentViewerEdit.h"
#include "nsIWebBrowserPrint.h"
#include "nsIWidget.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIWebBrowserChrome.h"
#include "nsIDOMDocument.h"
#include "nsPluginViewer.h"
#include "nsGUIEvent.h"
#include "nsIPluginViewer.h"
#include "nsContentCID.h"

#include "nsITimer.h"
#include "nsITimerCallback.h"

class nsIPrintSettings;
class nsIDOMWindow;

// Class IDs
static NS_DEFINE_IID(kChildWindowCID, NS_CHILD_CID);
static NS_DEFINE_IID(kIWidgetIID, NS_IWIDGET_IID);

// Interface IDs
static NS_DEFINE_IID(kIContentViewerIID, NS_ICONTENTVIEWER_IID);
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kCPluginManagerCID, NS_PLUGINMANAGER_CID);
static NS_DEFINE_IID(kIDocumentIID, NS_IDOCUMENT_IID);
static NS_DEFINE_IID(kHTMLDocumentCID, NS_HTMLDOCUMENT_CID);

class PluginViewerImpl;

class PluginListener : public nsIStreamListener {
public:
  PluginListener(PluginViewerImpl* aViewer);
  virtual ~PluginListener();

  // nsISupports
  NS_DECL_ISUPPORTS

  // nsIRequestObserver methods:
  NS_DECL_NSIREQUESTOBSERVER

  // nsIStreamListener methods:
  NS_DECL_NSISTREAMLISTENER

  PluginViewerImpl* mViewer;
  nsIStreamListener* mNextStream;
};
 
  
class pluginInstanceOwner : public nsIPluginInstanceOwner,
                            public nsITimerCallback
{
public:
  pluginInstanceOwner();
  virtual ~pluginInstanceOwner();

  NS_DECL_ISUPPORTS

  //nsIPluginInstanceOwner interface

  NS_IMETHOD SetInstance(nsIPluginInstance *aInstance);

  NS_IMETHOD GetInstance(nsIPluginInstance *&aInstance);

  NS_IMETHOD GetWindow(nsPluginWindow *&aWindow);

  NS_IMETHOD GetMode(nsPluginMode *aMode);

  NS_IMETHOD CreateWidget(void);

  NS_IMETHOD GetURL(const char *aURL, const char *aTarget, 
                    void *aPostData, PRUint32 aPostDataLen, 
                    void *aHeadersData, PRUint32 aHeadersDataLen, 
                    PRBool isFile = PR_FALSE);

  NS_IMETHOD ShowStatus(const char *aStatusMsg);

  NS_IMETHOD ShowStatus(const PRUnichar *aStatusMsg);

  NS_IMETHOD GetDocument(nsIDocument* *aDocument);

  NS_IMETHOD InvalidateRect(nsPluginRect *invalidRect);

  NS_IMETHOD InvalidateRegion(nsPluginRegion invalidRegion);

  NS_IMETHOD ForceRedraw();

  NS_IMETHOD GetValue(nsPluginInstancePeerVariable variable, void *value);

  //nsIEventListener interface
  nsEventStatus ProcessEvent(const nsGUIEvent & anEvent);

  //locals

  NS_IMETHOD Init(PluginViewerImpl *aViewer, nsIWidget *aWindow);

  // nsITimerCallback interface
  NS_IMETHOD_(void) Notify(nsITimer *timer);
  void CancelTimer();

#ifdef XP_MAC
  void GUItoMacEvent(const nsGUIEvent& anEvent, EventRecord& aMacEvent);
  nsPluginPort* GetPluginPort();
  void FixUpPluginWindow();
#endif
                   
private:
  nsPluginWindow    mPluginWindow;
  nsIPluginInstance *mInstance;
  nsIWidget         *mWindow;       //we do not addref this...
  PluginViewerImpl  *mViewer;       //we do not addref this...
  nsCOMPtr<nsITimer> mPluginTimer;
};

  static void GetWidgetPosAndClip(nsIWidget* aWidget,nscoord& aAbsX, nscoord& aAbsY,
                                nsRect& aClipRect);


class PluginViewerImpl : public nsIPluginViewer,
                         public nsIContentViewer,
                         public nsIContentViewerEdit,
                         public nsIWebBrowserPrint
{
public:
  PluginViewerImpl(const char* aCommand);
  nsresult Init(nsIStreamListener** aDocListener);
    
  NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW

  // nsISupports
  NS_DECL_ISUPPORTS

  // nsIPluginViewer
  NS_IMETHOD StartLoad(nsIRequest* request, nsIStreamListener*& aResult);

  // nsIContentViewer
  NS_DECL_NSICONTENTVIEWER

  // nsIContentViewerEdit
  NS_DECL_NSICONTENTVIEWEREDIT

  // nsIWebBrowserPrint
  NS_DECL_NSIWEBBROWSERPRINT

  virtual ~PluginViewerImpl();

  nsresult CreatePlugin(nsIRequest* request, nsIPluginHost* aHost, const nsRect& aBounds,
                        nsIStreamListener*& aResult);

  nsresult MakeWindow(nsNativeWidget aParent,
                      nsIDeviceContext* aDeviceContext,
                      const nsRect& aBounds);

  void ForceRefresh(void);

  nsresult GetURI(nsIURI* *aURI);

  nsresult GetDocument(nsIDocument* *aDocument);

  nsIWidget* mWindow;
  nsIDocument* mDocument;
  nsCOMPtr<nsISupports> mContainer;
  nsIChannel* mChannel;
  pluginInstanceOwner *mOwner;
  PRBool mEnableRendering;

};

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

nsresult
NS_NewPluginContentViewer(const char* aCommand,
                          nsIStreamListener** aDocListener,
                          nsIContentViewer** aDocViewer)
{
  PluginViewerImpl* it = new PluginViewerImpl(aCommand);
  if (nsnull == it) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  nsresult rv = it->Init(aDocListener);
  if (NS_FAILED(rv)) {
    delete it;
    return rv;
  }
  return it->QueryInterface(kIContentViewerIID, (void**) aDocViewer);
}

// Note: operator new zeros our memory
PluginViewerImpl::PluginViewerImpl(const char* aCommand)
{
  NS_INIT_REFCNT();
  mEnableRendering = PR_TRUE;
}

nsresult
PluginViewerImpl::Init(nsIStreamListener** aDocListener)
{
  nsIStreamListener* it = new PluginListener(this);
  if (it == nsnull)
    return NS_ERROR_OUT_OF_MEMORY;
  NS_ADDREF(it);
  *aDocListener = it;
  return NS_OK;
}

// ISupports implementation...
NS_IMPL_ADDREF(PluginViewerImpl)
NS_IMPL_RELEASE(PluginViewerImpl)
NS_IMPL_QUERY_INTERFACE4(PluginViewerImpl,
                         nsIPluginViewer,
                         nsIContentViewer,
                         nsIContentViewerEdit,
                         nsIWebBrowserPrint)


PluginViewerImpl::~PluginViewerImpl()
{
#ifdef XP_MAC
  if (mOwner) mOwner->CancelTimer();
#endif

  if(mOwner) {
    nsIPluginInstance * inst;

    if(NS_SUCCEEDED(mOwner->GetInstance(inst)) && inst) {
      nsCOMPtr<nsIPluginHost> host = do_GetService(kCPluginManagerCID);
      if(host)
        host->StopPluginInstance(inst);
      
      NS_RELEASE(inst);
    }
  }

  NS_IF_RELEASE(mOwner);
  if (nsnull != mWindow) {
    mWindow->Destroy();
    NS_RELEASE(mWindow);
  }
  NS_IF_RELEASE(mDocument);
  NS_IF_RELEASE(mChannel);
}

NS_IMETHODIMP
PluginViewerImpl::SetContainer(nsISupports* aContainer)
{
  mContainer = aContainer;
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::GetContainer(nsISupports** aResult)
{
   NS_ENSURE_ARG_POINTER(aResult);

   *aResult = mContainer;
   NS_IF_ADDREF(*aResult);

   return NS_OK;
}


NS_IMETHODIMP
PluginViewerImpl::Init(nsIWidget* aParentWidget,
                       nsIDeviceContext* aDeviceContext,
                       const nsRect& aBounds)
{
  nsresult rv = MakeWindow(aParentWidget->GetNativeData(NS_NATIVE_WIDGET),
   aDeviceContext, aBounds);
  if (NS_OK == rv) {
    mOwner = new pluginInstanceOwner();
    if (nsnull != mOwner) {
      NS_ADDREF(mOwner);
      rv = mOwner->Init(this, mWindow);
    }
  }
  return rv;
}

NS_IMETHODIMP
PluginViewerImpl::StartLoad(nsIRequest* request, nsIStreamListener*& aResult)
{
  nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
  if (!channel) return NS_ERROR_FAILURE;
  
  NS_IF_RELEASE(mChannel);
  mChannel = channel;
  NS_ADDREF(mChannel);

#ifdef DEBUG
  char* contentType;
  channel->GetContentType(&contentType);
  printf("PluginViewerImpl::StartLoad: content-type=%s\n", contentType);
  nsCRT::free(contentType);
#endif

  aResult = nsnull;

  // Only instantiate the plugin if our container can host it
  nsCOMPtr<nsIPluginHost> host;

  host = do_GetService(kCPluginManagerCID);
  nsresult rv = NS_ERROR_FAILURE;
  if(host) 
  {
    // create a document so that we can pass something back to plugin
    // instance if it wants one
    nsCOMPtr<nsIDocument> doc(do_CreateInstance(kHTMLDocumentCID));
    if (doc) {    
      mDocument = doc;
      NS_ADDREF(mDocument);  // released in ~nsPluginViewer
      
      // set the document's URL 
      // so it can be fetched later for resolving relative URLs
      nsCOMPtr<nsIURI> uri;
      GetURI(getter_AddRefs(uri));
      if (uri)
        mDocument->SetDocumentURL(uri);
    }

    nsRect r;
    mWindow->GetClientBounds(r);
    rv = CreatePlugin(request, host, nsRect(0, 0, r.width, r.height), aResult);
  }

  return rv;
}

nsresult
PluginViewerImpl::CreatePlugin(nsIRequest* request, nsIPluginHost* aHost, const nsRect& aBounds,
                               nsIStreamListener*& aResult)
{
  nsresult rv = NS_OK;

  if (nsnull != mOwner) {
    nsPluginWindow  *win;

    mOwner->GetWindow(win);

    win->x = aBounds.x;
    win->y = aBounds.y;
    win->width = aBounds.width;
    win->height = aBounds.height;
    win->clipRect.top = aBounds.y;
    win->clipRect.left = aBounds.x;
    win->clipRect.bottom = aBounds.YMost();
    win->clipRect.right = aBounds.XMost();
  #ifdef XP_UNIX
    win->ws_info = nsnull;   //XXX need to figure out what this is. MMP
  #endif

    nsIURI* uri;
    rv = mChannel->GetURI(&uri);
    if (NS_FAILED(rv)) return rv;

    nsCAutoString spec;
    rv = uri->GetSpec(spec);
    NS_RELEASE(uri);
    if (NS_FAILED(rv)) return rv;
    NS_ConvertUTF8toUCS2 str(spec);

    char* ct;
  
    nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
    channel->GetContentType(&ct);
    if (NS_FAILED(rv)) return rv;
    rv = aHost->InstantiateFullPagePlugin(ct, str, aResult, mOwner);
   
    delete[] ct;
  }

  return rv;
}

NS_IMETHODIMP
PluginViewerImpl::Stop(void)
{
  // XXX write this
  return NS_OK;
}

/*
 * This method is called by the Document Loader once a document has
 * been created for a particular data stream...  The content viewer
 * must cache this document for later use when Init(...) is called.
 */
NS_IMETHODIMP
PluginViewerImpl::LoadStart(nsISupports *aDoc)
{
#ifdef NS_DEBUG
  printf("PluginViewerImpl::LoadStart\n");
#endif
  return aDoc->QueryInterface(kIDocumentIID, (void**)&mDocument);
}


NS_IMETHODIMP
PluginViewerImpl::LoadComplete(nsresult aStatus)
{
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
PluginViewerImpl::Unload(void)
{
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::Close(void)
{
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::Destroy(void)
{
  // XXX ripped off from nsObjectFrame::Destroy()
  
  // we need to finish with the plugin before native window is destroyed
  // doing this in the destructor is too late.
  if(mOwner != nsnull)
  {
    nsIPluginInstance *inst;
    if(NS_OK == mOwner->GetInstance(inst))
    {
      PRBool doCache = PR_TRUE;
      PRBool doCallSetWindowAfterDestroy = PR_FALSE;

      // first, determine if the plugin wants to be cached
      inst->GetValue(nsPluginInstanceVariable_DoCacheBool, 
                     (void *) &doCache);
      if (!doCache) {
        // then determine if the plugin wants Destroy to be called after
        // Set Window.  This is for bug 50547.
        inst->GetValue(nsPluginInstanceVariable_CallSetWindowAfterDestroyBool, 
                       (void *) &doCallSetWindowAfterDestroy);
        !doCallSetWindowAfterDestroy ? inst->SetWindow(nsnull) : 0;
        inst->Stop();
        inst->Destroy();
        doCallSetWindowAfterDestroy ? inst->SetWindow(nsnull) : 0;      }
      else {
        inst->SetWindow(nsnull);
        inst->Stop();
      }
      NS_RELEASE(inst);
    }
  }

	
	return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::GetDOMDocument(nsIDOMDocument **aResult)
{
  return (mDocument) ? CallQueryInterface(mDocument, aResult) : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
PluginViewerImpl::SetDOMDocument(nsIDOMDocument *aDocument)
{
  return NS_ERROR_FAILURE;
}

nsEventStatus PR_CALLBACK
HandlePluginEvent(nsGUIEvent *aEvent)
{
  if (aEvent == nsnull || aEvent->widget == nsnull)   //null pointer check
    return nsEventStatus_eIgnore;

#ifdef XP_WIN
  // on Windows, the mouse click is converted to an NS_PLUGIN_ACTIVATE
  if( aEvent->message == NS_PLUGIN_ACTIVATE)  
    (nsIWidget*)(aEvent->widget)->SetFocus();  // send focus to child window
#else
  // the Mac, and presumably others, send NS_MOUSE_ACTIVATE
  if (aEvent->message == NS_MOUSE_ACTIVATE) {
    (nsIWidget*)(aEvent->widget)->SetFocus();  // send focus to child window
#ifdef XP_MAC
  // furthermore on the Mac nsMacEventHandler sends the NS_PLUGIN_ACTIVATE
  // followed by the mouse down event, so we need to handle this
  } else {
    // on Mac, we store a pointer to this class as native data in the widget
    void *clientData;
    (nsIWidget*)(aEvent->widget)->GetClientData(clientData);
    PluginViewerImpl * pluginViewer = (PluginViewerImpl *)clientData;
    if (pluginViewer != nsnull && pluginViewer->mOwner != nsnull)
      return pluginViewer->mOwner->ProcessEvent(*aEvent);
#endif // XP_MAC
  }
#endif // else XP_WIN
  return nsEventStatus_eIgnore;
}


nsresult
PluginViewerImpl::MakeWindow(nsNativeWidget aParent,
                             nsIDeviceContext* aDeviceContext,
                             const nsRect& aBounds)
{
  nsresult rv =
    nsComponentManager::CreateInstance(kChildWindowCID, nsnull, kIWidgetIID,
                                 (void**)&mWindow);
  if (NS_OK != rv) {
    return rv;
  }
  
 
  mWindow->Create(aParent, aBounds,HandlePluginEvent, aDeviceContext);
  mWindow->SetClientData(this);
  Show();
  return rv;
}

NS_IMETHODIMP
PluginViewerImpl::GetBounds(nsRect& aResult)
{
  NS_PRECONDITION(nsnull != mWindow, "null window");
  if (nsnull != mWindow) {
    mWindow->GetBounds(aResult);
  }
  else {
    aResult.SetRect(0, 0, 0, 0);
  }
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::GetPreviousViewer(nsIContentViewer** aViewer)
{
  *aViewer = nsnull;
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::SetPreviousViewer(nsIContentViewer* aViewer)
{
  if (aViewer)
    aViewer->Destroy();
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::SetBounds(const nsRect& aBounds)
{
  NS_PRECONDITION(nsnull != mWindow, "null window");
  if (nsnull != mWindow) {
    // Don't have the widget repaint. Layout will generate repaint requests
    // during reflow
    nsIPluginInstance *inst;
    mWindow->Resize(aBounds.x, aBounds.y, aBounds.width, aBounds.height, PR_FALSE);
    if ((nsnull != mOwner) && (NS_OK == mOwner->GetInstance(inst)) && (nsnull != inst)) {
      nsPluginWindow  *win;
      if (NS_OK == mOwner->GetWindow(win)) {
        win->x = aBounds.x;
        win->y = aBounds.y;
        win->width = aBounds.width;
        win->height = aBounds.height;
        win->clipRect.top = aBounds.y;
        win->clipRect.left = aBounds.x;
        win->clipRect.bottom = aBounds.YMost();
        win->clipRect.right = aBounds.XMost();

        inst->SetWindow(win);
      }
      NS_RELEASE(inst);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::Move(PRInt32 aX, PRInt32 aY)
{
  NS_PRECONDITION(nsnull != mWindow, "null window");
  if (nsnull != mWindow) {
    nsIPluginInstance *inst;
    mWindow->Move(aX, aY);
    if ((nsnull != mOwner) && (NS_OK == mOwner->GetInstance(inst)) && (nsnull != inst)) {
      nsPluginWindow  *win;
      if (NS_OK == mOwner->GetWindow(win)) {
        win->x = aX;
        win->y = aY;
        win->clipRect.bottom = (win->clipRect.bottom - win->clipRect.top) + aY;
        win->clipRect.right = (win->clipRect.right - win->clipRect.left) + aX;
        win->clipRect.top = aY;
        win->clipRect.left = aX;

        inst->SetWindow(win);
      }
      NS_RELEASE(inst);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::Show()
{
  NS_PRECONDITION(nsnull != mWindow, "null window");
  if (nsnull != mWindow) {
    mWindow->Show(PR_TRUE);
  }

  // XXX should we call SetWindow here?

  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::Hide()
{
  NS_PRECONDITION(nsnull != mWindow, "null window");
  if (nsnull != mWindow) {
    mWindow->Show(PR_FALSE);
  }

  // should we call SetWindow(nsnull) here?

  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::SetEnableRendering(PRBool aOn)
{
  mEnableRendering = aOn;
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::GetEnableRendering(PRBool* aResult)
{
  NS_PRECONDITION(nsnull != aResult, "null OUT ptr");
  if (aResult) {
    *aResult = mEnableRendering;
  }
  return NS_OK;
}

void
PluginViewerImpl::ForceRefresh()
{
  mWindow->Invalidate(PR_TRUE);
}

nsresult PluginViewerImpl::GetURI(nsIURI* *aURI)
{
  return mChannel->GetURI(aURI);
}

nsresult PluginViewerImpl::GetDocument(nsIDocument* *aDocument)
{
  NS_IF_ADDREF(mDocument);
  *aDocument = mDocument;
  return NS_OK;
}

NS_IMETHODIMP pluginInstanceOwner::InvalidateRect(nsPluginRect *invalidRect)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP pluginInstanceOwner::InvalidateRegion(nsPluginRegion invalidRegion)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP pluginInstanceOwner::ForceRedraw()
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP pluginInstanceOwner::GetValue(nsPluginInstancePeerVariable variable, void *value)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}
/* ========================================================================================
 * nsIContentViewerEdit
 * ======================================================================================== */

NS_IMETHODIMP PluginViewerImpl::Search()
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::GetSearchable(PRBool *aSearchable)
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::ClearSelection()
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::SelectAll()
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::CopySelection()
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::GetCopyable(PRBool *aCopyable)
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::CutSelection()
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::GetCutable(PRBool *aCutable)
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::Paste()
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP PluginViewerImpl::GetPasteable(PRBool *aPasteable)
{
  NS_ASSERTION(0, "NOT IMPLEMENTED");
  return NS_ERROR_NOT_IMPLEMENTED;
}

/* ========================================================================================
 * nsIWebBrowserPrint
 * ======================================================================================== */
NS_IMETHODIMP
PluginViewerImpl::Print(nsIPrintSettings* aPrintSettings,
                        nsIWebProgressListener* aWebProgressListener)
{
  nsPluginPrint npprint;
  npprint.mode = nsPluginMode_Full;
  npprint.print.fullPrint.pluginPrinted = PR_FALSE;
  npprint.print.fullPrint.printOne = PR_FALSE;
  npprint.print.fullPrint.platformPrint = nsnull;

  NS_ENSURE_TRUE(mOwner,NS_ERROR_FAILURE);
  nsCOMPtr<nsIPluginInstance> pi;
  mOwner->GetInstance(*getter_AddRefs(pi));
  NS_ENSURE_TRUE(pi,NS_ERROR_FAILURE);

  return pi->Print(&npprint);

}

NS_IMETHODIMP 
PluginViewerImpl::GetNewPrintSettings(nsIPrintSettings * *aNewPrintSettings)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* readonly attribute wstring defaultPrinterName; */
NS_IMETHODIMP 
PluginViewerImpl::GetDefaultPrinterName(PRUnichar * *aDefaultPrinterName)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* void initPrintSettingsFromPrinter (in wstring aPrinterName, in nsIPrintSettings aPrintSettings); */
NS_IMETHODIMP 
PluginViewerImpl::InitPrintSettingsFromPrinter(const PRUnichar *aPrinterName, nsIPrintSettings *aPrintSettings)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute nsIPrintSettings globalPrintSettings; */
NS_IMETHODIMP 
PluginViewerImpl::GetGlobalPrintSettings(nsIPrintSettings * *aGlobalPrintSettings)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* attribute nsIPrintSettings globalPrintSettingsValues; */
NS_IMETHODIMP 
PluginViewerImpl::GetGlobalPrintSettingsValues(nsIPrintSettings * *aGlobalPrintSettingsValues)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP 
PluginViewerImpl::SetGlobalPrintSettingsValues(nsIPrintSettings * aGlobalPrintSettingsValues)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
/* void printPreview (in nsIPrintSettings aThePrintSettings); */
NS_IMETHODIMP 
PluginViewerImpl::PrintPreview(nsIPrintSettings *aThePrintSettings)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* void printPreviewNavigate (in short aNavType, in long aPageNum); */
NS_IMETHODIMP 
PluginViewerImpl::PrintPreviewNavigate(PRInt16 aNavType, PRInt32 aPageNum)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* readonly attribute boolean doingPrintPreview; */
NS_IMETHODIMP
PluginViewerImpl::GetDoingPrintPreview(PRBool *aDoingPrintPreview)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* void cancel (); */
NS_IMETHODIMP 
PluginViewerImpl::Cancel()
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* void enumerateDocumentNames (out PRUint32 aCount, [array, size_is (aCount), retval] out wstring aResult); */
NS_IMETHODIMP 
PluginViewerImpl::EnumerateDocumentNames(PRUint32 *aCount, PRUnichar ***aResult)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* readonly attribute boolean isFramesetDocument; */
NS_IMETHODIMP 
PluginViewerImpl::GetIsFramesetDocument(PRBool *aIsFramesetDocument)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* readonly attribute long printPreviewNumPages; */
NS_IMETHODIMP 
PluginViewerImpl::GetPrintPreviewNumPages(PRInt32 *aPrintPreviewNumPages)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* void exitPrintPreview (); */
NS_IMETHODIMP 
PluginViewerImpl::ExitPrintPreview()
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
PluginViewerImpl::GetInLink(PRBool* aInLink)
{
  NS_ENSURE_ARG_POINTER(aInLink);
  *aInLink = PR_FALSE;
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::GetInImage(PRBool* aInImage)
{
  NS_ENSURE_ARG_POINTER(aInImage);
  *aInImage = PR_FALSE;
  return NS_OK;
}

NS_IMETHODIMP
PluginViewerImpl::CopyLinkLocation()
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
PluginViewerImpl::CopyImageLocation()
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
PluginViewerImpl::CopyImageContents()
{
  return NS_ERROR_NOT_IMPLEMENTED;
}


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

PluginListener::PluginListener(PluginViewerImpl* aViewer)
{
  NS_INIT_REFCNT();
  mViewer = aViewer;
  NS_ADDREF(aViewer);
}

PluginListener::~PluginListener()
{
  NS_RELEASE(mViewer);
  NS_IF_RELEASE(mNextStream);
}

NS_IMPL_ISUPPORTS1(PluginListener, nsIStreamListener)

NS_IMETHODIMP
PluginListener::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
{
  nsresult rv;
  char* contentType = nsnull;

  nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
  rv = channel->GetContentType(&contentType);

  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = mViewer->StartLoad(request, mNextStream);

  if (NS_FAILED(rv)) {
    return rv;
  }
  if (nsnull == mNextStream) 
    return NS_ERROR_FAILURE;
  return mNextStream->OnStartRequest(request, ctxt);
}

NS_IMETHODIMP
PluginListener::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
                              nsresult status)
{
  if (nsnull == mNextStream) {
    return NS_ERROR_FAILURE;
  }
  return mNextStream->OnStopRequest(request, ctxt, status);
}

NS_IMETHODIMP
PluginListener::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
                                nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count)
{
  if (nsnull == mNextStream) {
    return NS_ERROR_FAILURE;
  }
  return mNextStream->OnDataAvailable(request, ctxt, inStr, sourceOffset, count);
}

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

pluginInstanceOwner :: pluginInstanceOwner()
{
  NS_INIT_REFCNT();

  memset(&mPluginWindow, 0, sizeof(mPluginWindow));
  mInstance = nsnull;
  mWindow = nsnull;
  mViewer = nsnull;
}

pluginInstanceOwner :: ~pluginInstanceOwner()
{
  // shut off the timer.
  if (mPluginTimer != nsnull) {
    CancelTimer();
  }

  NS_IF_RELEASE(mInstance);

  mWindow = nsnull;
  mViewer = nsnull;
}

NS_IMPL_ISUPPORTS2(pluginInstanceOwner, nsIPluginInstanceOwner,nsITimerCallback);

NS_IMETHODIMP pluginInstanceOwner :: SetInstance(nsIPluginInstance *aInstance)
{
  NS_IF_RELEASE(mInstance);
  mInstance = aInstance;
  NS_IF_ADDREF(mInstance);

  return NS_OK;
}

NS_IMETHODIMP pluginInstanceOwner :: GetInstance(nsIPluginInstance *&aInstance)
{
  if(!mInstance)
    return NS_ERROR_FAILURE;

  NS_ADDREF(mInstance);
  aInstance = mInstance;

  return NS_OK;
}

NS_IMETHODIMP pluginInstanceOwner :: GetWindow(nsPluginWindow *&aWindow)
{
  aWindow = &mPluginWindow;
  return NS_OK;
}

NS_IMETHODIMP pluginInstanceOwner :: GetMode(nsPluginMode *aMode)
{
  *aMode = nsPluginMode_Full;
  return NS_OK;
}

NS_IMETHODIMP pluginInstanceOwner :: CreateWidget(void)
{
  PRBool    windowless;
  nsresult  rv = NS_OK;
  
  if (nsnull != mInstance)
  {
#if defined(XP_MAC)
          // start a periodic timer to provide null events to the plugin instance.
          mPluginTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
          if (rv == NS_OK)
            rv = mPluginTimer->Init(this, 1020 / 60, NS_PRIORITY_NORMAL, NS_TYPE_REPEATING_SLACK);
#endif


    mInstance->GetValue(nsPluginInstanceVariable_WindowlessBool, (void *)&windowless);

    if (PR_TRUE == windowless)
    {
      mPluginWindow.window = nsnull;    //XXX this needs to be a HDC
      mPluginWindow.type = nsPluginWindowType_Drawable;
    }
    else if (nsnull != mWindow)
    {
      mPluginWindow.window = (nsPluginPort *)mWindow->GetNativeData(NS_NATIVE_PLUGIN_PORT);
      mPluginWindow.type = nsPluginWindowType_Window;
    }
    else
      return NS_ERROR_FAILURE;
  }
  else
    return NS_ERROR_FAILURE;

#if defined(XP_MAC)
  FixUpPluginWindow();
#endif

  return rv;
}

NS_IMETHODIMP pluginInstanceOwner::GetURL(const char *aURL, const char *aTarget, void *aPostData, PRUint32 aPostDataLen, void *aHeadersData, 
                                            PRUint32 aHeadersDataLen, PRBool isFile)
{
  NS_ENSURE_TRUE(mViewer,NS_ERROR_NULL_POINTER);

  // the container of the pres context will give us the link handler
  nsCOMPtr<nsISupports> container;
  nsresult rv = mViewer->GetContainer(getter_AddRefs(container));
  NS_ENSURE_TRUE(container,NS_ERROR_FAILURE);
  nsCOMPtr<nsILinkHandler> lh = do_QueryInterface(container);
  NS_ENSURE_TRUE(lh, NS_ERROR_FAILURE);

  nsCOMPtr<nsIURI> uri;
  rv = mViewer->GetURI(getter_AddRefs(uri));
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv),NS_ERROR_FAILURE);

  char* absURIStr;
  NS_MakeAbsoluteURI(&absURIStr, aURL, uri);
  nsAutoString fullurl; fullurl.AssignWithConversion(absURIStr);
  nsCRT::free(absURIStr);
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv),NS_ERROR_FAILURE);

  nsCOMPtr<nsIInputStream> postDataStream;
  nsCOMPtr<nsIInputStream> headersDataStream;

  // deal with post data, either in a file or raw data, and any headers
  if (aPostData) {
    rv = NS_NewPluginPostDataStream(getter_AddRefs(postDataStream), (const char *)aPostData, aPostDataLen, isFile);
    NS_ASSERTION(NS_SUCCEEDED(rv),"failed in creating plugin post data stream");
    if (NS_FAILED(rv)) return rv;

    if (aHeadersData) {
      rv = NS_NewPluginPostDataStream(getter_AddRefs(headersDataStream), 
                                      (const char *) aHeadersData, 
                                      aHeadersDataLen,
                                      PR_FALSE,
                                      PR_TRUE);  // last arg says we are headers
      NS_ASSERTION(NS_SUCCEEDED(rv),"failed in creating plugin header data stream");
      if (NS_FAILED(rv)) return rv;
    }
  }


  nsAutoString  unitarget; unitarget.AssignWithConversion(aTarget);
  rv = lh->OnLinkClick(nsnull, eLinkVerb_Replace, 
                       fullurl.get(), unitarget.get(), 
                       postDataStream, headersDataStream);

  return rv;
}

NS_IMETHODIMP pluginInstanceOwner :: ShowStatus(const char *aStatusMsg)
{
  nsresult  rv = NS_ERROR_FAILURE;
  
  rv = this->ShowStatus(NS_ConvertUTF8toUCS2(aStatusMsg).get());
  
  return rv;
}

NS_IMETHODIMP pluginInstanceOwner::ShowStatus(const PRUnichar *aStatusMsg)
{
  nsresult  rv = NS_ERROR_FAILURE;
  
  if (!mViewer) {
    return rv;
  }
  nsCOMPtr<nsISupports> cont;
  nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
  
  rv = mViewer->GetContainer(getter_AddRefs(cont));
  if (NS_FAILED(rv) || !cont) {
    return rv;
  }

  nsCOMPtr<nsIDocShellTreeItem> docShellItem(do_QueryInterface(cont, &rv));
  if (NS_FAILED(rv) || !docShellItem) {
    return rv;
  }

  rv = docShellItem->GetTreeOwner(getter_AddRefs(treeOwner));
  if (NS_FAILED(rv) || !treeOwner) {
    return rv;
  }

  nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(treeOwner, &rv));
  if (NS_FAILED(rv) || !browserChrome) {
    return rv;
  }
  rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_SCRIPT, 
                                aStatusMsg);
  return rv;
}

NS_IMETHODIMP pluginInstanceOwner :: GetDocument(nsIDocument* *aDocument)
{
	return mViewer->GetDocument(aDocument);
}

NS_IMETHODIMP pluginInstanceOwner :: Init(PluginViewerImpl *aViewer, nsIWidget *aWindow)
{
  //do not addref
  mWindow = aWindow;
  mViewer = aViewer;

  return NS_OK;
}


// Here's where we forward events to plugins.

#ifdef XP_MAC

#if TARGET_CARBON
inline Boolean OSEventAvail(EventMask mask, EventRecord* event) { return EventAvail(mask, event); }
#endif

void pluginInstanceOwner::GUItoMacEvent(const nsGUIEvent& anEvent, EventRecord& aMacEvent)
{
       ::OSEventAvail(0, &aMacEvent);
       switch (anEvent.message) {
       case NS_GOTFOCUS:
       case NS_FOCUS_EVENT_START:
               aMacEvent.what = nsPluginEventType_GetFocusEvent;
               break;
       case NS_LOSTFOCUS:
       case NS_MOUSE_EXIT:
               aMacEvent.what = nsPluginEventType_LoseFocusEvent;
               break;
       case NS_MOUSE_MOVE:
       case NS_MOUSE_ENTER:
         mWindow->SetFocus();
               aMacEvent.what = nsPluginEventType_AdjustCursorEvent;
               break;
       case NS_PAINT:
          aMacEvent.what = updateEvt;
         break;
       case NS_KEY_DOWN:
       case NS_KEY_PRESS:
         break;
         
       default:
               aMacEvent.what = nullEvent;
               break;
       }
}
#endif

nsEventStatus pluginInstanceOwner::ProcessEvent(const nsGUIEvent& anEvent)
{
  nsEventStatus rv = nsEventStatus_eIgnore;
  if (!mInstance || !mWindow)   // if the instance or the window is null, we shouldn't be here
    return rv;

#ifdef XP_MAC
    //if (mWidget != NULL) {  // check for null mWidget
        EventRecord* event = (EventRecord*)anEvent.nativeMsg;
        if (event == NULL || event->what == nullEvent ||
            anEvent.message == NS_KEY_PRESS           ||
            anEvent.message == NS_CONTEXTMENU_MESSAGE_START) {
            EventRecord macEvent;
            GUItoMacEvent(anEvent, macEvent);
            event = &macEvent;
            if (event->what == updateEvt) {
              nsPluginPort* pluginPort = GetPluginPort();
                // Add in child windows absolute position to get make the dirty rect
                // relative to the top-level window.
                nscoord absWidgetX = 0;
                nscoord absWidgetY = 0;
               nsRect widgetClip(0,0,0,0);
               GetWidgetPosAndClip(mWindow,absWidgetX,absWidgetY,widgetClip);
               //mViewer->GetBounds(widgetClip);
               //absWidgetX = widgetClip.x;
               //absWidgetY = widgetClip.y;
               
                // set the port
    mPluginWindow.x = absWidgetX;
    mPluginWindow.y = absWidgetY;


    // fix up the clipping region
    mPluginWindow.clipRect.top = widgetClip.y;
    mPluginWindow.clipRect.left = widgetClip.x;
    mPluginWindow.clipRect.bottom =  mPluginWindow.clipRect.top + widgetClip.height;
    mPluginWindow.clipRect.right =  mPluginWindow.clipRect.left + widgetClip.width;  

    EventRecord updateEvent;
    ::OSEventAvail(0, &updateEvent);
    updateEvent.what = updateEvt;
    updateEvent.message = UInt32(pluginPort->port);

    nsPluginEvent pluginEvent = { &updateEvent, nsPluginPlatformWindowRef(pluginPort->port) };
    PRBool eventHandled = PR_FALSE;
    mInstance->HandleEvent(&pluginEvent, &eventHandled);
     }
            
            return  nsEventStatus_eConsumeNoDefault;
            
        }
        //nsPluginPort* port = (nsPluginPort*)mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT);
        nsPluginPort* port = (nsPluginPort*)mWindow->GetNativeData(NS_NATIVE_PLUGIN_PORT);
        nsPluginEvent pluginEvent = { event, nsPluginPlatformWindowRef(port->port) };
        PRBool eventHandled = PR_FALSE;
        mInstance->HandleEvent(&pluginEvent, &eventHandled);
        if (eventHandled && anEvent.message != NS_MOUSE_LEFT_BUTTON_DOWN)
            rv = nsEventStatus_eConsumeNoDefault;
   // }
#endif

//~~~
#ifdef XP_WIN
        nsPluginEvent * pPluginEvent = (nsPluginEvent *)anEvent.nativeMsg;
        PRBool eventHandled = PR_FALSE;
        mInstance->HandleEvent(pPluginEvent, &eventHandled);
        if (eventHandled)
            rv = nsEventStatus_eConsumeNoDefault;
#endif

  return rv;
}


// Here's how we give idle time to plugins.

NS_IMETHODIMP_(void) pluginInstanceOwner::Notify(nsITimer* /* timer */)
{
#ifdef XP_MAC
    // validate the plugin clipping information by syncing the plugin window info to
    // reflect the current widget location. This makes sure that everything is updated
    // correctly in the event of scrolling in the window.
    FixUpPluginWindow();
    if (mInstance != NULL) {
        EventRecord idleEvent;
        ::OSEventAvail(0, &idleEvent);
        idleEvent.what = nullEvent;
        
        nsPluginPort* pluginPort = GetPluginPort();
        nsPluginEvent pluginEvent = { &idleEvent, nsPluginPlatformWindowRef(pluginPort->port) };
        
        PRBool eventHandled = PR_FALSE;
        mInstance->HandleEvent(&pluginEvent, &eventHandled);
    }

#ifndef REPEATING_TIMERS
  // reprime the timer? currently have to create a new timer for each call, which is
  // kind of wasteful. need to get periodic timers working on all platforms.
  nsresult rv;
  mPluginTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  if (NS_SUCCEEDED(rv))
    mPluginTimer->Init(this, 1020 / 60);
#endif  // REPEATING_TIMERS
#endif // XP_MAC
}


void pluginInstanceOwner::CancelTimer()
{
    if (mPluginTimer) {
        mPluginTimer->Cancel();
        mPluginTimer = nsnull;
    }
}


#ifdef XP_MAC
nsPluginPort* pluginInstanceOwner::GetPluginPort()
{

  nsPluginPort* result = NULL;
    if (mWindow != NULL)
      result = (nsPluginPort*) mWindow->GetNativeData(NS_NATIVE_PLUGIN_PORT);
    return result;
}

  // calculate the absolute position and clip for a widget 
  // and use other windows in calculating the clip
static void GetWidgetPosAndClip(nsIWidget* aWidget,nscoord& aAbsX, nscoord& aAbsY,
                                nsRect& aClipRect) 
{
  aWidget->GetBounds(aClipRect); 
  aAbsX = aClipRect.x; 
  aAbsY = aClipRect.y; 
  
  nscoord ancestorX = -aClipRect.x, ancestorY = -aClipRect.y; 
  // Calculate clipping relative to the widget passed in 
  aClipRect.x = 0; 
  aClipRect.y = 0; 

   // Gather up the absolute position of the widget 
   // + clip window 
  nsCOMPtr<nsIWidget> widget = getter_AddRefs(aWidget->GetParent());
  while (widget != nsnull) { 
    nsRect wrect; 
    widget->GetClientBounds(wrect); 
    nscoord wx, wy; 
    wx = wrect.x; 
    wy = wrect.y; 
    wrect.x = ancestorX; 
    wrect.y = ancestorY; 
    aClipRect.IntersectRect(aClipRect, wrect); 
    aAbsX += wx; 
    aAbsY += wy; 
    widget = getter_AddRefs(widget->GetParent());
    if (widget == nsnull) { 
      // Don't include the top-level windows offset 
      // printf("Top level window offset %d %d\n", wx, wy); 
      aAbsX -= wx; 
      aAbsY -= wy; 
    } 
    ancestorX -=wx; 
    ancestorY -=wy; 
  } 

  aClipRect.x += aAbsX; 
  aClipRect.y += aAbsY; 

  //printf("--------------\n"); 
  //printf("Widget clip X %d Y %d rect %d %d %d %d\n", aAbsX, aAbsY,  aClipRect.x,  aClipRect.y, aClipRect.width,  aClipRect.height ); 
  //printf("--------------\n"); 
} 


void pluginInstanceOwner::FixUpPluginWindow()
{
  if (mWindow) {
    nscoord absWidgetX = 0;
    nscoord absWidgetY = 0;
    nsRect widgetClip(0,0,0,0);
    GetWidgetPosAndClip(mWindow,absWidgetX,absWidgetY,widgetClip);

    // set the port coordinates
    mPluginWindow.x = absWidgetX;
    mPluginWindow.y = absWidgetY;

    // fix up the clipping region
    mPluginWindow.clipRect.top = widgetClip.y;
    mPluginWindow.clipRect.left = widgetClip.x;
    mPluginWindow.clipRect.bottom =  mPluginWindow.clipRect.top + widgetClip.height;
    mPluginWindow.clipRect.right =  mPluginWindow.clipRect.left + widgetClip.width; 
  }
}

#endif
