/*****************************************************************************
 * JVLC.java: CNI interface for vlc Java Bindings
 *****************************************************************************
 * Copyright (C) 1998-2005 the VideoLAN team
 *
 * Authors: Filippo Carone <filippo@carone.org>
 *
 * This program 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.
 *
 * This program 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.
 *****************************************************************************/

/* 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 "org/videolan/jvlc/JVLC.h"
#include "org/videolan/jvlc/JVLCVariable.h"
#include "org/videolan/jvlc/JVLCIntVariable.h"
#include "org/videolan/jvlc/JVLCBoolVariable.h"
#include "org/videolan/jvlc/JVLCFloatVariable.h"
#include "org/videolan/jvlc/JVLCStringVariable.h"
#include "org/videolan/jvlc/JVLCTimeVariable.h"
#include "org/videolan/jvlc/JVLCVarVariable.h"
#include "org/videolan/jvlc/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 org::videolan::jvlc::JVLC::create () {
  return VLC_Create();
}

jint org::videolan::jvlc::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**
     * in a suitable way 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(0, argc, arguments);
}


jint org::videolan::jvlc::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 org::videolan::jvlc::JVLC::getVersion() {
  return JvNewStringUTF(VLC_Version());
}


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


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


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


jint org::videolan::jvlc::JVLC::setVariable(org::videolan::jvlc::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 = ((org::videolan::jvlc::JVLCIntVariable *)jvlcVariable)->getIntValue();
    }
    /* b_bool */
    else if (className->equals(JvNewStringUTF("VLCBoolVariable"))) {
      value.b_bool = ((org::videolan::jvlc::JVLCBoolVariable *)jvlcVariable)->getBoolValue();
    } 
    /* f_float */
    else if (className->equals(JvNewStringUTF("VLCFloatVariable"))) {
      value.f_float = ((org::videolan::jvlc::JVLCFloatVariable *)jvlcVariable)->getFloatValue();
    }
    /* psz_string */
    else if (className->equals(JvNewStringUTF("VLCStringVariable"))) {
      value.psz_string = (char* const) elements((((org::videolan::jvlc::JVLCStringVariable *)jvlcVariable)->getStringValue())->toCharArray());
    }
    /* i_time */
    else if (className->equals(JvNewStringUTF("VLCTimeVariable"))) {
      value.i_time = ((org::videolan::jvlc::JVLCTimeVariable *)jvlcVariable)->getTimeValue();
    }

    /* var */
    else if (className->equals(JvNewStringUTF("VLCVarVariable"))) {
      jstring varValueName = ((org::videolan::jvlc::JVLCVarVariable *)jvlcVariable)->getVarValue()->getName();
      value.var.psz_name = (char *) malloc(JvGetStringUTFLength(varValueName));
      setString(varValueName, value.var.psz_name);
      value.var.i_object_id = (((org::videolan::jvlc::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 org::videolan::jvlc::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 = (char **) malloc (options_number * sizeof(char*));
      //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]);
    }
  }
  
  int res = VLC_AddTarget(this->id, (char const *)psz_target, (const char **) ppsz_options, options_number, insertMode, position);

  if (ppsz_options != NULL) {
    for (int i = 0; i < options_number; i++) {
      free(ppsz_options[i]);
    }
    free(ppsz_options);
  }
  if (psz_target != NULL) {
    free(psz_target);
  }


  return res;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


/* XXX: in progress */
org::videolan::jvlc::JVLCVariable* org::videolan::jvlc::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);
  // Note that dest is a buffer, not a C string. It is not null terminated.
  dest[chars] = '\0';
}
