/**
 *  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.3 2007/11/10 09:14:27 root 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 <fbrenderer.h>

#include <ffmpeg/avcodec.h>
//#ifdef PLAN_FFMPEG_INC
//#  include <avcodec.h>
//#else
//#  include <libavutil/avcodec.h>
//#endif

#include <common.h>
#include <setup.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 // PVRFB

extern void *(* fast_memcpy)(void *to, const void *from, size_t len);
static unsigned char* frame_buffer;
static struct fb_var_screeninfo fb_vinfo;

//***************************************************************************
// Object
//***************************************************************************

FbRenderer::FbRenderer(int x, int y, int width, int height, 
                       string cfgPath, int utf, string thmPath)
   : ImlibRenderer(x, y, width, height, cfgPath, utf, thmPath)
{
	_showImage = no;
   fb_dev_name = 0;
   _initialized = -1;
#ifdef HAVE_SOFTMPEG
   decoder = 0;
#endif
}

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

//***************************************************************************
// 
//***************************************************************************

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))
      tell(4 , "fbdevout.c: Can't reset original fb_var_screeninfo: %s", strerror(errno));
		
   ::close(fb_dev_fd);

   if (frame_buffer) 
      munmap(frame_buffer, fb_size);

   _initialized = -1;
}

//***************************************************************************
// 
//***************************************************************************

int 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 = (char*)"/dev/fb0";

   tell(4 , "fbdevout.c: using %s", fb_dev_name);

	if ((fb_dev_fd = open(fb_dev_name, O_RDWR)) == -1) 
   {
		tell(0, "fbdevout.c: Can't open %s: %s", fb_dev_name, strerror(errno));
		return fail;
	}

	// read VScreen info from fb

   if (ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo)) 
   {
		tell(0, "fbdevout.c: Can't get VSCREENINFO: %s", strerror(errno));
		return fail;
	}

	// 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;

   // fb_vinfo.bits_per_pixel = 32;

	// write VScreen info

	if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_vinfo)) 
		tell(0, "fbdevout.c: Can't put VSCREENINFO: %s", 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)) 
   {
		tell(0, "fbdevout.c: Can't get VSCREENINFO: %s", strerror(errno));
      return fail;
	}

	// read VScreen info from fb

	if (ioctl(fb_dev_fd, FBIOGET_FSCREENINFO, &fb_finfo)) 
   {
		tell(0, "fbdevout.c: Can't get FSCREENINFO: %s", strerror(errno));
      return fail;
	}

   tell(0, "fb settings are (%d/%d) with a color depth of (%d)", 
        fb_vinfo.xres, fb_vinfo.yres, fb_vinfo.bits_per_pixel);

   dspWidth = fb_vinfo.xres;
   dspHeight = fb_vinfo.yres;

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

	switch (fb_vinfo.bits_per_pixel) 
   {
		case 32: tell(4, "fbdevout.c: using 32 bit depth"); fb_type = PIX_FMT_RGBA32; break;
		case 24: tell(4, "fbdevout.c: using 24 bit depth"); fb_type = PIX_FMT_RGB24;  break;
		case 16: tell(4, "fbdevout.c: using 16 bit depth"); fb_type = PIX_FMT_RGB565; break;
		default: tell(4, "fbdevout.c: color depth not supported -> %i bits per pixel",
                 fb_vinfo.bits_per_pixel);
	}

#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 = fb_vinfo.yres;
			osdcoords.offset = 0;
			osdcoords.pixel_stride = fb_vinfo.xres * 2;
			
			ioctl(fb_dev_fd, IVTVFB_IOCTL_SET_ACTIVE_BUFFER, &osdcoords);
			
		}
      else 
      {
         return fail;
      }
   }
   else
   {
      return fail;
   }

#else

   if ((frame_buffer = (unsigned char*)mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_dev_fd, 0)) == (unsigned char*)-1)
   {
		tell(0, "fbdevout.c: Can't mmap %s: %s", fb_dev_name, strerror(errno));
		tell(0, "fbdevout.c: Maybe you try to use PVR350 Framebuffer, compile with PVRFB=1");
		return fail;
	}

#endif

   //	_resized = imlib_create_image(fb_vinfo.xres, fb_vinfo.yres);
	_initialized = 1;

   ImlibRenderer::init(devnum);

   return success;
}

//***************************************************************************
// 
//***************************************************************************

#ifndef use_asm

void FbRenderer::fbdev_draw_32(unsigned char* frame, int force)
{
	fast_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, int force)
{
   fbdev32(frame);
}

#endif

//***************************************************************************
// 
//***************************************************************************

#ifndef use_asm

void FbRenderer::fbdev_draw_24(unsigned char* frame, int force)
{
  unsigned 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;
  }
}

#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, int force)
{
  fbdev24(frame);
}

#endif

//***************************************************************************
// fbdev draw 16
//***************************************************************************

void FbRenderer::fbdev_draw_16(unsigned char* frame, int force)
{
   static unsigned short* tmp = 0;
   static unsigned int size = fb_vinfo.yres * fb_vinfo.xres;
   static unsigned short* fb = (unsigned short*)frame_buffer;
   
   if (!tmp)
   {
      // for init, called only once

      tmp = new unsigned short[size];
      memset(tmp, 0, size * sizeof(unsigned short));
   }

   LogDuration ld("FbRenderer::fbdev_draw_16()");

   unsigned char B, G, R;
   unsigned int x, y;
   unsigned short v;
   unsigned int out_offset = 0, in_offset = 0;

   for (y = 0; y < fb_vinfo.yres; y++) 
   {
      for (x = 0; x < fb_vinfo.xres; x++, out_offset++, in_offset+=4) 
      {
         R = (frame[in_offset + 2] >> 3) & 0x1f;
         G = (frame[in_offset + 1] >> 2) & 0x3f;
         B = (frame[in_offset + 0] >> 3) & 0x1f;

         v = ((G << 5) | B)  |  ((R << 3) | (G >> 3)) << 8;

         if (force || tmp[out_offset] != v)
         {
            tmp[out_offset] = v;
            fb[out_offset] = v;
         }
      }
   }
}

//***************************************************************************
// Refresh
//***************************************************************************

void FbRenderer::refresh(int force)
{
   LogDuration ld("FbRenderer::refresh()");

   // refresh

   ImlibRenderer::refresh(force);

   // copy to buffer

   imlib_context_set_image(*pImageToDisplay);

   if (GraphTFTSetup.flipOSD)
   {
      imlib_image_flip_vertical();
      imlib_image_flip_horizontal();
   }

   UINT8* dataptr = (UINT8*)imlib_image_get_data_for_reading_only();
  
   tell(4, "copy image with a depth of (%d) to framebuffer", 
     fb_vinfo.bits_per_pixel);

	if (_showImage)
   {
		switch (fb_vinfo.bits_per_pixel)
      {
			case 16 : fbdev_draw_16(dataptr, force); break;
			case 24 : fbdev_draw_24(dataptr, force); break;
			case 32 : fbdev_draw_32(dataptr, force); break;

			default : tell(0, "fbdevout.c: color depth not supported "
                        "-> %i bits per pixel", fb_vinfo.bits_per_pixel);
		}

	}

#ifdef PVRFB	

	struct ivtvfb_ioctl_dma_host_to_ivtv_args prep;
	prep.source = frame_buffer;
	prep.dest_offset = 0;
	prep.count = width * height * 4;
	ioctl(fb_dev_fd, IVTVFB_IOCTL_PREP_FRAME, &prep);

#endif
}

void FbRenderer::clear()
{
	ImlibRenderer::clear();
}

//---------------------------------------------------------------------------------

void FbRenderer::setPlayMode(bool Video)
{
   _showImage = !Video;

#ifdef HAVE_SOFTMPEG
   if (Video)
	{
		tell(4 , "fbdevout.c: new decoder");
		decoder = new cMpeg2Decoder(frame_buffer, fb_type, fb_vinfo.xres, fb_vinfo.yres);
		tell(4 , "fbdevout.c: start decoder");
		decoder->Start();
	}
   else
	{
		if (decoder)
      {
			tell(4 , "fbdevout.c: stop and delete decoder");
			decoder->Stop();
			delete(decoder);
		}
	}
#endif
}

int FbRenderer::playVideo(const uchar *Data, int Length)
{
#ifdef HAVE_SOFTMPEG
	int result = decoder->Decode(Data, Length);

	// restart the decoder

	if (result == -1) 
   {
		delete decoder;
		decoder = new cMpeg2Decoder(frame_buffer, fb_type, fb_vinfo.xres, fb_vinfo.yres);
		return 0;
	}

	return Length;
#else
   return 0;
#endif
}

bool FbRenderer::devicePoll(cPoller &Poller, int TimeoutMs)
{
	return true;      	// For now lets say we always ready
}

void FbRenderer::deviceClear(void)
{
	// Fill Framebuffer with zero

	memset(frame_buffer, 0, 4*fb_vinfo.yres*fb_vinfo.xres);
}
