/*$cb
 *    This file is a part of libopenmhp.
 *
 *    Copyright (C) 2005 Mikko Novaro <miknov@gmail.com>
 *
 *    See main header file (libopenmhp/openmhp.h) for licensing and
 *    contact information.
 $ce*/

#include "xlet.hpp"
#include "xml/handler.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>

#include "macros.h"

#define WIDTH   720
#define HEIGHT  576
#define LAYER_COUNT 2 // number of shared images
#define BYTES_PER_PIXEL 4
#define SHMSIZE WIDTH*HEIGHT*BYTES_PER_PIXEL*LAYER_COUNT

cXlet *cXlet::activeInstance = NULL;


cXlet::cXlet(const char *Name, const char *Class, const char *Path) {
  name = strdup(Name);
  xletClass = strdup(Class);
  path = strdup(Path);
  java_pid = -1;
  clientToMhp = NULL;
  read_thread = 0;
  connected = false;
  cXlet::activeInstance = this;

  // create empty observers structure:
  observers = new std::list<cXletEventObserver*>*[EVENT_TYPE_CNT];
  for (int i=0; i<EVENT_TYPE_CNT; i++)
    observers[i] = new std::list<cXletEventObserver*>;
}

cXlet::~cXlet(void) {
  Stop();
  free(name);
  name = NULL;
  free(path);
  path = NULL;
  free(xletClass);

  // delete observers structure, but not the observers:
  for (int i=0; i<EVENT_TYPE_CNT; i++) {
    delete observers[i];
    observers[i] = NULL;
  }
  delete observers;
  observers = NULL;

  xletClass = NULL;
}

char *cXlet::GetName(void) { return name; }
char *cXlet::GetClass(void) { return xletClass; }
char *cXlet::GetPath(void) { return path; }

cXlet *cXlet::GetActiveInstance() {
  return cXlet::activeInstance;
}

void cXlet::Start(const char *virtualXServer, int virtualXDisplay,
		  const char *openmhpOutput) {
  java_pid = fork();
  switch(java_pid) {
  case -1: // error
    ERR("creating a new process for running java failed.");
    break;
  case 0: // child process
    /* Create a new process group, so the whole child (java) process
     * tree can be killed off: */
    setpgid(0,0);
    ExecuteJava(virtualXServer, virtualXDisplay, openmhpOutput);
    break;
  default: // parent
    Connect();
    cXMLHandler *handler = new cXMLHandler(MHPTOCLIENT_FIFO);
    handler->RunHandler();
    delete handler;

    return;
    break;
  }
}



void cXlet::Stop(void) {
  if (connected) {
    Disconnect();
  }
    
  if (java_pid != -1) {
    MSG("Ending OpenMHP -process...");
    /* Kill the entire process group (to get rid of all java
     * processes). Group created at fork. */
    kill(-java_pid, SIGKILL);
    int java_return;
    MSG(" ...waitpid done with return value [%i]\n", waitpid(java_pid, &java_return, 0));
    java_pid = -1;
  }
}




/* Connecting to OpenMHP is currently one sided -- there is no
   handshake etc. */
void cXlet::Connect(void) {
  bool succ = true;
  MSG("connecting to OpenMHP ");

  if (mkfifo(CLIENTTOMHP_FIFO, 0600) == -1 && errno != EEXIST) {
    ERR("error creating client-to-mhp fifo at path %s", CLIENTTOMHP_FIFO);
    succ = false;
  }
  
  if (mkfifo(MHPTOCLIENT_FIFO, 0600) == -1 && errno != EEXIST) {
    ERR("error creating mhp-to-client fifo at path %s", MHPTOCLIENT_FIFO);
  }

  DBG("opening client-to-mhp pipe...");
  
  if (succ) {
    clientToMhp = fopen(CLIENTTOMHP_FIFO, "w");
    if (clientToMhp == NULL) {
      ERR("opening client-to-mhp fifo failed.");
      succ = false;
    }
    else DBG("...client-to-mhp pipe opened.");
  }

  // create/acquire shared memory area for receiving xlet image
  int shmkey = ftok(LIBOPENMHP_PATHNAME, LIBOPENMHP_PROJID);
  if (succ) {
    if ((shmid = shmget(shmkey, SHMSIZE, 0600 | IPC_CREAT)) == -1) {
      // failed
      ERR("getting shared memory area failed");
      succ = false;
    }
    else DBG("got shared memory, id %i", shmid);
  }
  
  // attach shared memory
  if (succ) {
    shm = (int*)shmat(shmid, NULL, SHM_RND);
    if (shm == (int *)-1) {
      // failed
      ERR("attaching shared memory area failed");
      shmctl(shmid, IPC_RMID, NULL); // remove shared memory
      shm = NULL;
      succ = false;
    }
  }

  if (succ) {
    // send begin root xml element
    fprintf(clientToMhp, "<clienttomhp>");
    fflush(clientToMhp);
    connected = true;
  }
  else {
    // clean up if connecting failed
    if (clientToMhp) {fclose(clientToMhp); clientToMhp = NULL;}
    connected = false;
  }
}


void cXlet::Disconnect(void) {
  if (connected) {
    connected = false;
    
    // end xml connection
    if (clientToMhp != NULL) {
      // send end root XML element:
      fprintf(clientToMhp, "</clienttomhp>");
      fflush(clientToMhp);
      fclose(clientToMhp);
      clientToMhp = NULL;
    }

    // detach & remove shared memory
    if (shm) {
      if (shmdt(shm) == -1) ERR("detaching shared memory failed");
      shm = NULL;
    }
    if (shmid != -1) {
      if (shmctl(shmid, IPC_RMID, NULL) == -1) ERR("removing shared memory failed");
      shmid = -1;
    }
  }
}


void cXlet::SendKeyEvent(int code) {
  if (connected) {
    // send keyevent XML message
    DBG("Sending key %i", code);
    fprintf(clientToMhp, "<keyevent><keycode>%i</keycode></keyevent>", code);
    fflush(clientToMhp);
  }
}


int cXlet::GetPixel(int x, int y, int layerIndex) {
  if (connected) {
    int layerOffset = WIDTH*HEIGHT*layerIndex;
    int val = shm[layerOffset + WIDTH*y + x];
    return val;
  }
  else return 0;
}

// Private function (called by Start) that actually executes java/openmhp
void cXlet::ExecuteJava(const char *virtualXServer, int virtualXDisplay,
			const char *openmhpOutput) {
  char *command = "sh";
  char script[100];
  sprintf(script, "%s/exec_java.sh", LIBOPENMHP_DIR);
  char display[10];
  sprintf(display, "%i", virtualXDisplay);
  execlp(command, command, script, name, xletClass, path,
	 virtualXServer, display, openmhpOutput, NULL);
  ERR("error starting java");
  exit(1);
}


// ------------- EVENTS --------------------------------

void cXlet::RegisterObserver(eEventType type, cXletEventObserver *observer) {
  observers[type]->push_back(observer);
}

void cXlet::NotifyEvent(eEventType type, const cXletEvent *event) {
  DBG("notifying event type %i", type);
  std::list<cXletEventObserver*>::iterator observer;
  for (observer = observers[type]->begin();
       observer != observers[type]->end();
       observer++) {
    (*observer)->EventReceived(type, event);
  }  
}


// ------ cXletFactory ---------------------------------

cXletFactory::cXletFactory(char *mhpCacheDir, int channel) {
  char command[100];
  // Parse ait file with perl script, fill channel number with zeroes:
  sprintf(command, "perl %s/parse_ait_file.pl %s %03i",
          LIBOPENMHP_DIR, mhpCacheDir, channel);
  pipe_handle = popen(command, "r");
}

cXletFactory::~cXletFactory(void) {
  pclose(pipe_handle);
}

#define PARSE_BUFFER 300

cXlet *cXletFactory::Next(void) {
  if (feof(pipe_handle)) return NULL;
  char name[PARSE_BUFFER];
  char className[PARSE_BUFFER];
  char basedir[PARSE_BUFFER];
  fgets(name, PARSE_BUFFER, pipe_handle);
  if (feof(pipe_handle)) return NULL;
  fgets(className, PARSE_BUFFER, pipe_handle);
  fgets(basedir, PARSE_BUFFER, pipe_handle);
  name[strlen(name)-1] = 0;
  className[strlen(className)-1] = 0;
  basedir[strlen(basedir)-1] = 0;
  return new cXlet(name, className, basedir);
}
