#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif

#include <wine/driver.h>
#include <wine/pe_image.h>
#include <wine/winreg.h>
#include <wine/vfw.h>
#include <registry.h>
#include "ext.h"
#include "win32.h"
#include "driver.h"

#if defined(__FreeBSD__) || defined(__NetBSD__)
#include <sys/time.h>
#endif

#if 1
#define STORE_ALL
#define REST_ALL
#else
// this asm code doesn't work - why ???
// kabi@i.am
#define STORE_ALL \
    __asm__ ( \
    "push %%ebx\n\t" \
    "push %%ecx\n\t" \
    "push %%edx\n\t" \
    "push %%esi\n\t" \
    "push %%edi\n\t"::)

#define REST_ALL \
    __asm__ ( \
    "pop %%edi\n\t" \
    "pop %%esi\n\t" \
    "pop %%edx\n\t" \
    "pop %%ecx\n\t" \
    "pop %%ebx\n\t"::)
#endif

extern void setup_FS_Segment(void);

static DWORD dwDrvID = 0;

LRESULT WINAPI SendDriverMessage( HDRVR hDriver, UINT message,
                                    LPARAM lParam1, LPARAM lParam2 )
{
    DRVR* module=(DRVR*)hDriver;
    int result;
#if defined(__FreeBSD__) || defined(__NetBSD__)
    struct itimerval itoval;
    struct itimerval itval;
#endif
#ifdef DETAILED_OUT    
    printf("SendDriverMessage: driver %X, message %X, arg1 %X, arg2 %X\n", hDriver, message, lParam1, lParam2);
#endif
    if(module==0)return -1;
    if(module->hDriverModule==0)return -1;
    if(module->DriverProc==0)return -1;
#if defined(__FreeBSD__) || defined(__NetBSD__)
    memset(&itval,0,sizeof(struct itimerval));
    setitimer(ITIMER_PROF ,&itval,&itoval);
#endif
    STORE_ALL;
    result=module->DriverProc(module->dwDriverID,1,message,lParam1,lParam2);
    REST_ALL;
#if defined(__FreeBSD__) || defined(__NetBSD__)
    setitimer(ITIMER_PROF,&itoval,NULL);
#endif
#ifdef DETAILED_OUT    
    printf("\t\tResult: %X\n", result);
#endif    
    return result;
}				    

static NPDRVR DrvAlloc(HDRVR*lpDriver, LPUINT lpDrvResult)
{
    NPDRVR npDriver;
    /* allocate and lock handle */
    if (lpDriver)
    {
        if ((*lpDriver = (HDRVR) malloc(sizeof(DRVR))) != 0 )
        {
            if ((npDriver = (NPDRVR) *lpDriver) != NULL)
            {
                    *lpDrvResult = MMSYSERR_NOERROR;
                    return (npDriver);
            }
            free((NPDRVR)*lpDriver);
        }
        return (*lpDrvResult = MMSYSERR_NOMEM, (NPDRVR) 0);
    }
    return (*lpDrvResult = MMSYSERR_INVALPARAM, (NPDRVR) 0);
}

typedef struct
{
    HMODULE handle;
    char name[64];
    int usage;
}codec_t;

static int needs_free=0;
extern char* def_path;
void SetCodecPath(const char* path)
{
    if(needs_free)free(def_path);
    if(path==0)    
    {
	def_path=WIN32_PATH;
	needs_free=0;
	return;
    }	
    def_path=malloc(strlen(path)+1);
    strcpy(def_path, path);
    needs_free=1;
}    

static codec_t avi_codecs[]={
 {0, "divxa32.acm", 0},	
 {0, "msadp32.acm", 0},
 {0, "imc32.acm", 0},
 {0, "l3codeca.acm", 0},
};

                                                                                                                    
static void DrvFree(HDRVR hDriver)
{
    int i;

    setup_FS_Segment();

    if(hDriver)
    	if(((DRVR*)hDriver)->hDriverModule)
    	if(((DRVR*)hDriver)->DriverProc)
	(((DRVR*)hDriver)->DriverProc)(((DRVR*)hDriver)->dwDriverID, hDriver, DRV_CLOSE, 0, 0);
    if(hDriver)
    for(i=0; i<sizeof(avi_codecs)/sizeof(avi_codecs[0]); i++)
	if(avi_codecs[i].handle==((DRVR*)hDriver)->hDriverModule)
	{
	    avi_codecs[i].usage--;	
	    if(avi_codecs[i].usage==0)
	    { 
    		avi_codecs[i].handle=0;	    
                 if(((DRVR*)hDriver)->hDriverModule)
    		if(((DRVR*)hDriver)->DriverProc)
			(((DRVR*)hDriver)->DriverProc)(0, hDriver, DRV_FREE, 0, 0);
		FreeLibrary(((DRVR*)hDriver)->hDriverModule);
    		if (hDriver)
        	    		free((NPDRVR)hDriver);
		return;	
	    }	
	}      
}       

void DrvClose(HDRVR hdrvr)
{
    DrvFree(hdrvr);
}
    
HDRVR
//DrvOpen(LPCSTR lpszDriverName, LPCSTR lpszSectionName, LPARAM lParam2)
DrvOpen(LPARAM lParam2)
{
    int drv_id;
    char filename[MAX_PATH], *f;
    UINT uDrvResult;
    HDRVR hDriver;
    NPDRVR npDriver;
    char unknown[0x24];
    int seg;
    int qwe;
    int regs[10];


    int fccHandler=*((int*)lParam2+2);
    int fccType=*((int*)lParam2+1);
    switch(fccHandler)
    {
    case 0x160://DivX audio
    case 0x161://DivX audio
        drv_id=0; 
        break;
    case 0x2://MS ADPCM
        drv_id=1;
        break;
    case 0x401:
	drv_id=2;
	break;
    case 0x55:
	drv_id=3;
	break;
    default:
        printf("Unknown ACM codec 0x%X\n", fccHandler);
        return (HDRVR)0;
    }

    if (!(npDriver = DrvAlloc(&hDriver, &uDrvResult)))
	return ((HDRVR) 0);

    if(avi_codecs[drv_id].handle==0)
    {
     printf("Load DLL driver %s\n", avi_codecs[drv_id].name);
     if (!(avi_codecs[drv_id].handle=npDriver->hDriverModule
	   = LoadLibraryA(avi_codecs[drv_id].name)))
     {
     	printf("Can't open library %s\n", avi_codecs[drv_id].name);
        DrvFree(hDriver);
        return ((HDRVR) 0);
     }
     else
	 avi_codecs[drv_id].usage=1;
    }
    else
    {
	npDriver->hDriverModule=avi_codecs[drv_id].handle;
	avi_codecs[drv_id].usage++;
    }	
     if (!(npDriver->DriverProc = (DRIVERPROC)
             GetProcAddress(npDriver->hDriverModule, "DriverProc")))
     {
         printf("Library %s is not a valid codec\n", avi_codecs[drv_id].name);
         FreeLibrary(npDriver->hDriverModule);
         DrvFree(hDriver);
         return ((HDRVR) 0);
     }
     
    TRACE("DriverProc == %X\n", npDriver->DriverProc);
     npDriver->dwDriverID = ++dwDrvID;

     setup_FS_Segment();

     if (avi_codecs[drv_id].usage==1)
     {
	STORE_ALL;
        (npDriver->DriverProc)(0, hDriver, DRV_LOAD, 0, 0);
	REST_ALL;
	TRACE("DRV_LOAD Ok!\n");
	STORE_ALL;
	(npDriver->DriverProc)(0, hDriver, DRV_ENABLE, 0, 0);
	REST_ALL;
	TRACE("DRV_ENABLE Ok!\n");
     }

     // open driver 
    STORE_ALL;
     npDriver->dwDriverID=(npDriver->DriverProc)(npDriver->dwDriverID, hDriver, DRV_OPEN,
         (LPARAM) (LPSTR) unknown, lParam2);
    REST_ALL;

    TRACE("DRV_OPEN Ok!(%X)\n", npDriver->dwDriverID);

    if (uDrvResult)
    {
         DrvFree(hDriver);
         hDriver = (HDRVR) 0;
     }
     return (hDriver);
}
