/*
 * Copyright (C) 2000-2017 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 St, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * xine plugin for mozilla
 *
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <strings.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"

#include "config.h"

/*
#define LOG
*/

#ifdef LOG
#include <stdarg.h>
#endif

/* FIXME: browser exit on URL box destroy, segfault on 'close' click
#define URLBOX
*/

/*
 * what player do we try to emulate?
 */

#define EMU_NONE    0
#define EMU_MPLAYER 1 /* media player */
#define EMU_QT      2 /* quicktime player */
#define EMU_REAL    3 /* real player */

#define STATUS_STR_SIZE 1024

static char IsInitialised=0;

/* global */
typedef struct {

  char           *url;
  int             gxine_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                   emu_mode;

  char                 *real_controls;
  int                   autostart;

  char                  status_str[STATUS_STR_SIZE];

  Pixel                 black, white;

  int                   is_visible;
} plugin_instance_t;


static void __attribute__ ((format (printf, 1, 2)))
xprintf (const char *format, ...)
{
#ifdef LOG
  static FILE *log_fd=NULL;

  va_list argp;

  if (!log_fd) {

    log_fd = fopen ("/tmp/t.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;"
	 "audio/mpegurl: m3u: MPEG URL;"
	 "audio/x-mpegurl: m3u: MPEG URL;"
	 "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"
	 ;
}

NPError NPP_GetValue (NPP instance, NPPVariable variable, void *value)
{
  NPError err = NPERR_NO_ERROR;
  xprintf("NPP_GetValue: variable=%d\n", variable);

  switch (variable) {
  case NPPVpluginNameString:
    *((char **)value) = "gxine starter plugin";
    break;
  case NPPVpluginDescriptionString:
    *((char **)value) =
      "will start external gxine 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.gxine_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_gxine (plugin_instance_t *this) {

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

  if (!fork2()) {
    char executable[FILENAME_MAX];
    char arg[4096];

    snprintf(executable, sizeof (executable), "%s/gxine", GXINE_BINDIR);

    if (this->emu_mode == EMU_MPLAYER)
      snprintf(arg, sizeof (arg), "mms%s", globals.url);
    else
      snprintf(arg, sizeof (arg), "%s", globals.url);

    xprintf ("launch_gxine: exe = %s, arg = %s\n", executable, arg);

    if (execlp("gxine", executable, "-a", arg, NULL) == -1) {
       perror("Error while launching gxine");
       _exit(0);
    }
  }

  xprintf ("gxine launched.\n");

#if 0
  if (this->gtk_wind) {
    print_status (this, "gxine launched");

    xprintf ("gxine launched\n");

    gtk_entry_set_text (GTK_ENTRY (this->mrl_entry), globals.url);

    xprintf ("launch_gxine: done\n");
  }
#endif

  globals.gxine_started = 1;
}

static void got_url (const char *url_) {

  if (strstr (url_, ":/"))
  {
    free (globals.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;

  if (this != NULL) {

    int i;

    this->real_controls = NULL;
    this->autostart     = 0;

    /* parse args */

    this->emu_mode = EMU_NONE;

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

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

      if (!strcasecmp (argn[i], "type")) {
	if (!strncmp (argv[i], "video/x-ms-asf-plugin", 21)) {
	  xprintf ("plugin: switching to mms_mode\n");
	  this->emu_mode = EMU_MPLAYER;
	} else if (!strncmp (argv[i], "application/x-mplayer2", 22)) {
	  xprintf ("plugin: switching to mms_mode\n");
	  this->emu_mode = EMU_MPLAYER;
	} else if (!strncmp (argv[i], "video/quicktime", 15)) {
	  xprintf ("plugin: switching to quicktime emulation mode\n");
	  this->emu_mode = EMU_QT;
	} else if (!strncmp (argv[i], "audio/x-pn-realaudio-plugin", 27)) {
	  xprintf ("plugin: switching to real player emulation mode\n");
	  this->emu_mode = EMU_REAL;
	}
      } else if (!strcasecmp (argn[i], "name")) {
	if (!strcmp (argv[i], "nsplay")) {
	  xprintf ("plugin: switching to mms_mode\n");
	  this->emu_mode = EMU_MPLAYER;
	}
      } else 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], "controls") && (this->emu_mode == EMU_REAL)) {
	this->real_controls = strdup (argv[i]);
	xprintf ("got controls %s\n", this->real_controls);
      } else if (!strcasecmp (argn[i], "autostart") && (this->emu_mode == EMU_REAL)) {
	this->autostart = !strcasecmp (argv[i], "true");
	xprintf ("got autostart %d\n", this->autostart);
      }

    }

    if ( (this->emu_mode == EMU_REAL)
	 && this->autostart && globals.url
	 && !globals.gxine_started)
      launch_gxine (this);

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

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

    return NPERR_OUT_OF_MEMORY_ERROR;
  }
}

static void play_cb (Widget w, XtPointer client_data, XtPointer call_data) {

  plugin_instance_t *this = (plugin_instance_t *) client_data;

  xprintf ("play_cb\n");

  launch_gxine (this);
}

/*
static void close_cb (Widget w, XtPointer client_data, XtPointer call_data) {
  Widget shell = (Widget) client_data;

  XtPopdown (shell);
}
*/

#ifdef URLBOX
static void url_cb (Widget w, XtPointer client_data, XtPointer call_data) {

  plugin_instance_t *this = (plugin_instance_t *) client_data;
  Widget  shell, box, entry, button;
  XawTextBlock tb;

  xprintf ("url_cb\n");

  if (!globals.url)
    return;

  xprintf ("popup\n");

  shell = XtVaCreatePopupShell ("url popup", transientShellWidgetClass,
				this->top_level,
				XtNpopdownCallback, XtCallbackNone, NULL);

  box = XtVaCreateManagedWidget ("box", boxWidgetClass, shell,
				 NULL);

  entry = XtVaCreateManagedWidget ("entry", asciiTextWidgetClass, box,
				   XtNstring, globals.url,
				   XtNdisplayCaret, False,
				   XtNresize, XawtextResizeBoth,
				   XtNwidth, 340, NULL);

  button = XtVaCreateManagedWidget ("close", commandWidgetClass, box, NULL);
  XtAddCallback (button, XtNcallback, close_cb, shell);

  XtPopup (shell, XtGrabNone);
}
#endif

NPError NPP_SetWindow(NPP instance, NPWindow* window) {

  plugin_instance_t* this;
  Widget /*hello,*/ button, 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=%u, y=%u, w=%u, h=%u\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);

  /*hello = */XtVaCreateManagedWidget ("gxine browser plugin", labelWidgetClass, box,
				   XtNbackground, this->black,
				   XtNforeground, this->white,
				   XtNwidth, this->width,
				   XtNheight, this->height, NULL);
  if (this->real_controls && !strcasecmp (this->real_controls, "PlayonlyButton")) {
    button = XtVaCreateManagedWidget (">", commandWidgetClass, box,
				      XtNbackground, this->black,
				      XtNforeground, this->white,
				      XtNborderColor, this->white, NULL);
    XtAddCallback (button, XtNcallback, play_cb, this);
  } else {
#ifdef URLBOX
     button = XtVaCreateManagedWidget ("url", commandWidgetClass, box,
				      XtNbackground, this->black,
				      XtNforeground, this->white,
				      XtNborderColor, this->white, NULL);
     XtAddCallback (button, XtNcallback, url_cb, this);
#else
    Pixel grey = (((this->black     & 0xFF)*3 + (this->white     & 0xFF)) / 4)     |
		 (((this->black>> 8 & 0xFF)*3 + (this->white>> 8 & 0xFF)) / 4)<< 8 |
		 (((this->black>>16 & 0xFF)*3 + (this->white>>16 & 0xFF)) / 4)<< 16|
		 (((this->black>>24 & 0xFF)*3 + (this->white>>24 & 0xFF)) / 4)<< 24;
    XtVaCreateManagedWidget ("url", asciiTextWidgetClass, box,
			     XtNstring, globals.url,
			     XtNdisplayCaret, False,
			     XtNresize, XawtextResizeBoth,
			     XtNwidth, this->width,
			     XtNscrollHorizontal, XawtextScrollWhenNeeded,
			     XtNscrollVertical, XawtextScrollWhenNeeded,
			     XtNwrap, XawtextWrapLine,
			     XtNbackground, grey, //this->black,
			     XtNforeground, this->white,
			     XtNborderWidth, 0,
			     NULL);
#endif
  }
  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;

  }

  if (globals.url)
  {
    free (globals.url);
    globals.url = NULL;
  }

  globals.gxine_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);

  if ( (this->emu_mode != EMU_QT) || !globals.url) {
    xprintf ("NPP_NewStream: copying url because emu_mode=%d, globals.url=%s\n",
	     this->emu_mode, globals.url);
    got_url (stream->url);
  }

  if (!globals.gxine_started) {
    if ( (this->emu_mode != EMU_REAL)
	 || !this->real_controls
	 || (!strcasecmp (this->real_controls, "imagewindow"))) {
      /*
       * now start gxine as an orphaned child
       *
       * poor gxine :>
       */

      launch_gxine (this);
      xprintf ("NPP_NewStream: gxine 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 len;		/* The number of bytes accepted */
}


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;

  /*
    if (instance != NULL) {
    plugin_instance_t* this = (plugin_instance_t*) instance->pdata;

    if (printInfo->mode == NP_FULL) {

    void* platformPrint =
    printInfo->print.fullPrint.platformPrint;
    NPBool printOne =
    printInfo->print.fullPrint.printOne;

    printInfo->print.fullPrint.pluginPrinted = FALSE;

    } else {

    NPWindow* printWindow =
    &(printInfo->print.embedPrint.window);
    void* platformPrint =
    printInfo->print.embedPrint.platformPrint;
    }
    }
  */
}

int16 NPP_HandleEvent(NPP instance, void* ev) {

  xprintf("NPP_HandleEvent\n");

  return 1;
}
