#ifndef MODULE_HPP
#define MODULE_HPP

#include "config.h"

#include "common.hpp"
#include "themes.hpp"
#include "renderer.hpp"
#include "config.hpp"
#include "simplefile.hpp"
#include "entry.hpp"
#include "functor.hpp"

#ifdef use_media
#include "cd.hpp"
#endif

#include "rectangle_object.hpp"

#include "input.hpp"
#include "keyboard.hpp"
#ifdef use_lirc
#include "lirc.hpp"
#endif
#ifdef use_evdev
#include "evdev.hpp"
#endif

#include <cmath>
#include <string>
#include <list>
#include <vector>

#define X_DISPLAY_MISSING

#include <Imlib2.h>

class Module
{
protected:

  // implemented in terms of:
  Input_master *input_master;
  Themes *themes;
  Render *render;
  Config *conf;

#ifdef use_media
  Cd *cd;
#endif

  std::map<std::string, std::string> cover_map;

  // returns the empty string if no cover is found
  std::string get_cover(const std::string& s);

  template<typename T, typename L>
  const std::vector<T> gen_searched_list(std::vector<T>& list, const std::string& search_word, L* representation)
  {
    // optimization
    if (search_word == "")
      return list;

    std::vector<T> newlist;

    lowercase_search_str = string_format::lowercase(search_word);

    for (int i = 0, size = list.size(); i < size; ++i)
      if (string_format::lowercase(representation->call(vector_lookup(list, i))).substr(0, search_word.size())
	  == lowercase_search_str)
	newlist.push_back(vector_lookup(list, i));
    
    return newlist;
  }

  // FIXME: move to common?
  std::string greatest_common_substring(const std::string& lhs, const std::string& rhs);
  
  // helper functions
  std::list<std::string> keyboard_not_accepted();
  std::vector<std::vector<std::string> > lirc_keys();
  std::vector<std::vector<std::string> > parse_string(std::string all_chars);

  template<typename T, typename F, typename K, typename L>
  void search(std::vector<T>& list, int *position, F* print_func, K* compare, L* representation)
  {
    input_map *old_map = input_master->cur_map;
    input_master->cur_map = &input_master->search;

    search_str = "";
    offset = 0;
    search_mode = true;

    Input input;

    Keyboard *k = dynamic_cast<Keyboard*>(input_master->device);

    bool canceled = true;

    if (k != 0) {

      // keyboard
      k->enable_all_input();
      
      std::list<std::string> not_accepted_chars = keyboard_not_accepted();

      while (input.command != "exit") {

	lowercase_search_str = string_format::lowercase(search_str);

	std::vector<T> tmp = gen_search(list, compare);
	
	print_func->call(tmp);

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

	if (input.mode == "search" && input.command == "accept") {
	  canceled = false;
	  break;
	} 
	else if (input.command == "complete") {

	  std::vector<T> searched = gen_searched_list(list, search_str, representation);

	  if (searched.size() != 0) {
	    if (searched.size() == 1)
	      search_str = representation->call(vector_lookup(searched, 0));
	    else {
	      std::string gcss = greatest_common_substring(string_format::lowercase(representation->call(vector_lookup(searched, 0))),
							   string_format::lowercase(representation->call(vector_lookup(searched, 1))));
	      for (int i = 2; i < searched.size(); ++i)
		gcss = greatest_common_substring(gcss, string_format::lowercase(representation->call(vector_lookup(searched, i))));

	      search_str = gcss;
	    }
	  }
	} 
	
	if (input.command != "erase") {
	  bool found_match = false;

	  if (input.key == "SPACE")
	    input.key = " ";
	  else
	    for (std::list<std::string>::iterator i = not_accepted_chars.begin(), end = not_accepted_chars.end();
		 i != end; ++i)
	      if (input.key == *i)
		found_match = true;

	  if (!found_match)
	    search_str += input.key;
	} else // erase
	  if (input.mode == "search" && search_str.size() != 0)
	    search_str = search_str.substr(0, search_str.size()-1);

	if (input.mode == "search" && input.command == "next") {
	  if (offset == gen_searched_list(list, search_str, representation).size()-1)
	    offset = 0;
	  else
	    ++offset;
	} else if (input.mode == "search" && input.command == "prev") {
	  --offset;
	  if (offset == -1)
	    offset = gen_searched_list(list, search_str, representation).size()-1;
	} else 
	  offset = 0;
      }

      k->disable_all_input();
    }
    
    Input_device *remote = NULL;

#ifdef use_lirc
    if (conf->p_input() == LIRC)
      remote = dynamic_cast<Lirc*>(input_master->device);
#endif
#ifdef use_evdev
    if (conf->p_input() == EVDEV)
      remote = dynamic_cast<Evdev*>(input_master->device);
#endif
    
#if defined (use_lirc) || defined(use_evdev)

    if (remote != NULL) {

      remote->enable_all_input();

      std::vector<std::vector<std::string> > keys = lirc_keys();
      std::string last_letter = "";
      std::string last_key = "";

      int pos;

      int last_keypress;

      while (input.command != "exit") {
	  
	lowercase_search_str = string_format::lowercase(search_str);

	std::vector<T> tmp = gen_search(list, compare);
	
	print_func->call(tmp);

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

	if (input.mode == "search" && input.command == "accept") {
	  canceled = false;
	  break;
	}
	else if (input.command == "complete") {

	  std::vector<T> searched = gen_searched_list(list, search_str, representation);

	  if (searched.size() != 0) {
	    if (searched.size() == 1)
	      search_str = representation->call(vector_lookup(searched, 0));
	    else {
	      std::string gcss = greatest_common_substring(string_format::lowercase(representation->call(vector_lookup(searched, 0))),
							   string_format::lowercase(representation->call(vector_lookup(searched, 1))));
	      for (int i = 2; i < searched.size(); ++i)
		gcss = greatest_common_substring(gcss, string_format::lowercase(representation->call(vector_lookup(searched, i))));

	      search_str = gcss;
	    }
	  }
	} 

	if (input.key == "0")
	  pos = 9;
	else if (input.key == "1")
	  pos = 0;
	else if (input.key == "2")
	  pos = 1;
	else if (input.key == "3")
	  pos = 2;
	else if (input.key == "4")
	  pos = 3;
	else if (input.key == "5")
	  pos = 4;
	else if (input.key == "6")
	  pos = 5;
	else if (input.key == "7")
	  pos = 6;
	else if (input.key == "8")
	  pos = 7;
	else if (input.key == "9")
	  pos = 8;
	else if (input.mode == "search" && input.command == "next") {
	  if (offset == gen_searched_list(list, search_str, representation).size()-1)
	    offset = 0;
	  else
	    ++offset;
	  continue;
	} else if (input.mode == "search" && input.command == "prev") {
	  --offset;
	  if (offset == -1)
	    offset = gen_searched_list(list, search_str, representation).size()-1;
	  continue;
	} else if (input.command == "erase") {
	  search_str = search_str.substr(0, search_str.size()-1);
	  continue;
	} else
	  continue;

	offset = 0;

	std::vector<T> searched = gen_searched_list(list, search_str, representation);
	int good_key = 0;

	bool other_key_possible = false;
	bool found_key = false;

	int chars_for_cur_key = vector_lookup(keys, pos).size();

	if (searched.size() > 0) {

	  if ((time(0)-last_keypress) > 2)
	    last_key = "";
	  else {
	    // generate list of all chars already tried for the current key
	    std::vector<std::string> f;

	    if (last_key == input.key) {
	      for (int i = 0; i < chars_for_cur_key; ++i)
		if (last_letter == "")
		  break;
		else if (vector_lookup(vector_lookup(keys, pos), i) != last_letter)
		  f.push_back(vector_lookup(vector_lookup(keys, pos), i));
		else if (vector_lookup(vector_lookup(keys, pos), i) == last_letter) {
		  f.push_back(vector_lookup(vector_lookup(keys, pos), i));
		  break;
		}
	    }

	    // get the list of items the last time we searched, to look for new
	    // letters possible
	    if (f.size() > 0) {

	      std::vector<T> searched_before = gen_searched_list(list, search_str.substr(0, search_str.size()-1), representation);

	      for (int i = 0; i < chars_for_cur_key; ++i) {
		if (found_key)
		  break;
		for (int j = 0; j < searched_before.size(); ++j) {
		  if (string_format::lowercase(representation->call(vector_lookup(searched_before, j)))[search_str.size()-1]
		      == vector_lookup(vector_lookup(keys, pos), i)[0]) {
		    bool g = false;
		    if (i < f.size()) {
		      if (vector_lookup(vector_lookup(keys, pos), i) != vector_lookup(f, i))
			g = true;
		    } else
		      g = true;
		  
		    if (g) {
		      other_key_possible = true;
		      found_key = true;
		      good_key = i;
		      break;
		    }
		  }
		}
	      }
	    }
	  }
	  
	  if (!other_key_possible) {
	    for (int i = 0; i < chars_for_cur_key; ++i) {
	      if (found_key)
		break;
	      for (int j = 0; j < searched.size(); ++j) {
		if (string_format::lowercase(representation->call(vector_lookup(searched, j)))[search_str.size()]
		    == vector_lookup(vector_lookup(keys, pos), i)[0]) {
		  good_key = i;
		  found_key = true;
		  break;
		}
	      }
	    }
	  }
	}

	if (other_key_possible)
	  search_str = search_str.substr(0, search_str.size()-1);

	if (found_key) {
	  std::string key = vector_lookup(vector_lookup(keys, pos), good_key);
	  search_str += key;
	  last_letter = key;
	} else
	  last_letter = "";

	last_key = input.key;

	last_keypress = time(0);
      }
      remote->disable_all_input();
    }

#endif // if defined (use_lirc) || defined(use_evdev)

    if (!canceled) {

      std::vector<T> searched = gen_searched_list(list, search_str, representation);

      if (searched.size() > 0) {
	T element = vector_lookup(searched, offset%searched.size());

	for (int i = 0, size = list.size(); i < size; ++i)
	  if (vector_lookup(list, i) == element) {
	    *position = i;
	    break;
	  }
      }
    }

    input_master->cur_map = old_map;

    search_mode = false;
  }

  enum GenerateReturn { ABORT, OK, NEXT };

  template<typename F>
  GenerateReturn generate_string(std::string& str, F* print_func)
  {
    input_map *old_map = input_master->cur_map;
    input_master->cur_map = &input_master->search;

    Input input;

    Keyboard *k = dynamic_cast<Keyboard*>(input_master->device);

    GenerateReturn exit_value = ABORT;

    if (k != 0) {
    
      // keyboard
      k->enable_all_input();
      
      std::list<std::string> not_accepted_chars = keyboard_not_accepted();

      while (input.command != "exit") {

	input = print_func->call(str);

	if (input.mode == "search" && input.command == "accept") {
	  exit_value = OK;
	  break;
	} else if (input.mode == "search" && input.command == "next") {
	  exit_value = NEXT;
	  break;
	}

	if (input.command != "erase") {
	  bool found_match = false;

	  if (input.key == "SPACE") {
	    input.key = " ";
	  } else {
	    for (std::list<std::string>::iterator i = not_accepted_chars.begin(), 
		   end = not_accepted_chars.end(); i != end; ++i)
	      if (input.key == *i) {
		found_match = true;
		break;
	      }
	  }
	  
	  if (!found_match)
	    str += input.key;
	} else // erase
	  if (input.mode == "search" && str.size() != 0)
	    str = str.substr(0, str.size()-1);
      }

      k->disable_all_input();
    }

    Input_device *remote = 0;

#ifdef use_lirc
    if (conf->p_input() == LIRC)
      remote = dynamic_cast<Lirc*>(input_master->device);
#endif
#ifdef use_evdev
    if (conf->p_input() == EVDEV)
      remote = dynamic_cast<Evdev*>(input_master->device);
#endif
    
#if defined (use_lirc) || defined(use_evdev)

    if (remote != 0) {

      remote->enable_all_input();

      std::vector<std::vector<std::string> > keys = lirc_keys();

      std::vector<std::string> last_keys;
      for (int i = 0; i < 5; ++i)
	last_keys.push_back("");

      int pos, last_key = time(0);
      
      while (input.command != "exit") {
	  
	input = print_func->call(str);

	if (input.mode == "search" && input.command == "accept") {
	  exit_value = OK;
	  break;
	} else if (input.mode == "general" && input.command == "next") {
	  exit_value = NEXT;
	  break;
	}

	if (input.key == "0")
	  pos = 9;
	else if (input.key == "1")
	  pos = 0;
	else if (input.key == "2")
	  pos = 1;
	else if (input.key == "3")
	  pos = 2;
	else if (input.key == "4")
	  pos = 3;
	else if (input.key == "5")
	  pos = 4;
	else if (input.key == "6")
	  pos = 5;
	else if (input.key == "7")
	  pos = 6;
	else if (input.key == "8")
	  pos = 7;
	else if (input.key == "9")
	  pos = 8;
	else if (input.command == "erase") {
	  str = str.substr(0, str.size()-1);
	  continue;
	} else
	  continue;

	int amount = vector_lookup(keys, pos).size();

	int idle = time(0)-last_key;
	if (idle > 2) {
	  for (int i = 0; i < 5; ++i)
	    last_keys[i] = "";
	}
	
	int keys_pos = 0;
	for (int p = 5; p > 5-amount; --p) {
	  if (vector_lookup(last_keys, p-1) == input.key)
	    ++keys_pos;
	  else
	    break;
	}

	if (keys_pos == amount) {
	  keys_pos = 0;
	  for (int i = 0; i < 5; ++i)
	    last_keys[i] = "";
	  str = str.substr(0, str.size()-1);
	} 
      
	if (keys_pos != 0)
	  str = str.substr(0, str.size()-1);
      
	str += vector_lookup(vector_lookup(keys, pos), keys_pos);
      
	for (int i = 1; i < 5; ++i)
	  last_keys[i-1] = vector_lookup(last_keys, i);
	last_keys[4] = input.key;

	last_key = time(0);
      }
      remote->disable_all_input();
    }
#endif // if defined (use_lirc) || defined(use_evdev)

    input_master->cur_map = old_map;

    return exit_value;
  }

  void print_range_top(int int_position, int list_size, int& y, int& start, int& range)
  {
    y = 105;

    if (conf->p_h_res() == 405)
      y = 85;

    if (search_mode)
      y += 35;

    int buttom = 90;
    
    if (conf->p_h_res() == 405)
      buttom = 65;

    range = (conf->p_h_res()-y-buttom)/28;

    int width = conf->p_v_res()-2*55;
    int height = range*28+25;

    if (search_mode)
      render->current.add(new RObj(55, y-15, width, height, 
				   themes->general_rectangle_color1, themes->general_rectangle_color2,
				   themes->general_rectangle_color3, themes->general_rectangle_color4, 1));
    else
      render->current.add(new RObj(55, y-15, width, height, 
				   themes->general_rectangle_color1, themes->general_rectangle_color2,
				   themes->general_rectangle_color3, themes->general_rectangle_color4, 1));

    start = int_position - (range/2);

    // special cases
    if (range < list_size) {
      if (int_position >= list_size - (range/2)) // end
	start = list_size - range;
      else if ((range/2) >= int_position) // beginning
	start = 0;
    } else { // playlist smaller than range
      start = 0;
      range = list_size;
    }
  }
  
  template<typename T, typename F>
  void print_range(const std::vector<T>& files, const T& position,
		   const int int_position, F* print_func)
  {
    int y, start, range;
    print_range_top(int_position, files.size(), y, start, range);
  
    // goto start of print
    typename std::vector<T>::const_iterator iter = files.begin() + start;

    // print 
    for (int j = 0; j < range; ++j)
      {
	print_func->call(*iter, position, y);
	
	y += 28;
	++iter;
      }
  }

  template<typename T, typename F>
  void print_range_modifying(std::vector<T>& files, const T& position,
			     const int int_position, F* print_func)
  {
    int y, start, range;
    print_range_top(int_position, files.size(), y, start, range);
  
    // goto start of print
    typename std::vector<T>::iterator iter = files.begin() + start;

    // print 
    for (int j = 0; j < range; ++j)
      {
	print_func->call(*iter, position, y);

	y += 28;
	++iter;
      }
  }

  // dir
  int id;

  MyPair check_type(const std::string& s, const std::list<MyPair>& temp);
  Simplefile addsimplefile(const std::string& name, const MyPair& filetype, bool from_media = false);

public:

  // returns the empty string if no cover is found
  std::string get_cover_from_filename(const Simplefile& s);
  std::string get_cover_from_dir(const std::string& dir);

  // search variables
  // gcc 2.95 fix
  std::string search_str;
  std::string lowercase_search_str;
  int offset;
  bool search_mode;

  template<typename T, typename F>
  std::vector<T> gen_search(std::vector<T>& list, F* compare)
  {
    std::vector<T> newlist;

    for (int i = 0, size = list.size(); i < size; ++i)
      if (compare->call(vector_lookup(list, i))) 
	newlist.push_back(vector_lookup(list, i));

    return newlist;
  }

  virtual ~Module() {};

  Module();

  virtual std::string mainloop() = 0;
};

#endif
