#include "config.h"

#include "xine.hpp"

#include <xine/xineutils.h>

#include "config.hpp"
#include "audio.hpp"
#include "audio_s.hpp"
#include "common.hpp"
#include "gettext.hpp"

#include "kernelheaders.hpp"
#include <iostream>

void Xine::event_listener(void *user_data, const xine_event_t *event)
{
  Xine* xine = (Xine*) user_data;
  switch(event->type) {

  case XINE_EVENT_UI_PLAYBACK_FINISHED:
#ifdef XINE_PARAM_GAPLESS_SWITCH
    if (xine_check_version(1,1,1))
      xine_set_param(xine->stream, XINE_PARAM_GAPLESS_SWITCH, 1);
#endif
    break;
  case XINE_EVENT_PROGRESS:
    {
      xine_progress_data_t *pevent = reinterpret_cast<xine_progress_data_t *>(event->data);
      //      printf("%s [%d%%]\n", pevent->description, pevent->percent);
      Audio_s *audio_state = Audio_s::get_instance();
      audio_state->p->buffering_state = pevent->percent;
    }
    break;
  }
#if 0
  case XINE_MSG_AUDIO_OUT_UNAVAILABLE:
    // stop
    Audio_s *audio_state = Audio_s::get_instance();
    audio_state->set_playing(false);

    DebugPrint perror(gettext("The audio device is busy. Is another application using it?"),
		      Print::DEBUGGING, DebugPrint::CRITICAL, "XINE");
    break;
#endif
}

void Xine::check_stream()
{
  if (stream == NULL && !stream_valid) {
    stream = xine_stream_new(xine, ao_port, vo_port);
    event_queue = xine_event_new_queue(stream);
    xine_event_create_listener_thread(event_queue, &Xine::event_listener, (void*)this);
  
#ifdef XINE_PARAM_EARLY_FINISHED_EVENT
    if (xine_check_version(1,1,1)) {
      // enable gapless playback
      xine_set_param(stream, XINE_PARAM_EARLY_FINISHED_EVENT, 1);
    }
#endif
  }
}

int Xine::is_playing()
{
  Audio_s *audio_state = Audio_s::get_instance();

  if (audio_state->p_pause())
    return 1;
  else {
    stream_mutex.enterMutex();
    check_stream();
    int result = (xine_get_status(stream) == XINE_STATUS_PLAY
		   && xine_get_param(stream, XINE_PARAM_SPEED) == XINE_SPEED_NORMAL);
    stream_mutex.leaveMutex();
    return result;
  }
}

int Xine::is_buffering()
{
  return (buffering_state > 0 && buffering_state != 100);
}

void Xine::gather_info()
{
  stream_mutex.enterMutex();
  check_stream();
  if (xine_get_status(stream) != XINE_STATUS_IDLE)
    gather_info_internal(); 
  stream_mutex.leaveMutex();
}

void Xine::gather_info_internal()
{
  const char *meta_info = xine_get_meta_info(stream, XINE_META_INFO_TITLE);
  title = (meta_info) ? meta_info : "";
  meta_info = xine_get_meta_info(stream, XINE_META_INFO_ARTIST);
  artist = (meta_info) ? meta_info : "";
  meta_info = xine_get_meta_info(stream, XINE_META_INFO_ALBUM);
  album = (meta_info) ? meta_info : "";

  int position_, length_ = 0;
  int t = 0, ret = 0;

  for(;;) {
    ret = xine_get_pos_length(stream, 0, &position_, &length_);
    if(ret || 10 < ++t)
      break;
    xine_usec_sleep(100000); /* wait before trying again */
  }
  cur_time = position_/1000;
  total_time = length_/1000;

  volume = xine_get_param(stream, XINE_PARAM_AUDIO_VOLUME); 
}

void Xine::collect_info(const std::string& filename)
{
  stream_mutex.enterMutex();
  check_stream();
  xine_open(stream, filename.c_str());
  gather_info_internal();
  xine_close(stream);
  stream_mutex.leaveMutex();
}

void Xine::addfile(const Simplefile &file)
{
  stream_mutex.enterMutex();
  check_stream();

  buffering_state = 0;

  if ((!xine_open(stream, file.path.c_str())) || (!xine_play(stream, 0, 0))) {
    if (file.type == "web") {
      DialogWaitPrint pdialog(3000);
      pdialog.add_line(gettext("Error connecting to server"));
      pdialog.add_line(gettext("The server might be down or busy"));
      pdialog.add_line(gettext("Please try again later"));
      pdialog.print();
    } else {
#ifdef XINE_PARAM_GAPLESS_SWITCH
      if (xine_check_version(1,1,1))
	xine_set_param(stream, XINE_PARAM_GAPLESS_SWITCH, 0);
#endif
      DialogWaitPrint pdialog(gettext("Could not open file"), 2000);
    }
  } else {
  
    stream_valid = true;

    while ((xine_get_status(stream) == XINE_STATUS_PLAY 
	    && xine_get_param(stream, XINE_PARAM_SPEED) == XINE_SPEED_NORMAL) == false)
      usleep(50000);

    gather_info_internal();

    Audio_s *audio_state = Audio_s::get_instance();

    audio_state->set_playing(true);
    audio_state->set_pause(false);
  }
  stream_mutex.leaveMutex();
}

void Xine::play()
{
  if (!cur_nr.path.empty()) {
    addfile(cur_nr);

    stream_mutex.enterMutex();
    check_stream();
    int speed = xine_get_param(stream, XINE_PARAM_SPEED);
    int status = xine_get_status(stream);

    if (speed != XINE_SPEED_NORMAL && status == XINE_STATUS_PLAY)
      xine_set_param(stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL);
    else
      xine_play(stream, 0, 0);

    stream_mutex.leaveMutex();
  }
}

void Xine::stop_player()
{
  Audio_s *audio_state = Audio_s::get_instance();

  audio_state->set_playing(false);

  if (stream != NULL && stream_valid) {
    stream_mutex.enterMutex();
    xine_stop(stream);
    xine_close(stream);
    xine_event_dispose_queue(event_queue);
    xine_dispose(stream);
    stream = NULL;
    stream_valid = false;
    stream_mutex.leaveMutex();
  }
}

void Xine::pause()
{
  Audio_s *audio_state = Audio_s::get_instance();

  stream_mutex.enterMutex();
  check_stream();

  if(xine_get_param(stream, XINE_PARAM_SPEED) != XINE_SPEED_PAUSE) {
    xine_set_param(stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE);
    audio_state->set_pause(true);
  } else {
    xine_set_param(stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL);
    audio_state->set_pause(false);
  }
  stream_mutex.leaveMutex();
}

void Xine::ff()
{
  stream_mutex.enterMutex();
  check_stream();

  int position;
  xine_get_pos_length(stream, 0, &position, 0);
  cur_time = position/1000;

  if ((cur_time + 10) < total_time)
    set_time(cur_time + 10);
  stream_mutex.leaveMutex();
}

void Xine::fb()
{
  stream_mutex.enterMutex();
  check_stream();

  int position;
  xine_get_pos_length(stream, 0, &position, 0);
  cur_time = position/1000;

  if (cur_time == 0) {
    stream_mutex.leaveMutex();
    return;
  } else {
    if ((cur_time - 10) < 0)
      set_time(0);
    else
      set_time(cur_time - 10);
    stream_mutex.leaveMutex();
  }
}

int Xine::getpos()
{
  stream_mutex.enterMutex();
  check_stream();

  int position;
  xine_get_pos_length(stream, 0, &position, 0);
  stream_mutex.leaveMutex();

  return position/1000;
}

void Xine::setpos(int p)
{
  stream_mutex.enterMutex();
  check_stream();

  set_time(p);
  stream_mutex.leaveMutex();
}

void Xine::set_time(int time)
{
  xine_play(stream, 0, time * 1000);
  cur_time = time;
  xine_usec_sleep(150000);
}

int Xine::is_mute()
{
  return volume == 0;
}

void Xine::mute()
{
  stream_mutex.enterMutex();
  check_stream();

  if (volume == 0) {
    // set back to what it was before (unmute)
    xine_set_param(stream, XINE_PARAM_AUDIO_VOLUME, old_volume);
  } else {
    old_volume = volume;
    xine_set_param(stream, XINE_PARAM_AUDIO_VOLUME, 0);
  }
  stream_mutex.leaveMutex();
}

void Xine::volup()
{
  volume += 3;
  if (volume > 100)
    volume = 100;

  setvol();
}

void Xine::voldown()
{
  volume -= 3;
  if (volume < 0)
    volume = 0;

  setvol();
}

int Xine::getvol()
{
  return volume;
}

void Xine::setvol(int vol)
{
  volume = vol;
}

void Xine::setvol()
{
  stream_mutex.enterMutex();
  check_stream();
  xine_set_param(stream, XINE_PARAM_AUDIO_VOLUME, volume);
  stream_mutex.leaveMutex();
}

Xine::Xine() 
  : Player("", "", "", 0, 0, 0), stream(NULL), stream_valid(false)
{
  char configfile[2048];
  const char *ao_driver;

  if (conf->p_audio_output_type() != "")
    ao_driver = conf->p_audio_output_type().c_str();
  else
    ao_driver = "auto";

  xine = xine_new();
  snprintf(configfile, 2048, "%s%s", xine_get_homedir(), "/.xine/config");
  xine_config_load(xine, configfile);

  xine_init(xine);

  ao_port = xine_open_audio_driver(xine, ao_driver, NULL);
  vo_port = NULL;

  int digital = 0;
  if (conf->p_audio_output_mode() == "-d")
    digital = 1;

  int speakers = 1; // "Stereo 2.0"

  if (digital == 1)
    speakers = 12; // "Pass Through"

  config_set_num("audio.speaker_arrangement", speakers);
#if 0 // doesn't work, only works when setting oss_device_name to 0, but then
      // we can just skip setting it. Please manually set your device in the
      // xine config file
  config_set_num("audio.oss_device_name", 1);

  int device_num = 0;

  if (conf->p_audio_dev() == "/dev/dsp1")
    device_num = 1;
  else if (conf->p_audio_dev() == "/dev/dsp2")
    device_num = 2;

  config_set_num("audio.oss_device_number", device_num);
#endif
  config_set_num("audio.passthrough_offset", digital);

  xine_config_save(xine, configfile);
}

Xine::~Xine()
{
  stream_mutex.enterMutex();
  if (stream != NULL) {
    xine_stop(stream);
    xine_close(stream);
    xine_event_dispose_queue(event_queue);
    xine_dispose(stream);
    stream = NULL;
  }
  
  if (ao_port != NULL)
    xine_close_audio_driver(xine, ao_port);
  if (vo_port != NULL)
    xine_close_video_driver(xine, vo_port);

  xine_exit(xine);
  stream_mutex.leaveMutex();
}

void Xine::update_cdrom(const std::string& cdrom)
{
  config_update_string("input.cdda_device", cdrom.c_str());
}

void Xine::config_update(xine_cfg_entry_t *entry, int type, int min, int max, int value, const char *string)
{
  switch(type) {

  case XINE_CONFIG_TYPE_UNKNOWN:
    {
      Print perror(std::string("Config key ") + entry->key + 
		   " is not registered yet", 
		   Print::INFO, "XINE");
    }
    return;
    break;

  case XINE_CONFIG_TYPE_RANGE:
    entry->range_min = min;
    entry->range_max = max;
    break;

  case XINE_CONFIG_TYPE_STRING:
    strcpy(entry->str_value, string);
    break;

  case XINE_CONFIG_TYPE_ENUM:
  case XINE_CONFIG_TYPE_NUM:
  case XINE_CONFIG_TYPE_BOOL:
    entry->num_value = value;
    break;

  default:
    {
      Print perror("Unknown config type", Print::INFO, "XINE");
    }
    return;
    break;
  }

  xine_config_update_entry(xine, entry);
}

void Xine::config_update_string(char *key, const char *string)
{
  xine_cfg_entry_t entry;

  if((xine_config_lookup_entry(xine, key, &entry)) && string)
    config_update(&entry, XINE_CONFIG_TYPE_STRING, 0, 0, 0, string);
  else {
    if(string == NULL)
      Print perror("string is NULL", Print::INFO, "XINE");
    else
      Print perror(std::string("string key") + key + "isn not registered", 
		   Print::INFO, "XINE");
  }
}

void Xine::config_set_num(char *key, int val)
{
  xine_cfg_entry_t entry;

  if (xine_config_lookup_entry(xine, key, &entry))
    config_update(&entry, XINE_CONFIG_TYPE_NUM, 0, 0, val, 0);
  else {
    xine_config_register_num (xine, key, val, 0, NULL, 10,
			      NULL, NULL);
    xine_config_lookup_entry (xine, key, &entry);
  }
}

void Xine::send_event(int event)
{
  xine_event_t xine_event;
   		
  xine_event.type = event;
  xine_event.data_length = 0;
  xine_event.data = 0;
  xine_event.stream = stream;
  gettimeofday(&xine_event.tv, NULL);
  
  xine_event_send(stream, &xine_event);
}
