/*
 *  Plugin for lame encoding
 *
 *  this version loads the library in runtime
 *  might look a bit complicated but has few advantages - you do not have
 *  to have libmp3lame installed on your system while you still could build
 *  this working plugin
 */

#include "lameencoder.h"
#include <dlfcn.h>
#include <string.h>
#include <iostream>

using namespace std;

static const char* mp3lamename = "libmp3lame.so";

LameEncoder::LameEncoder(const CodecInfo& info, WAVEFORMATEX* format)
    :IAudioEncoder(info)
{
    handle = dlopen(mp3lamename, RTLD_LAZY);

    if (!handle)
    {
	cerr<<"WARNING: Lame library "
	    <<mp3lamename<<" could not be opened: "<<dlerror()<<endl;
	throw;
    }

    // resolve all needed function calls
    p_lame_init = (lame_global_flags * CDECL (*)(void))
	dlsym(handle, "lame_init");
    p_lame_init_params = (int CDECL (*)(lame_global_flags *))
	dlsym(handle, "lame_init_params");
    p_lame_set_padding_type = (int CDECL (*)(lame_global_flags *, Padding_type))
	dlsym(handle, "lame_set_padding_type");
    p_lame_set_VBR = (int CDECL (*)(lame_global_flags *, vbr_mode))
	dlsym(handle, "lame_set_VBR");
    p_lame_set_in_samplerate = (int CDECL (*)(lame_global_flags *, int))
	dlsym(handle, "lame_set_in_samplerate");
    p_lame_set_num_channels = (int CDECL (*)(lame_global_flags *, int))
	dlsym(handle, "lame_set_num_channels");
    p_lame_set_mode = (int CDECL (*)(lame_global_flags *, MPEG_mode))
	dlsym(handle, "lame_set_mode");
    p_lame_set_brate = (int CDECL (*)(lame_global_flags *, int))
	dlsym(handle, "lame_set_brate");
    p_lame_set_quality = (int CDECL (*)(lame_global_flags *, int))
	dlsym(handle, "lame_set_quality");
    p_lame_get_framesize = (int CDECL (*)(const lame_global_flags *))
	dlsym(handle, "lame_get_framesize");

    p_lame_encode_buffer_interleaved = (int CDECL (*)(lame_global_flags*  gfp,
						      short int           pcm[],
						      int                 num_samples,
						      unsigned char*      mp3buf,
						      int                 mp3buf_size))
	dlsym(handle, "lame_encode_buffer_interleaved");

    p_lame_encode_finish = (int CDECL (*)(lame_global_flags*  gfp,
					  unsigned char*      mp3buf,
					  int                 size))
	dlsym(handle, "lame_encode_finish");

    p_lame_encode_buffer = (int CDECL (*)(lame_global_flags*  gfp,
					  const short int     buffer_l [],
					  const short int     buffer_r [],
					  const int           nsamples,
					  unsigned char*      mp3buf,
					  const int           mp3buf_size))
	dlsym(handle, "lame_encode_buffer");

    in_fmt = *format;

    gf = p_lame_init();
    p_lame_set_padding_type(gf, PAD_ADJUST);
    p_lame_set_VBR(gf, vbr_off);
    p_lame_set_in_samplerate(gf, in_fmt.nSamplesPerSec);
    p_lame_set_num_channels(gf, in_fmt.nChannels);

    if (in_fmt.nChannels==1)
	p_lame_set_mode(gf, MONO);
    else
	p_lame_set_mode(gf, JOINT_STEREO);
    /* 0,1,2,3 stereo,jstereo,dual channel,mono */

    cout << "LameEncoder initialized" << endl;
}

LameEncoder::~LameEncoder()
{
    Close(0,0,0);
    dlclose(handle);
}

int LameEncoder::SetBitrate(int bitrate)
{
    brate = bitrate/125;
    cerr<<"Setting bit rate to "<<bitrate<<" "<<brate<<endl;

    return p_lame_set_brate(gf, brate);
}

int LameEncoder::SetQuality(int quality)
{
    return p_lame_set_quality(gf, quality);
}
struct __attribute__((__packed__)) strf_mp3
{
    short wID;
    int fdwFlags;
    short  nBlockSize;
    short  nFramesPerBlock;
    short  nCodecDelay;
};

/* here are some flags pertaining to the above structure */
#define MPEGLAYER3_ID_UNKNOWN            0
#define MPEGLAYER3_ID_MPEG               1
#define MPEGLAYER3_ID_CONSTANTFRAMESIZE  2
#define MPEGLAYER3_FLAG_PADDING_ISO      0x00000000
#define MPEGLAYER3_FLAG_PADDING_ON       0x00000001
#define MPEGLAYER3_FLAG_PADDING_OFF      0x00000002

int LameEncoder::GetFormatSize()
{
    return 30;
}

int LameEncoder::GetFormat(void* extension, size_t size)
{
    if(size<30)return -1;
    WAVEFORMATEX wf;
    strf_mp3 mp3extra;
    memcpy(&wf, &in_fmt, 18);
    wf.wFormatTag=0x55;
    wf.nAvgBytesPerSec = brate * 125;
    wf.nBlockAlign=1;
    wf.wBitsPerSample=0;
    wf.cbSize=12;
    memcpy(extension, &wf, 18);

    p_lame_init_params(gf);
    mp3extra.wID             = 1;        //
    mp3extra.fdwFlags        = 2;        // These values
    mp3extra.nBlockSize      = p_lame_get_framesize(gf);      // based on an
    mp3extra.nFramesPerBlock = 1;        // old Usenet post!!
    mp3extra.nCodecDelay     = 1393;     //
    memcpy((uint8_t*)extension+18, &mp3extra, 12);

    return 0;
}
int LameEncoder::Start()
{
    p_lame_init_params(gf);
    cout << "start done" << endl;
    return 0;
}
int LameEncoder::Convert(const void* in_data, size_t in_size,
			 void* out_data, size_t out_size,
			 size_t* size_read, size_t* size_written)
{
#warning FIXME 8-bit?
    int result;

    if (in_fmt.nChannels == 1)
	result=p_lame_encode_buffer(gf, (short*)in_data,
				    (short*)in_data, in_size,
				    (unsigned char*)out_data, out_size);
    else
	result=p_lame_encode_buffer_interleaved(gf, (short*)in_data, in_size,
						(unsigned char*)out_data, out_size);
    if (result < 0)
	result = 0;
    if (size_read)
	*size_read = in_size;
    if (size_written)
	*size_written = result;
    return 0;
}

int LameEncoder::Close(void* out_data, size_t out_size, size_t* size_read)
{
    uint8_t buffer[7200];
    cout << "LameEncoder::Close()" << endl;
    size_t bytes = p_lame_encode_finish(gf, buffer, sizeof(buffer));
    if (out_size < bytes)
	bytes = out_size;
    if (out_data)
	memcpy(out_data, buffer, bytes);
    if (out_data && size_read)
	*size_read = bytes;
    return 0;
}
