/**
 *  GraphTFT plugin for the Video Disk Recorder 
 * 
 *  fbrenderer.c - A plugin for the Video Disk Recorder
 *
 *  (c) 2004 Lars Tegeler, Sascha Volkenandt  
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * $Id: fbrenderer.c,v 1.5 2004/05/25 08:41:15 tegeler Exp $
 *
 **/

//The most part of this Code is from the MMS V.2 Project:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#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/user.h>
#include <sys/kd.h>
#include <sys/types.h>
#include <stdlib.h>

#include "fbout.h"

#ifdef PVRFB
#define IVTVFB_IOCTL_GET_ACTIVE_BUFFER  _IOR('@', 5, struct ivtv_osd_coords)
#define IVTVFB_IOCTL_SET_ACTIVE_BUFFER  _IOW('@', 6, struct ivtv_osd_coords)
#define IVTVFB_IOCTL_PREP_FRAME         _IOW('@', 3, struct ivtvfb_ioctl_dma_host_to_ivtv_args)
#define IVTVFB_IOCTL_GET_STATE          _IOR('@', 1, struct ivtvfb_ioctl_state_info)
#define IVTVFB_IOCTL_SET_STATE          _IOW('@', 2, struct ivtvfb_ioctl_state_info)
#define IVTVFB_STATUS_ENABLED           (1 << 0)
#define IVTVFB_STATUS_GLOBAL_ALPHA      (1 << 1)
#define IVTVFB_STATUS_LOCAL_ALPHA       (1 << 2)

struct ivtvfb_ioctl_state_info {
	unsigned long status;
	unsigned long alpha;
};

struct ivtv_osd_coords {
	unsigned long offset;
	unsigned long max_offset;
	int pixel_stride;
	int lines;
	int x;
	int y;
};

struct ivtvfb_ioctl_dma_host_to_ivtv_args {
	 void* source;
	 unsigned long dest_offset;
	 int count;
};
#endif

static unsigned char *frame_buffer;
static struct fb_var_screeninfo fb_vinfo;

//----------------------------------------------------------------------
void FbRenderer::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))
    printf("fbdevout.c: Can't reset original fb_var_screeninfo: %s\n", strerror(errno));
		
  ::close(fb_dev_fd);
  if(frame_buffer) 
    munmap(frame_buffer, fb_size);
  _initialized = -1;
}

//----------------------------------------------------------------------
void FbRenderer::init(int devnum)
{
	asprintf(&fb_dev_name,"/dev/fb%d", devnum);
	// open framebuffer
	if (!fb_dev_name && !(fb_dev_name = getenv("FRAMEBUFFER"))) fb_dev_name = "/dev/fb0";
		printf("fbdevout.c: using %s\n", fb_dev_name);
	if ((fb_dev_fd = open(fb_dev_name, O_RDWR)) == -1) {
		printf("fbdevout.c: Can't open %s: %s\n", fb_dev_name, strerror(errno));
		std::exit(1);
	}

	// read VScreen info from fb
	 if (ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo)) {
		printf("fbdevout.c: Can't get VSCREENINFO: %s\n", strerror(errno));
		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)) 
		printf("fbdevout.c: Can't put VSCREENINFO: %s\n", strerror(errno));

	// read VScreen info from fb (again) this will be the 'accepted' resolutions from the fb
	if (ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo)) {
		printf("fbdevout.c: Can't get VSCREENINFO: %s\n", strerror(errno));
		std::exit(1);
	}

	// read VScreen info from fb
	if (ioctl(fb_dev_fd, FBIOGET_FSCREENINFO, &fb_finfo)) {
		printf("fbdevout.c: Can't get FSCREENINFO: %s\n", strerror(errno));
		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 : printf("fbdevout.c: using 32 bit depth\n"); break;
		case 24 : printf("fbdevout.c: using 24 bit depth\n"); break;
		case 16 : printf("fbdevout.c: using 16 bit depth\n"); break; 
		default : printf("fbdevout.c: color depth not supported -> %i bits per pixel\n",fb_depth);
	}

#ifdef PVRFB
	struct ivtvfb_ioctl_state_info fbstate;
	struct ivtv_osd_coords osdcoords;
	struct ivtvfb_ioctl_dma_host_to_ivtv_args prep;

	unsigned char *osdbuf_aligned;
        if (!ioctl(fb_dev_fd, IVTVFB_IOCTL_GET_STATE, &fbstate)) {
		fbstate.status &= ~IVTVFB_STATUS_GLOBAL_ALPHA;
		fbstate.status |= IVTVFB_STATUS_LOCAL_ALPHA;
		fbstate.alpha = 0;
	
		if (!ioctl(fb_dev_fd, IVTVFB_IOCTL_SET_STATE, &fbstate)) {
	
        		frame_buffer = new unsigned char[fb_size + PAGE_SIZE];
			osdbuf_aligned = (unsigned char *)((int)frame_buffer + (PAGE_SIZE - 1));
			osdbuf_aligned = (unsigned char *)((int)osdbuf_aligned & PAGE_MASK);
			
                  	memset(osdbuf_aligned, 0x00, fb_size);
		        
			ioctl(fb_dev_fd, IVTVFB_IOCTL_GET_ACTIVE_BUFFER, &osdcoords);
			
			prep.source = osdbuf_aligned;
			prep.dest_offset = 0;
			prep.count = osdcoords.max_offset;
			memset(osdbuf_aligned, 0x00, fb_size);
			
			ioctl(fb_dev_fd, IVTVFB_IOCTL_PREP_FRAME, &prep);
			
			osdcoords.lines = 576;
			osdcoords.offset = 0;
			osdcoords.pixel_stride = 720 * 2;
			
			ioctl(fb_dev_fd, IVTVFB_IOCTL_SET_ACTIVE_BUFFER, &osdcoords);
			
		}else std::exit(1);
	}else std::exit(1);
#else
        if ((frame_buffer = (unsigned char *) mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_dev_fd, 0)) == (unsigned char *) -1) {
		printf("fbdevout.c: Can't mmap %s: %s\n", fb_dev_name, strerror(errno));
		printf("fbdevout.c: Maybe you try to use PVR350 Framebuffer, compile with PVRFB=1\n");
		std::exit(1);
	}
#endif

	_initialized = 1;
}

//----------------------------------------------------------------------
FbRenderer::FbRenderer()
{
	_initialized = -1;
	fb_dev_name = NULL;
}

FbRenderer::~FbRenderer()
{
	deinit();
}


//----------------------------------------------------------------------
#ifndef use_asm
void FbRenderer::fbdev_draw_32(unsigned char * frame)
{
	memcpy(frame_buffer, frame, 4*fb_vinfo.yres*fb_vinfo.xres);
}
#else

/* FIXME evil hack */

static void fbdev32(unsigned char * frame)
{
  __asm__ __volatile__(

	"		pushl	%%esi				\n\t"
	"		pushl	%%edi				\n\t"
	"		pushl	%%eax				\n\t"
	"		pushl	%%ecx				\n\t"

	"		movl	fb_vinfo,%%eax		        \n\t"	// Height
	"		movl	fb_vinfo+4,%%ecx	        \n\t"	// width
	"		imul	%%eax,%%ecx			\n\t"	// mul
	"		movl	frame_buffer,%%edi	        \n\t"	// fbdev mmap'd buffer 
	"		movl	8(%%ebp), %%esi		        \n\t"	// Imlib2 buffer (frame)
	"		rep	movsl				\n\t"	// move all longs at a time (4 bytes)

	"		popl	%%ecx				\n\t"
	"		popl	%%eax				\n\t"
	"		popl	%%edi				\n\t"
	"		popl	%%esi				\n\t"

	:/*no output*/:/*no input*/:"memory","cc");
}

void FbRenderer::fbdev_draw_32(unsigned char * frame)
{
  fbdev32(frame);
}
#endif

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

  x = static_cast<int>(fb_vinfo.xres*4);

  for (i = 0; i < static_cast<int>(fb_vinfo.yres); ++i) {
    for (a=0,b=0,c=0; a < static_cast<int>(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;
  }
}
#else

/* FIXME evil hack */

static void fbdev24(unsigned char * frame)
{
  __asm__ __volatile__(

	"		pushl	%%esi				\n\t"
	"		pushl	%%edi				\n\t"
	"		pushl	%%eax				\n\t"
	"		pushl	%%ebx				\n\t"
	"		pushl	%%ecx				\n\t"
	"		pushl	%%edx				\n\t"

	"		movl	fb_vinfo,%%eax	                \n\t"	// fbdev mmap'd buffer 
	"		movl	fb_vinfo+4,%%edx	        \n\t"	// fbdev mmap'd buffer 
	"		imul	%%eax,%%edx			\n\t"	// fbdev mmap'd buffer 
	"		movl	8(%%ebp), %%esi		        \n\t"	// Imlib2 buffer (frame)
	"		movl	frame_buffer,%%edi    	        \n\t"	// fbdev mmap'd buffer 

	"	.lop:	                                        \n\t"
        "               leal	3,%%ecx				\n\t"	// fbdev mmap'd buffer 
	"		rep		movsb			\n\t"	// move 3 bytes at a time 
	"		inc		%%esi			\n\t"	// increment one byte, bypass Alpha
	"		dec		%%edx			\n\t"	// dec counter 
	"		jnz .lop                                \n\t"	// loop :) 

	"		popl	%%edx				\n\t"
	"		popl	%%ecx				\n\t"
	"		popl	%%ebx				\n\t"
	"		popl	%%eax				\n\t"
	"		popl	%%edi				\n\t"
	"		popl	%%esi				\n\t"

	:/*no output*/:/*no input*/:"memory","cc");
}

void FbRenderer::fbdev_draw_24(unsigned char * frame)
{
  fbdev24(frame);
}
#endif

//----------------------------------------------------------------------
void FbRenderer::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 = static_cast<int>(fb_vinfo.xres*4);

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

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

void FbRenderer::refresh()
{

    Imlib_Image _resized = imlib_create_cropped_scaled_image(0,0, 720,576,fb_vinfo.xres,fb_vinfo.yres);
    imlib_context_set_image(_resized);
    unsigned char *dataptr = (unsigned char *) imlib_image_get_data_for_reading_only();     
  
	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 : printf("fbdevout.c: color depth not supported -> %i bits per pixel\n",fb_depth);
	}
#ifdef PVRFB	
	struct ivtvfb_ioctl_dma_host_to_ivtv_args prep;
	prep.source = frame_buffer;
	prep.dest_offset = 0;
	prep.count = 720 * 576 * 4;
	ioctl(fb_dev_fd, IVTVFB_IOCTL_PREP_FRAME, &prep);
#endif
	imlib_free_image();
}
