#include "config.h"

#include "sdlout.hpp"

#include "renderer.hpp"

#ifdef benchmark_output
#include "bench.hpp"
#endif

#include "print.hpp"
#include "gettext.hpp"

#include <sstream>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/kd.h>
#include <sys/types.h>
#include <stdlib.h>

void SDLdev::deinit()
{
  if (initialized == -1)
    return;

  locked_mutex.enterMutex();

  SDL_Quit();

  initialized = -1;
  locked_mutex.leaveMutex();
}

void SDLdev::init()
{
  if(SDL_Init(SDL_INIT_VIDEO) < 0) {
    DebugPrint perror(gettext("Could not initialize") + std::string(" ") + 
		      SDL_GetError(), 
		      Print::DEBUGGING, DebugPrint::INFO, "SDL");
    std::exit(1);
  }

  atexit(SDL_Quit);

  if (conf->p_v_res() < 720 || conf->p_v_res() < 405) {
    DebugPrint perror(gettext("a resolution below 720x540 or 720x405 is not supported"), 
		      Print::DEBUGGING, DebugPrint::INFO, "SDL");
    std::exit(1);
  }
  
  screen = SDL_SetVideoMode(width, height, 32, SDL_SWSURFACE);

  if(!screen) {
    DebugPrint perror(gettext("Could not set video mode ") + 
		      conv::itos(width) + "x" + conv::itos(height) + 
		      "x32," + gettext("error") + " " + SDL_GetError(), 
		      Print::DEBUGGING, DebugPrint::INFO, "SDL");
    std::exit(1);
  }

  rgbsurface = SDL_CreateRGBSurface(SDL_SRCCOLORKEY, width, height, 32, 
				    0x00FF0000, 0x0000FF00, 0x000000FF, 0/*0xFF000000*/);

  // set the caption
  SDL_WM_SetCaption("mms", "mms");

  bpp = screen->format->BitsPerPixel;
  line_len = screen->format->BytesPerPixel * width;

  if(conf->p_fullscreen())
    toggle_fullscreen();
  
  initialized = 1; 
}

void SDLdev::lock()
{
  locked_mutex.leaveMutex();

  Render *render = Render::get_instance();
  render->complete_redraw();

  if(conf->p_fullscreen())
    toggle_fullscreen();
}

void SDLdev::unlock()
{
  if(conf->p_fullscreen())
    toggle_fullscreen();

  locked_mutex.enterMutex();
}

SDLdev::SDLdev() : Render_device(), width(conf->p_v_res()), height(conf->p_h_res()), 
		   initialized(-1), verbose(3), fullscreen(false), input_wants_event(false)
{}

void SDLdev::draw_rectangle(int x, int y, int w, int h)
{
  SDL_Rect rect;
  rect.x = x;
  rect.y = y;

  if (w > conf->p_v_res())
    rect.w = conf->p_v_res();
  else
    rect.w = w;

  if (h > conf->p_h_res())
    rect.h = conf->p_h_res();
  else
    rect.h = h;

#if 0
  std::ostringstream out;
  out << "blitting " << rect.x << ":" << rect.y << ":" << rect.w << ":" << rect.h;
  DebugPrint print(out.str(), Print::INFO, DebugPrint::ALL, "SDL out");
#endif

  SDL_BlitSurface(rgbsurface, &rect, screen, &rect);
  SDL_UpdateRect(screen, rect.x, rect.y, rect.w, rect.h);
}

void SDLdev::sdldev_draw_32()
{
  Render *render = Render::get_instance();

  rgbsurface->pixels = reinterpret_cast<UINT8 *>(imlib_image_get_data_for_reading_only());

  for (std::vector<Render::rect>::const_iterator i = render->draw_rects.begin(), 
	 end = render->draw_rects.end(); i != end; ++i)
    draw_rectangle(i->x, i->y, i->w, i->h);
}

void SDLdev::sdldev_draw() 
{
  locked_mutex.enterMutex();
  switch (bpp) {
  case 32 : sdldev_draw_32(); break;
    //  case 24 : sdldev_draw_24(dataptr); break;
    //  case 16 : sdldev_draw_16(dataptr); break;
  default : 
    { 
      DebugPrint perror("Color depth not supported",
			Print::INFO, DebugPrint::ALL, "SDL");
    }
  }
  locked_mutex.leaveMutex();
}

//----------------------------------------------------------------------
void SDLdev::run()
{
#ifdef benchmark_output
  Bench bench;
#endif

  while (true) {

    drawing = false;
    new_to_draw = false;

    output_done.signal();

    while (true) {
      if (!output_ready.wait(50)) {

	if (initialized == -1)
	  break;

	// draw timed out, check for input
	if (input_wants_event) {
	  if (getSDLkey()) {
	    get_input_event.signal();
	    get_input_event.reset();
	    input_wants_event = false;
	  }
	}
      } else
	break;
    }

    output_ready.reset();

    drawing = true;

#ifdef benchmark_output
    bench.start();
#endif

    sdldev_draw();

#ifdef benchmark_output
    bench.stop();
    bench.print("Rendering using SDL");
#endif
  }
}

void SDLdev::get_input()
{
  input_wants_event = true;
}

bool SDLdev::getSDLkey()
{
  SDL_KeyboardEvent event;  

  locked_mutex.enterMutex();
  if (SDL_PollEvent(reinterpret_cast<SDL_Event *>(&event)) ) {
    if (event.type == SDL_KEYDOWN) {
      set_key_string(event.keysym.sym);
      locked_mutex.leaveMutex();
      return true;
    } else if (event.type == SDL_KEYUP && event.keysym.sym == SDLK_UNKNOWN ) {
      char kbuf[10] = {0};
      snprintf(kbuf, sizeof(kbuf), "#%d", event.keysym.scancode);
      input_key = string_format::convert(kbuf);
      locked_mutex.leaveMutex();
      return true;
    }
  }
  locked_mutex.leaveMutex();
  return false;
}

void SDLdev::redraw()
{
  sdldev_draw();
}

void SDLdev::toggle_fullscreen()
{
  locked_mutex.enterMutex();

  SDL_LockSurface(screen);

  int opts = SDL_SWSURFACE;
  
  if (!fullscreen)
    opts |= SDL_FULLSCREEN;
  
  screen = SDL_SetVideoMode(width, height, 32,  opts);

  SDL_UnlockSurface(screen);

  locked_mutex.leaveMutex();

  if (fullscreen) {
    fullscreen = false;

    Render *render = Render::get_instance();
    render->prepare_new_image();
    render->clear();
    render->draw("clear");
    render->image_mut.leaveMutex();
    
  } else 
    fullscreen = true;

  locked_mutex.enterMutex();

  SDL_LockSurface(screen);

  SDL_ShowCursor((screen->flags & SDL_FULLSCREEN) > 0 ? SDL_DISABLE:SDL_ENABLE);  

  SDL_UnlockSurface(screen);

  locked_mutex.leaveMutex();
}

void SDLdev::set_key_string(SDLKey key)
{
  char kbuf[4] = {static_cast<char>(key), 0};

  switch(key) {
  case SDLK_UNKNOWN:   input_key =  "Unknown"; return;
  case SDLK_TAB:       input_key =  "TAB"; return;
  case SDLK_RETURN:    input_key =  "ENTER"; return;
  case SDLK_SPACE:     input_key =  "SPACE"; return;
  case SDLK_BACKSPACE: input_key =  "BACKSPACE"; return;
  case SDLK_DELETE:    input_key =  "DEL"; return;
  case SDLK_INSERT:    input_key =  "INS"; return;
  case SDLK_HOME:      input_key =  "HOME"; return;
  case SDLK_END:       input_key =  "END"; return;
  case SDLK_PAGEUP:    input_key =  "PGUP"; return;
  case SDLK_PAGEDOWN:  input_key =  "PGDOWN"; return;
  case SDLK_ESCAPE:    input_key =  "ESC"; return;
  case SDLK_RIGHT:     input_key =  "RIGHT"; return;
  case SDLK_LEFT:      input_key =  "LEFT"; return;
  case SDLK_UP:        input_key =  "UP"; return;
  case SDLK_DOWN:      input_key =  "DOWN"; return;
  case SDLK_RSHIFT:    input_key =  "RSHIFT"; return;
  case SDLK_LSHIFT:    input_key =  "LSHIFT"; return;
  case SDLK_RCTRL:     input_key =  "RCTRL"; return;
  case SDLK_LCTRL:     input_key =  "LCTRL"; return;
  case SDLK_RALT:      input_key =  "RALT"; return;
  case SDLK_LALT:      input_key =  "LALT"; return;
  case SDLK_KP0:       input_key =  "KP_0"; return;
  case SDLK_KP1:       input_key =  "KP_1"; return;
  case SDLK_KP2:       input_key =  "KP_2"; return;
  case SDLK_KP3:       input_key =  "KP_3"; return;
  case SDLK_KP4:       input_key =  "KP_4"; return;
  case SDLK_KP5:       input_key =  "KP_5"; return;
  case SDLK_KP6:       input_key =  "KP_6"; return;
  case SDLK_KP7:       input_key =  "KP_7"; return;
  case SDLK_KP8:       input_key =  "KP_8"; return;
  case SDLK_KP9:       input_key =  "KP_9"; return;
  case SDLK_KP_PERIOD: input_key =  "KP_PERIOD"; return;
  case SDLK_KP_DIVIDE: input_key =  "KP_DIVIDE"; return;
  case SDLK_KP_MULTIPLY: input_key =  "KP_MULTIPLY"; return;
  case SDLK_KP_MINUS:  input_key =  "KP_MINUS"; return;
  case SDLK_KP_PLUS:   input_key =  "KP_PLUS"; return;
  case SDLK_KP_ENTER:   input_key =  "KP_ENTER"; return;
  case SDLK_KP_EQUALS:   input_key =  "KP_EQUALS"; return;
  case SDLK_NUMLOCK:   input_key =  "NUMLOCK"; return;
  case SDLK_CAPSLOCK:   input_key =  "CAPSLOCK"; return;
  case SDLK_SCROLLOCK:   input_key =  "SCROLLOCK"; return;
  case SDLK_F1:
  case SDLK_F2:
  case SDLK_F3:
  case SDLK_F4:
  case SDLK_F5:
  case SDLK_F6:
  case SDLK_F7:
  case SDLK_F8:
  case SDLK_F9:
  case SDLK_F10:
  case SDLK_F11:
  case SDLK_F12:
  case SDLK_F13:
  case SDLK_F14:
  case SDLK_F15: snprintf(kbuf, sizeof(kbuf), "F%d", key - SDLK_F1 + 1);

  default:
    ;
  }
  input_key =  string_format::convert(kbuf);
}
