#define __MAKE_PLUGIN__

#include<hgl/plugin/PlugIn.h>
#include<hgl/plugin/PlugInInterface.h>
#include<malloc.h>
#include<string.h>
#include<hgl/al/al.h>

#include<vorbis/vorbisfile.h>

using namespace hgl;
using namespace openal;

struct ogg_fileread
{
    unsigned char *data;
    size_t pos;
    size_t size;
};

size_t VorbisRead(void *buffer,size_t size,size_t count,void *ptr)
{
    ogg_fileread *obj=(ogg_fileread *)ptr;

    size*=count;

    if(obj->pos+size>obj->size)
        size=obj->size-obj->pos;

    if(size)
    {
        memcpy(buffer,obj->data+obj->pos,size);

        obj->pos+=size;
    }

    return(size);
}

int VorbisSeek(void *ptr,ogg_int64_t offset,int whence)
{
    ogg_fileread *obj=(ogg_fileread *)ptr;

    if(whence==SEEK_SET)
    {
        obj->pos=offset;
    }
    else
    if(whence==SEEK_CUR)
    {
        obj->pos+=offset;
    }else
    if(whence==SEEK_END)
    {
        obj->pos=obj->size+offset;
    }

    if(obj->pos>obj->size)obj->pos=obj->size;

    return(0);
}

int VorbisClose(void *)
{
    return(0);
}

long VorbisTell(void *ptr)
{
    return ((ogg_fileread *)ptr)->pos;
}

ALvoid LoadOGG(ALbyte *memory, ALsizei memory_size,ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq, ALboolean *loop)
{
    int             result;
    char *          ptr=nullptr;

    ov_callbacks    func;
    OggVorbis_File  ogg_stream;
    vorbis_info *   info;
    int             section;

    ogg_fileread    ogg_memory;

    func.read_func  =VorbisRead;
    func.close_func =VorbisClose;
    func.seek_func  =VorbisSeek;
    func.tell_func  =VorbisTell;

    ogg_memory.data=(unsigned char *)memory;
    ogg_memory.pos=0;
    ogg_memory.size=memory_size;

    *size=0;

    if(ov_open_callbacks(&ogg_memory,&ogg_stream,NULL,0,func)!=0)
        return;

    info    =ov_info(&ogg_stream,-1);

    if(info->channels==1)*format=AL_FORMAT_MONO16;
                    else *format=AL_FORMAT_STEREO16;

    const int pcm_total_bytes = ov_pcm_total(&ogg_stream,0)*info->channels*2;

    int out_size = 0;

    ptr = new char[pcm_total_bytes];

    while(true)
    {
        result=ov_read(&ogg_stream,ptr+out_size,pcm_total_bytes-out_size,
                       0,           // 0: little-endian     1: big_endian
                       2,           // 1: 8 bit-samples     2: 16 bit-samples
                       1,           // 0: unsigned          1: signed
                       &section);

        if(result<=0)break;

        out_size += result;
    }

    *freq=info->rate;
    *size=out_size;
    *data=ptr;

    ov_clear(&ogg_stream);
}

void ClearOGG(ALenum, ALvoid *data, ALsizei, ALsizei)
{
    if (data)
        delete[] (char *)data;
}
//--------------------------------------------------------------------------------------------------
struct OggStream
{
    ov_callbacks    func;

    ogg_fileread    ogg_memory;
    OggVorbis_File    ogg_stream;
};

void *OpenOGG(ALbyte *memory,ALsizei memory_size,ALenum *format,ALsizei *rate,double *total_time)
{
    vorbis_info *info;

    OggStream *ptr=new OggStream;

    ptr->func.read_func  =VorbisRead;
    ptr->func.close_func =VorbisClose;
    ptr->func.seek_func  =VorbisSeek;
    ptr->func.tell_func  =VorbisTell;

    ptr->ogg_memory.data=(unsigned char *)memory;
    ptr->ogg_memory.pos=0;
    ptr->ogg_memory.size=memory_size;

    if(ov_open_callbacks(&(ptr->ogg_memory),&(ptr->ogg_stream),NULL,0,ptr->func)!=0)
        return(nullptr);

    info    =ov_info(&(ptr->ogg_stream),-1);

    if(info->channels==1)*format=AL_FORMAT_MONO16;
                    else *format=AL_FORMAT_STEREO16;

    *rate=info->rate;

    *total_time=ov_time_total(&(ptr->ogg_stream),-1);

    return(ptr);
}

void CloseOGG(void *ptr)
{
    OggStream *obj=(OggStream *)ptr;

    ov_clear(&(obj->ogg_stream));

    delete obj;
}

uint ReadOGG(void *ptr,char *data,uint buf_max)
{
    OggStream *obj=(OggStream *)ptr;
    int section;
    int result;
    uint size=0;

    while(size<buf_max)
    {
        result=ov_read(&(obj->ogg_stream),data+size,buf_max-size,0,2,1,&section);

        if(result>0)size+=result;else
        if(result<0)return(0);else break;
    }

    if(size==0)return(0);

    return(size);
}

void RestartOGG(void *ptr)
{
    OggStream *obj=(OggStream *)ptr;

    ov_pcm_seek(&(obj->ogg_stream),0);
}
//--------------------------------------------------------------------------------------------------
struct OutInterface
{
    void (*Load)(ALbyte *,ALsizei,ALenum *,ALvoid **,ALsizei *,ALsizei *,ALboolean *);
    void (*Clear)(ALenum,ALvoid *,ALsizei,ALsizei);

    void *(*Open)(ALbyte *,ALsizei,ALenum *,ALsizei *,double *);
    void  (*Close)(void *);
    uint  (*Read)(void *,char *,uint);
    void  (*Restart)(void *);
};

static OutInterface out_interface=
{
    LoadOGG,
    ClearOGG,

    OpenOGG,
    CloseOGG,
    ReadOGG,
    RestartOGG
};
//--------------------------------------------------------------------------------------------------
#if HGL_OS != HGL_OS_Windows
const u16char plugin_intro[]=U16_TEXT("Vorbis OGG 音频文件解码(使用操作系统内置解码器,2016-09-16)");
#else
const u16char plugin_intro[]=U16_TEXT("Vorbis OGG 音频文件解码(LibOGG 1.3.5,LibVorbis 1.3.7,2022-03-31)");
#endif//

HGL_PLUGIN_FUNC uint32 GetPlugInVersion()
{
    return(2);        //版本号
                    //根据版本号取得不同的API
}

u16char * GetPlugInIntro()
{
    return((u16char *)plugin_intro);
}

bool GetPlugInInterface(uint32 ver,void *data)
{
    if(ver==2)
    {
        memcpy(data,&out_interface,sizeof(OutInterface));
    }
    else
        return(false);

    return(true);
}

static PlugInInterface pii=
{
    nullptr,

    GetPlugInVersion,
    GetPlugInIntro,

    GetPlugInInterface,
    nullptr,

    nullptr,
    nullptr
};

HGL_PLUGIN_FUNC PlugInInterface *InitPlugIn()
{
    return &pii;
}
//--------------------------------------------------------------------------------------------------
