/*
  [処理系無依存]
    無圧縮wavを読み込みます。
*/
#include <windows.h>
#include "kpi.h"
#include "kpi_decoder.h"

#include "xstdlib.h"
#include "wav_load.h"

extern IKpiUnkProvider *g_pProvider;

static unsigned GetDwordLE(unsigned char *p)
{
	return p[0] + ((unsigned)p[1] << 8) + ((unsigned)p[2] << 16) + ((unsigned)p[3] << 24);
}

static unsigned GetWordLE(unsigned char *p)
{
	return p[0] + ((unsigned)p[1] << 8);
}

typedef struct {
	XFILE *fp;
	unsigned unit;
} RTLOADERF;
/*
const static char smp_byte_tbl[4] = { 0, 1, 1, 2 };

static void RTLClose(void *ctx)
{
	RTLOADERF *rtl = (RTLOADERF*)ctx;
	if (rtl->fp)
	{
		XCLOSE(rtl->fp);
		rtl->fp = 0;
	}
}
static int RTLLoader(void *ctx, BMS_WAV *wavp)
{
	unsigned ret, next;
	RTLOADERF *rtl = (RTLOADERF*)ctx;
	next = wavp->lenb - wavp->load;
	if (next > rtl->unit) next = rtl->unit;
	ret = XREAD(rtl->fp, (unsigned char *)wavp->p + wavp->load, next);
	wavp->load += ret;
	if (ret < next || wavp->load == wavp->lenb)
	{
		XMEMSET((unsigned char *)wavp->p + wavp->load, (wavp->type < 2) ? 0x80 : 0, 8);
		wavp->len = wavp->load >> smp_byte_tbl[wavp->type & 3];
		wavp->load += 8;
		RTLClose(ctx);
		wavp->ctx = 0;
	}
	return ret;
}
*/
typedef struct {
	IKpiDecoder *pDecoder;
	DWORD dwUnitSample;
    DWORD dwBlockAlign;
} RTLOADERFKPI;

static void RTLCloseKpi(void *ctx)
{
	RTLOADERFKPI *rtl = (RTLOADERFKPI*)ctx;
	if (rtl->pDecoder){
        rtl->pDecoder->Release();
        rtl->pDecoder = NULL;
	}
    free(rtl);
}
static int RTLLoaderKpi(void *ctx, BMS_WAV *wavp)
{
	unsigned ret, next;
	RTLOADERFKPI *rtl = (RTLOADERFKPI*)ctx;
    DWORD dwBlockAlign = rtl->dwBlockAlign;
	next = wavp->lenb - wavp->load;
	if (next >= rtl->dwUnitSample*dwBlockAlign){
        next = rtl->dwUnitSample*dwBlockAlign;
    	ret = rtl->pDecoder->Render(((BYTE*)wavp->p) + wavp->load, rtl->dwUnitSample);
    }
    else{
        BYTE *tmp = (BYTE*)malloc(rtl->dwUnitSample*dwBlockAlign);
    	ret = rtl->pDecoder->Render(tmp, rtl->dwUnitSample);
        if(wavp->load + ret * dwBlockAlign > wavp->lenb){
            wavp->lenb += ret * dwBlockAlign;
            wavp->p = realloc(wavp->p, wavp->lenb + 8);
        }
        memcpy(((BYTE*)wavp->p) + wavp->load, tmp, ret * dwBlockAlign);
        free(tmp);
    }
	wavp->load += ret*dwBlockAlign;
	if (ret < rtl->dwUnitSample || wavp->load == wavp->lenb)
	{
		memset((unsigned char *)wavp->p + wavp->load, (wavp->type < 2) ? 0x80 : 0, 8);
		wavp->len = wavp->load / dwBlockAlign;
		wavp->load += 8;
		RTLCloseKpi(ctx);
		wavp->ctx = 0;
	}
	return ret;
}

int WavLoad(BMS_WAV *wavp, IKpiFolder *pFolder, const wchar_t *wavpath, int srate)
{
    const UINT64 qwMaxLength = 30 * 60 * 1000 * 10000ui64;//30分
    //拡張子が .wav の場合は .mp3 と .ogg で開き直してみる
    static const wchar_t *ext_list[] = {L".mp3", L".ogg", NULL};
	ZeroMemory(wavp, sizeof(BMS_WAV));
    wchar_t path[MAX_PATH];
    wcsncpy_s(path, wavpath, _TRUNCATE);
    wchar_t *pszExt = wcsrchr(path, L'.');
    if(!pszExt){
        return 0;
    }
    bool bIsWav = _wcsicmp(pszExt, L".wav") == 0;
    IKpiFile *pFile = NULL;
    IKpiDecoder *pDecoder = NULL;
    pFolder->OpenFile(path, &pFile);
    int nExt = 0;
    do{
        if(!pFile){
            if(!bIsWav){
                return 0;
            }
            while(ext_list[nExt]){
                pszExt[0] = 0;
                wcsncat_s(path, ext_list[nExt], _TRUNCATE);
                if(pFolder->OpenFile(path, &pFile)){
                    break;
                }
                nExt++;
            }
            if(!pFile){
                return 0;
            }
        }
        KPI_MEDIAINFO request;
        kpi_InitMediaInfo(&request);
        request.dwSampleRate = srate;//再生周波数はプラグインによる
        request.nBitsPerSample = 16; //ビット数は要求通り返ってくる(本体が必要に応じて変換)
        request.dwChannels = 2;      //チャンネル数は 2.80beta41 では要求通りにはならない
        kpi_CreateDecoder(g_pProvider, &request, pFile, pFolder, &pDecoder);
        if(!pDecoder){
            break;
        }
        const KPI_MEDIAINFO *cpMediaInfo = NULL;
        if(!pDecoder->Select(1, &cpMediaInfo, NULL, 0)){
            break;
        }
        if(cpMediaInfo->dwFormatType != KPI_MEDIAINFO::FORMAT_PCM){//DoP 非対応
            break;
        }
        UINT64 qwLength = cpMediaInfo->qwLength;
        if(cpMediaInfo->nBitsPerSample != 16){//ビット数が要求通りにならない場合は処理しない
            break;
        }
        if(cpMediaInfo->dwChannels != 1 &&
           cpMediaInfo->dwChannels != 2){//マルチチャンネル非対応
            break;
        }
        if(qwLength == (UINT64)-1){//曲長不明
            qwLength = 5*60*1000*10000ui64;//5分
        }
        else if(qwLength > qwMaxLength){//長すぎ
            qwLength = qwMaxLength;
        }
        UINT64 qwLenSmp = kpi_100nsToSample(qwLength, cpMediaInfo->dwSampleRate) + 100;//少し多めに確保しておく
        DWORD  dwBlockAlign = (abs(cpMediaInfo->nBitsPerSample)/8) * cpMediaInfo->dwChannels;
        UINT64 qwLenBytes = qwLenSmp * dwBlockAlign;
        DWORD dd = qwLenBytes;
        wavp->p = malloc(dd + 8);// + sizeof(RTLOADERFKPI));
        if(!wavp->p){
            break;
        }
        RTLOADERFKPI *rtl;
        rtl = (RTLOADERFKPI*)malloc(sizeof(RTLOADERFKPI));
        rtl->pDecoder = pDecoder;
        rtl->dwUnitSample = cpMediaInfo->dwUnitSample;
        if(rtl->dwUnitSample == 0){
            rtl->dwUnitSample = 1024;
        }
        rtl->dwBlockAlign = dwBlockAlign;
        if(cpMediaInfo->dwChannels == 1){
            wavp->type = 0x02;
        }else{
            wavp->type = 0x03;
        }
        wavp->load = 0;
        wavp->lenb = dd;
        wavp->len = dd / dwBlockAlign;
        wavp->freq = cpMediaInfo->dwSampleRate;
        wavp->ctx = rtl;
        wavp->Loader = RTLLoaderKpi;
        wavp->Close = RTLCloseKpi;
        pFile->Release();    //kbbms.kpi としては不要。必要があれば本体orプラグイン側で AddRef されている
        pDecoder->Seek(0, 0);//Render 呼び出し前に Seek しなければならない
        return 1;
    }while(1);
	if(pDecoder){
        pDecoder->Release();
    }
    if(pFile){
        pFile->Release();
    }
    if(wavp->p){
        free(wavp->p);
    }
	ZeroMemory(wavp, sizeof(BMS_WAV));
    return 0;
}
