/***************************************************************************
                          plugin.c
                          A modified gxine mozilla plugin,
                          to start kaffeine instead of gxine :-)
                             -------------------
    begin                : Wen Oct 30 2003
    revision             : $Revision: 1.1.1.1 $
    last modified        : $Date: 2004/05/12 08:12:52 $
    copyright            : (C) 2003-2004 by J. Kofler
    email                : kaffeine@gmx.net
 ***************************************************************************/


/*
 * Copyright (C) 2000-2004 the xine project
 *
 * This file is part of xine, a free video player.
 *
 * xine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * xine 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * Id: plugin.c,v 1.22 2003/04/08 22:07:47 guenter Exp
 *
 * xine plugin for mozilla
 *
 */




#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <errno.h>
#include <signal.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Text.h>
#include <X11/Xaw/AsciiText.h>

#include <stdio.h>
#include <math.h>
#include <unistd.h>


#define XP_UNIX

#include "npapi.h"
#include "plugin.h"


/* define LOG to write debug messages to /tmp/kaffeine_plugin.log */
/*
#define LOG
*/


#define STATUS_STR_SIZE 1024

static char IsInitialised=0;

/* global */
typedef struct {

  char           *url;
  int             kaffeine_started;

} plugin_global_t;

static plugin_global_t globals;

/* per-instance */
typedef struct {

  Display              *display;
  Screen               *screen;
  Window                window;
  Widget                top_level;
  int                   width,height;

  int                   autostart;
  int                   imageWindow;

  char                  status_str[STATUS_STR_SIZE];

  Pixel                 black, white;

  int                   is_visible;
} plugin_instance_t;


static void xprintf (const char *format, ...) {

#ifdef LOG
  static FILE *log_fd=NULL;

  va_list argp;

  if (!log_fd) {

    log_fd = fopen ("/tmp/kaffeine_plugin.log", "a+");

    if (log_fd) {

      setvbuf (log_fd, NULL, _IONBF, 0);

      fprintf (log_fd, "\n---------------------------------------------\n\n");

    }
  }

  va_start (argp, format);

  if (log_fd) vfprintf (log_fd, format, argp);

  va_end (argp);
#endif
}

char *NPP_GetMIMEDescription(void) {

  xprintf("NPP_GetMIMEDescription:\n");

  return "video/mpeg: mpeg, mpg, mpe: MPEG animation;"
         "video/x-mpeg: mpeg, mpg, mpe: MPEG animation;"
         "audio/mpeg2: mp2: MPEG audio;"
         "audio/x-mpeg2: mp2: MPEG audio;"
         "audio/mpeg3: mp3: MPEG audio;"
         "audio/x-mpeg3: mp3: MPEG audio;"
         "audio/mpeg: mpa,abs,mpega: MPEG audio;"
         "audio/x-mpeg: mpa,abs,mpega: MPEG audio;"
         "video/quicktime: mov,qt: Quicktime animation;"
         "video/x-quicktime: mov,qt: Quicktime animation;"
         "video/msvideo: avi: AVI animation;"
         "video/x-msvideo: avi: AVI animation;"
         "application/x-mplayer2: asf,asx,asp: mplayer2;"
         "video/x-ms-asf-plugin: asf,asx,asp: mms animation;"
         "audio/x-pn-realaudio-plugin: rpm: Real audio;"
         "audio/x-ogg: ogg,ogm: OGG Media;"
         "audio/x-scpls: pls: MPEG audio;"
         "audio/x-ms-wma: wma: Microsoft Media Audio;"
         "video/x-ms-wmv: wmv: Microsoft Media Video;"
         "audio/x-mpegurl: m3u: Streaming-MPEG-Audio;"
	 ;
}

NPError NPP_GetValue(void *future, NPPVariable variable, void *value){

  NPError err = NPERR_NO_ERROR;
  xprintf("NPP_GetValue: variable=%d\n", variable);

  switch (variable) {
  case NPPVpluginNameString:
    *((char **)value) = "Kaffeine Starter Plugin";
    break;
  case NPPVpluginDescriptionString:
    *((char **)value) =
      "Will start external Kaffeine Media Player for embedded media streams.";
    break;
  default:
    err = NPERR_GENERIC_ERROR;
  }
  return err;
}

NPError NPP_Initialize(void) {

  xprintf("NPP_Initialize:\n");
  if(!IsInitialised){

    IsInitialised=1;

    globals.url           = NULL;
    globals.kaffeine_started = 0;

  }
  return NPERR_NO_ERROR;
}


void * NPP_GetJavaClass() {
  xprintf("NPP_GetJavaClass:\n");
  return NULL;
}

void NPP_Shutdown(void) {
 xprintf("NPP_Shutdown:\n");
}

static void print_status (plugin_instance_t *this, const char *format, ...) {

  va_list argp;

  va_start (argp, format);

  vsnprintf (this->status_str, STATUS_STR_SIZE, format, argp);

  va_end (argp);

#if 0
  paint_it (this);
#endif
}

/* fork2() -- like fork, but the new process is immediately orphaned
 *            (won't leave a zombie when it exits)
 * Returns 1 to the parent, not any meaningful pid.
 * The parent cannot wait() for the new process (it's unrelated).
 */

/* This version assumes that you *haven't* caught or ignored SIGCHLD. */
/* If you have, then you should just be using fork() instead anyway.  */

static int fork2() {
  pid_t pid;
  int status;

  sigset_t set,oset;
  sigfillset(& set);
  xprintf (">>>>>>>>Forking<<<<<<<<,\n");
  sigprocmask(SIG_SETMASK,&set,&oset);

  if (!(pid = fork())) {
    xprintf ("child\n");
    switch (fork()) {
    case 0:
      xprintf ("child 2\n");
      sigprocmask(SIG_SETMASK,&oset,&set);
      return 0;
    case -1:
      xprintf ("fork 2 failed!\n");
      _exit(errno);    /* assumes all errnos are <256 */
    default:
      xprintf ("parent 2\n");
      _exit(0);
    }
  }

  xprintf ("parent, child pid =%d\n", pid);

  if (pid < 0 || waitpid(pid,&status,0) < 0) {
      xprintf ("waitpid failed! (pid==%d)\n", pid);
    sigprocmask(SIG_SETMASK,&oset,&set);
    return -1;
  }
  sigprocmask(SIG_SETMASK,&oset,&set);

  xprintf ("waitpid done\n");

  if (WIFEXITED(status))
    if (WEXITSTATUS(status) == 0) {
      xprintf ("fork 2 succeeded\n");
      return 1;
    } else
      errno = WEXITSTATUS(status);
  else
    errno = EINTR;  /* well, sort of :-) */

  xprintf ("parent done\n");

  return -1;
}

static void launch_kaffeine(plugin_instance_t *this) {

  if (!globals.url) {
    xprintf ("launch_kaffeine: no url!\n");
    return;
  }

  if (!fork2()) {
   
    xprintf ("launch_kaffeine: url = %s\n", globals.url);

    execlp("kaffeine","" , globals.url, NULL);

    if (execlp("kaffeine", NULL) == -1) {
       perror("Error while launching Kaffeine");
       _exit(0);
    }
  }

  xprintf ("Kaffeine launched.\n");

  globals.kaffeine_started = 1;
}

static void got_url (const char *url_) {

  if (strstr (url_, ":/"))
    globals.url = strdup (url_);
  else
    xprintf ("got_url: ignoring this url (%s) because it is a relative one.\n",
	     url_);

}

NPError NPP_New(NPMIMEType pluginType, NPP instance,
		uint16 mode,
		int16 argc, char* argn[], char* argv[],
		NPSavedData* saved) {

  plugin_instance_t* this;
  xprintf("NPP_New:\n");

  if (instance == NULL)
    return NPERR_INVALID_INSTANCE_ERROR;

  instance->pdata = NPN_MemAlloc(sizeof(plugin_instance_t));

  this = (plugin_instance_t*) instance->pdata;

  globals.url           = NULL;
  this->autostart     = 0;
  this->imageWindow   = 1;

  if (this != NULL) {

  int i;
    
    /* parse args */

    for (i=0; i<argc; i++) {

     xprintf ("plugin: argument '%s'='%s'\n",
	      argn[i], argv[i]);

  if (!strcasecmp (argn[i], "href")) {
	got_url (argv[i]);
	xprintf ("got href url %s\n", globals.url);
      } else if (!strcasecmp (argn[i], "src") && !globals.url) {
	got_url (argv[i]);
	xprintf ("got src url %s\n", globals.url);
      } else if (!strcasecmp (argn[i], "autostart")) {
	this->autostart = !strcasecmp (argv[i], "true");
	xprintf ("got autostart %d\n", this->autostart);
      } else if (!strcasecmp (argn[i], "controls")) {
	this->imageWindow = !strcasecmp (argv[i], "imagewindow");
  if (!this->imageWindow)
    xprintf("This is no ImageWindow!\n");
      } 

    }

    if ( globals.url && !globals.kaffeine_started && this->imageWindow )
      launch_kaffeine(this);

    xprintf ("plugin: NPP_New done\n");

    return NPERR_NO_ERROR;
  } else {
    xprintf ("plugin: out of memory :(\n");

    return NPERR_OUT_OF_MEMORY_ERROR;
  }
}


NPError NPP_SetWindow(NPP instance, NPWindow* window) {

  plugin_instance_t* this;
  Widget hello, box;

  xprintf("NPP_SetWindow: 42\n");

  if (instance == NULL) {
   xprintf("NPERR_INVALID_INSTANCE_ERROR\n");
    return NPERR_INVALID_INSTANCE_ERROR;
  }

  if (window == NULL) {
    xprintf("window == NULL, NPERR_NO_ERROR\n");
    return NPERR_NO_ERROR;
  }

  this = (plugin_instance_t*) instance->pdata;

  this->display   = ((NPSetWindowCallbackStruct *) window->ws_info)->display;
  this->window    = (Window) window->window;
  this->width     = window->width;
  this->height    = window->height;
  this->top_level = XtWindowToWidget (this->display, this->window);
  this->screen    = XtScreen (this->top_level);

  xprintf("x=%lu, y=%lu, w=%lu, h=%lu\n", window->x, window->y, window->width, window->height);
  xprintf("window = %lu NPERR_NO_ERROR\n", this->window);

  this->black = BlackPixelOfScreen (this->screen);
  this->white = WhitePixelOfScreen (this->screen);

  XResizeWindow(this->display,
                this->window, this->width, this->height);
  /* XSetWindowBackground (this, this->window, this->black); */
  XSync (this->display, FALSE);


  
  box = XtVaCreateManagedWidget ("form", formWidgetClass, this->top_level,
				 XtNbackground, this->black,
				 XtNwidth, this->width,
				 XtNheight, this->height, NULL);

  if (this->imageWindow) {         
  hello = XtVaCreateManagedWidget ("Kaffeine Starter Plugin", labelWidgetClass, box,
				   XtNbackground, this->black,
				   XtNforeground, this->white,
				   XtNwidth, this->width,
				   XtNheight, this->height, NULL); }
           
 
  XtRealizeWidget (box); 


  xprintf("NPP_SetWindow: done.\n");

  return NPERR_NO_ERROR;
}

NPError NPP_Destroy(NPP instance, NPSavedData** save) {

  plugin_instance_t* this;
  xprintf("NPP_Destroy:\n");

  if (instance == NULL)
    return NPERR_INVALID_INSTANCE_ERROR;

  this = (plugin_instance_t*) instance->pdata;

  if (this != NULL) {

    NPN_MemFree(instance->pdata);
    instance->pdata = NULL;

  }

  globals.kaffeine_started = FALSE;

  xprintf("NPP_Destroy: closed.\n");

  return NPERR_NO_ERROR;
}


NPError NPP_NewStream (NPP instance,
		       NPMIMEType type,
		       NPStream *stream,
		       NPBool seekable,
		       uint16 *stype) {

  /* NPByteRange        range; */
  plugin_instance_t *this;

  xprintf("NPP_NewStream:\n");

  if (instance == NULL)
    return NPERR_INVALID_INSTANCE_ERROR;

  this = (plugin_instance_t*) instance->pdata;

  xprintf("NPP_NewStream: url is %s \n", stream->url);

  
  got_url (stream->url);

  if (!globals.kaffeine_started) {
   
      /*
       * now start kaffeine as an orphaned child
       */

      launch_kaffeine(this);
      xprintf ("NPP_NewStream: Kaffeine started successfully\n");
    
  }

  xprintf ("NPP_NewStream: done\n");

  return NPERR_NO_ERROR;
}


/* PLUGIN DEVELOPERS:
 *	These next 2 functions are directly relevant in a plug-in which
 *	handles the data in a streaming manner. If you want zero bytes
 *	because no buffer space is YET available, return 0. As long as
 *	the stream has not been written to the plugin, Navigator will
 *	continue trying to send bytes.  If the plugin doesn't want them,
 *	just return some large number from NPP_WriteReady(), and
 *	ignore them in NPP_Write().  For a NP_ASFILE stream, they are
 *	still called but can safely be ignored using this strategy.
 */

int32 STREAMBUFSIZE = 0X0FFFFFFF; /* If we are reading from a file in NPAsFile
				   * mode so we can take any size stream in our
				   * write call (since we ignore it) */

int32 NPP_WriteReady(NPP instance, NPStream *stream) {

  plugin_instance_t* this;
  xprintf("NPP_WriteReady:\n");
  if (instance != NULL)
    this = (plugin_instance_t*) instance->pdata;

  return STREAMBUFSIZE;
}


int32 NPP_Write(NPP instance, NPStream *stream, int32 offset, int32 len, void *buffer) {

 xprintf("NPP_Write:\n");
  /*
    if (instance != NULL) {
    plugin_instance_t* this = (plugin_instance_t*) instance->pdata;
    }
  */

  return -1;		/* close stream */
}


NPError NPP_DestroyStream(NPP instance, NPStream *stream, NPError reason) {

  plugin_instance_t* this;

  xprintf("NPP_DestroyStream: \n");
  if (instance == NULL)
    return NPERR_INVALID_INSTANCE_ERROR;
  this = (plugin_instance_t*) instance->pdata;

  return NPERR_NO_ERROR;
}


void NPP_StreamAsFile(NPP instance, NPStream *stream, const char* fname) {

  plugin_instance_t* this;

  xprintf("NPP_StreamAsFile:\n");
  if (instance != NULL)
    this = (plugin_instance_t*) instance->pdata;
}


void NPP_Print(NPP instance, NPPrint* printInfo) {

  xprintf("NPP_Print:\n");
  if(printInfo == NULL)
    return;

  xprintf("NPP_Print: Not implemented. \n");
}

int16 NPP_HandleEvent(NPP instance, void* ev) {

  xprintf("NPP_HandleEvent\n");

  return 1;
}
