#include <vlc/vlc.h>
#include <vlc/decoder.h>
#include <vlc/input.h>

#include <stdio.h>
#include <stdlib.h>


#ifdef HAVE_DLFCN_H
    #include <dlfcn.h>
#endif

static int Open( vlc_object_t *);
static picture_t *DecodeVideo( decoder_t *p_dec, block_t **pp_blocks);

vlc_module_begin();
    set_shortname( "Twolame");
    set_description( _("Libtwolame audio encoder") );
    set_capability( "decoder", 50 );
    set_callbacks( Open, NULL );
    set_category( CAT_INPUT );
    set_subcategory( SUBCAT_INPUT_VCODEC );
vlc_module_end();

struct decoder_sys_t
{
    vlc_bool_t b_loaded;
    void *p_context;
};


/*
 * Structures for data packets.  These used to be tables of unsigned ints, but
 * that does not work on 64 bit platforms (e.g. Alpha).  The entries that are
 * pointers get truncated.  Pointers on 64 bit platforms are 8 byte longs.
 * So we have to use structures so the compiler will assign the proper space
 * for the pointer.
 */
typedef struct cmsg_data_s {
        uint32_t data1;
        uint32_t data2;
        uint32_t* dimensions;
} cmsg_data_t;

typedef struct transform_in_s {
        uint32_t len;
        uint32_t unknown1;
        uint32_t chunks;
        uint32_t* extra;
        uint32_t unknown2;
        uint32_t timestamp;
} transform_in_t;

// Definition of the exported functions
static unsigned long (*pf_rvyuv_custom_message)(cmsg_data_t* ,void*);
static unsigned long (*pf_rvyuv_free)(void*);
static unsigned long (*pf_rvyuv_hive_message)(unsigned long,unsigned long);
static unsigned long (*pf_rvyuv_init)(void*, void*); // initdata,context
static unsigned long (*pf_rvyuv_transform)(char*, char*,transform_in_t*,unsigned int*,void*);

#if 0
#ifdef USE_WIN32DLL
static unsigned long WINAPI (*wrvyuv_custom_message)(cmsg_data_t* ,void*);
static unsigned long WINAPI (*wrvyuv_free)(void*);
static unsigned long WINAPI (*wrvyuv_hive_message)(unsigned long,unsigned long);
static unsigned long WINAPI (*wrvyuv_init)(void*, void*); // initdata,context
static unsigned long WINAPI (*wrvyuv_transform)(char*, char*,transform_in_t*,unsigned int*,void*);
#endif
#endif

static void *p_rv_handle=NULL;
static int inited=0;
#ifdef USE_WIN32DLL
static int dll_type = 0; /* 0 = unix dlopen, 1 = win32 dll */
#endif

void *__builtin_vec_new(unsigned long size) {
        return malloc(size);
}

void __builtin_vec_delete(void *mem) {
        free(mem);
}

void __pure_virtual(void) {
	printf("FATAL: __pure_virtual() called!\n");
//	exit(1);
}

#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
void ___brk_addr(void) {exit(0);}
char **__environ={NULL};
#undef stderr
FILE *stderr=NULL;
#endif

/* exits program when failure */
#ifdef HAVE_DLFCN_H
static int LoadLinuxSO( decoder_t *p_dec, char *path )
{
    void *handle;
    msg_Dbg( p_dec, "opening shared obj '%s'\n", path);
    handle = dlopen (path, RTLD_LAZY);
    if( !handle )
    {
        msg_Err( p_dec, "Error : %s\n", dlerror() );
        return 0;
    }

    pf_rvyuv_custom_message = dlsym(handle, "RV20toYUV420CustomMessage");
    pf_rvyuv_free           = dlsym(handle, "RV20toYUV420Free");
    pf_rvyuv_hive_message   = dlsym(handle, "RV20toYUV420HiveMessage");
    pf_rvyuv_init           = dlsym(handle, "RV20toYUV420Init");
    pf_rvyuv_transform      = dlsym(handle, "RV20toYUV420Transform");

    if( pf_rvyuv_custom_message &&
        pf_rvyuv_free &&
        pf_rvyuv_hive_message &&
        pf_rvyuv_init &&
        pf_rvyuv_transform)
    {
        p_rv_handle = handle;
        fprintf( stderr, "RV20 symbols successfully loaded\n");
        return 1;
    }
    pf_rvyuv_custom_message = dlsym(handle, "RV40toYUV420CustomMessage");
    pf_rvyuv_free = dlsym(handle, "RV40toYUV420Free");
    pf_rvyuv_hive_message = dlsym(handle, "RV40toYUV420HiveMessage");
    pf_rvyuv_init = dlsym(handle, "RV40toYUV420Init");
    pf_rvyuv_transform = dlsym(handle, "RV40toYUV420Transform");

    if( pf_rvyuv_custom_message &&
        pf_rvyuv_free &&
        pf_rvyuv_hive_message &&
        pf_rvyuv_init &&
        pf_rvyuv_transform )
    {
        p_rv_handle = handle;
        fprintf( stderr, "RV40 symbols successfully loaded\n");
        return 1;
    }

    msg_Dbg( p_dec, "Unable to find symbols in library") ;
    dlclose(handle);
    return 0;
}
#endif

#if 0
#ifdef USE_WIN32DLL

#ifdef WIN32_LOADER
#include "../loader/ldt_keeper.h"
#endif
void* WINAPI LoadLibraryA(char* name);
void* WINAPI GetProcAddress(void* handle,char* func);
int WINAPI FreeLibrary(void *handle);

static int load_syms_windows(char *path) {
    void *handle;

    mp_msg(MSGT_DECVIDEO,MSGL_INFO, "opening win32 dll '%s'\n", path);
#ifdef WIN32_LOADER
    Setup_LDT_Keeper();
#endif
    handle = LoadLibraryA(path);
    mp_msg(MSGT_DECVIDEO,MSGL_V,"win32 real codec handle=%p  \n",handle);
    if (!handle) {
        mp_msg(MSGT_DECVIDEO,MSGL_WARN,"Error loading dll\n");
        return 0;
    }

    wrvyuv_custom_message = GetProcAddress(handle, "RV20toYUV420CustomMessage");
    wrvyuv_free = GetProcAddress(handle, "RV20toYUV420Free");
    wrvyuv_hive_message = GetProcAddress(handle, "RV20toYUV420HiveMessage");
    wrvyuv_init = GetProcAddress(handle, "RV20toYUV420Init");
    wrvyuv_transform = GetProcAddress(handle, "RV20toYUV420Transform");

    if(wrvyuv_custom_message &&
       wrvyuv_free &&
       wrvyuv_hive_message &&
       wrvyuv_init &&
       wrvyuv_transform)
    {
	dll_type = 1;
	rv_handle = handle;
	return 1;
    }
    mp_msg(MSGT_DECVIDEO,MSGL_WARN,"Error resolving symbols! (version incompatibility?)\n");
    FreeLibrary(handle);
    return 0; // error
}
#endif
#endif

/* we need exact positions */
struct rv_init_t {
    short unk1;
    short w;
    short h;
    short unk3;
    int unk2;
    int subformat;
    int unk5;
    int format;
} rv_init_t;

// init driver
static int Open( vlc_object_t *p_this )
{
    decoder_t *p_dec = (decoder_t*)p_this;
    decoder_sys_t *p_sys = p_dec->p_sys;
    fprintf( stderr, "video size %ix%i\n" , p_dec->fmt_in.video.i_width,
                        p_dec->fmt_in.video.i_height);
    fprintf( stderr, "Truc %p\n", p_dec->fmt_in.p_extra );
    fprintf( stderr, "Codec id 0x%08X - 0x%08X\n",
                    ((uint32_t*)p_dec->fmt_in.p_extra)[0],
                    ((uint32_t*)p_dec->fmt_in.p_extra)[1] );
//    fprintf( stderr, "Codec id %d - %d\n", ((uint32_t*)p_dec->fmt_in.p_extra)[0],
//                    ((uint32_t*)p_dec->fmt_in.p_extra)[1] );

    p_dec->p_sys = p_sys = malloc( sizeof( decoder_sys_t ) );
    fprintf (stderr, "Init done ....\n");
        
    p_dec->pf_decode_video = DecodeVideo;
    p_sys->p_context = NULL;
    p_sys->b_loaded = VLC_FALSE;

    msg_Dbg( p_dec, "Real video successfully initialized" );

    return VLC_SUCCESS;
}
    
static int LoadDLL( decoder_t *p_dec )
{
    char *psz_path;
    decoder_sys_t *p_sys = p_dec->p_sys;
    void *p_context;
    psz_path = strdup( "/usr/lib/win32/drv4.so.6.0" );
    int i_result;
    struct rv_init_t init_data=
    {
        11,
        p_dec->fmt_in.video.i_width,
        p_dec->fmt_in.video.i_height,
        0,0,
        ((uint32_t*)p_dec->fmt_in.p_extra)[0],
        1,
        ((uint32_t*)p_dec->fmt_in.p_extra)[1]
    };
    /* Try to load Linux DLL */
#ifdef HAVE_DLFCN_H
    if( !LoadLinuxSO( p_dec, psz_path ) )
    {
#endif
#ifdef USE_WIN32DLL
        if (!load_syms_windows(sh->codec->dll))
#endif
        {
            msg_Err( p_dec, "Unable to load codec" );
            free( psz_path );
            return VLC_EGENERIC;
        }
        free( psz_path );
    }
#if 0
#ifdef USE_WIN32DLL
    if (dll_type == 1)
        result=(*wrvyuv_init)(&init_data, &sh->context);
    else
#endif
#endif
//    fprintf( stderr, "%d", VLC_FOURCC( 'R','V','3','0' ) ); 
    i_result= (*pf_rvyuv_init) (&init_data, p_context );
//    sleep( 4 );
    //    fprintf( stderr, "Called init symbol\n");
    if( i_result )
    {
        msg_Err( p_dec, "Unable to use codec, error code %X\n", i_result );
        return VLC_EGENERIC;
    }
 
    fprintf( stderr, "Comparing with %08X\n", ((uint32_t*)p_dec->fmt_in.p_extra)[1] );   
    if(  ((uint32_t*)p_dec->fmt_in.p_extra)[1] >= 0x20200002 )
    {
        msg_Err( p_dec, "I'll call custom\n" );
        fprintf( stderr, "Building message data\n");
        uint32_t cmsg24[8]=
        {
            p_dec->fmt_in.video.i_width,
            p_dec->fmt_in.video.i_height,
            ((unsigned char *)(p_dec->fmt_in.p_extra))[8]*4,
            ((unsigned char *)(p_dec->fmt_in.p_extra))[9]*4,
            ((unsigned char *)(p_dec->fmt_in.p_extra))[10]*4,
            ((unsigned char *)(p_dec->fmt_in.p_extra))[11]*4,
            ((unsigned char *)(p_dec->fmt_in.p_extra))[12]*4,
            ((unsigned char *)(p_dec->fmt_in.p_extra))[13]*4
        };
        fprintf( stderr, "Building message data 2 (disabled)\n");
        
        cmsg_data_t cmsg_data =
        {
                0x24,
                1+ ((((uint32_t *)(p_dec->fmt_in.p_extra))[0])>>16)&7,
                &cmsg24[0]
        };
        fprintf( stderr, "Custom message real call\n");    
        (*pf_rvyuv_custom_message)(&cmsg_data, p_context );
        
    }
#if 0 
	// setup rv30 codec (codec sub-type and image dimensions):
	if((sh->format<=0x30335652) && (extrahdr[1]>=0x20200002)){
	    // We could read nonsense data while filling this, but input is big enough so no sig11
	    uint32_t cmsg24[8]=
            {
                sh->disp_w,sh->disp_h,((unsigned char *)extrahdr)[8]*4,((unsigned char *)extrahdr)[9]*4,
	                        ((unsigned char *)extrahdr)[10]*4,((unsigned char *)extrahdr)[11]*4,
	                        ((unsigned char *)extrahdr)[12]*4,((unsigned char *)extrahdr)[13]*4};
	    cmsg_data_t cmsg_data={0x24,1+((extrahdr[0]>>16)&7), &cmsg24[0]};
#endif

#if 0
#ifdef USE_WIN32DLL
           if (dll_type == 1)
                (*wrvyuv_custom_message)(&cmsg_data,sh->context);
            else
#endif
        }
#endif
        p_sys->p_context = p_context;
        p_sys->b_loaded = VLC_TRUE;
        return i_result;
}

// uninit driver
static int Close( vlc_object_t *p_this )
{
#if 0
#ifdef USE_WIN32DLL
	if (dll_type == 1)
	{
	    if (wrvyuv_free) wrvyuv_free(sh->context);
	} else
#endif
#endif
    void *p_context;
    if ( pf_rvyuv_free ) pf_rvyuv_free( p_context);

#if 0
#ifdef USE_WIN32DLL
	if (dll_type == 1)
	{
	    if (rv_handle) FreeLibrary(rv_handle);
	} else
#endif
#endif
                
#ifdef HAVE_DLFCN_H
        if( p_rv_handle ) dlclose( p_rv_handle );
#endif
        p_rv_handle=NULL;
        return VLC_SUCCESS;
}

// copypaste from demux_real.c - it should match to get it working!
typedef struct dp_hdr_s {
    uint32_t chunks;	// number of chunks
    uint32_t timestamp; // timestamp from packet header
    uint32_t len;	// length of actual data
    uint32_t chunktab;	// offset to chunk offset array
} dp_hdr_t;

// decode a frame
static picture_t *DecodeVideo( decoder_t *p_dec, block_t **pp_blocks)
{
    fprintf( stderr, "Decoding\n");
    decoder_sys_t *p_sys = p_dec->p_sys;
    block_t *p_block;
    int i_result;
    if( pp_blocks == NULL || *pp_blocks == NULL )
    {
        return NULL;
    }
    p_block = *pp_blocks;
    *pp_blocks = NULL;
    mtime_t i_pts = p_block->i_pts ? p_block->i_pts : p_block->i_dts;

    picture_t *p_pic;


//#ifdef LOADER
    if( !p_sys->b_loaded )
    {
        fprintf( stderr, "Loading DLL\n" );
        if( LoadDLL( p_dec ) != VLC_SUCCESS )
        {
            msg_Err( p_dec, "Error loading DLL" );
            p_dec->b_error = VLC_TRUE;
            return NULL;
        }
        fprintf( stderr, "DLL Successfully loaded\n");
    }
//#endif

    
//	mp_image_t* mpi;
    uint32_t *p_data = (uint32_t* )(p_block->p_buffer);
    uint8_t *dato = (uint8_t*)(p_block->p_buffer );

            fprintf( stderr, "BEGINNING %i-%i-%i-%i %i-%i-%i-%i %i-%i-%i-%i %i-%i-%i-%i %i-%i-%i-%i %i-%i-%i-%i\n",
                                    dato[0],dato[1],dato[2],dato[3],
                                            dato[4],dato[5],dato[6],dato[7],
                                                    dato[8],dato[9],dato[10],dato[11],
                                                            dato[12],dato[13],dato[14],dato[15] ,
                                                                    dato[16],dato[17],dato[18],dato[19],
                                                                            dato[20],dato[21],dato[22],dato[23] );

    
    dp_hdr_t* dp_hdr=(dp_hdr_t*)(p_data);
    fprintf( stderr, "Packet length %i - Subpackages %i - Timestamp %i- Chunktab %i\n", 
                    dp_hdr->len, dp_hdr->chunks, dp_hdr->timestamp , dp_hdr->chunktab);
    unsigned char* dp_data=((unsigned char*)(p_block->p_buffer))+sizeof(dp_hdr_t);
    uint32_t* extra=(uint32_t*)(((char*)(p_block->p_buffer))+dp_hdr->chunktab);
    uint8_t *p_buffer;


    if( dp_hdr->len == 671 ) return NULL;
    unsigned int transform_out[5];
    transform_in_t transform_in =
    {
         dp_hdr->len,	// length of the packet (sub-packets appended)
         0,		// unknown, seems to be unused
 	 dp_hdr->chunks,	// number of sub-packets - 1
	 extra,		// table of sub-packet offsets
	 0,		// unknown, seems to be unused
	 dp_hdr->timestamp,// timestamp (the integer value from the stream)
	};


    // rv30 width/height not yet known
    if( inited )
    {
        // FIX ME
        p_dec->fmt_out.video.i_width = p_dec->fmt_in.video.i_width;
        p_dec->fmt_out.video.i_height= p_dec->fmt_in.video.i_height;
        p_dec->fmt_out.video.i_visible_width = p_dec->fmt_in.video.i_width;
        p_dec->fmt_out.video.i_visible_height= p_dec->fmt_in.video.i_height;
        fprintf (stderr, "I'm inited, let's create image %ix%i\n", 
                    p_dec->fmt_out.video.i_width, 
                    p_dec->fmt_out.video.i_height);
        p_pic = p_dec->pf_vout_buffer_new( p_dec );
        if( !p_pic )
        {
            fprintf( stderr, "NO PICTURE ! fuck \n") ;
            return NULL;
        }
        p_buffer = p_pic->p_data;
    }
    else
    {
        fprintf( stderr, "I'm not inited... Should malloc %i %i\n", p_dec->fmt_in.video.i_width, p_dec->fmt_in.video.i_height );
        p_buffer = malloc( p_dec->fmt_in.video.i_width *
                           p_dec->fmt_in.video.i_height * 3/2 );
    }
#if 0
#ifdef USE_WIN32DLL
        if (dll_type == 1)
               i_result=(*wrvyuv_transform)(dp_data, buffer, &transform_in,
                transform_out, sh->context);
        else
#endif
#endif
        fprintf( stderr, "Transforming %X %X %X %X %X\n", dp_data[0], dp_data[1], dp_data[2], dp_data[3], dp_data[4]);
        i_result=(* pf_rvyuv_transform)(dp_data, p_buffer, &transform_in,
                                        transform_out, p_sys->p_context);
        fprintf( stderr,"Transform result %X\n", i_result );

	if( !inited )
        {  // rv30 width/height now known
//	    sh->aspect=(float)sh->disp_w/(float)sh->disp_h;
	    fprintf( stderr, "Not Inited ... W*H = %ix%i\n", transform_out[3],
                                                             transform_out[4] );
            //sh->disp_w=transform_out[3];
	    //sh->disp_h=transform_out[4];
#if 0
            if (!mpcodecs_config_vo(sh,sh->disp_w,sh->disp_h,IMGFMT_I420)) return 0;
	    mpi=mpcodecs_get_image(sh, MP_IMGTYPE_TEMP, 0 /*MP_IMGFLAG_ACCEPT_STRIDE*/,
		    sh->disp_w, sh->disp_h);
	    if(!mpi) return NULL;
	    memcpy(mpi->planes[0],buffer,sh->disp_w*sh->disp_h*3/2);
            free(buffer);
#endif
            inited = 1;
	} 

	return ( i_result?NULL: p_pic );
}
