/* -*- Mode: C; c-file-style: "gnu" -*-
   japharJVMPluginInstance.cpp -- japhar's mozilla java runtime support (this class is instanciated for APPLET tags.)
   Created: Chris Toshok <toshok@hungry.com>, 8-Aug-1998
 */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include "config.h"

#include <assert.h>
#include <stdlib.h>

#include <Xm/XmP.h>
#include <Xm/DrawingA.h>

extern "C" {
#include "../libnative/sun.awt.motif/common.h" /* XXXX */
};

#include "native-threads.h"
#include "nsIJVMPluginTagInfo.h"
#include "nsIEventHandler.h"
#include "nsIPluginTagInfo2.h"
#include "japharJVMPluginInstance.h"

japharJVMPluginInstance::japharJVMPluginInstance(nsISupports *browserInterfaces)
{
  _browser = browserInterfaces;
  _started = JNI_FALSE;
}

/////////////////////////////////////////////////
// nsISupports
/////////////////////////////////////////////////

NS_IMPL_ADDREF(japharJVMPluginInstance)
  NS_IMPL_RELEASE(japharJVMPluginInstance)

  nsresult
japharJVMPluginInstance::QueryInterface(nsID const& aIID,
					void** aInstancePtr)
{
  printf ("japharJVMPluginInstance::QueryInterface()\n");
  if (NULL == aInstancePtr) {
    return NS_ERROR_NULL_POINTER;
  }

  *aInstancePtr = NULL;

  static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
  static NS_DEFINE_IID(kIEventHandlerIID, NS_IEVENTHANDLER_IID);
  static NS_DEFINE_IID(kIPluginInstanceIID, NS_IPLUGININSTANCE_IID);
  static NS_DEFINE_IID(kIJVMPluginInstanceIID, NS_IJVMPLUGININSTANCE_IID);
  if (aIID.Equals(kIJVMPluginInstanceIID)) {
    *aInstancePtr = (void*) ((nsIJVMPluginInstance*)this);
    AddRef();
    printf ("  + returning nsIJVMPluginInstance\n");
    return NS_OK;
  }
  if (aIID.Equals(kIPluginInstanceIID)) {
    *aInstancePtr = (void*) ((nsIPluginInstance*)this);
    AddRef();
    printf ("  + returning nsIPluginInstance\n");
    return NS_OK;
  }
  if (aIID.Equals(kIEventHandlerIID)) {
    *aInstancePtr = (void*) ((nsIEventHandler*)this);
    AddRef();
    printf ("  + returning nsIEventHandler\n");
    return NS_OK;
  }
  if (aIID.Equals(kISupportsIID)) {
    *aInstancePtr = (void*) ((nsISupports*)this);
    AddRef();
    printf ("  + returning nsISupports\n");
    return NS_OK;
  }
  printf("  + returning NS_NOINTERFACE\n");
  return NS_NOINTERFACE;
}

/////////////////////////////////////////////////
// nsIEventHandler
/////////////////////////////////////////////////

/**
 * Handles an event. An nsIEventHandler can also get registered with with
 * nsIPluginManager2::RegisterWindow and will be called whenever an event
 * comes in for that window.
 *
 * Note that for Unix and Mac the nsPluginEvent structure is different
 * from the old NPEvent structure -- it's no longer the native event
 * record, but is instead a struct. This was done for future extensibility,
 * and so that the Mac could receive the window argument too. For Windows
 * and OS2, it's always been a struct, so there's no change for them.
 *
 * (Corresponds to NPP_HandleEvent.)
 *
 * @param event - the event to be handled
 * @param handled - set to PR_TRUE if event was handled
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::HandleEvent(nsPluginEvent* event,
				     PRBool* handled)
{
  /* UNIX Plugins do not use HandleEvent */
  printf ("japharJVMPluginInstance::HandleEvent\n");
  return 0;
}


/////////////////////////////////////////////////
// nsIPluginInstance
/////////////////////////////////////////////////

/**
 * Initializes a newly created plugin instance, passing to it the plugin
 * instance peer which it should use for all communication back to the browser.
 * 
 * @param peer - the corresponding plugin instance peer
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::Initialize(nsIPluginInstancePeer* peer)
{
  printf ("japharJVMPluginInstance::Initialize\n");
  nsIJVMPluginTagInfo *info;
  nsIPluginTagInfo2 *info2;
  nsresult res;

  _peer = peer;

  _peer->ShowStatus("Initializing JVM Plugin Instance...");

  static NS_DEFINE_IID(kIJVMPluginTagInfoIID, NS_IJVMPLUGINTAGINFO_IID);
  static NS_DEFINE_IID(kIPluginTagInfo2IID, NS_IPLUGINTAGINFO2_IID);

  res = _peer->QueryInterface(kIJVMPluginTagInfoIID,
			      (void**)&info);

  if (res == NS_OK)
    {
      /* get information about the applet (code, codebase, archive, name, mayscript) */
      nsresult r;
      
      r = info->GetCode(&_code);
      if (r != NS_OK)
	printf ("error %d getting code for JVM Plugin Instance\n", r);
      else
	printf ("code == %s\n", _code);
	  
      r = info->GetCodeBase(&_codebase);
      if (r != NS_OK)
	printf ("error %d getting codebase for JVM Plugin Instance\n", r);
      else if (_codebase == NULL)
	printf ("codebase not set in applet tag\n");
      else
	printf ("codebase == %s\n", _codebase);

      r = info->GetArchive(&_archive);
      if (r != NS_OK)
	printf ("error %d getting archive for JVM Plugin Instance\n", r);
      else
	printf ("archive == %s\n", _archive);

      r = info->GetName(&_name);
      if (r != NS_OK)
	printf ("error %d getting name for JVM Plugin Instance\n", r);
      else
	printf ("name == %s\n", _name);

      r = info->GetMayScript(&_mayscript);
      if (r != NS_OK)
	printf ("error %d getting mayscript for JVM Plugin Instance\n", r);
      else
	printf ("mayscript == %d\n", _mayscript);

      info->Release();
    }

  res = _peer->QueryInterface(kIPluginTagInfo2IID,
			      (void**)&info2);

  if (res == NS_OK)
    {
      /* get the parameters to the applet */
      nsresult r;
      PRUint16 n;
      char** names;
      char** values;

      r = info2->GetParameters(n, names, values);
      if (r != NS_OK)
	printf ("error %d getting parameters for JVM Plugin Instance\n", r);
      else
	{
	  int i;
	  for (i = 0; i < n; i ++)
	    printf ("param(%s) = %s\n", names[i], values[i]);
	}

      info2->Release();

      if (_codebase == NULL)
	{
	  r = info2->GetDocumentBase(&_codebase);
	  if (r != NS_OK)
	    printf ("error %d getting document base for JVM Plugin Instance\n", r);
	  else
	    printf ("_codebase == %s\n", _codebase);

	  if (_codebase != NULL)
	    {
	      char *slash = strrchr(_codebase, '/');
	      
	      if (slash)
		slash[1] = 0; /* terminate the string after the slash. */
	    }
	}
    }

  /* if we couldn't get a valid codebase, error out. */
  if (_codebase == NULL)
    return NS_ERROR_FAILURE;

  /* if it's got a ':', assume it's got a protocol in it someplace and
     use it unmolested */
  if (!strchr(_codebase, ':'))
    {
      const char *doc_base;
      nsresult r;

      r = info2->GetDocumentBase(&doc_base);
      if (r != NS_OK)
	return NS_ERROR_FAILURE;
	
      if (_codebase[0] == '/')
	{
	  /* it's an absolute URL, just stick the hostname and protocol on
	     the front of it. */
	  char *end_hostname;

	  end_hostname = strstr(doc_base, "//");
	  if (end_hostname == NULL)
	    return NS_ERROR_FAILURE;

	  end_hostname += 2; /* strlen("//") */
	  end_hostname = strchr(end_hostname, '/');
	  if (end_hostname)
	    {
	      *end_hostname = 0;
	      char *real_codebase = (char*)malloc(strlen(doc_base)
						  + strlen(_codebase)
						  + 2);
	      strcpy(real_codebase, doc_base);
	      strcat(real_codebase, _codebase);
	      strcat(real_codebase, "/"); /* XXXX */

	      *end_hostname = '/';

	      free((char*)_codebase);
	      _codebase = real_codebase;
	    }
	  else
	    {
	      assert(0);
	    }
	}
      else
	{
	}
    }

  if (_codebase[strlen(_codebase) - 1] != '/')
    {
      char *new_codebase = (char*)malloc(strlen(_codebase) + 2);
      strcpy(new_codebase, _codebase);
      strcat(new_codebase, "/");
      free((char*)_codebase);
      _codebase = new_codebase;
    }

  printf ("final codebase = %s\n", _codebase);

  _env = THREAD_getEnv(); /* XXX should be passed in to the constructor? */
  assert(_env);
  if (_env == NULL)
    {
      printf ("_env == NULL\n");
      return NS_ERROR_FAILURE;
    }

  printf ("setting up applet context\n");
  res = InitializeAppletContext();
  if (res != NS_OK)
    {
      printf ("japharJVMPluginInstance::InitializeAppletContext failed\n");
      return res;
    }

  printf ("setting up class loader\n");
  res = InitializeClassLoader();
  if (res != NS_OK)
    {
      printf ("japharJVMPluginInstance::InitializeClassLoader failed\n");
      return res;
    }

  printf ("loading applet\n");
  res = InitializeApplet();
  if (res != NS_OK)
    {
      printf ("japharJVMPluginInstance::InitializeApplet failed\n");
      return res;
    }

  return res;
}

/**
 * Returns a reference back to the plugin instance peer. This method is
 * used whenever the browser needs to obtain the peer back from a plugin
 * instance. The implementation of this method should be sure to increment
 * the reference count on the peer by calling AddRef.
 *
 * @param resultingPeer - the resulting plugin instance peer
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::GetPeer(nsIPluginInstancePeer* *resultingPeer)
{
  printf ("japharJVMPluginInstance::GetPeer\n");

  *resultingPeer = _peer;
  return NS_OK;
}

/**
 * Called to instruct the plugin instance to start. This will be called after
 * the plugin is first created and initialized, and may be called after the
 * plugin is stopped (via the Stop method) if the plugin instance is returned
 * to in the browser window's history.
 *
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::Start(void)
{
  if (_started == JNI_FALSE)
    {
      _started = JNI_TRUE;

      printf ("japharJVMPluginInstance::Start\n");
      _peer->ShowStatus("Starting JVM Plugin Instance...");
      
      if (_start_method)
	_env->CallVoidMethod(_obj, _start_method);
      
    }
  return NS_OK;
}
  
/**
 * Called to instruct the plugin instance to stop, thereby suspending its state.
 * This method will be called whenever the browser window goes on to display
 * another page and the page containing the plugin goes into the window's history
 * list.
 *
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::Stop(void)
{
  if (_started == JNI_TRUE)
    {
      _started = JNI_FALSE;

      printf ("japharJVMPluginInstance::Stop\n");
      _peer->ShowStatus("Stopping JVM Plugin Instance...");
      
      if (_stop_method)
	_env->CallVoidMethod(_obj, _stop_method);
    }

  return NS_OK;
}
  
/**
 * Called to instruct the plugin instance to destroy itself. This is called when
 * it become no longer possible to return to the plugin instance, either because 
 * the browser window's history list of pages is being trimmed, or because the
 * window containing this page in the history is being closed.
 *
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::Destroy(void)
{
  printf ("japharJVMPluginInstance::Destroy\n");
  _peer->ShowStatus("Destroying JVM Plugin Instance...");

  if (_destroy_method)
    _env->CallVoidMethod(_obj, _destroy_method);

  return NS_OK;
}

extern "C" void initialize_awt_with_stuff(Widget, XtAppContext);

static Widget
find_toplevel(Widget w)
{
  while (XtParent(w))
    {
      w = XtParent(w);
    }

  return w;
}
  
/**
 * Called when the window containing the plugin instance changes.
 *
 * (Corresponds to NPP_SetWindow.)
 *
 * @param window - the plugin window structure
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::SetWindow(nsPluginWindow* window)
{
  Widget netscape_widget;
  extern XtAppContext fe_XtAppContext;

  printf ("japharJVMPluginInstance::SetWindow(%p)\n", window);

  if (window != NULL)
    {
      if ((Window)window->window != _window)
	{
	  _window = (Window) window->window;
	  _x = window->x;
	  _y = window->y;
	  _width = window->width;
	  _height = window->height;
	  _display = ((nsPluginSetWindowCallbackStruct *)window->ws_info)->display;
	  
	  netscape_widget = XtWindowToWidget(_display, _window);
	  
	  initialize_awt_with_stuff(find_toplevel(netscape_widget),
				    fe_XtAppContext);

	  jfieldID peer_field = _env->GetFieldID(_env->FindClass("java/awt/Component"),
						 "peer",
						 "Ljava/awt/peer/ComponentPeer;");
	  jobject peer = _env->GetObjectField(_obj,
					      peer_field);
	  Widget w = get_component_widget(_env, peer);

	  assert(w != NULL);

	  printf ("XtWindow(w) = %p\n_window = %p\n", XtWindow(w), _window);

	  {
	    CompositeWidgetClass c = (CompositeWidgetClass) XtClass(_awt_toplevelWidget);
	    (c->composite_class.delete_child)(w);
	  }

	  XtParent(w) = netscape_widget;

	  {
	    CompositeWidgetClass c = (CompositeWidgetClass) XtClass(netscape_widget);
	    (c->composite_class.insert_child)(w);
	  }

	  if (XtWindow(w))
	    XReparentWindow(_display, XtWindow(w), _window, 0, 0);
	  else
	    XtRealizeWidget(w);

#if 0
	  XtAddEventHandler(netscape_widget, ExposureMask, FALSE, (XtEventHandler)Redraw, this);
	  Redraw(netscape_widget, (XtPointer)this, NULL);
#endif
	}
    }
  else
    {
      // window == NULL when we're being destroyed.
    }

  return NS_OK;
}

/**
 * Called when a new plugin stream must be constructed in order for the plugin
 * instance to receive a stream of data from the browser. 
 *
 * (Corresponds to NPP_NewStream.)
 *
 * @param peer - the plugin stream peer, representing information about the
 * incoming stream, and stream-specific callbacks into the browser
 * @param result - the resulting plugin stream
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::NewStream(nsIPluginStreamPeer* peer,
				   nsIPluginStream* *result)
{
  printf ("japharJVMPluginInstance::NewStream\n");
  return NS_OK;
}
  
/**
 * Called to instruct the plugin instance to print itself to a printer.
 *
 * (Corresponds to NPP_Print.)
 *
 * @param platformPrint - platform-specific printing information
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::Print(nsPluginPrint* platformPrint)
{
  printf ("japharJVMPluginInstance::Print\n");
  return NS_OK;
}

/**
 * Called to notify the plugin instance that a URL request has been
 * completed. (See nsIPluginManager::GetURL and nsIPluginManager::PostURL).
 *
 * (Corresponds to NPP_URLNotify.)
 *
 * @param url - the requested URL
 * @param target - the target window name
 * @param reason - the reason for completion
 * @param notifyData - the notify data supplied to GetURL or PostURL
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::URLNotify(const char* url, const char* target,
				   nsPluginReason reason, void* notifyData)
{
  printf ("japharJVMPluginInstance::URLNotify\n");
  return NS_OK;
}

/**
 * Returns the value of a variable associated with the plugin instance.
 *
 * @param variable - the plugin instance variable to get
 * @param value - the address of where to store the resulting value
 * @result - NS_OK if this operation was successful
 */
nsresult
japharJVMPluginInstance::GetValue(nsPluginInstanceVariable variable,
				  void *value)
{
  printf ("japharJVMPluginInstance::GetValue(variable == %d)\n", variable);
  if (variable == nsPluginInstanceVariable_WindowlessBool)
    {
      /* these should be PRBool's, but the plugin guys have their heads up their
	 collective asses. */
      *(unsigned char*)value = 0;
      return NS_OK;
    }
  else if (variable == nsPluginInstanceVariable_TransparentBool)
    {
      *(unsigned char*)value = PR_FALSE;
      return NS_OK;
    }
  else
    {
      return NS_ERROR_ILLEGAL_VALUE;
    }
}


/////////////////////////////////////////////////
// nsIJVMPluginInstance
/////////////////////////////////////////////////
  
// This method is called when LiveConnect wants to find the Java object
// associated with this plugin instance, e.g. the Applet or JavaBean object.
nsresult
japharJVMPluginInstance::GetJavaObject(jobject *result)
{
  printf ("japharJVMPluginInstance::GetJavaObject\n");

  *result = _obj;
  return NS_OK;
}

nsresult
japharJVMPluginInstance::GetText(const char* *result)
{
  printf ("japharJVMPluginInstance::GetText\n");
  return NS_OK;
}

nsISupports*
japharJVMPluginInstance::GetBrowser()
{
  printf ("japharJVMPluginInstance::GetBrowser\n");
  return _browser;
}

void
japharJVMPluginInstance::Redraw(Widget w,
				XtPointer closure,
				XEvent *event)
{
  GC gc;
  XGCValues gcv;
  const char* text = "I am a Java Applet";
  japharJVMPluginInstance* inst = (japharJVMPluginInstance*)closure;

  XtVaGetValues(w, XtNbackground, &gcv.background,
                XtNforeground, &gcv.foreground, 0);
  gc = XCreateGC(inst->_display, inst->_window, 
                 GCForeground|GCBackground, &gcv);
#if 0
  XDrawRectangle(inst->_display, inst->_window, gc, 
                 0, 0, inst->_width-1, inst->_height-1);
#endif
  XDrawString(inst->_display, inst->_window, gc, 
              inst->_width/2 - 100, inst->_height/2,
              text, strlen(text));
}

nsresult
japharJVMPluginInstance::InitializeClassLoader()
{
  jmethodID class_loader_ctor;
  jclass url_class;
  jmethodID url_ctor;
  jobject url;
  jstring codebase = _env->NewStringUTF(_codebase);

  url_class = _env->FindClass("java/net/URL");
  url_ctor = _env->GetMethodID(url_class, "<init>",
			       "(Ljava/lang/String;)V");
  url = _env->NewObject(url_class, url_ctor, codebase);

  if (_env->ExceptionOccurred())
    {
      _env->ExceptionDescribe();
      _env->ExceptionClear();
      return NS_ERROR_FAILURE;
    }

  _class_loader_class = _env->FindClass("com/hungry/java/japhar/JapharClassLoader");
  class_loader_ctor = _env->GetMethodID(_class_loader_class,
					"<init>", "(Ljava/net/URL;)V");

  _class_loader = _env->NewObject(_class_loader_class,
				  class_loader_ctor, url);

  return NS_OK;
}

nsresult
japharJVMPluginInstance::InitializeAppletContext()
{
  jclass _applContext_class;
  jmethodID applContext_ctor;

  printf ("+  getting class\n");
  _applContext_class = _env->FindClass("com/hungry/java/japhar/JapharAppletContext");
  assert(_applContext_class);
  if (!_applContext_class)
    return NS_ERROR_FAILURE;

  printf ("+  getting constructor\n");
  applContext_ctor= _env->GetMethodID(_applContext_class, "<init>", "(I)V");
  assert(applContext_ctor);
  if (!applContext_ctor)
    return NS_ERROR_FAILURE;

  printf ("+  making object\n");
  _applContext = _env->NewObject(_applContext_class, applContext_ctor, (jint)this);
  assert(_applContext);
  if (!_applContext)
    return NS_ERROR_FAILURE;

  return NS_OK;
}

nsresult
japharJVMPluginInstance::InitializeApplet()
{
  jmethodID loadCode = _env->GetMethodID(_class_loader_class,
					 "loadCode", "(Ljava/lang/String;)Ljava/lang/Class;");
  jstring code_string = _env->NewStringUTF(_code);
  
  _applet_class = _env->CallObjectMethod(_class_loader,
					 loadCode, code_string);

  if (_env->ExceptionOccurred())
    {
      _env->ExceptionDescribe();
      _env->ExceptionClear();
      return NS_ERROR_FAILURE;
    }
  assert(_applet_class);
  if (!_applet_class)
    return NS_ERROR_FAILURE;

  jmethodID ctor = _env->GetMethodID(_applet_class, "<init>", "()V");
  assert(ctor);
  if (!ctor)
    return NS_ERROR_FAILURE;
  _obj = _env->NewObject(_applet_class, ctor);
  assert(_obj);
  if (!_obj)
    return NS_ERROR_FAILURE;

  jmethodID addNotify = _env->GetMethodID(_applet_class, "addNotify", "()V");
  assert(addNotify);
  if (!addNotify)
    return NS_ERROR_FAILURE;
  _env->CallVoidMethod(_obj, addNotify);

  _init_method = _env->GetMethodID(_applet_class, "init", "()V");
  _start_method = _env->GetMethodID(_applet_class, "start", "()V");
  _stop_method = _env->GetMethodID(_applet_class, "stop", "()V");
  _destroy_method = _env->GetMethodID(_applet_class, "destroy", "()V");

  jmethodID setStub = _env->GetMethodID(_applet_class, "setStub", "(Ljava/applet/AppletStub;)V");
  _env->CallVoidMethod(_obj, setStub, _applContext);

  if (_init_method)
    _env->CallVoidMethod(_obj, _init_method);
      
  return NS_OK;
}
