/*
 *	Blade DLL Interface for LAME.
 *
 *	Copyright (c) 1999 A.L. Faber
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

#include "BladeMP3EncDLL.h"
#include <assert.h>
#include "version.h"  
#include "VbrTag.h"   
#include "lame.h"
/*
#include "get_audio.h"
#include "globalflags.h"
#include "machine.h"
#include "util.h"
*/

#ifdef _DEBUG
	#define _DEBUGDLL 1
#endif


const int MAJORVERSION=1;
const int MINORVERSION=11;
const int CURRENT_STRUCT_VERSION=1;
const int CURRENT_STRUCT_SIZE=sizeof(BE_CONFIG);


// Local variables
static int		nPsychoModel=2;
static BOOL		bFirstFrame=TRUE;
static DWORD	dwSampleBufferSize=0;


#ifdef _DEBUGDLL
void dump_config( char *inPath, char *outPath);
#endif

lame_global_flags gf;

static void InitParams()
{
    bFirstFrame=TRUE;
    lame_init(&gf);

}




__declspec(dllexport) BE_ERR	beInitStream(PBE_CONFIG pbeConfig, PDWORD dwSamples, PDWORD dwBufferSize, PHBE_STREAM phbeStream)
{
	int			nDllArgC=0;
	BE_CONFIG	lameConfig;

	InitParams();

	// clear out structure
	memset(&lameConfig,0x00,CURRENT_STRUCT_SIZE);

	// Check if this is a regular BLADE_ENCODER header
	if (pbeConfig->dwConfig!=BE_CONFIG_LAME)
	{
		int	nCRC=pbeConfig->format.mp3.bCRC;
		int nVBR=(nCRC>>12)&0x0F;

		// Copy parameter from old Blade structure
		lameConfig.format.LHV1.dwSampleRate	=pbeConfig->format.mp3.dwSampleRate;
		//for low bitrates, LAME will automatically downsample for better
		//sound quality.  Forcing output samplerate = input samplerate is not a good idea 
		//unless the user specifically requests it:
		//lameConfig.format.LHV1.dwReSampleRate=pbeConfig->format.mp3.dwSampleRate;
		lameConfig.format.LHV1.nMode		=(pbeConfig->format.mp3.byMode&0x0F);
		lameConfig.format.LHV1.dwBitrate	=pbeConfig->format.mp3.wBitrate;
		lameConfig.format.LHV1.bPrivate		=pbeConfig->format.mp3.bPrivate;
		lameConfig.format.LHV1.bOriginal	=pbeConfig->format.mp3.bOriginal;
		lameConfig.format.LHV1.bCRC			=nCRC&0x01;
		lameConfig.format.LHV1.bCopyright	=pbeConfig->format.mp3.bCopyright;
	
		// Fill out the unknowns
		lameConfig.format.LHV1.dwStructSize=CURRENT_STRUCT_VERSION;
		lameConfig.format.LHV1.dwStructVersion=CURRENT_STRUCT_SIZE;

		// Get VBR setting from fourth nibble
		if (nVBR>0)
		{
			lameConfig.format.LHV1.bWriteVBRHeader=TRUE;
			lameConfig.format.LHV1.bEnableVBR=TRUE;
			lameConfig.format.LHV1.nVBRQuality=nVBR-1;
		}

		// Get Quality from third nibble
		lameConfig.format.LHV1.nQuality=(MPEG_QUALITY)((nCRC>>8)&0x0F);

	}
	else
	{
		// Copy the parameters
		memcpy(&lameConfig,pbeConfig,pbeConfig->format.LHV1.dwStructSize);
	}


	// Not used, always assign stream 1
	*phbeStream=1;



	// --------------- Set arguments to LAME encoder -------------------------

	// Set zero argument, the filename
	//strcpy(DllArgV[nDllArgC++],"LameDLLEncoder");

  	switch (lameConfig.format.LHV1.nMode)
	{
		case BE_MP3_MODE_STEREO:
			gf.mode=0;
			gf.mode_fixed=1;  /* dont allow LAME to change the mode */
			gf.num_channels=2;
		break;
		case BE_MP3_MODE_JSTEREO:
			gf.mode=1;
			gf.mode_fixed=1;
			gf.num_channels=2;
		break;
		case BE_MP3_MODE_MONO:
			gf.mode=3;
			gf.mode_fixed=1;
			gf.num_channels=1;
		break;
		case BE_MP3_MODE_DUALCHANNEL:
			gf.force_ms=1;
			gf.mode=1;
			gf.mode_fixed=1;
			gf.num_channels=2;
		break;
		default:
		{
			char lpszError[255];
			sprintf(lpszError,"Invalid lameConfig.format.LHV1.nMode, value is %d\n",lameConfig.format.LHV1.nMode);
			OutputDebugString(lpszError);
			return BE_ERR_INVALID_FORMAT_PARAMETERS;
		}
	}

	switch (lameConfig.format.LHV1.nQuality)
	{
		case NORMAL_QUALITY:	// Nothing special
			break;
		case LOW_QUALITY:		// -f flag
			gf.quality=9;
			break;
		case HIGH_QUALITY:		// -h flag for high qualtiy
			gf.quality=2;
			break;
		case VOICE_QUALITY:		// --voice flag for experimental voice mode
			gf.lowpassfreq=12000;
			gf.VBR_max_bitrate_kbps=160;
			gf.no_short_blocks=1;
		break;
	}

	if (lameConfig.format.LHV1.bEnableVBR)
	{
		// 0=no vbr 1..10 is VBR quality setting -1
		gf.VBR=1;
		gf.VBR_q=lameConfig.format.LHV1.nVBRQuality;

		if (lameConfig.format.LHV1.bWriteVBRHeader==TRUE)
		{
			gf.bWriteVbrTag=TRUE;
		}
		else
		{
			gf.bWriteVbrTag=FALSE;
		}
	}



	// Set frequency
	gf.in_samplerate=lameConfig.format.LHV1.dwSampleRate;

	// Set frequency resampling rate, if specified
	if (lameConfig.format.LHV1.dwReSampleRate>0)
		gf.out_samplerate=lameConfig.format.LHV1.dwReSampleRate;
		
	
	// Set bitrate.  (CDex users always specify bitrate=Min bitrate when using VBR)
	gf.brate=lameConfig.format.LHV1.dwBitrate;
	gf.VBR_min_bitrate_kbps=gf.brate;
			
	// Set Maxbitrate, if specified
	if (lameConfig.format.LHV1.dwMaxBitrate>0)
		gf.VBR_max_bitrate_kbps=lameConfig.format.LHV1.dwMaxBitrate;
	
	// Set copyright flag?
	if (lameConfig.format.LHV1.bCopyright)
		gf.copyright=1;

	// Do we have to tag  it as non original 
	if (!lameConfig.format.LHV1.bOriginal)
	{
		gf.original=0;
	}
	else
	{
		gf.original=1;
	}

	// Add CRC?
	if (lameConfig.format.LHV1.bCRC)
	{
		gf.error_protection=1;
	}
	else
	{
		gf.error_protection=0;
	}

	gf.silent=1;  /* disable status ouput */

	// Set private bit?
	if (lameConfig.format.LHV1.bPrivate)
	{
		gf.extension = 1;
	}
	else
	{
		gf.extension = 0;
	}
	
	lame_init_params(&gf);	

	//LAME encoding call will accept any number of samples.  
	*dwSamples=1152*gf.num_channels;

	// Set the input sample buffer size, so we know what we can expect
	dwSampleBufferSize=*dwSamples;

	// Set MP3 buffer size
	// conservative estimate
	*dwBufferSize=1.25*(*dwSamples/gf.num_channels) + 7200;


#ifdef _DEBUGDLL
	dump_config(gf.inPath,gf.outPath);
#endif

	// Everything went OK, thus return SUCCESSFUL
	return BE_ERR_SUCCESSFUL;
}



__declspec(dllexport) BE_ERR	beDeinitStream(HBE_STREAM hbeStream, PBYTE pOutput, PDWORD pdwOutput)
{

	*pdwOutput = lame_encode_finish(&gf,pOutput,0);

	if (*pdwOutput<0) {
		*pdwOutput=0;
		return BE_ERR_BUFFER_TOO_SMALL;
	}

	return BE_ERR_SUCCESSFUL;
}


__declspec(dllexport) BE_ERR	beCloseStream(HBE_STREAM hbeStream)
{
	// DeInit encoder
//	return DeInitEncoder();
	return BE_ERR_SUCCESSFUL;
}



__declspec(dllexport) VOID		beVersion(PBE_VERSION pbeVersion)
{
	// DLL Release date
	char lpszDate[20];
	char lpszTemp[5];


	// Set DLL interface version
	pbeVersion->byDLLMajorVersion=MAJORVERSION;
	pbeVersion->byDLLMinorVersion=MINORVERSION;

	// Set Engine version number (Same as Lame version)
	pbeVersion->byMajorVersion=LAME_MAJOR_VERSION;
	pbeVersion->byMinorVersion=LAME_MINOR_VERSION;

	// Get compilation date
	strcpy(lpszDate,__DATE__);

	// Get the first three character, which is the month
	strncpy(lpszTemp,lpszDate,3);
	lpszTemp[3]='\0';
	pbeVersion->byMonth=1;

	// Set month
	if (strcmp(lpszTemp,"Jan")==0)	pbeVersion->byMonth=1;
	if (strcmp(lpszTemp,"Feb")==0)	pbeVersion->byMonth=2;
	if (strcmp(lpszTemp,"Mar")==0)	pbeVersion->byMonth=3;
	if (strcmp(lpszTemp,"Apr")==0)	pbeVersion->byMonth=4;
	if (strcmp(lpszTemp,"May")==0)	pbeVersion->byMonth=5;
	if (strcmp(lpszTemp,"Jun")==0)	pbeVersion->byMonth=6;
	if (strcmp(lpszTemp,"Jul")==0)	pbeVersion->byMonth=7;
	if (strcmp(lpszTemp,"Aug")==0)	pbeVersion->byMonth=8;
	if (strcmp(lpszTemp,"Sep")==0)	pbeVersion->byMonth=9;
	if (strcmp(lpszTemp,"Oct")==0)	pbeVersion->byMonth=10;
	if (strcmp(lpszTemp,"Nov")==0)	pbeVersion->byMonth=11;
	if (strcmp(lpszTemp,"Dec")==0)	pbeVersion->byMonth=12;

	// Get day of month string (char [4..5])
	pbeVersion->byDay=atoi(lpszDate+4);

	// Get year of compilation date (char [7..10])
	pbeVersion->wYear=atoi(lpszDate+7);

	memset(pbeVersion->zHomepage,0x00,BE_MAX_HOMEPAGE);

	strcpy(pbeVersion->zHomepage,"http://www.sulaco.org/mp3/");
}

__declspec(dllexport) BE_ERR	beEncodeChunk(HBE_STREAM hbeStream, DWORD nSamples, 
			 PSHORT pSamples, PBYTE pOutput, PDWORD pdwOutput)
{

	// Encode it
        int dwSamples;
	dwSamples=nSamples/gf.num_channels;

	// old versions of lame_enc.dll required exactly 1152 samples
	// and worked even if nSamples accidently set to 2304
	// simulate this behavoir:
	if (gf.num_channels==1 && nSamples == 2304)
	  dwSamples/=2;

	*pdwOutput=lame_encode_buffer_interleaved(&gf,pSamples,dwSamples,pOutput,0);


	if (*pdwOutput<0) {
		*pdwOutput=0;
		return BE_ERR_BUFFER_TOO_SMALL;
	}

	return BE_ERR_SUCCESSFUL;
}


__declspec(dllexport) BE_ERR beWriteVBRHeader(LPCSTR lpszFileName)
{
	if (gf.bWriteVbrTag)
	{
		// Calculate relative quality of VBR stream 
		// 0=best, 100=worst
		int nQuality=gf.VBR_q*100/9;

		// Write Xing header again
		return PutVbrTag(&gf,(LPSTR)lpszFileName,nQuality);
	}
	return BE_ERR_INVALID_FORMAT_PARAMETERS;
}


BOOL APIENTRY DllMain(HANDLE hModule, 
                      DWORD  ul_reason_for_call, 
                      LPVOID lpReserved)
{
    switch( ul_reason_for_call )
	{
		case DLL_PROCESS_ATTACH:
#ifdef _DEBUGDLL
			OutputDebugString("Attach Process \n");
#endif
		break;
		case DLL_THREAD_ATTACH:
#ifdef _DEBUGDLL
			OutputDebugString("Attach Thread \n");
#endif
		break;
		case DLL_THREAD_DETACH:
#ifdef _DEBUGDLL
			OutputDebugString("Detach Thread \n");
#endif
		break;
		case DLL_PROCESS_DETACH:
#ifdef _DEBUGDLL
			OutputDebugString("Detach Process \n");
#endif
		break;
    }
    return TRUE;
}


#ifdef _DEBUGDLL
void dump_config( char *inPath, char *outPath)
{
  	char strTmp[255];

	OutputDebugString("Encoding configuration:\n");


	sprintf(strTmp,"Write VBR Header=%s\n",(gf.bWriteVbrTag)?"Yes":"No");
	OutputDebugString(strTmp);

	sprintf(strTmp,"version=%d\n",gf.version);
	OutputDebugString(strTmp);


	sprintf(strTmp,"Layer=3   mode=%d  \n",gf.mode);
	OutputDebugString(strTmp);


	sprintf(strTmp,"samp frq=%.1f kHz   total bitrate=%d kbps\n",gf.in_samplerate/1000.0);
	OutputDebugString(strTmp);

	sprintf(strTmp,"de-emph=%d   c/right=%d   orig=%d   errprot=%s\n",gf.emphasis, gf.copyright, gf.original,((gf.error_protection) ? "on" : "off"));
	OutputDebugString(strTmp);

//	sprintf(strTmp,"16 Khz cut off is %s\n",(0)?"enabled":"disabled");
//	OutputDebugString(strTmp);

	sprintf(strTmp,"Fast mode is %s\n",(gf.quality==9)?"enabled":"disabled");
	OutputDebugString(strTmp);

	sprintf(strTmp,"Force ms %s\n",(gf.force_ms)?"enabled":"disabled");
	OutputDebugString(strTmp);

//	sprintf(strTmp,"GPsycho acoustic model is %s\n",(gpsycho)?"enabled":"disabled");
//	OutputDebugString(strTmp);

	sprintf(strTmp,"VRB is %s, VBR_q value is  %d\n",(gf.VBR)?"enabled":"disabled",gf.VBR_q);
	OutputDebugString(strTmp);

	sprintf(strTmp,"input file: '%s'   output file: '%s'\n", inPath, outPath);
	OutputDebugString(strTmp);

//	sprintf(strTmp,"Voice mode %s\n",(voice_mode)?"enabled":"disabled");
//	OutputDebugString(strTmp);

	sprintf(strTmp,"Encoding as %.1f kHz %d kbps MPEG-%d LayerIII file\n",gf.out_samplerate/1000.0,gf.brate,gf.mode,3 - gf.version);
	OutputDebugString(strTmp);
}


void DispErr(LPSTR strErr)
{
	MessageBox(NULL,strErr,"",MB_OK);
}

#endif
