// used for #ifdef
#include "config.h"

#include "libfspp.hpp"

#include "pictures.hpp"

//Input
#include "input.hpp"

#include "config.hpp"
#include "common.hpp"
#include "functor.hpp"
#include "graphics.hpp"
#include "updater.hpp"
#include "gettext.hpp"

#include "startmenu.hpp"
#include "busy_indicator.hpp"
#include "shutdown.hpp"
#include "extra_menu.hpp"

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

// ostringstream
#include <sstream>

#include <iostream>

#include <unistd.h>

using std::list;
using std::vector;
using std::string;

Pictures& Pictures::get_instance() 
{
  static Pictures s;
  return s;
}

Pictures::Pictures() 
  : exit_loop(false), in_fullscreen(false), orientation(0), 
    zoom_level(1), zoom_position(1), 
    pause(false), thumbnailed_all_files(false)
{
  reset();

  y_start = 100;

  if (conf->p_v_res() == 640)
    y_start = 30;
  else if (conf->p_v_res() == 720)
    y_start = 70;
  else if (conf->p_v_res() == 800)
    y_start = 90;
  else if (conf->p_v_res() == 1024)
    y_start = 110;
  else if (conf->p_v_res() == 1280)
    y_start = 130;

  image_width = 125;
  image_height = 96;

  if (conf->p_h_res() == 405) {
    image_width = 80;
    image_height = 67;
  }

  // including text and space around image
  image_width_all = image_width+25;
  image_height_all = image_height+55;
  image_height_all_eks_text = image_height + 6;

  images_per_row = (conf->p_v_res()-2*25)/image_width_all;

  rows = (conf->p_h_res()-2*y_start+20)/image_height_all;
  rows_search = (conf->p_h_res()-2*y_start+20-40)/image_height_all;

  image_height_all = (conf->p_h_res()-2*y_start+20)/rows;
  image_height_all_search = (conf->p_h_res()-2*y_start+20-40)/rows_search;

  // lets make some more room for text 
  image_width_all += ((conf->p_v_res()-2*25)%image_width_all)/images_per_row;
}

void Pictures::startup_updater()
{
  Updater &updater = Updater::get_instance();
  TimeFunctori<Pictures> *time_func = new TimeFunctori<Pictures>(this, &Pictures::check_thumbnail);
  TimeFunctorv<Pictures> *call_func = new TimeFunctorv<Pictures>(this, &Pictures::background_thumbnails);
  updater.timer.add(TimeElement("extra_thumbnails", time_func, call_func));
}

void Pictures::reset()
{
  for (int i = 0, size = folders.size(); i < size; ++i)
    folders.pop();

  list<string> dirs = conf->p_picture_dirs();
  for (list<string>::iterator iter = dirs.begin(); iter != dirs.end(); ++iter)
    if ((*iter)[iter->size()-1] == '/')
      *iter = iter->substr(0, iter->size()-1);

  folders.push(std::make_pair(dirs, 0)); 
}
  
Pictures::PicOpts::PicOpts() 
{
  // header
  header = gettext("Picture Options");

  // values

  // reload
  vector<string> none;
  reload_p = new Option(false, gettext("reload"), "reload", 0, none, none);
  val.push_back(reload_p);

  // sorting order
  val.push_back(go->dir_order);

  vector<string> slideshow_values;

  for (int i = 0; i < 120; ++i)
    slideshow_values.push_back(conv::itos(i));

  slideshow_p = new Option(false, gettext("slideshow delay"), "slideshow delay", 10, 
			   slideshow_values, slideshow_values);
  val.push_back(slideshow_p);

  vector<string> zoom_values;

  zoom_values.push_back(gettext("no"));
  zoom_values.push_back(gettext("yes"));

  vector<string> english_zoom_values;

  english_zoom_values.push_back("no");
  english_zoom_values.push_back("yes");

  zoom_p = new Option(false, gettext("zoom to fullscreen"), "zoom to fullscreen", 0, 
		      zoom_values, english_zoom_values);
  val.push_back(zoom_p);

  vector<string> preview_values;

  preview_values.push_back(gettext("no"));
  preview_values.push_back(gettext("yes"));

  vector<string> english_preview_values;

  english_preview_values.push_back("no");
  english_preview_values.push_back("yes");

  preview_p = new Option(false, gettext("show previews"), "show previews", 1, 
			 preview_values, english_preview_values);
  val.push_back(preview_p);

  vector<string> recurse_values;

  recurse_values.push_back(gettext("no"));
  recurse_values.push_back(gettext("yes"));

  vector<string> english_recurse_values;

  english_recurse_values.push_back("no");
  english_recurse_values.push_back("yes");

  recurse_p = new Option(false, gettext("recurse dirs in fullscreen"), "recurse dirs in fullscreen", 0, 
			 recurse_values, english_recurse_values);
  val.push_back(recurse_p);

  if (conf->p_idle_time() != 0) {
    vector<string> thumbnail_extract_values;

    thumbnail_extract_values.push_back(gettext("no")); 
    thumbnail_extract_values.push_back(gettext("yes"));

    vector<string> english_thumbnail_extract_values;

    english_thumbnail_extract_values.push_back("no"); 
    english_thumbnail_extract_values.push_back("yes");

    thumbnail_extract_p = new Option(false, gettext("bg thumbnail generation"), "bg thumbnail generation", 0, 
				     thumbnail_extract_values, english_thumbnail_extract_values);
    val.push_back(thumbnail_extract_p);
  }
}

Pictures::PicOpts::~PicOpts() 
{
  delete reload_p;
  delete slideshow_p;
  delete zoom_p;
  delete preview_p;
  delete recurse_p;
  if (conf->p_idle_time() != 0) {
    delete thumbnail_extract_p;
  }
}

void Pictures::PicOpts::read_dirs() 
{ 
  Pictures &pictures = Pictures::get_instance(); 
  pictures.read_dirs(); 
  pictures.reset();
}

bool Pictures::file_sort::operator()(const Simplefile& lhs, const Simplefile& rhs)
{
  return sort_order(lhs.lowercase_name, rhs.lowercase_name, lhs.type == "dir", rhs.type == "dir");
}

void Pictures::action_no_find_recursion()
{
  if (vector_lookup(pic_list, folders.top().second).type != "dir") {
    pictures_fullscreen();
  } else {
    enter_dir();
  }
}

// commands
void Pictures::action()
{
  if (vector_lookup(pic_list, folders.top().second).type != "dir" && 
      conv::stob(opts.recurse_in_fullscreen())) {

    recurse_files = parse_dirs_recursion();

    if (find_recursion_pos())
      action_no_find_recursion();
  } else 
    action_no_find_recursion();
}

bool Pictures::enter_dir()
{
  // go into the directory.
  string temp_cur_dir = vector_lookup(pic_list, folders.top().second).path;

  // get the total number of files:
  int nr_files = 0;
  for (file_iterator<file_t, default_order> i (temp_cur_dir); i != i.end (); i.advance(false))
    if (!isDirectory (i->getName()))
      ++nr_files;

  Updater &updater = Updater::get_instance();

  // disable screensaver if it's running
  bool audio_fullscreen_running = true;

  if (!updater.timer.status("audio_fullscreen"))
    audio_fullscreen_running = false;
  else
    updater.timer.deactivate("audio_fullscreen");

  updater.status_progressbar = 0;
  updater.total_progressbar = nr_files;
  updater.header = gettext("Generating thumbnails");

  TimeFunctoriNC cp(&check_progressbar);
  TimeFunctorvNC pg(&print_progressbar);

  updater.timer.add(TimeElement("progressbar", &cp, &pg));

  if (conf->p_priority_change()) 
    nice(19); //Lower priority to keep audio from skipping
  
  // process picture dir 
  vector<Simplefile> templist = rdir(temp_cur_dir);

  if (conf->p_priority_change()) 
    nice(-19);
  
  updater.timer.del("progressbar");
  cleanup_progressbar();  

  // reenable screensaver
  if (audio_fullscreen_running)
    updater.timer.activate("audio_fullscreen");

  if (templist.size() != 0) {
    list<string> templs; templs.push_back(temp_cur_dir);
    folders.push(std::make_pair(templs, 0));
    pic_list = templist;
    return true;
  } else {
    DialogWaitPrint pdialog(gettext("Folder is empty"), 1000);
    return false;
  }
}

void Pictures::go_back()
{
  if (folders.size() > 1) {
    folders.pop();

    load_current_dirs();
  }
}

void Pictures::load_current_dirs()
{
  pic_list = parse_dir(folders.top().first);

  if (folders.top().first.size() > 1 && folders.size() == 1)
    std::sort(pic_list.begin(), pic_list.end(), file_sort());
}

bool Pictures::find_recursion_pos()
{
  bool found_match = false;
  list<string> dirs = folders.top().first;
  pos_recursion = 0;

  // find the first image compared to the current position
  string cur_file = vector_lookup(pic_list, folders.top().second).path;
  for (vector<Simplefile>::iterator p = recurse_files.begin(), pend = recurse_files.end(); 
       p != pend; ++p, ++pos_recursion) {
    if (p->path.substr(0, cur_file.size()) == cur_file) {
      found_match = true;
      break;
    }
  }

  return found_match;
}

void Pictures::recurse_dirs()
{
  in_fullscreen = true;

  // create list, find the next image compared to where we stand currently
  recurse_files = parse_dirs_recursion();

  bool found_match = find_recursion_pos();

  if (found_match) {

    if (recurse_files.size() > 1) {
      if (pos_recursion == 0)
	pos_recursion = recurse_files.size() -1;
      else
	pos_recursion -= 1;
    }
    
    next_no_skip_folders();

    action_no_find_recursion();
  }
}

void Pictures::options()
{
  BusyIndicator::get_instance().idle();
	  
  if (opts.mainloop()) {
    // reload
    folders.top().second = 0;
    thumbnailed_all_files = false;
  }
}

void Pictures::search_func()
{
  BusyIndicator::get_instance().idle();

  TSpecificFunctor<Pictures, Simplefile> print_func(this, &Pictures::print);
  CompareFunctor<Pictures, Simplefile> compare_func(this, &Pictures::search_compare);
  RepFunctor<Pictures, Simplefile> rep_func(this, &Pictures::get_name_from_file);
  search(pic_list, &folders.top().second, &print_func, &compare_func, &rep_func);
}

void Pictures::exit()
{
  exit_loop = true;
}

void Pictures::exit_fullscreen()
{
  Updater &updater = Updater::get_instance();
  updater.timer.del("pictures");

  exit();

  in_fullscreen = false;

  if (conv::stob(opts.recurse_in_fullscreen())) {
    if (folders.size() > 1) {
      folders.pop();
      load_current_dirs();
      enter_dir();
    }

    string recursion_file = vector_lookup(recurse_files, pos_recursion).path;

    int pos = 0;
    for (vector<Simplefile>::const_iterator iter = pic_list.begin(); iter != pic_list.end();
	 ++iter, ++pos) {
      if (!isDirectory(iter->path)) {
	if (iter->path == recursion_file) {
	  folders.top().second = pos;
	  return;
	}
      }
    }
  }
}

void Pictures::find_recursion_file()
{
  string next_file = vector_lookup(recurse_files, pos_recursion).path;
  string next_path = next_file;
  string::size_type pos;
  if ((pos = next_path.rfind('/')) != string::npos)
    next_path = next_path.substr(0, pos);

  vector<string> tested_dirs;

  while (true) {

    // quick check

    // next
    if (folders.top().second + 1 <= pic_list.size() - 1) {
      if (vector_lookup(pic_list, folders.top().second + 1).path == next_file) {
	folders.top().second += 1;
	return;
      }
    } 

    // prev
    if (folders.top().second - 1 >= 0) {
      if (vector_lookup(pic_list, folders.top().second - 1).path == next_file) {
	folders.top().second -= 1;
	return;
      }
    }

    // check if on current level
    list<string> dirs = folders.top().first;

    int pos = 0;
    for (vector<Simplefile>::const_iterator iter = pic_list.begin(); iter != pic_list.end(); ++iter, ++pos) {
      if (!isDirectory(iter->path)) {
	if (iter->path == next_file) {
	  folders.top().second = pos;
	  return;
	}
      }
    }
    
    // try to find the correct subdir in current dir
    bool found_dir = false;
    pos = 0;
    for (vector<Simplefile>::const_iterator iter = pic_list.begin(); iter != pic_list.end(); ++iter, ++pos) {
      string cur_file = iter->path;
      if (cur_file.size() <= next_path.size())
	if (next_path.substr(0, cur_file.size()) == cur_file &&
	    !already_tested(tested_dirs, cur_file))
	  if (isDirectory(cur_file)) {
	    tested_dirs.push_back(cur_file);
	    folders.top().second = pos;
	    enter_dir();
	    found_dir = true;
	    break;
	  }
    }
    
    if (!found_dir) {
      // it must be on a level below
      go_back();
    }
  }
}

bool Pictures::already_tested(const vector<string>& tested_dirs, string dir)
{
  for (vector<string>::const_iterator iter = tested_dirs.begin();
       iter != tested_dirs.end(); ++iter)
    if (*iter == dir)
      return true;
  return false;
}

// helper functions, to minimize code copying, functions used in mainloops.
void Pictures::prev()
{
  if (folders.top().second == 0)
    folders.top().second = pic_list.size()-1;
  else
    --(folders.top().second);
}

void Pictures::next()
{
  folders.top().second = (folders.top().second+1)%pic_list.size();
}

void Pictures::prev_no_skip_folders()
{
  if (pos_recursion != 0)
    --pos_recursion;
  else
    pos_recursion = recurse_files.size() - 1;
  
  find_recursion_file();
}

void Pictures::prev_skip_folders()
{
  do { 
    prev();
  } while (vector_lookup(pic_list, folders.top().second).type == "dir");
}

void Pictures::next_no_skip_folders()
{
  if (pos_recursion != recurse_files.size() - 1)
    ++pos_recursion;
  else
    pos_recursion = 0;
  
  find_recursion_file();
}

void Pictures::next_skip_folders()
{
  do { 
    next();
  } while (vector_lookup(pic_list, folders.top().second).type == "dir");
}

string Pictures::mainloop()
{
  input_master->cur_map = &input_master->pictures;

  Startmenu &startmenu = Startmenu::get_instance();
  startmenu.module = "pictures";

  Input input;

  bool update_needed = true;

  BusyIndicator &busy_indicator = BusyIndicator::get_instance();
  Shutdown &sd = Shutdown::get_instance();

  while (!exit_loop)
    {
      if (update_needed)
	print(pic_list);

      busy_indicator.idle();

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

      busy_indicator.busy();
      
      if (sd.is_enabled()) {
      	sd.cancel();
      	continue;
      }

      update_needed = true;
	  
      if (input.command == "back" && input.mode == "general" && 
	  folders.size() == 1)
	{
	  exit();
	}
      else if (input.command == "prev")
 	{
	  if (pic_list.size() > images_per_row) {
	    for (int i = 0; i < images_per_row; ++i) {
	      if (folders.top().second == 0) {
		folders.top().second = pic_list.size()-1;
		i += images_per_row-pic_list.size()%images_per_row;
	      }
	      else 
		--(folders.top().second);
	    }
	  }
 	}
      else if (input.command == "next")
 	{
	  if (pic_list.size() > images_per_row) {
	    for (int i = 0; i < images_per_row; ++i) {
	      if (folders.top().second == pic_list.size()-1 && i == 0) {
		folders.top().second = 0;
		break;
	      } else if (folders.top().second == pic_list.size()-1) {
		break;
	      } else {
		++(folders.top().second);
	      }
	    }
	  }
 	}
      else if (input.command == "left")
 	{
	  prev();
 	}
      else if (input.command == "right")
 	{
	  next();
 	}
      else if (input.command == "page_up")
 	{
	  int jump = images_per_row*rows;

	  if (search_mode)
	    jump = images_per_row*rows_search;

	  if (pic_list.size() > jump) {
	    int diff = folders.top().second-jump;
	    if (folders.top().second == 0)
	      folders.top().second = (pic_list.size()-1)+diff;
	    else if (diff < 0)
	      folders.top().second = 0;
	    else
	      folders.top().second = diff;
	  }
 	}
      else if (input.command == "page_down")
 	{
	  int jump = images_per_row*rows;

	  if (search_mode)
	    jump = images_per_row*rows_search;

	  if (pic_list.size() > jump) {
	    if (folders.top().second >= (pic_list.size() - jump) && folders.top().second != (pic_list.size()-1))
	      folders.top().second = pic_list.size()-1;
	    else
	      folders.top().second = (folders.top().second+jump)%pic_list.size();
	  }
 	}
      else if (input.command == "recurse_dirs" && conv::stob(opts.recurse_in_fullscreen()))
	{
	  recurse_dirs();
	}
      else if (input.command == "action")
	{
	  action();
	}
      else if (input.command == "back")
	{
	  go_back();
	}
      else if (input.command == "second_action")
	{
	  ExtraMenu em;

	  TimeFunctorv<Pictures> t_a = TimeFunctorv<Pictures>(this, &Pictures::action);
	  string t = gettext("Show picture in fullscreen");
	  
	  if (vector_lookup(pic_list, folders.top().second).type == "dir")
	    t = gettext("Enter directory");
	  
	  em.add_item(ExtraMenuItem(t, input_master->find_shortcut("action"), &t_a));

	  TimeFunctorv<Pictures> t_rd = TimeFunctorv<Pictures>(this, &Pictures::recurse_dirs);

	  if (conv::stob(opts.recurse_in_fullscreen())) {
	    if (vector_lookup(pic_list, folders.top().second).type == "dir")
	      em.add_item(ExtraMenuItem(gettext("Recurse all folders on current level"), 
					input_master->find_shortcut("recurse_dirs"), 
					&t_rd));
	  }
	  
	  TimeFunctorv<Pictures> t_sf = TimeFunctorv<Pictures>(this, &Pictures::search_func);
	  em.add_item(ExtraMenuItem(gettext("Search"), input_master->find_shortcut("search"), 
				    &t_sf));
	  TimeFunctorv<Pictures> t_o = TimeFunctorv<Pictures>(this, &Pictures::options);
	  em.add_item(ExtraMenuItem(gettext("Options"), input_master->find_shortcut("options"), 
				    &t_o));

	  TimeFunctorv<Pictures> t_back = TimeFunctorv<Pictures>(this, &Pictures::exit);
	  TimeFunctorv<Pictures> t_gb = TimeFunctorv<Pictures>(this, &Pictures::go_back);

	  if (folders.size() == 1)
	    em.add_item(ExtraMenuItem(gettext("Return to startmenu"), 
				      input_master->find_shortcut("back"), &t_back));
	  else
	    em.add_item(ExtraMenuItem(gettext("Go up one directory"), 
				      input_master->find_shortcut("back"), &t_gb));

#ifdef c_music
	  TimeFunctorv<Audio> t_player = TimeFunctorv<Audio>(Audio::get_instance(), &Audio::playlist_commands);
	  em.add_item(ExtraMenuItem(gettext("Playlist commands"), input_master->find_shortcut("playlist_commands"), &t_player));
#endif
	  em.mainloop();
	}
      else if (input.command == "options")
	{
	  options();
	}
      else if (input.command == "search")
	{
	  search_func();
	}
      else
	update_needed = !general_loop_check(input);
    }

  exit_loop = false;

  return "";
}

vector<string> Pictures::images_in_dir(const list<string>& dirs)
{
  std::stack<string> queued_dirs;
  vector<string> all_image_files;

  for (list<string>::const_iterator iter = dirs.begin(); iter != dirs.end(); ++iter) {
    queued_dirs.push(*iter);
    while (!queued_dirs.empty()) {
      string cur_dir = queued_dirs.top();
      queued_dirs.pop();
      for (file_iterator<file_t, default_order> i (cur_dir); i != i.end (); i.advance(false)) {

	string cur_file = i->getName();

	// exclude folders ?
	if (conf->p_excluded_picture_dirs().size() > 0) {
	  list<string> excluded = conf->p_excluded_picture_dirs();
	  bool stop = false;
	      
	  for (list<string>::const_iterator epd = excluded.begin(), epd_end = excluded.end(); 
	       epd != epd_end; ++epd)
	    if (cur_file.substr(cur_file.rfind("/")+1) == *epd) {
	      stop = true;
	      break;
	    }
	      
	  if (stop)
	    continue;
	}

	if (isDirectory(cur_file))
	  queued_dirs.push(cur_file);
	else
	  all_image_files.push_back(cur_file);
      }
    }
  }

  return all_image_files;
}


void Pictures::background_thumbnails()
{
  if (!thumbnailed_all_files && (conf->p_idle_time() > 0) && 
      (opts.thumbnail_extract() == gettext("yes"))) {

    int nr_extracts = 3;

    // check if we are idle
    int idle_time = time(0)-conf->p_last_key();
    if (idle_time < 3)
      return;
    else if (idle_time > 60 * conf->p_idle_time())
      nr_extracts = 4;

    if (all_image_files.empty()) {
      all_image_files = images_in_dir(conf->p_picture_dirs());
      // optimization, we don't want to both make the list and start extracting
      return;
    }

    if (conf->p_priority_change()) 
      nice(19); //Lower priority to keep audio from skipping

    while (!all_image_files.empty()) {

      bool extracted = false;

      if (!file_exists(render->image_get_fullpath(all_image_files.back(), image_width, image_height))) {
	extracted = true;
	render->image_mut.enterMutex();
	render->device->wait_for_not_drawing();
	render->create_scaled_image_wrapper(all_image_files.back(), image_width, image_height);
	render->image_mut.leaveMutex();
      } 

      if (!file_exists(render->image_get_fullpath(all_image_files.back(), conf->p_v_res(), conf->p_h_res()))) {
	extracted = true;
	render->image_mut.enterMutex();
	render->device->wait_for_not_drawing();
	render->create_scaled_image_wrapper(all_image_files.back(), conf->p_v_res(), conf->p_h_res());
	render->image_mut.leaveMutex();
      }
      
      if (nr_extracts == 0) {
	if (conf->p_priority_change()) 
	  nice(-19);
	return;
      } else if (extracted)
	--nr_extracts;

      all_image_files.erase(all_image_files.end());
    }

    if (conf->p_priority_change()) 
      nice(-19);

    thumbnailed_all_files = true;
  } 
}

int Pictures::check_thumbnail()
{
  return 3000; // ms
}

string Pictures::get_name_from_file(const Simplefile& s)
{
  return s.name;
}

bool Pictures::search_compare(const Simplefile& s)
{
  return (s.lowercase_name.substr(0, search_str.size()) == lowercase_search_str);
}

void Pictures::prepare_and_show_fullscreen()
{
  string left_path, right_path;

  if (conv::stob(opts.recurse_in_fullscreen())) {

    int left_pos_recursion;
    if (pos_recursion != 0)
      left_pos_recursion = pos_recursion - 1;
    else
      left_pos_recursion = recurse_files.size() - 1;

    left_path = vector_lookup(recurse_files, left_pos_recursion).path;
    right_path = vector_lookup(recurse_files, (pos_recursion + 1) % recurse_files.size()).path;

  } else {

    int left = folders.top().second;
    do {
      left -= 1;
      if (left == -1)
	left=pic_list.size()-1;
    } while (vector_lookup(pic_list, left).type == "dir");

    int right = folders.top().second;
    do {
      right += 1;
      if (right == pic_list.size())
	right=0;
    } while (vector_lookup(pic_list, right).type == "dir");

    left_path = pic_list[left].path;
    right_path = pic_list[right].path;
  }
  
  if (conf->p_priority_change()) 
    nice(19); //Lower priority to keep audio from skipping
  
  print_fullscreen(pic_list[folders.top().second], left_path, right_path);

  if (conf->p_priority_change()) 
    nice(-19);
}

void Pictures::zoom() 
{
  if (zoom_level == 1) {

    // calculate zoom position
    render->image_mut.enterMutex();
    PFSObj p = PFSObj(pic_list[folders.top().second].path, false, orientation, 
		      zoom_level, zoom_position, 1, 1, 1);
    render->image_mut.leaveMutex();

    // don't zoom small images
    if (p.real_w < conf->p_v_res() && p.real_h < conf->p_h_res())
      return;

    zoom_level = 2;

    int pic_res_w = p.real_w*zoom_level;
    max_pos_v = 3; // default
    if (pic_res_w <= conf->p_v_res())
      max_pos_v = 1;
    else if (pic_res_w <= int(1.5*conf->p_v_res()))
      max_pos_v = 2;

    int pic_res_h = p.real_h*zoom_level;
    max_pos_h = 3; // default
    if (pic_res_h <= conf->p_h_res())
      max_pos_h = 1;
    else if (pic_res_h <= int(1.5*conf->p_h_res()))
      max_pos_h = 2;

    zoom_pos_h = int(max_pos_h/2)+1;
    zoom_pos_v = int(max_pos_v/2)+1;

    zoom_position = (zoom_pos_h-1)*max_pos_v+zoom_pos_v-1;

  } else {
    zoom_level = 1;
    zoom_position = 1;
  }
}

void Pictures::suspend()
{
  pause = !pause;
}

void Pictures::rotate_left()
{
  orientation = (orientation+1)%4;
}

void Pictures::rotate_right()
{
  orientation = (orientation == 0) ? 3 : orientation - 1;
}

void Pictures::options_fullscreen()
{
#ifdef c_music
  Audio *audio = Audio::get_instance();
  audio->set_audio_playback_pos(0);
#endif

  bool recursion_before = conv::stob(opts.recurse_in_fullscreen());

  options();

  if (!recursion_before && conv::stob(opts.recurse_in_fullscreen())) {
    recurse_files = parse_dirs_recursion();
    find_recursion_pos();
  }

#ifdef c_music
  audio->set_audio_playback_pos(image_width + 10);
#endif
}

void Pictures::deactivate_updaters()
{
  Updater &updater = Updater::get_instance();
  updater.timer.deactivate("pictures");
  updater.timer.deactivate("audio_fullscreen");
}

void Pictures::activate_updaters()
{
  Updater &updater = Updater::get_instance();
  if (opts.slideshow() != "0")
    updater.timer.activate("pictures");
  else
    updater.timer.activate("audio_fullscreen");
}

void Pictures::pictures_fullscreen()
{
  Startmenu &startmenu = Startmenu::get_instance();

  in_fullscreen = true;

  Input input;

  Updater &updater = Updater::get_instance();

  TimeFunctori<Pictures> time_func(this, &Pictures::check_picture_time);
  TimeFunctorv<Pictures> call_func(this, &Pictures::pictures_check);
  updater.timer.add(TimeElement("pictures", &time_func, &call_func));

  if (opts.slideshow() != "0")
    updater.timer.deactivate("audio_fullscreen");
  else
    updater.timer.deactivate("pictures");

#ifdef c_music
  Audio *audio = Audio::get_instance();
#endif

#ifdef c_music
  audio->set_audio_playback_pos(image_width + 10);
#endif

  bool update_needed = true;

  BusyIndicator &busy_indicator = BusyIndicator::get_instance();

  while (!exit_loop)
    {
      prepare_and_show_fullscreen();

      busy_indicator.idle();

      input = input_master->device->get_input();
      
      last_press = time(0);

      busy_indicator.busy();

      update_needed = true;
	  
      if (zoom_level != 1) {

	if (input.command == "prev") {
	  if (zoom_pos_h != 1)
	    --zoom_pos_h;
	} else if (input.command == "next") {
	  if (zoom_pos_h < max_pos_h)
	    ++zoom_pos_h;
	} else if (input.command == "left") {
	  if (zoom_pos_v != 1)
	    --zoom_pos_v;
	} else if (input.command == "right") {
	  if (zoom_pos_v < max_pos_v)
	    ++zoom_pos_v;
	} else if (input.command == "action" || (input.command == "back" && input.mode == "general")) {
	  zoom_level = 1;
	  zoom_position = 1;
	  activate_updaters();
	  continue;
	}

	// calculate zoom_position
	zoom_position = (zoom_pos_h-1)*max_pos_v + zoom_pos_v - 1;

	continue;
      }

      if (input.command == "back" && input.mode == "general")
	{
	  exit_fullscreen();
	}
      else if (input.command == "left")
	{
	  if (conv::stob(opts.recurse_in_fullscreen()))
	    prev_no_skip_folders();
	  else
	    prev_skip_folders();
	  orientation = 0;
	}
      else if (input.command == "right")
	{
	  if (conv::stob(opts.recurse_in_fullscreen()))
	    next_no_skip_folders();
	  else
	    next_skip_folders();
	  orientation = 0;
	}
      else if (input.command == "page_up")
 	{
	  int jump = images_per_row*rows;

	  if (search_mode)
	    jump = images_per_row*rows_search;

	  int list_size = pic_list.size();
	  if (conv::stob(opts.recurse_in_fullscreen()))
	    list_size = recurse_files.size();

	  if (list_size > jump) {
	    for (int i = 0; i < jump; ++i) {
	      if (conv::stob(opts.recurse_in_fullscreen()))
		prev_no_skip_folders();
	      else
		prev_skip_folders();
	    }

	    orientation = 0;
	  }
 	}
      else if (input.command == "page_down")
 	{
	  int jump = images_per_row*rows;

	  if (search_mode)
	    jump = images_per_row*rows_search;

	  int list_size = pic_list.size();
	  if (conv::stob(opts.recurse_in_fullscreen()))
	    list_size = recurse_files.size();

	  if (list_size > jump) {
	    for (int i = 0; i < jump; ++i) {
	      if (conv::stob(opts.recurse_in_fullscreen()))
		next_no_skip_folders();
	      else
		next_skip_folders();
	    }

	    orientation = 0;
	  }
 	}
      else if (input.command == "next") 
	{
	  rotate_left();
	}
      else if (input.command == "prev") 
	{
	  rotate_right();
	}
      else if (input.command == "action")
	{
	  zoom();

	  if (zoom_level != 1)
	    deactivate_updaters();
	  else
	    activate_updaters();
	}
      else if (input.command == "suspend")
	{
	  deactivate_updaters();
	  suspend();
	  activate_updaters();
	}
      else if (input.command == "second_action")
	{
	  ExtraMenu em;

	  TimeFunctorv<Pictures> t_z = TimeFunctorv<Pictures>(this, &Pictures::zoom);
	  em.add_item(ExtraMenuItem(gettext("Zoom"), input_master->find_shortcut("action"), &t_z));

	  string suspend = gettext("Suspend slideshow");

	  if (pause)
	    suspend = gettext("Resume slideshow");

	  TimeFunctorv<Pictures> t_s = TimeFunctorv<Pictures>(this, &Pictures::suspend);
	  em.add_item(ExtraMenuItem(suspend, input_master->find_shortcut("suspend"), &t_s));

	  TimeFunctorv<Pictures> t_rl = TimeFunctorv<Pictures>(this, &Pictures::rotate_left);
	  em.add_item(ExtraMenuItem(gettext("Rotate left"), input_master->find_shortcut("left"), &t_rl));

	  TimeFunctorv<Pictures> t_rr = TimeFunctorv<Pictures>(this, &Pictures::rotate_right);
	  em.add_item(ExtraMenuItem(gettext("Rotate right"), input_master->find_shortcut("right"), &t_rr));

	  TimeFunctorv<Pictures> t_o = TimeFunctorv<Pictures>(this, &Pictures::options_fullscreen);
	  em.add_item(ExtraMenuItem(gettext("Options"), input_master->find_shortcut("options"), 
				    &t_o));

	  TimeFunctorv<Pictures> t_back = TimeFunctorv<Pictures>(this, &Pictures::exit_fullscreen);
	  em.add_item(ExtraMenuItem(gettext("Leave fullscreen"), 
				    input_master->find_shortcut("back"), 
				    &t_back));

#ifdef c_music
	  TimeFunctorv<Audio> t_player = TimeFunctorv<Audio>(Audio::get_instance(), &Audio::playlist_commands);
	  em.add_item(ExtraMenuItem(gettext("Playlist commands"), input_master->find_shortcut("playlist_commands"), 
				    &t_player));
#endif

	  deactivate_updaters();
	    
	  em.mainloop();

	  if (zoom_level == 1)
	    activate_updaters();
	}
      else if (input.command == "options")
	{
	  deactivate_updaters();
	  options_fullscreen();
	  activate_updaters();
	}
      else
	update_needed = !general_loop_check(input);
    }

  if (zoom_level != 1) {
    zoom_level = 1;
    zoom_position = 1;
  }

  exit_loop = false;

#ifdef c_music
  audio->set_audio_playback_pos(0);
#endif

  updater.timer.activate("audio_fullscreen");
}

void Pictures::print(const vector<Simplefile>& cur_list)
{
  render->prepare_new_image();

  render->current.add(new PObj(themes->pictures_background, 0, 0, 0, SCALE_FULL));

  render->current.add(new PObj(themes->startmenu_pictures_dir, 25, 10, 2, NOSCALING));

  if (themes->show_header) {
    render->current.add(new TObj(gettext("Pictures"), "Vera/28", 100, 10, 
				 themes->pictures_header_font1, themes->pictures_header_font2, 
				 themes->pictures_header_font3, 2));
  }
  
  if (search_mode) {

    int y = 60;
    if (conf->p_v_res() == 720) {
      y -= 3;
    }

    render->current.add(new RObj(0, y, conf->p_v_res(), 45, 
				 themes->general_search_rectangle_color1, 
				 themes->general_search_rectangle_color2, 
				 themes->general_search_rectangle_color3, 
				 themes->general_search_rectangle_color4, 2));

    render->current.add(new PObj(themes->general_search, 47, y, 3, NOSCALING));

    render->current.add(new TObj(gettext("Search: ") + search_str, "Vera/22", 100, y+5, 
				 themes->pictures_font1, themes->pictures_font2, 
				 themes->pictures_font3, 3));
  }

  // print the range
  int pos, start, pos_in_list, cur_rows, cur_image_height_all;

  if (search_mode) {
    pos_in_list = offset;
    cur_rows = rows_search;
    cur_image_height_all = image_height_all_search;
  } else {
    pos_in_list = folders.top().second;
    cur_rows = rows;
    cur_image_height_all = image_height_all;
  }
  
  int nr_in_last_row = cur_list.size()%images_per_row;
  if (nr_in_last_row == 0)
    nr_in_last_row = images_per_row;

  if (cur_list.size() > cur_rows*images_per_row) {
    if (pos_in_list < std::ceil(double(cur_rows)/2.0)*images_per_row) { // first two rows
      pos = pos_in_list;
      start = 0;
    } else if (pos_in_list >= cur_list.size() - (images_per_row+nr_in_last_row)) { // last two rows
      start = cur_list.size()-((cur_rows-1)*images_per_row+nr_in_last_row);
      pos = pos_in_list-start;
    } else {
      pos = pos_in_list%images_per_row+images_per_row;
      if (pos == images_per_row-1)
	pos = 2*images_per_row-1;
      start = pos_in_list-pos;
    }
  } else {
    pos = pos_in_list;
    start = 0;
  }

  int x_start, x = (conf->p_v_res()-images_per_row*image_width_all)/2, y = y_start;
  x_start = x;

  if (search_mode)
    y = y_start + 50;

  for (int j = 0; j < cur_rows*images_per_row && j < cur_list.size(); ++j) {

    Simplefile s = vector_lookup(cur_list, start);

    if (s.type == "dir") {

      PObj *p;
      
      if (pos == j)
	p = new PObj(themes->pictures_dir_marked, x, y, 3, NOSCALING);
      else
	p = new PObj(themes->pictures_dir, x, y, 3, NOSCALING);

      p->x = p->real_x = x + (image_width_all - p->w)/2;
      p->y = p->real_y = y + (image_height_all_eks_text - p->h)/2;
      
      render->current.add(p);

    } else {
      bool border = false;
	
      if (pos == j)
	border = true;
	
      render->create_scaled_image_wrapper(s.path, image_width, image_height);
      PFObj *p = new PFObj(s.path, x, y, image_width, image_height, border, 3);
	
      p->x = p->real_x = x + (image_width_all - (p->w + 10))/2;
      p->y = p->real_y = y + (image_height_all_eks_text - (p->h + 10))/2;

      if (p->border) {
	p->real_x -= 5;
	p->real_y -= 5;
      } else {
	p->real_x -= 1;
	p->real_y -= 1;
      }

      render->current.add(p);
    }

    string name = s.name;
    string old_name = name;
    
    string_format::format_to_size(name, "Vera/13", image_width_all-10, false);

    string name_second_part = old_name.substr(name.size());

    if (name_second_part.size() > 0 && name_second_part[0] != ' ') {
      // splitting words
      string::size_type last_whole_word = name.rfind(' ');
      if (last_whole_word != string::npos) {
	name = name.substr(0, last_whole_word);
	name_second_part = old_name.substr(name.size());
      }
    }

    int x_size, y_size;
    string_format::calculate_string_size(name, "Vera/13", &x_size, &y_size);


    string_format::format_to_size(name_second_part, "Vera/13", image_width_all-10, false);

    int x_size2;
    string_format::calculate_string_size(name_second_part, "Vera/13", &x_size2, &y_size);

    if (pos == j) {
      render->current.add(new TObj(name, "Vera/13", x + (image_width_all-x_size)/2, y+image_height_all_eks_text, 
				   themes->pictures_marked_font1, themes->pictures_marked_font2, 
				   themes->pictures_marked_font3, 3));
      render->current.add(new TObj(name_second_part, "Vera/13", x + (image_width_all-x_size2)/2, y+image_height_all_eks_text+20, 
				   themes->pictures_marked_font1, themes->pictures_marked_font2, 
				   themes->pictures_marked_font3, 3));
    } else {
      render->current.add(new TObj(name, "Vera/13", x + (image_width_all-x_size)/2, y+image_height_all_eks_text, 
				   themes->pictures_font1, themes->pictures_font2, 
				   themes->pictures_font3, 3));
      render->current.add(new TObj(name_second_part, "Vera/13", x + (image_width_all-x_size2)/2, y+image_height_all_eks_text+20, 
				   themes->pictures_font1, themes->pictures_font2, 
				   themes->pictures_font3, 3));
    }

    x += image_width_all;

    if (start == cur_list.size()-1) {
      if ((j+1)%images_per_row != 0)
	j += (images_per_row-(j+1)%images_per_row);
      
      x = x_start;
      y += cur_image_height_all;

      start = 0;
      continue;
      
    } else
      ++start;
    
    if ((j+1)%images_per_row == 0) {
      y += cur_image_height_all;
      x = x_start;
    }
  }

  std::ostringstream position;

  if (search_mode)
    if (cur_list.size() > 0)
      position << offset + 1 << "/" << cur_list.size();
    else
      position << "";
  else
    position << folders.top().second+1 << "/" << cur_list.size();

  int x_size, y_size;
  string_format::calculate_string_size(position.str(), "Vera/18", &x_size, &y_size);

  render->current.add(new TObj(position.str(), "Vera/18", conf->p_v_res()-(60+x_size), 20, 
			       themes->pictures_font1, themes->pictures_font2, 
			       themes->pictures_font3, 3));

  render->draw("pictures");

  render->image_mut.leaveMutex();
}

void Pictures::print_fullscreen(const Simplefile& e, const string& left_path, const string& right_path)
{
  // sanity
  if (e.type == "dir")
    return;

  if (conf->p_priority_change()) 
    nice(19);

  render->prepare_new_image();

  render->current.add(new RObj(0, 0, conf->p_v_res(), conf->p_h_res(), 0, 0, 0, 255, 0));

  if (zoom_level != 1 || orientation != 0)
    render->current.add(new PFSObj(e.path, conv::stob(opts.zoom()), orientation, 
				   zoom_level, zoom_position, max_pos_v, max_pos_h, 1));
  else {
    // use thumbnailed for performance
    render->create_scaled_image_wrapper(e.path, conf->p_v_res(), conf->p_h_res());
    render->current.add(new PFSObj(render->image_get_fullpath(e.path, conf->p_v_res(), conf->p_h_res()), 
				   conv::stob(opts.zoom()), orientation, zoom_level, zoom_position, 
				   max_pos_v, max_pos_h, 1));
  }

  if (conv::stob(opts.show_preview()) && zoom_level == 1) {

    // make sure the thumbnails are there
    render->create_scaled_image_wrapper(left_path, image_width, image_height);
    render->create_scaled_image_wrapper(right_path, image_width, image_height);

    bool is_transparent;

    // left
    std::pair<int, int> dimensions = render->image_dimensions(render->image_get_fullpath(left_path, image_width, image_height), &is_transparent);
    render->current.add(new PFObj(left_path, 5, conf->p_h_res()-dimensions.first-5, image_width, image_height, true, 2));
    // right
    dimensions = render->image_dimensions(render->image_get_fullpath(right_path, image_width, image_height), &is_transparent);
    render->current.add(new PFObj(right_path, conf->p_v_res()-dimensions.second-5, conf->p_h_res()-dimensions.first-5, image_width, image_height, true, 2));
  }
  
  std::ostringstream position;
  if (conv::stob(opts.recurse_in_fullscreen()))
    position << pos_recursion+1 << "/" << recurse_files.size();
  else
    position << folders.top().second+1 << "/" << pic_list.size();

  int x_size, y_size;
  string_format::calculate_string_size(position.str(), "Vera/18", &x_size, &y_size);

  int x_pos = 20, y_pos = 20;

  if (conf->p_busy_indicator()) {
    x_pos = 35;
    y_pos = 10;
  }
  
  render->current.add(new TObj(position.str(), "Vera/18", conf->p_v_res()-(x_pos+x_size), y_pos, 
			       themes->pictures_font1, themes->pictures_font2, 
			       themes->pictures_font3, 2));

  string name = e.name;

  if (pause && conv::stob(opts.slideshow()))
    name += gettext(" ( paused )");

  string_format::format_to_size(name, "Vera/16", conf->p_v_res()-(20+x_size+30), false);
  
  render->current.add(new TObj(name, "Vera/16", 20, 20, 
			       themes->pictures_font1, themes->pictures_font2, 
			       themes->pictures_font3, 2));

  render->draw("pictures full");

  render->image_mut.leaveMutex();

  if (conf->p_priority_change()) 
    nice(-19);
}

#ifdef use_media
void Pictures::play_pictures_cd()
{
  if (cd->data_disc()) {
    // backup ;)
    vector<Simplefile> old_list = pic_list;
    std::stack<std::pair<list<string>, int > > temp_folders = folders;
    while (!folders.empty())
      folders.pop();
    list<string> templs; templs.push_back(cd->get_mount_point());
    folders.push(std::make_pair(templs, 0));
  
    //mount the disc
    run::my_system_no_kill(("mount " + cd->get_mount_point()).c_str());

    bool valid_files = testdir(cd->get_mount_point());
  
    // get the total number of files:
    int nr_files = 0;
    for (file_iterator<file_t, default_order> i (cd->get_mount_point()); i != i.end (); i.advance(false))
      if (!isDirectory (i->getName()))
	++nr_files;

    Updater &updater = Updater::get_instance();

    // disable screensaver if it's running
    bool audio_fullscreen_running = true;

    if (!updater.timer.status("audio_fullscreen"))
      audio_fullscreen_running = false;
    else
      updater.timer.deactivate("audio_fullscreen");

    updater.status_progressbar = 0;
    updater.total_progressbar = nr_files;
    updater.header = gettext("Generating thumbnails");

    TimeFunctoriNC cp(&check_progressbar);
    TimeFunctorvNC pg(&print_progressbar);
  
    updater.timer.add(TimeElement("progressbar", &cp, &pg));

    pic_list = rdir(cd->get_mount_point());

    updater.timer.del("progressbar");
    cleanup_progressbar();  

    if (audio_fullscreen_running)
      // reenable screensaver
      updater.timer.activate("audio_fullscreen");

    if (pic_list.size() == 0 || !valid_files)
      Print pdialog(gettext("The disc does not contain any recognizable files"), 
		    Print::SCREEN);
    else
      mainloop();
    
    //umount the disc
    run::my_system_no_kill(("umount " + cd->get_mount_point()).c_str());

    pic_list = old_list;
    folders = temp_folders;
  } else
    Print pdialog(gettext("The disc does not contain any recognizable files"), 
		  Print::SCREEN);
}
#endif

// ====================================================================
//
// dir
//
// ====================================================================

bool Pictures::testdir(const string& dir)
{
  for (file_iterator<file_t, default_order> i (dir); i != i.end (); ++i) {
    MyPair filetype = check_type((*i).getName(), conf->p_filetypes_p());
    if (filetype != emptyMyPair)
      return true;
  }

  return false;
}

// Add an picture to the list 
Simplefile Pictures::addfile(const string& name, const MyPair& filetype)
{
  render->image_mut.enterMutex();
  render->device->wait_for_not_drawing();
  render->create_scaled_image_wrapper(name, image_width, image_height);
  render->image_mut.leaveMutex();

  return addsimplefile(name, filetype);
}

// read a dir and spit out a list of files and dirs
vector<Simplefile> Pictures::rdir(const string& argv)
{
  vector<Simplefile> files;
  Simplefile temp;

  for (file_iterator<file_t, default_order> i (argv); i != i.end (); i.advance(false))
    {
      string cur_file = filesystem::FExpand(i->getName());

      if (isDirectory(cur_file)) { 
	// dir

	// exclude folders ?
	if (conf->p_excluded_picture_dirs().size() > 0) {
	  list<string> excluded = conf->p_excluded_picture_dirs();
	  bool stop = false;
	  
	  for (list<string>::const_iterator epd = excluded.begin(), epd_end = excluded.end(); 
	       epd != epd_end; ++epd)
	    if (cur_file.substr(cur_file.rfind("/")+1) == *epd) {
	      stop = true;
	      break;
	    }

	  if (stop)
	    continue;
	}

	Simplefile r;
	r.id = ++id;
	r.name = cur_file.substr(argv.size()+1);
	if (conf->p_convert())
	  r.name = string_format::convert(r.name);
	r.lowercase_name = r.name;
	r.path = cur_file;
	r.type="dir";
	files.push_back(r);
      } else {
	// file
	const MyPair filetype = check_type(cur_file, conf->p_filetypes_p());
	if (filetype != emptyMyPair) {
	  if (in_fullscreen)
	    files.push_back(addsimplefile(cur_file, filetype));
	  else
	    files.push_back(addfile(cur_file, filetype));
	}
	Updater &updater = Updater::get_instance();
	++updater.status_progressbar;
      }
    }
  return files;
}

// The �ber function for parsing dirs.
vector<Simplefile> Pictures::parse_dir(const list<string>& dirs)
{
  vector<Simplefile> files;

  for (list<string>::const_iterator iter = dirs.begin(); iter != dirs.end(); ++iter)
    {
      // Add files
      vector<Simplefile> tempfiles = rdir(*iter);
      files.insert(files.end(), tempfiles.begin(), tempfiles.end());
    }

  return files;
}

// functions for creating lists for recursion

vector<Simplefile> Pictures::parse_dirs_recursion()
{
  vector<string> image_files = images_in_dir(folders.top().first);

  // we need them in the correct order for recursion
  sort(image_files.begin(), image_files.end());

  vector<Simplefile> files;

  for (vector<string>::const_iterator i = image_files.begin(), iend = image_files.end();
       i != iend; ++i)
    {
      const MyPair filetype = check_type(*i, conf->p_filetypes_p());
      if (filetype != emptyMyPair)
	files.push_back(addsimplefile(*i, filetype));
    }

  return files;
}

void Pictures::read_dirs() 
{
  // get the total number of files:
  int nr_files = 0;
  list<string> dirs = conf->p_picture_dirs();
  for (list<string>::const_iterator iter = dirs.begin(); iter != dirs.end(); ++iter)
    for (file_iterator<file_t, default_order> i (*iter); i != i.end (); i.advance(false))
      if (!isDirectory (i->getName()))
	++nr_files;

  Updater &updater = Updater::get_instance();

  // disable screensaver if it's running
  bool audio_fullscreen_running = true;

  if (!updater.timer.status("audio_fullscreen"))
    audio_fullscreen_running = false;
  else
    updater.timer.deactivate("audio_fullscreen");

  updater.status_progressbar = 0;
  updater.total_progressbar = nr_files;
  updater.header = gettext("Generating thumbnails");

  TimeFunctoriNC cp(&check_progressbar);
  TimeFunctorvNC pg(&print_progressbar);
  
  updater.timer.add(TimeElement("progressbar", &cp, &pg));

  // process picture dir 
  for (list<string>::iterator iter = dirs.begin(); iter != dirs.end(); ++iter)
    if ((*iter)[iter->size()-1] == '/')
      *iter = iter->substr(0, iter->size()-1);

  pic_list = parse_dir(dirs);

  updater.timer.del("progressbar");
  cleanup_progressbar();  

  if (audio_fullscreen_running)
    // reenable screensaver
    updater.timer.activate("audio_fullscreen");

  if (conf->p_picture_dirs().size() > 1)
    std::sort(pic_list.begin(), pic_list.end(), file_sort());
  
  if (pic_list.size() != 0)
    folders.top().second = 0;
}

void Pictures::pictures_check()
{
  if (opts.slideshow() != "0" && !pause && last_press <= time(0) - conv::atoi(opts.slideshow())) {

    if (conv::stob(opts.recurse_in_fullscreen()))
      next_no_skip_folders();
    else
      next_skip_folders();
    
    prepare_and_show_fullscreen();
  }
}

int Pictures::check_picture_time()
{
  // here we can assume that opts.slideshow() > 0 from the calling code
  return std::min(static_cast<long int>(conv::atoi(opts.slideshow())), time(0)-last_press)*1000;
}
