/******************************************************************************
 * vout_fb.c: Linux framebuffer video output display method
 * (c)1998 VideoLAN
 ******************************************************************************/

/******************************************************************************
 * Preamble
 ******************************************************************************/

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/uio.h>                                        /* for input.h */

#include "config.h"
#include "common.h"
#include "mtime.h"
#include "vlc_thread.h"

#include "input.h"
#include "video.h"
#include "video_output.h"
#include "video_sys.h"
#include "intf_msg.h"
#include "main.h"

/******************************************************************************
 * vout_sys_t: video output framebuffer method descriptor
 ******************************************************************************
 * This structure is part of the video output thread descriptor.
 * It describes the FB specific properties of an output thread.
 ******************************************************************************/
typedef struct vout_sys_s
{
    /* System informations */
    int                         i_fb_dev;        /* framebuffer device handle */
    struct fb_var_screeninfo    var_info;    /* framebuffer mode informations */

    /* Video memory */
    byte_t *                    p_video;                       /* base adress */    
    size_t                      i_page_size;                     /* page size */
} vout_sys_t;

/******************************************************************************
 * Local prototypes
 ******************************************************************************/
static int     FBOpenDisplay   ( vout_thread_t *p_vout );
static void    FBCloseDisplay  ( vout_thread_t *p_vout );

/******************************************************************************
 * vout_SysCreate: allocates FB video thread output method
 ******************************************************************************
 * This function allocates and initializes a FB vout method.
 ******************************************************************************/
int vout_SysCreate( vout_thread_t *p_vout, char *psz_display, int i_root_window )
{
    /* Allocate structure */
    p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
    if( p_vout->p_sys == NULL )
    {   
        intf_ErrMsg("error: %s\n", strerror(ENOMEM) );
        return( 1 );
    }

    /* Open and initialize device */
    if( FBOpenDisplay( p_vout ) )
    {
        intf_ErrMsg("vout error: can't open display\n");
        free( p_vout->p_sys );
        return( 1 );
    }

    return( 0 );
}

/******************************************************************************
 * vout_SysInit: initialize framebuffer video thread output method
 ******************************************************************************/
int vout_SysInit( vout_thread_t *p_vout )
{
    return( 0 );
}

/******************************************************************************
 * vout_SysEnd: terminate FB video thread output method
 ******************************************************************************/
void vout_SysEnd( vout_thread_t *p_vout )
{       
    ;    
}

/******************************************************************************
 * vout_SysDestroy: destroy FB video thread output method
 ******************************************************************************
 * Terminate an output method created by vout_FBCreateOutputMethod
 ******************************************************************************/
void vout_SysDestroy( vout_thread_t *p_vout )
{
    FBCloseDisplay( p_vout );
    free( p_vout->p_sys );
}

/******************************************************************************
 * vout_SysManage: handle FB events
 ******************************************************************************
 * This function should be called regularly by video output thread. It manages
 * console events. It returns a non null value on error.
 ******************************************************************************/
int vout_SysManage( vout_thread_t *p_vout )
{
    return 0;
}

/******************************************************************************
 * vout_SysDisplay: displays previously rendered output
 ******************************************************************************
 * This function send the currently rendered image to FB image, waits until
 * it is displayed and switch the two rendering buffers, preparing next frame.
 ******************************************************************************/
void vout_SysDisplay( vout_thread_t *p_vout )
{
    /* tout est bien affich�, on peut �changer les 2 �crans */
    p_vout->p_sys->var_info.xoffset = 0;
    p_vout->p_sys->var_info.yoffset =
        0;
        //p_vout->p_sys->i_buffer_index ? 0 : p_vout->p_sys->var_info.yres;

    //ioctl( p_vout->p_sys->i_fb_dev, FBIOPUT_VSCREENINFO, &p_vout->p_sys->var_info );	
    ioctl( p_vout->p_sys->i_fb_dev, FBIOPAN_DISPLAY, &p_vout->p_sys->var_info );	
}

/* following functions are local */

/******************************************************************************
 * FBOpenDisplay: open and initialize framebuffer device 
 ******************************************************************************
 * ?? The framebuffer mode is only provided as a fast and efficient way to
 * display video, providing the card is configured and the mode ok. It is
 * not portable, and is not supposed to work with many cards. Use at your
 * own risk !
 ******************************************************************************/

static int FBOpenDisplay( vout_thread_t *p_vout )
{
    char *psz_device;                               /* framebuffer device path */
    struct fb_fix_screeninfo    fix_info;       /* framebuffer fix information */

    /* Open framebuffer device */
    psz_device = main_GetPszVariable( VOUT_FB_DEV_VAR, VOUT_FB_DEV_DEFAULT );
    p_vout->p_sys->i_fb_dev = open( psz_device, O_RDWR);
    if( p_vout->p_sys->i_fb_dev == -1 )
    {
        intf_ErrMsg("vout error: can't open %s (%s)\n", psz_device, strerror(errno) );
        return( 1 );
    }

    // ?? here would be the code used to save the current mode and 
    // ?? change to the most appropriate mode...

    /* Get framebuffer device informations */
    if( ioctl( p_vout->p_sys->i_fb_dev, FBIOGET_VSCREENINFO, &p_vout->p_sys->var_info ) )
    {
        intf_ErrMsg("vout error: can't get framebuffer informations (%s)\n", strerror(errno) );
        close( p_vout->p_sys->i_fb_dev );
        return( 1 );
    }

    /* Framebuffer must have some basic properties to be usable */
    //??

    /* Set some attributes */
    p_vout->p_sys->var_info.activate = FB_ACTIVATE_NXTOPEN;
    p_vout->p_sys->var_info.xoffset =  0;
    p_vout->p_sys->var_info.yoffset =  0;
    fprintf(stderr, "ypanstep is %i\n", fix_info.ypanstep);
    //??ask sam p_vout->p_sys->mode_info.sync = FB_SYNC_VERT_HIGH_ACT;
    //???
    if( ioctl( p_vout->p_sys->i_fb_dev, FBIOPUT_VSCREENINFO, &p_vout->p_sys->var_info ) )
    {
        intf_ErrMsg("vout error: can't set framebuffer informations (%s)\n", strerror(errno) );
        close( p_vout->p_sys->i_fb_dev );
        return( 1 );
    }
    
    /* Get some informations again, in the definitive configuration */
    if( ioctl( p_vout->p_sys->i_fb_dev, FBIOGET_FSCREENINFO, &fix_info ) ||
        ioctl( p_vout->p_sys->i_fb_dev, FBIOGET_VSCREENINFO, &p_vout->p_sys->var_info ) )
    {
        intf_ErrMsg("vout error: can't get framebuffer informations (%s)\n", strerror(errno) );
        // ?? restore fb config
        close( p_vout->p_sys->i_fb_dev );
        return( 1 );
    }

    p_vout->i_width =                   p_vout->p_sys->var_info.xres;
    p_vout->i_height =                  p_vout->p_sys->var_info.yres;
    p_vout->i_bytes_per_line =          p_vout->i_width * 2;
    p_vout->i_screen_depth =            p_vout->p_sys->var_info.bits_per_pixel;
    switch( p_vout->i_screen_depth )
    {
    case 15:                        /* 15 bpp (16bpp with a missing green bit) */
    case 16:                                          /* 16 bpp (65536 colors) */
        p_vout->i_bytes_per_pixel = 2;
        break;

    case 24:                                    /* 24 bpp (millions of colors) */
        p_vout->i_bytes_per_pixel = 3;
        break;

    case 32:                                    /* 32 bpp (millions of colors) */
        p_vout->i_bytes_per_pixel = 4;
        break;

    default:                                       /* unsupported screen depth */
        intf_ErrMsg("vout error: screen depth %d is not supported\n",
		                     p_vout->i_screen_depth);
        return( 1  );
        break;
    }
    p_vout->p_sys->i_page_size = p_vout->p_sys->var_info.xres *
                p_vout->p_sys->var_info.yres * p_vout->i_bytes_per_pixel;

    /* Map two framebuffers a the very beginning of the fb */
    p_vout->p_sys->p_video = mmap(0, p_vout->p_sys->i_page_size * 2,
                                  PROT_READ | PROT_WRITE, MAP_SHARED,
                                  p_vout->p_sys->i_fb_dev, 0 );
    if( (int)p_vout->p_sys->p_video == -1 ) //?? according to man, it is -1. What about NULL ?
    {
        intf_ErrMsg("vout error: can't map video memory (%s)\n", strerror(errno) );
        // ?? restore fb config
        close( p_vout->p_sys->i_fb_dev );
        return( 1 );
    }

    /* Set and initialize buffers */
    vout_SetBuffers( p_vout, p_vout->p_sys->p_video, 
                     p_vout->p_sys->p_video + p_vout->p_sys->i_page_size );
    intf_DbgMsg("framebuffer type=%d, visual=%d, ypanstep=%d, ywrap=%d, accel=%d\n",
                fix_info.type, fix_info.visual, fix_info.ypanstep, fix_info.ywrapstep, fix_info.accel );
    return( 0 );
}    

/******************************************************************************
 * FBCloseDisplay: close and reset framebuffer device 
 ******************************************************************************
 * Returns all resources allocated by FBOpenDisplay and restore the original
 * state of the device.
 ******************************************************************************/
static void FBCloseDisplay( vout_thread_t *p_vout )
{
    // Destroy window and close display
    close( p_vout->p_sys->i_fb_dev );    
}