#include "config.h"

#include "common.hpp"
#include <errno.h>             /* per  errno */
#include "renderer.hpp"
#include "input.hpp"
#include "config.hpp"
#include "themes.hpp"
#include "graphics.hpp"
#include "simplefile.hpp"
#include "updater.hpp"
#include "print.hpp"
#include "cd.hpp"

// lcd
#include "display.hpp"

#ifdef c_music
#include "audio.hpp"
#include "audio_s.hpp"
#endif

#include "startmenu.hpp"

#include <iostream>
#include <sstream>
#include <algorithm>
#include <cmath>

#include <unistd.h>

// file exists 
#include <sys/stat.h>

// ugly c fork code
#include <sys/wait.h>

#include "gettext.hpp"

// conversion
#ifdef use_iconv
#include <iconv.h>
#include <langinfo.h>
#endif

#include <cassert>

#ifdef use_sdl
#include "renderer.hpp"
#include "sdlout.hpp"
#include <SDL/SDL.h>
#endif

using std::string;

int conv::atoi(const std::string& s)
{
  return ::atoi(s.c_str());
}

std::string conv::itos(int i)
{
  std::stringstream s;
  s << i;
  return s.str();
}

std::string conv::ftos(float f)
{
  std::stringstream s;
  s << f;
  if (int(f) == f)
    s << ".0";
  return s.str();
}

bool conv::stob(const std::string& s)
{
  return !(s == "0" || s == "f" || s == "n" ||
	   s == "false" || s == "no" || s == "off" ||
	   s == gettext("false") || s == gettext("no") || s == gettext("off"));
}

std::string conv::btos(bool b)
{
  return (b) ? "true" : "false";
}

void string_format::format_to_size(std::string& s, const std::string& font, int size, bool dots, bool long_string)
{
  int x, y;
  string_format::calculate_string_size(s, font, &x, &y);

  if (x > size) {
    std::string test = s;
    int chars = int(size/(double(x)/s.size()));

    if (chars == 0) {
      s = "";
      return;
    }
    
    assert(chars > 0);

    if (dots)
      pretty_print_string(test, chars);
    else
      test = test.substr(0, chars);

    calculate_string_size(test, font, &x, &y);

    while (x < int(size*0.97)) {

      if (chars == s.size())
	break;

      test = s;
      ++chars;

      if (dots)
	pretty_print_string(test, chars);
      else
	test = test.substr(0, chars);

      calculate_string_size(test, font, &x, &y);
    }

    while (x > int(size*1.03)) {

      if (chars == 0) {
	test = "";
	break;
      }

      test = s;
      --chars;

      if (dots)
	pretty_print_string(test, chars);
      else
	test = test.substr(0, chars);

      calculate_string_size(test, font, &x, &y);
    }

    if (long_string)
      s = test.substr(0, test.size()-2); // utf-8 fix
    else
      s = test;
  } else {
    if (dots)
      pretty_print_string(s, s.size());
  }
}

void string_format::pretty_print_string(std::string& s, int maxlen)
{
  int dif=s.length()-maxlen;
  if (dif > 0) s.replace((s.length()>>1)-(dif>>1), dif+1, "..." );

  for (std::string::size_type i=0; (i=s.rfind("_")) != std::string::npos; s.replace(i,1," "));
}

std::string string_format::remove_doubles(const std::string& doubles)
{
  std::string temp = doubles;

  std::string::size_type pos = temp.rfind("//");

  while (pos != std::string::npos) {
    temp.replace(pos, 2, "/");
    pos = temp.rfind("//");
  }

  return temp;
}

std::string string_format::lowercase(std::string s) {
  transform(s.begin(), s.end(), s.begin(), tolower);
  return s;
}

std::string string_format::trim(std::string s)
{
  if (s.empty())
    return s;

  while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n')
    if (s.size() == 1)
      return "";
    else
      s = s.substr(1);

  while (s[s.size()-1] == ' ' || s[s.size()-1] == '\t' || s[s.size()-1] == '\n')
    s = s.substr(0, s.size()-1);
  
  return s;
}

void string_format::calculate_string_size(const std::string& text, const std::string& font, int *text_w, int *text_h)
{
  if (Render::get_instance()->set_font(font)) {
    // " " is there to compensate for imlib fix when drawing
    imlib_get_text_size((text + " ").c_str(), text_w, text_h);
    imlib_free_font();

    // during rendering we add black borders
    *text_w += 2;
    *text_h += 2;
  }
}

std::string string_format::get_line(const std::string& text, int &pos, int width, const std::string& font)
{
  bool padding = false;

  std::string desc = text.substr(pos);
  std::string last_desc = desc;

  string_format::format_to_size(desc, font, width, false, true);

  if (desc.size() != last_desc.size() && desc.size() < last_desc.size() 
      && last_desc.size() >= 2) {
    char next_char = last_desc[desc.size()];
    char last_char = last_desc[desc.size()-1];
    if (next_char == ' ' || next_char == ',' || next_char == '.') {
      desc += next_char;
      if (last_desc[desc.size()] == ' ')
	desc += last_desc.substr(desc.size(), 1);
    } else if (last_char != ' ' && last_char != ',' && last_char != '.') {

      // if last word was small to not try to split it
      // or if you just get one char of a new word then don't try to
      // break the word onto multiple lines
      int first_space = desc.rfind(" ");

      if (first_space != std::string::npos) {

	std::string rest_of_string = last_desc.substr(desc.size());

	int last_space = rest_of_string.find(" ");
	int last_dot = rest_of_string.find(".");

	if (last_dot != std::string::npos) {
	  if (last_space == std::string::npos)
	    last_space = last_dot;
	  else
	    if (last_space > last_dot)
	      last_space = last_dot;
	} else if (last_space == std::string::npos)
	  last_space = 0;

	int last_word_size = desc.size() - (first_space+1) + last_space;

	if (last_word_size < 4 || first_space + 2 >= desc.size())
	  desc = last_desc.substr(0, first_space+1);
	else {
	  desc += "-";
	  padding = true;
	}

      }
    }
  }

  int size = desc.size();
  if (padding)
    --size;

  while (mblen(text.substr(pos+size).c_str(), 10) == -1) {
    //utf8 char cut off

    // increase string size
    ++size;
    
    desc = text.substr(pos, size);
    if (padding)
      desc += "-";
  }

  pos += size;

  return desc;
}

int string_format::difference(const std::string& lhs, const std::string& rhs)
{
  int min_size = std::min(lhs.size(), rhs.size());

  int difference = 0;

  for(int i = 0; i < min_size; ++i)
    if (lhs[i] != rhs[i])
      ++difference;

  return difference + std::abs(int(lhs.size()-rhs.size()));
}

std::string string_format::convert(const std::string& in, bool mp3_locale)
{
#ifdef use_iconv
  Config *conf = Config::get_instance();

  std::string from = conf->p_convert_locale();
  if (mp3_locale)
    from = conf->p_convert_mp3_locale();

  const char *fromencoding = from.c_str();
  const char *toencoding = "UTF-8";

  size_t insize = in.length();

  char *inbuf = const_cast<char*>(in.data());

  size_t outsiz = insize * 6; // max length of utf-8 char is 6 per input char
  size_t outsiz_t = outsiz;

  char *outbuf = new char[outsiz];
  char *outbuf_t = outbuf;

  iconv_t conversion = iconv_open(toencoding, fromencoding);

  if (conversion == reinterpret_cast<iconv_t>(-1)) {
    DebugPrint perror(gettext("Failed to open iconv"), 
		      Print::DEBUGGING, DebugPrint::CRITICAL, "StringFormat");
    delete[] outbuf;
    return "";
  }

  if (iconv(conversion, &inbuf, &insize, &outbuf_t, &outsiz_t) == -1)
    DebugPrint perror(gettext("String conversion failed"), 
		      Print::DEBUGGING, DebugPrint::CRITICAL, "StringFormat");

  iconv_close(conversion);

  std::string tmp_str= std::string(outbuf, outsiz - outsiz_t);
  delete[] outbuf;
  return tmp_str;
#else
  return in;
#endif
}

const int round_to_int(double number)
{
  return int(number + 0.5);
}

const int min(int time)
{
  return static_cast<int>(floor(time/60));
}
 
const int sec(int time)
{
  return (time%60);
}

bool file_exists(const std::string& filename) {
  struct stat file_stat;
  return ((stat(filename.c_str(), &file_stat) == 0) ? true:false);
}

void create_if_not_exists(const std::string& dir)
{
  if (!file_exists(dir)) {

    string::size_type loc = dir.find("/", 0); // root
    
    while (loc = dir.find("/", loc+1)) {

      if (!file_exists(dir.substr(0, loc+1)))
	mkdir(dir.substr(0, loc+1).c_str(), 0777);
      
      if (loc == string::npos)
	break;
    }
  }  
}

// fork another program into the forground
int run::my_system(const char *command)
{
  Input_master *input_master = Input_master::get_instance();
  input_master->device->suspend();

  Config *conf = Config::get_instance();

#ifdef c_music 
  bool was_started = false, was_playing = false, was_paused = false;
  int pos = 0;
  Simplefile track;

  if (conf->p_audio_started()) {
    
    was_started = true;

    Audio_s *audio_state = Audio::get_instance()->audio_state;

    if (audio_state->p->is_playing()) {
      if (audio_state->p_pause())
	was_paused = true;
      was_playing = true;
      pos = audio_state->p->getpos();
      track = audio_state->p->p_cur_nr();
    }

    audio_state->p->stop();

    // stop the updater as it otherwise might query the player
    Updater &updater = Updater::get_instance();
    updater.deactivate();

    // to make sure we clear the possible drawn stuff
    Audio *audio = Audio::get_instance();
    audio->print_audiotrack();

    sleep(1);
  }
#endif

  Render *render = Render::get_instance();
  render->device->unlock();

  int status = my_system_no_kill(command);

  render->device->lock();

#ifdef c_music 
  if (was_started) {

    if (was_playing) {
      Audio *audio = Audio::get_instance();

#ifdef use_media
      audio->check_mount_before(track);
#endif
      audio->audio_state->p->addfile(track);
      audio->audio_state->p->set_cur_nr(track);
      audio->audio_state->p->setpos(pos);
      if (was_paused)
	audio->audio_state->p->pause();

      // restore volume
      audio->audio_state->p->setvol(audio->audio_state->p->getvol());

      sleep(1);
    }

    // restart the updater
    Updater &updater = Updater::get_instance();
    updater.activate();
  }
#endif

  conf->s_last_key(time(0));
  
  input_master->device->wake_up();

  return status;
}

// not so extreme forker :)
int run::my_system_no_kill(const char *command)
{
  int retstatus = 0;
  pid_t waitpid;
  
  pid_t pid = fork();

  if (pid == 0)
    {
      /* this is the child process. Execute the shell command. */
      // child inherits file descriptors
      // we don't want it to mess with them so we close them all
      // except  stdin, stdout and stderr
      for(int x = 3; x <sysconf(_SC_OPEN_MAX) - 1; x++)
	close(x);
 
      execl ("/bin/sh", "sh", "-c", command, NULL);

      // if we're here something went wrong
      fprintf(stderr, "Got error '%s' launching external program\n", strerror(errno));
      exit(1);
    }
  else if (pid < 0) {
    /* the fork failed. Report failure. */
    fprintf(stderr, "Got error %s: Couldn't fork\n", strerror(errno));
    return -1;
  } else {
    /* This is the parent process. Wait for child to end. */
    waitpid=wait(&retstatus);
    if (waitpid <0)  //wait failed
      {
	fprintf(stderr, "Got error %s: Couldn't wait for child to exit\n", strerror(errno));
	return -1;
      }
  
    //let's process return status
    if (WIFEXITED(retstatus)) {
      //child ended normally
      //      printf("Child ended with status %d\n",WEXITSTATUS(retstatus));
      return 0;
    }
    else if (WIFSIGNALED(retstatus)) { //let's see what went wrong
      fprintf(stderr, "Child exited due to %d signal\n", WTERMSIG(retstatus));
      return -1;
    }
    else if(WIFSTOPPED(retstatus)) {
      fprintf(stderr, "Child process was stopped\n");
      return -1;
    }

    //this should never be executed
    fprintf(stderr, "Undefined or unhandled child terminatio\n");
    return -1;
  }
}

void print_dialog_step1(const std::list<std::string>& messages, Overlay &o)
{
  print_dialog_time_wait(messages, o, 0);
}

void print_dialog_step2(Overlay &o)
{
  Render *render = Render::get_instance();

  render->image_mut.enterMutex();
  render->device->wait_for_output_done();
  o.cleanup();
  render->image_mut.leaveMutex();
}

void print_dialog(const std::list<std::string>& messages)
{
  Input_master *input_master = Input_master::get_instance();

  Overlay overlay = Overlay("dialog");

  print_dialog_step1(messages, overlay);

  // wait for keypress
  input_master->device->get_input();

  print_dialog_step2(overlay);
}

void print_dialog_time_wait(const std::list<std::string>& messages, Overlay& overlay, int time)
{
  Render *render = Render::get_instance();
  Themes *themes = Themes::get_instance();
  Config *config = Config::get_instance();

  if ((messages.size() * 20) > config->p_h_res())
    DebugPrint perror(gettext("Too many strings to print"),
		      Print::DEBUGGING, DebugPrint::WARNING, "PrintDialog");
  else if (messages.size() == 0)
    DebugPrint perror(gettext("Nothing to print"),
		      Print::DEBUGGING, DebugPrint::WARNING, "PrintDialog");

  int x, y, max_x = 0, max_y = 0;

  // determine size
  for (std::list<std::string>::const_iterator iter = messages.begin();
       iter != messages.end(); ++iter) {
    string_format::calculate_string_size(*iter, "Vera/17", &x, &y);
    max_x = std::max(x, max_x);
    max_y = std::max(y, max_y);
  }

  x = (config->p_v_res()-(max_x+30))/2;
  y = (config->p_h_res()-(max_y*messages.size()+30))/2;

  render->image_mut.enterMutex();

  render->device->wait_for_output_done();

  overlay.add(new RObj(x-15+2, y-15+2, max_x+30+1, max_y*messages.size()+30+1, 
			       0, 0, 0, 225, 4));
  overlay.add(new RObj(x-15, y-15, max_x+30, max_y*messages.size()+30,  
			       themes->dialog_background1, themes->dialog_background2, 
			       themes->dialog_background3, themes->dialog_background4, 5));

  for (std::list<std::string>::const_iterator iter = messages.begin();
       iter != messages.end(); ++iter) {
    
    overlay.add(new TObj(*iter, "Vera/17", x, y, themes->dialog_font1, 
				 themes->dialog_font2, themes->dialog_font3, 6));
    
    y += 25;
  }

  render->draw("dialog", true);
  render->image_mut.leaveMutex();

  if (overlay.name == "dialog default") {
    usleep(time*1000);

    render->image_mut.enterMutex();
    render->device->wait_for_output_done();
    overlay.cleanup();
    render->image_mut.leaveMutex();
  }
}

void print_dialog_wait(const std::list<std::string>& messages, int time)
{
  Overlay dialog = Overlay("dialog default");
  print_dialog_time_wait(messages, dialog, time);
}

Input print_dialog_fixed_size(const std::string& message, int size, Overlay& dialog)
{
  Render *render = Render::get_instance();
  Themes *themes = Themes::get_instance();
  Config *config = Config::get_instance();

  render->image_mut.enterMutex();

  render->device->wait_for_output_done();

  if (dialog.elements.size() > 0)
    dialog.partial_cleanup();

  int x = (config->p_v_res()-size)/2+15;
  int y = (config->p_h_res()-17)/2+15;

  int max_x = size;
  int max_y = 17;

  dialog.add(new RObj(x-15+2, y-15+2, max_x+30+1, max_y+30+1, 
		      0, 0, 0, 200, 3));
  dialog.add(new RObj(x-15, y-15, max_x+30, max_y+30,  
		      themes->dialog_background1, themes->dialog_background2, 
		      themes->dialog_background3, themes->dialog_background4, 4));

  std::string tmp_message = message;

  string_format::format_to_size(tmp_message, "Vera/17", size, true);

  dialog.add(new TObj(tmp_message, "Vera/17", x, y, themes->dialog_font1, 
		      themes->dialog_font2, themes->dialog_font3, 5));
    
  render->draw("fixed dialog", true);
  render->image_mut.leaveMutex();

  Input_master *input_master = Input_master::get_instance();

  Input input = input_master->device->get_input();

  return input;
}

void print_progressbar()
{
  Config *conf = Config::get_instance();
  Render *render = Render::get_instance();
  Themes *themes = Themes::get_instance();
  Updater &updater = Updater::get_instance();

  render->image_mut.enterMutex();

  render->device->wait_for_output_done();

  int x_size, y_size;
  string_format::calculate_string_size(updater.header, "Vera/17", &x_size, &y_size);

  int box_width = (x_size < 350) ? 350 : x_size + 50;
  int box_height = 120;

  int x = (conf->p_v_res()-box_width)/2;
  int y = (conf->p_h_res()-box_height)/2;

  if (updater.progressbar.elements.size() > 0)
    updater.progressbar.partial_cleanup();

  updater.progressbar.add(new RObj(x+2, y+2, box_width+1, box_height+1, 0, 0, 0, 200, 0));
  updater.progressbar.add(new RObj(x, y, box_width, box_height,  
				   themes->dialog_background1, themes->dialog_background2, 
				   themes->dialog_background3, themes->dialog_background4, 1));

  updater.progressbar.add(new TObj(updater.header, "Vera/17", x+box_width/2-x_size/2, y+10, 
				   themes->dialog_font1, themes->dialog_font2, 
				   themes->dialog_font3, 2));

  int blocks = std::min((updater.total_progressbar>0)?int(std::ceil((double(updater.status_progressbar)/updater.total_progressbar)*5.0)):0, 5);

  for (int i = 0; i < blocks; ++i)
    updater.progressbar.add(new PObj(themes->progressbar_bar, x+25+i*62, y+50, 2, NOSCALING));

  render->draw("progressbar", true);
  
  render->image_mut.leaveMutex();
}

void cleanup_progressbar()
{
  Render *render = Render::get_instance();
  Updater &updater = Updater::get_instance();

  render->image_mut.enterMutex();
  render->device->wait_for_output_done();
  updater.progressbar.cleanup();
  render->image_mut.leaveMutex();
}

int check_progressbar()
{
  return 100; // ms
}

void register_keypress(Input input) 
{
  // volume changes should not count as a keypress
  if (input.mode != "audio_general" || 
      (input.command == "playlist_mode" || input.command == "mode" || input.command == "playlist_commands")) {
    // update time of last keypress
    Config *conf = Config::get_instance();
    conf->s_last_key(time(0));
  }
}

bool general_loop_check(Input input, bool disable_mode_change)
{
  bool key_ok = false;

#if 0 // SDL is simply to buggy to enable this
  if (input.command == "fullscreen") {
#ifdef use_sdl
    Render *render = Render::get_instance();
    SDLdev *s = dynamic_cast<SDLdev*>(render->device);
    if (s != 0) {
      s->toggle_fullscreen();
    }
#endif
  }
#endif

#ifdef c_music
  Audio *audio = Audio::get_instance();
  key_ok = audio->check_playback_keys(input, disable_mode_change);
#endif

#ifdef use_media
#ifdef openclose
  if (input.command == "eject") {
    Cd::get_instance()->openclose_cddrive();
    key_ok = true;
  }
#endif
#endif

  return key_ok;
}

void deinit_mms(bool from_cleanup) 
{
  Updater &updater = Updater::get_instance();
  updater.deactivate();

  // hack since updater needs time to finish
  sleep(1);

  Render *render = Render::get_instance();

  render->prepare_new_image();
  render->clear();
  render->draw("exit");
  render->image_mut.leaveMutex();

  sleep(1);
  
  // save options
  Startmenu &startmenu = Startmenu::get_instance();
  startmenu.opts.save();

  Config *conf = Config::get_instance();

#ifdef c_music 
  if (conf->p_audio_started()) {
    Audio_s *audio_state = Audio_s::get_instance();
    audio_state->p->stop();
  }

  Audio *audio = Audio::get_instance();
  delete audio; // save playlist
#endif

  // lcd
  if (conf->p_lcd()) {
    display_close();
  }

  Input_master *Input_master = Input_master::get_instance();
  delete Input_master->device;

  // deinit the render device after input, because input can also use SDL
  render->device->deinit();
  delete render->device;

  sleep(1);

  if (startmenu.epg_update_running) {
    // change our group id so that we don't ourselves when we quit (this
    // requires root privs.)
    pid_t old_gpid = getpgid(0);
    setpgid(0, old_gpid+1);
    
    // kill updater
    system(("kill -9 -" + conv::itos(old_gpid)).c_str());
  }
  
  if (!conf->p_shutdown_script().empty() && !from_cleanup)
    system(conf->p_shutdown_script().c_str());
}

void clean_up(int signal)
{
  deinit_mms(true);
  exit(1);
}

