/* These are a must*/
#include <gcj/cni.h>
#include <vlc/vlc.h>

#include <stdio.h> // for printf
#include <stdlib.h> // for calloc

/* JVLC internal imports, generated by gcjh */
#include "JVLC.h"
#include "JVLCVariable.h"
#include "JVLCIntVariable.h"
#include "JVLCBoolVariable.h"
#include "JVLCFloatVariable.h"
#include "JVLCStringVariable.h"
#include "JVLCTimeVariable.h"
#include "JVLCVarVariable.h"
#include "JVLCVarValue.h"

/* Java classes used throughout here */
#include <java/lang/String.h>
#include <java/lang/Class.h>
#include <java/lang/System.h>
#include <java/io/PrintStream.h>

void setString(jstring orig, char* dest);

jint JVLC::create () {
  return VLC_Create();
}

jint JVLC::init(JArray< ::java::lang::String *> *args) {

  int argc = 0;
  char* arguments[argc];

  if (args != NULL) // It's a very bad day otherwise
  {
    argc = args->length;
    arguments[argc];

    /*
     * convert the JArray<String*>*  in char**
     * so in a way suitable for VLC_Init
     */
    jstring* argsElements = elements(args);

    for (int i = 0; i < argc; i++) {
      arguments[i] = (char*) malloc(JvGetStringUTFLength(argsElements[i]) + 1);
      setString(argsElements[i], arguments[i]);
    }
  }

  return VLC_Init(this->id, argc, arguments);
}


jint JVLC::addInterface(java::lang::String* moduleName, jboolean blocking, jboolean startPlay) {

  char* psz_module = NULL;
  if (moduleName != NULL) {
    psz_module = (char *) malloc(JvGetStringUTFLength(moduleName));
    setString(moduleName, psz_module);
  }

  int i_blocking = 0;
  int i_startPlay = 0;
  if (blocking) i_blocking = 1;
  if (startPlay) i_startPlay = 1;

  int addIntf_res = VLC_AddIntf(this->id, (char* const) psz_module, i_blocking, i_startPlay);
  
  if (psz_module != NULL)
    free(psz_module);
  
  return addIntf_res;

}


jstring JVLC::getVersion() {
  return JvNewStringUTF(VLC_Version());
}


jstring JVLC::getError(jint errorCode) {
  return JvNewStringUTF(VLC_Error(errorCode));
}


jint JVLC::die() {
  return VLC_Die(this->id);
}


jint JVLC::cleanUp() {
  return VLC_CleanUp(this->id);
}


jint JVLC::setVariable(::JVLCVariable *jvlcVariable) {

  /* these are the two parameters given the the
   * VLC_VariableSet() function
   */
  vlc_value_t value;
  char* psz_var = NULL;

  if (jvlcVariable != NULL) {
    jclass variableClass = jvlcVariable->getClass();

    /* We use the class name for kinda of instanceof */
    jstring className = variableClass->getName();

    /**
     * VLC_SetVariable takes a union as its second argument.
     * The union members are mapped 1:1 to java types which
     * extend JVLCVariable. So here we check the runtime type
     * of the actual variable and act consequently. Here is the
     * mapping:
     *
     * typedef union
     *{
     * int             i_int;      JVLCIntVariable
     * vlc_bool_t      b_bool;     JVLCBoolVariable
     * float           f_float;    JVLCFloatVariable
     * char *          psz_string; JVLCStringVariable
     * void *          p_address;  -- NOT IMPLEMENTED --
     * vlc_object_t *  p_object;   -- NOT IMPLEMENTED --
     * vlc_list_t *    p_list;     JVLCListVariable XXX:TODO
     * signed long long i_time;    JVLCTimeVariable
     * struct { char *psz_name; int i_object_id; } var; JVLCVarVariable <- this name sucks
     * // Make sure the structure is at least 64bits
     * struct { char a, b, c, d, e, f, g, h; } padding; <- Do we really need this?
     *
     * } vlc_value_t;
     */

    /* i_int */
    if (className->equals(JvNewStringUTF("VLCIntVariable" ))) {
      value.i_int = ((::JVLCIntVariable *)jvlcVariable)->getIntValue();
    }
    /* b_bool */
    else if (className->equals(JvNewStringUTF("VLCBoolVariable"))) {
      value.b_bool = ((::JVLCBoolVariable *)jvlcVariable)->getBoolValue();
    } 
    /* f_float */
    else if (className->equals(JvNewStringUTF("VLCFloatVariable"))) {
      value.f_float = ((::JVLCFloatVariable *)jvlcVariable)->getFloatValue();
    }
    /* psz_string */
    else if (className->equals(JvNewStringUTF("VLCStringVariable"))) {
      value.psz_string = (char* const) elements((((::JVLCStringVariable *)jvlcVariable)->getStringValue())->toCharArray());
    }
    /* i_time */
    else if (className->equals(JvNewStringUTF("VLCTimeVariable"))) {
      value.i_time = ((::JVLCTimeVariable *)jvlcVariable)->getTimeValue();
    }

    /* var */
    else if (className->equals(JvNewStringUTF("VLCVarVariable"))) {
      jstring varValueName = ((::JVLCVarVariable *)jvlcVariable)->getVarValue()->getName();
      value.var.psz_name = (char *) malloc(JvGetStringUTFLength(varValueName));
      setString(varValueName, value.var.psz_name);
      value.var.i_object_id = (((::JVLCVarVariable *)jvlcVariable)->getVarValue())->getOID();
    }
    psz_var = (char *) malloc(JvGetStringUTFLength(jvlcVariable->getName()));
    setString(jvlcVariable->getName(), psz_var);
  }
  
  return VLC_VariableSet(this->id, (char* const) psz_var, value);

}

jint JVLC::addTarget(::java::lang::String *URI, JArray< ::java::lang::String *> *options, jint insertMode, jint position) {

  char*  psz_target   = NULL;
  char** ppsz_options = NULL;
  int options_number = 0;
  
  if (URI != NULL) {
    psz_target = (char *) malloc( JvGetStringUTFLength(URI));
    setString(URI, psz_target);
  }

  if (options != NULL) {
    options_number = options->length;
    ppsz_options[options_number];
    jstring* jstr_options = elements(options);

    for (jint i = 0; i < options_number; i++) {
      ppsz_options[i] = (char *) malloc(JvGetStringUTFLength(jstr_options[i]));
      setString(jstr_options[i], ppsz_options[i]);
    }
  }
  
  return VLC_AddTarget(this->id, (char const *)psz_target, (const char **) ppsz_options, options_number, insertMode, position);
}

jint JVLC::play() {
  return VLC_Play(this->id);
}

jint JVLC::pause() {
  return VLC_Pause(this->id);
}

jint JVLC::stop() {
  return VLC_Stop(this->id);
}

jboolean JVLC::isPlaying() {
  return VLC_IsPlaying(this->id);
}

jfloat JVLC::getPosition() {
  return VLC_PositionGet(this->id);
}

jfloat JVLC::setPosition(jfloat position) {
  return VLC_PositionSet(this->id, position);
}

jint JVLC::getTime() {
  return VLC_TimeGet(this->id);
}

jint JVLC::setTime(jint seconds, jboolean relative) {
  return VLC_TimeSet(this->id, seconds, relative);
}

jint JVLC::getLength() {
  return VLC_LengthGet(this->id);
}

jfloat JVLC::speedFaster() {
  return VLC_SpeedFaster(this->id);
}

jfloat JVLC::speedSlower() {
  return VLC_SpeedSlower(this->id);
}

jint JVLC::getPlaylistIndex() {
  return VLC_PlaylistIndex(this->id);
}

jint JVLC::getPlaylistItems() {
  return VLC_PlaylistNumberOfItems(this->id);
}

jint JVLC::playlistNext() {
  return VLC_PlaylistNext(this->id);
}

jint JVLC::playlistPrev() {
  return VLC_PlaylistPrev(this->id);
}

jint JVLC::playlistClear() {
  return VLC_PlaylistClear(this->id);
}

jint JVLC::setVolume(jint volume) {
  return VLC_VolumeSet(this->id, volume);
}

jint JVLC::getVolume() {
  return VLC_VolumeGet(this->id);
}

jint JVLC::muteVolume() {
  return VLC_VolumeMute(this->id);
}

jint JVLC::fullScreen() {
  return VLC_FullScreen(this->id);
}


/* XXX: in progress */
::JVLCVariable* JVLC::getVariable(::java::lang::String* varName) {

  char* const psz_var = (char* const) elements( varName->toCharArray());
  vlc_value_t value;
  if (VLC_VariableGet(this->id, psz_var, &value) != VLC_SUCCESS) {
    // throw exception
    return NULL;
  }
  return NULL;
}

/*
 * This is an helper function to convert jstrings to char*s
 * setString _assumes_ the char* dest has been allocated
 * XXX: should return >= 0 on success, < 0 on error
 */
void setString(jstring orig, char* dest) {
  jsize chars = JvGetStringUTFRegion(orig, 0, orig->length(), dest);
  dest[chars] = '\0';
}
