/*

        framebuffer support for MMS

	This code uses some parts which were copy-pasted from vo_fbdev.c 
	from the wonderfull linux video player, MPlayer 0.90-rc4

	All these changes and adaptations are (C) 2003 Luis Correia
	c++'ified by Anders Rune Jensen
	removed assembler as it was unmaintained and broken.

	This code is GPLv2

*/

#include "fbdevout.hpp"

#include "config.h"

#include "renderer.hpp"

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

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

#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>

#include <string>
#include <iostream>

static unsigned char *frame_buffer;
static struct fb_var_screeninfo fb_vinfo;

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

  fb_orig_vinfo.xoffset = fb_vinfo.xoffset;
  fb_orig_vinfo.yoffset = fb_vinfo.yoffset;

  if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_orig_vinfo))
    DebugPrint perror(std::string("Can't reset original fb_var_screeninfo ") + 
		      strerror(errno),
		      Print::INFO, DebugPrint::ALL, "FBDEV");
  
  ::close(fb_dev_fd);
  if(frame_buffer) 
    munmap(frame_buffer, fb_size);
  initialized = -1;
}

//----------------------------------------------------------------------
void Fbdev::init()
{
  // open framebuffer
  if (!fb_dev_name && !(fb_dev_name = getenv("FRAMEBUFFER"))) 
    fb_dev_name = "/dev/fb0";

  DebugPrint perror(std::string("Using ") + fb_dev_name,
		    Print::INFO, DebugPrint::ALL, "FBDEV");

  if ((fb_dev_fd = open(fb_dev_name, O_RDWR)) == -1) {
    DebugPrint perror(gettext("Could not open device"),
		      Print::DEBUGGING, DebugPrint::INFO, "FBDEV");
    std::exit(1);
  }

  // read VScreen info from fb
  if (ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo)) {
    DebugPrint perror(std::string("Can't get VSCREENINFO ") + strerror(errno),
		      Print::INFO, DebugPrint::ALL, "FBDEV");
    std::exit(1);
  }

  // Save VScreen info and try to set virtual image
  fb_orig_vinfo = fb_vinfo;
  fb_vinfo.xres_virtual = fb_vinfo.xres;
  fb_vinfo.yres_virtual = fb_vinfo.yres;

  fb_vinfo.xoffset = 0;
  fb_vinfo.yoffset = 0;

  // write VScreen info
  if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_vinfo)) 
    DebugPrint perror(std::string("Can't put VSCREENINFO ") + strerror(errno),
		      Print::INFO, DebugPrint::ALL, "FBDEV");

  // read VScreen info from fb (again) this will be the 'accepted' resolutions from the fb
  if (ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo)) {
    DebugPrint perror(std::string("Can't get VSCREENINFO ") + strerror(errno),
		      Print::INFO, DebugPrint::ALL, "FBDEV");
    std::exit(1);
  }

  // read VScreen info from fb
  if (ioctl(fb_dev_fd, FBIOGET_FSCREENINFO, &fb_finfo)) {
    DebugPrint perror(std::string("Can't get FSCREENINFO ") + strerror(errno),
		      Print::INFO, DebugPrint::ALL, "FBDEV");
    std::exit(1);
  }

  y_offset = fb_vinfo.yoffset;
  fb_line_len = fb_finfo.line_length;
  fb_size = fb_finfo.smem_len;
  frame_buffer = NULL;

  fb_depth = fb_vinfo.bits_per_pixel;
  switch (fb_depth) {
  case 32 : 
    {
      DebugPrint perror("Using 32 bit depth",
			Print::INFO, DebugPrint::ALL, "FBDEV");
    }
    break;
  case 24 :
    {
      DebugPrint perror("Using 24 bit depth",
			Print::INFO, DebugPrint::ALL, "FBDEV");
    }
    break;
  case 16 : 
    {
      DebugPrint perror("Using 16 bit depth",
			Print::INFO, DebugPrint::ALL, "FBDEV");
    }
    break;
  default : 
    {
      DebugPrint perror("Color depth not supported",
			Print::INFO, DebugPrint::ALL, "FBDEV");
    }
  }

  if ((frame_buffer = 
       reinterpret_cast<unsigned char *>(mmap(0, fb_size, 
					      PROT_READ | PROT_WRITE,
					      MAP_SHARED, fb_dev_fd, 0))) 
      == reinterpret_cast<unsigned char *>(-1)) {
    DebugPrint perror(gettext("mmap failed"),
		      Print::DEBUGGING, DebugPrint::INFO, "FBDEV");
    std::exit(1);
  }

  initialized = 1; 

  resized = imlib_create_image(fb_vinfo.xres,fb_vinfo.yres);
}

//----------------------------------------------------------------------
Fbdev::Fbdev() : Render_device(), fb_dev_name(0), initialized(-1), verbose(3)
{
}

//----------------------------------------------------------------------
void Fbdev::fbdev_draw_32(unsigned char * frame)
{
	memcpy(frame_buffer, frame, 4*fb_vinfo.yres*fb_vinfo.xres);
}

//----------------------------------------------------------------------
void Fbdev::fbdev_draw_24(unsigned char * frame)
{
  int i,a,b,c,x, out_offset = 0, in_offset = 0;

  x = fb_vinfo.xres*4;

  for (i = 0; i < fb_vinfo.yres; ++i) {
    for (a=0,b=0,c=0; a < fb_vinfo.xres; ++a,b+=3,c+=4) 
      {
	frame_buffer [out_offset + b +0] = frame [in_offset + c +0] ;
	frame_buffer [out_offset + b +1] = frame [in_offset + c +1] ;
	frame_buffer [out_offset + b +2] = frame [in_offset + c +2] ;
      }
    out_offset += fb_line_len;
    in_offset  += x;
  }
}

//----------------------------------------------------------------------
void Fbdev::fbdev_draw_16(unsigned char * frame)
{
  int i,a,b,c,x, out_offset = 0, in_offset = 0;
  unsigned int B = 0, G = 0, R = 0;

  x = fb_vinfo.xres*4;

  for (i = 0; i < fb_vinfo.yres; ++i) {
    for (a=0,b=0,c=0; a < fb_vinfo.xres; ++a,b+=2,c+=4) 
      {
	B = (frame [in_offset + c +0] >> 3) & 0x1f;
	G = (frame [in_offset + c +1] >> 3) & 0x1f;
	R = (frame [in_offset + c +2] >> 3) & 0x1f;

	frame_buffer [out_offset + b +0] = (G << 5) | ( B );
	frame_buffer [out_offset + b +1] = ((R << 2) | (G >> 3) ) & 0xfb;
      }
    out_offset += fb_line_len;
    in_offset  += x;
  }
}

void Fbdev::fbdev_draw(UINT8 *dataptr) 
{
  fb_depth = fb_vinfo.bits_per_pixel;
  switch (fb_depth) {
  case 32 : fbdev_draw_32(dataptr); break;
  case 24 : fbdev_draw_24(dataptr); break;
  case 16 : fbdev_draw_16(dataptr); break;
  default : 
    DebugPrint perror("Color depth not supported",
		      Print::INFO, DebugPrint::ALL, "FBDEV");
  }
}

//----------------------------------------------------------------------
void Fbdev::run()
{
  Render *render = Render::get_instance();

#ifdef benchmark_output
    Bench bench;
    Bench bench2;
#endif

  while (true) {

    drawing = false;
    new_to_draw = false;

    output_done.signal();
    output_ready.wait();
    output_ready.reset();
    
    drawing = true;

#ifdef benchmark_output
    bench.start();
    bench2.start();
#endif

    imlib_context_set_image(resized);
    imlib_free_image();
    imlib_context_set_image(render->current.image_data);
    resized = imlib_create_cropped_scaled_image(0,0, conf->p_v_res(),conf->p_h_res(),
						fb_vinfo.xres,fb_vinfo.yres);

#ifdef benchmark_output
    bench2.stop();
    bench2.print("scaled image for fbdev");
    bench2.start();
#endif

    imlib_context_set_image(resized);
    UINT8 *dataptr = reinterpret_cast<UINT8 *>(imlib_image_get_data_for_reading_only());     
  
    fbdev_draw(dataptr);

#ifdef benchmark_output
    bench2.stop();
    bench2.print("send data to the fbdev");

    bench.stop();
    bench.print("fbdev complete draw");
#endif
  }
}

void Fbdev::redraw()
{
  // the generated image is scaled to the fb current resolution ;)
  UINT8 *dataptr = reinterpret_cast<UINT8 *>(imlib_image_get_data());     

  imlib_context_set_image(resized);
  fbdev_draw(dataptr);
  imlib_image_put_back_data(reinterpret_cast<DATA32 *>(dataptr)); 
}
