#define STRICT
#define WIN32_LEAN_AND_MEAN
#include "leakchk.h"

#include <windows.h>
#include <windowsx.h>
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>

#include <kmp_pi.h>

#include "xsfdrv.h"

#include "../pversion.h"
#include "tagget.h"

#include "xsfcfg.h"
#include "xsfui.rh"

#ifndef ENABLE_LOADPE
#if !defined(_DEBUG)
#define ENABLE_LOADPE 1
#else
#define ENABLE_LOADPE 0
#endif
#endif

#if ENABLE_LOADPE
#include "../loadpe/loadpe.h"
#else
#define XLoadLibraryA LoadLibraryA
#define XLoadLibraryW LoadLibraryW
#define XGetProcAddress(h,n) GetProcAddress((HMODULE)h,n)
#define XFreeLibrary(h) FreeLibrary((HMODULE)h)
#endif

#if _MSC_VER >= 1200
#pragma comment(linker, "/EXPORT:kmp_GetTestModule=_kmp_GetTestModule@0")
#pragma comment(linker, "/EXPORT:kmp_Config=_kmp_Config@12")
#pragma warning(disable:4290)
#endif

#if defined(_MSC_VER) && !defined(_DEBUG)
#pragma comment(linker,"/MERGE:.rdata=.text")
#endif

static HMODULE hDLL = 0;
static xsfc::TAutoPtr<IConfigIO> pcfg;
static void LoadConfig();


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	(void)lpvReserved;
	if (fdwReason == DLL_PROCESS_ATTACH)
	{
#if defined(_MSC_VER) && defined(_DEBUG)
		_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
		hDLL = hinstDLL;
		DisableThreadLibraryCalls(hinstDLL);
	}
	return TRUE;
}

#define CHECK_SILENCE_BIAS 0x8000000
#ifndef CHECK_SILENCE_LEVEL
#define CHECK_SILENCE_LEVEL 7
#endif

#define SIZE_OF_BUFFER (576 * 4)
typedef signed short xsfsample_t;


//***********************************************************************************************
//XSF Driver class
//***********************************************************************************************
class XSFDriver
{
public:
	IXSFDRV *lpif;

	xsfc::TAutoBuffer<char> xsfdata;

	unsigned cur_smp;
	unsigned len_smp;
	unsigned fad_smp;

	int hasvolume;
	bool haslength;
	float volume;
	unsigned long m_samplerate;

	enum
	{
#ifndef XSFDRIVER_SAMPLERATE
		samplerate = 44100,
#else
		samplerate = XSFDRIVER_SAMPLERATE,
#endif
		samplebits = 16,
		ch = 2,
		blockshift = 2,
	};
	int length_in_ms; int fade_in_ms;
	volatile unsigned seek_pos;
	volatile int seek_req;
	volatile int seek_kil;

protected:
	unsigned detectedSilenceSec;
	unsigned detectedSilenceSample;
	unsigned skipSilenceOnStartSec;
	unsigned long prevSampleL;
	unsigned long prevSampleR;

	void *hDriver;
	xsfc::TString sLibBasePath;
	bool fUTF8;

	static void *liballoc_cb(void *pwork, size_t s)
	{
		IXSFDRV *lpif = static_cast<IXSFDRV *>(pwork);
		return lpif->LibAlloc(DWORD(s));
	}
	static void libfree_cb(void *pwork, void *p)
	{
		IXSFDRV *lpif = static_cast<IXSFDRV *>(pwork);
		lpif->LibFree(p);
	}

	static int PASCAL XSFGETLIBCALLBACK(void *lpWork, LPSTR lpszFilename, void **ppBuffer, DWORD *pdwSize)
	{
		XSFDriver *lpDrv = static_cast<XSFDriver *>(lpWork);

		size_t libsize;
		xsfc::TString sLibName = xsfc::TString(lpDrv->fUTF8, lpszFilename);

		void *libdata = xsfc::TWin32::LoadEx(xsfc::TWin32::CanonicalizePath(lpDrv->sLibBasePath + sLibName), libsize, lpDrv->lpif, liballoc_cb, libfree_cb);
		if (!libdata)
		{
			/* in_zip.dll */
			libdata = xsfc::TWin32::LoadEx(lpDrv->sLibBasePath + xsfc::TWin32::ExtractFileName(sLibName), libsize, lpDrv->lpif, liballoc_cb, libfree_cb);
		}
		if (!libdata)
			return 1;

		*ppBuffer = libdata;
		*pdwSize = DWORD(libsize);

		return 0;
	}

	void SeekTop()
	{
		cur_smp = 0;

		skipSilenceOnStartSec = CFGGetSkipSilenceOnStartSec();

		detectedSilenceSec = 0;
		detectedSilenceSample = 0;

		prevSampleL = CHECK_SILENCE_BIAS;
		prevSampleR = CHECK_SILENCE_BIAS;
	}

public:

	XSFDriver() throw()
		: hDriver(0), lpif(0), m_samplerate(samplerate)
	{
	}
	~XSFDriver()
	{
	}

	void Free()
	{
		if (lpif)
		{
			lpif->Term();
			lpif = 0;
		}
		if (hDriver)
		{
			XFreeLibrary(hDriver);
			hDriver = 0;
		}
		xsfdata.Free();
	}

	bool Init(HINSTANCE hDll)
	{
		xsfc::TString sDrvPath = xsfc::TWin32::ExtractPath(xsfc::TWin32::ModulePath(hDll)) + XSFDRIVER_MODULENAME;
		hDriver = xsfc::TWin32::IsUnicodeSupportedOS() ? XLoadLibraryW(sDrvPath) : XLoadLibraryA(xsfc::TStringM(sDrvPath));
		if (!hDriver)
			return false;
		LPFNXSFDRVSETUP lpfnXSFSetup = (LPFNXSFDRVSETUP)XGetProcAddress(hDriver, XSFDRIVER_ENTRYNAME);
		lpif = lpfnXSFSetup ? lpfnXSFSetup(XSFGETLIBCALLBACK, this) : 0;
		if (!lpif)
		{
			Free();
			return false;
		}
		return true;
	}

	bool Load(xsfc::TString fn, SOUNDINFO *pInfo)
	{

		length_in_ms = CFGGetDefaultLength();
		fade_in_ms = CFGGetDefaultFade();
#ifdef XSFDRIVER_SAMPLERATE_COUNT
		m_samplerate = dwSampleRate;
#endif
		hasvolume = 0;
		volume = 1;

		sLibBasePath = xsfc::TWin32::ExtractPath(fn);

		xsfdata = xsfc::TWin32::Map(fn);
		if (!xsfdata.Ptr())
			return false;

		fUTF8 = XSFTag::Exists("utf8", xsfdata.Ptr(), xsfdata.Len()) != 0;
		if (lpif->dwInterfaceVersion >= 3)
		{
			lpif->SetExtendParam(1, CFGGetExtendParam1());
			lpif->SetExtendParam(2, CFGGetExtendParam2());
		}
		if (lpif->dwInterfaceVersion >= 5)
		{
			lpif->SetExtendParamVoid(1, &dwInterpolation);
			double cfgvolume = 1;
			CFGGetVolume(cfgvolume);
			float cfgvolume_f = (float)cfgvolume;
			lpif->SetExtendParamVoid(2, &cfgvolume_f);
			lpif->SetExtendParamVoid(3, &dwDisableEffects);
			lpif->SetExtendParamVoid(4, &dwUseDeAliasFilter);
		}

		if (lpif->Start(xsfdata.Ptr(), xsfdata.Len()))
		{
			return false;
		}

		if (!XSFTag::GetVolume(&volume, "volume", xsfdata, 0) && dwIgnoreVolumeTag == 0)
			hasvolume = 1;
		//if (XSFTag::Exists("replaygain_album_gain", xsfdata.Ptr(), xsfdata.Len()))
			//hasvolume = 0;
		//else if (XSFTag::Exists("replaygain_track_gain", xsfdata.Ptr(), xsfdata.Len()))
			//hasvolume = 0;

		length_in_ms = XSFTag::GetLengthMS(xsfdata, CFGGetDefaultLength());
		fade_in_ms = XSFTag::GetFadeMS(xsfdata, CFGGetDefaultFade());

		haslength = XSFTag::Exists("length", xsfdata.Ptr(), xsfdata.Len()) != 0;

#ifndef XSFDRIVER_SAMPLERATE
		pInfo->dwSamplesPerSec = 44100;
#else
		pInfo->dwSamplesPerSec = XSFDRIVER_SAMPLERATE;
#endif
		pInfo->dwChannels = 2;
		pInfo->dwBitsPerSample = 16;
		pInfo->dwLength = length_in_ms + fade_in_ms;
		pInfo->dwSeekable = 1;
		pInfo->dwUnitRender = SIZE_OF_BUFFER;
		pInfo->dwReserved1 = 0;
		pInfo->dwReserved2 = 0;

		return true;
	}

	void Start()
	{
		len_smp = MulDiv(length_in_ms, m_samplerate, 1000);
		fad_smp = MulDiv(fade_in_ms, m_samplerate, 1000);
		seek_pos = 0;
		seek_kil = 0;
		seek_req = 0;
		SeekTop();
	}

	int Gen(void *pbuf, unsigned bufsize)
	{
		xsfsample_t *ptop = static_cast<xsfsample_t *>(pbuf);
		unsigned detectSilence = CFGGetDetectSilenceSec();
		unsigned pos = 0;

#ifdef VIOPSF2_REMOVED
		if (lpif->dwInterfaceVersion >= 2)
		{
			bool output = false;
			for (int i = 0; i < 4; i++)
			{
				unsigned long mute = CFGGetChannelMute(i);
				output |= (mute != ~unsigned long(0));
				lpif->SetChannelMute(i, mute);
			}
			if (!output) detectSilence = 0;
		}
#endif
		while (pos < bufsize)
		{
			unsigned ofs;
			xsfsample_t *pblk = ptop + (pos << 1);
			unsigned remain = bufsize - pos;
			lpif->Gen(pblk, remain);
			if (detectSilence || skipSilenceOnStartSec)
			{
				xsfsample_t *pskp = 0;
				xsfsample_t *pcur = pblk;
				for (ofs = 0; ofs < remain; ofs++)
				{
					long smpl = pcur[0];
					long smpr = pcur[1];
					bool silence = (((unsigned long)(smpl + CHECK_SILENCE_BIAS + CHECK_SILENCE_LEVEL)) - prevSampleL <= (CHECK_SILENCE_LEVEL)* 2) && (((unsigned long)(smpr + CHECK_SILENCE_BIAS + CHECK_SILENCE_LEVEL)) - prevSampleR <= (CHECK_SILENCE_LEVEL)* 2);

					if (silence)
					{
						if (++detectedSilenceSample >= m_samplerate)
						{
							detectedSilenceSample -= m_samplerate;
							detectedSilenceSec++;
							if (skipSilenceOnStartSec && detectedSilenceSec >= skipSilenceOnStartSec)
							{
								skipSilenceOnStartSec = 0;
								detectedSilenceSec = 0;
								if (pblk != pcur) pskp = pcur;
							}
						}
					}
					else
					{
						detectedSilenceSample = 0;
						detectedSilenceSec = 0;
						if (skipSilenceOnStartSec)
						{
							skipSilenceOnStartSec = 0;
							if (pblk != pcur) pskp = pcur;
						}
					}
					prevSampleL = smpl + CHECK_SILENCE_BIAS;
					prevSampleR = smpr + CHECK_SILENCE_BIAS;
					pcur += 2;
				}
				if (skipSilenceOnStartSec)
				{
				}
				else if (pskp)
				{
					while (pskp < pcur)
					{
						*(pblk++) = *(pskp++);
						*(pblk++) = *(pskp++);
						pos++;
					}
				}
				else
				{
					pos += remain;
				}
			}
			else
			{
				pos += remain;
			}
		}
		return bufsize;
	}
	unsigned DetectedSilenceSec()
	{
		return detectedSilenceSec;
	}

	int FillBuffer(void *pbuf, unsigned bufbytes, unsigned *writebytes)
	{
		int end_flag = 0;
		unsigned bufsize = bufbytes >> blockshift;
		unsigned cur = MulDiv(cur_smp, 1000, m_samplerate);
		int len = bufsize;
		Gen(pbuf, bufsize);

		/* detect end */
		if (!CFGGetPlayInfinitely())
		{
			if (cur_smp >= len_smp + fad_smp)
			{
				end_flag = 1;
				*writebytes = 0;
				return 1;
			}
			if (cur_smp + len >= len_smp + fad_smp)
			{
				len = len_smp + fad_smp - cur_smp;
				end_flag = 1;
			}
			if (!haslength && CFGGetDetectSilenceSec() && CFGGetDetectSilenceSec() <= detectedSilenceSec)
			{
				end_flag = 1;
				*writebytes = 0;
				return 1;
			}
		}

		

		//We directly control SPU-2X's master volume.
		double cfgvolume = 1;
		bool hascfgvolume = false/*!isdecoder && CFGGetVolume(cfgvolume)*/;
		/* volume */
		if (hasvolume || hascfgvolume)
		{
			float scale = float(volume *  cfgvolume);
			xsfsample_t *p = static_cast<xsfsample_t *>(pbuf);
			unsigned i;
			for (i = cur_smp; i < cur_smp + len; i++)
			{
				float s1, s2;
				s1 = float(p[0]);
				s2 = float(p[1]);
				s1 *= scale;
				if (s1 > float(0x7fff))
					s1 = float(0x7fff);
				else if (s1 < float(-0x8000))
					s1 = float(-0x8000);
				s2 *= scale;
				if (s2 > float(0x7fff))
					s2 = float(0x7fff);
				else if (s2 < float(-0x8000))
					s2 = float(-0x8000);
				p[0] = xsfsample_t(s1);
				p[1] = xsfsample_t(s2);
				p += 2;
			}
		}

		/* fader */
		if ((!CFGGetPlayInfinitely()) && fad_smp && cur_smp + len >= len_smp)
		{
			xsfsample_t *p = static_cast<xsfsample_t *>(pbuf);
			unsigned i;
			for (i = cur_smp; i < cur_smp + len; i++)
			{
				if (i < len_smp)
					;
				else if (i < len_smp + fad_smp)
				{
					int scale = MulDiv(len_smp + fad_smp - i, 0x10000, fad_smp);
					p[0] = (p[0] * scale) >> 16;
					p[1] = (p[1] * scale) >> 16;
				}
				else
				{
					p[0] = 0;
					p[1] = 0;
				}
				p += 2;
			}
		}

		cur_smp += len;
		*writebytes = len << blockshift;
		return end_flag;
	}

	int Seek(unsigned seek_pos, void *pbuf, unsigned bufbytes)
	{
		unsigned bufsize = bufbytes >> blockshift;
		unsigned seek_smp = MulDiv(seek_pos, m_samplerate, 1000);

		if (seek_smp < cur_smp)
		{
			lpif->Start(xsfdata.Ptr(), xsfdata.Len());
			SeekTop();
		}
		while (seek_smp - cur_smp > bufsize)
		{
			unsigned cur = MulDiv(cur_smp, 1000, m_samplerate);
			Gen(pbuf, bufsize);
			cur_smp += bufsize;
		}
		if (seek_smp - cur_smp > 0)
		{
			Gen(pbuf, seek_smp - cur_smp);
			cur_smp = seek_smp;
		}
		return 0;
	}

	void Reinit()
	{
		Free();
		seek_pos = 0;
	}
};


//**********************************************************************************************
//KMPMODULE
//**********************************************************************************************
static void WINAPI Init(void)
{
	LoadConfig();
}

static HKMP WINAPI Open(const char *cszFileName, SOUNDINFO *pInfo)
{
	XSFDriver* ret = NULL;
	if (!pInfo) return ret;

	//LoadConfig();
	
	ret = new XSFDriver;
	if (ret)
	{
		ret->Free();
		if (!ret->Init(hDLL) || !ret->Load(xsfc::TString(false, cszFileName), pInfo))
		{
			ret->Free();
			delete ret;
			ret = NULL;
		}
		else
			ret->Start();
	}

	return ret;
}


static void  WINAPI Close(HKMP hKMP)
{
	XSFDriver *p = (XSFDriver*)hKMP;
	if (p)
	{
		p->Free();
		delete p;
		p = NULL;
	}
}

static DWORD WINAPI Render(HKMP hKMP, BYTE* Buffer, DWORD dwSize)
{
	XSFDriver *p = (XSFDriver*)hKMP;
	unsigned int dwFillSize = 0;
	if (p->FillBuffer(Buffer, dwSize, &dwFillSize))	//endflag == 1
		return 0;

	return dwSize;
}

static DWORD WINAPI SetPosition(HKMP hKMP, DWORD dwPos)
{
	XSFDriver *p = (XSFDriver*)hKMP;
	BYTE DummyBuf[SIZE_OF_BUFFER<<1];
	p->Seek(dwPos, DummyBuf, sizeof(DummyBuf));
	return dwPos;
}

KBMEDIAPLUGIN_EXTS(exts)

static const KMPMODULE mod =
{
	KMPMODULE_VERSION,
	KBMEDIAPLUGIN_VERSION,
	KBMEDIAPLUGIN_COPYRIGHT,
	KBMEDIAPLUGIN_NAME,
	(const char **)exts,
#if ENABLE_LOADPE
	1,
#else
	0,
#endif
	Init,
	NULL,
	Open,
	NULL,
	Close,
	Render,
	SetPosition
};

extern "C" KMPMODULE* WINAPI kmp_GetTestModule(void)
{
	return (KMPMODULE*)&mod;
}


//********************************************************************************************************
// Config                                                                                               
//********************************************************************************************************
class IniConfig : public IConfigIO
{
	xsfc::TString sIniPath;
protected:
	IniConfig() throw()
	{
	}

	bool Initialize() throw(xsfc::EShortOfMemory)
	{
		sIniPath = xsfc::TWin32::ExtractPath(xsfc::TWin32::ModulePath(hDLL)) + KBMEDIAPLUGIN_INI_NAME;
		return true;
	}

public:
	~IniConfig() throw()
	{
	}

	void SetULong(const wchar_t *name, const unsigned long value) throw()
	{
		try
		{
			xsfc::TWin32::SetPrivateProfile(sIniPath, xsfc::TString(XSFDRIVER_SIMPLENAME), name, xsfc::TString(value));
		}
		catch (xsfc::EShortOfMemory e)
		{
		}
	}
	unsigned long GetULong(const wchar_t *name, const unsigned long defaultvalue = 0) throw()
	{
		try
		{
			xsfc::TString value = xsfc::TWin32::GetPrivateProfile(sIniPath, xsfc::TString(XSFDRIVER_SIMPLENAME), name, xsfc::TString(defaultvalue));
			if (value[0]) return value.GetULong();
		}
		catch (xsfc::EShortOfMemory e)
		{
		}
		return defaultvalue;
	}
	void SetFloat(const wchar_t *name, const double value) throw()
	{
		try
		{
			xsfc::TWin32::SetPrivateProfile(sIniPath, xsfc::TString(XSFDRIVER_SIMPLENAME), name, xsfc::TString(value));
		}
		catch (xsfc::EShortOfMemory e)
		{
		}
	}
	double GetFloat(const wchar_t *name, const double defaultvalue = 0) throw()
	{
		try
		{
			xsfc::TString value = xsfc::TWin32::GetPrivateProfile(sIniPath, xsfc::TString(XSFDRIVER_SIMPLENAME), name, xsfc::TString(defaultvalue));
			if (value[0]) return value.GetFloat();
		}
		catch (xsfc::EShortOfMemory e)
		{
		}
		return defaultvalue;
	}
	void SetString(const wchar_t *name, const wchar_t *value) throw()
	{
		try
		{
			xsfc::TWin32::SetPrivateProfile(sIniPath, xsfc::TString(XSFDRIVER_SIMPLENAME), name, value);
		}
		catch (xsfc::EShortOfMemory e)
		{
		}
	}
	xsfc::TString GetString(const wchar_t *name, const wchar_t *defaultvalue = 0) throw(xsfc::EShortOfMemory)
	{
		return xsfc::TWin32::GetPrivateProfile(sIniPath, xsfc::TString(XSFDRIVER_SIMPLENAME), name, defaultvalue);
	}

	static LPIConfigIO Create() throw(xsfc::EShortOfMemory)
	{
		xsfc::TAutoPtr<IniConfig> cfg(new(xsfc::nothrow) IniConfig);
		return (cfg && cfg->Initialize()) ? cfg.Detach() : 0;
	}
};

static BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_COMMAND:
		switch (GET_WM_COMMAND_ID(wParam, lParam))
		{
		case IDOK:
			CFGUpdate(pcfg, hwndDlg);
			CFGSave(pcfg);
			::EndDialog(hwndDlg, IDOK);
			break;
		case IDCANCEL:
			::EndDialog(hwndDlg, IDCANCEL);
			break;
		case 0x206:
			if (GET_WM_COMMAND_CMD(wParam, lParam) == LBN_SELCHANGE)
			{
				CFGMuteChange(hwndDlg, 0x206);
			}
			break;
		default:
			return FALSE;
		}
		break;
	case WM_INITDIALOG:
		::SetWindowTextA(hwndDlg, WINAMPPLUGIN_NAME);
		::ShowWindow(GetDlgItem(hwndDlg, IDOK), SW_SHOWNA);
		::ShowWindow(GetDlgItem(hwndDlg, IDCANCEL), SW_SHOWNA);
		::EnableWindow(GetDlgItem(hwndDlg, IDOK), TRUE);
		::EnableWindow(GetDlgItem(hwndDlg, IDCANCEL), TRUE);
		::EnableWindow(GetDlgItem(hwndDlg, IDC_SUPPORT_PSF2), FALSE);

		CFGLoad(pcfg);
		CFGReset(pcfg, hwndDlg);
		return TRUE;
	}
	return FALSE;
}


//Load Config
void LoadConfig()
{
	if (!pcfg)
	{
		pcfg = IniConfig::Create();
		if (!pcfg) pcfg = NullConfig::Create();
		CFGLoad(pcfg);
	}
}

//kmp_Config
extern "C" DWORD WINAPI kmp_Config(HWND hWnd, DWORD dwVersion, DWORD dwReserved)
{
	LoadConfig();

	HRSRC hrsrc;
	DWORD dwSize;
	HGLOBAL hGlobal;
	LPVOID pDlgTemplate;
	xsfc::TSimpleArray<BYTE> resbuf;

	hrsrc = ::FindResourceA(hDLL, MAKEINTRESOURCE(IDD_DIALOG1), RT_DIALOG);
	if (!hrsrc) return 0;

	dwSize = ::SizeofResource(hDLL, hrsrc);
	hGlobal = ::LoadResource(hDLL, hrsrc);
	if (!hGlobal)
		return 0;
	pDlgTemplate = ::LockResource(hGlobal);

	if (dwSize < 4 || !pDlgTemplate) return 0;

	if (!resbuf.Resize(dwSize)) return 0;

	LPBYTE lpResource = resbuf.Ptr();

	::CopyMemory(lpResource, pDlgTemplate, dwSize);


	//Change Window Style -> DS_CENTER | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
	lpResource[0] = '\xc0';
	lpResource[1] = '\x08';
	lpResource[2] = '\xc8';
	lpResource[3] = '\x80';

	xsfc::TWin32::DlgInvoke(hDLL, lpResource, hWnd, DialogProc, NULL);
	return 0;
}