#include <cpuinfo.h>
#include <creators.h>
#include <plugin.h>

#include "../plugins/libwin32/fillplugins.h"
#include "../plugins/libaudiodec/fillplugins.h"
#include "../plugins/libffmpeg/fillplugins.h"
#include "../plugins/libmp3lame_audioenc/fillplugins.h"
#include "../plugins/libmp3lamebin_audioenc/fillplugins.h"
#include "../plugins/libmpeg_audiodec/fillplugins.h"
#include "Uncompressed.h"

#include <stdlib.h>
#include <dirent.h>
#include <dlfcn.h>

#include <fstream>
#include <strstream>
#include <iostream>
#include <cstdio>

using namespace std;

#ifndef	RTLD_PARENT
#define	RTLD_PARENT 0
#endif

#define Debug if(0)
vector<CodecInfo> video_codecs;
vector<CodecInfo> audio_codecs;

const char* plugin_def_path=PLUGIN_PATH;
CPU_Info freq; // will be initialized as the first thing

class CodecInfoKeeper
{
public:
    CodecInfoKeeper();
};

CodecInfoKeeper codec_keeper;

static string last_error;

static void* PluginOpen(const char* name)
{
    void* handle = dlopen(name, RTLD_LAZY | RTLD_PARENT);
    if(!handle)
    {
	cerr << "WARNING: plugin " << name << " could not be opened: "
	    << dlerror() << endl;
    }
    else
    {
	int (*Version)() = (int(*)()) dlsym(handle, "GetPluginVersion");
	if (!Version || (Version() != PLUGIN_API_VERSION))
	{
	    if (Version)
		cerr << "WARNING: plugin " << name << " is version "
		    << Version() << ", expected "
		    << PLUGIN_API_VERSION << endl;
	    else
		cerr << "WARNING: plugin " << name
		    << " is in incompatible format" << endl;
	    dlclose(handle);
	    handle = 0;
	}
    }
    return handle;
}

static void PluginAddList(vector<CodecInfo>& ci, const string fullname)
{
    int vp = 0;
    for (unsigned i = 0; i < ci.size(); i++)
    {
	ci[i].modulename = fullname;
	if (ci[i].media == CodecInfo::Video)
	{
	    vp++;
	    //cout << "add " << ci[i].text << endl;
	    video_codecs.push_back(ci[i]);
	}
	else
	    audio_codecs.push_back(ci[i]);
    }
    cerr << fullname << ": found " << ci.size()
	<< ((ci.size() == 1) ? " plugin" : " plugins")
	<< "   (A: " << ci.size() - vp << "  V: " << vp << ")"
	<< endl;
}

static void PluginGetError(void* handle)
{
    string (*getError)() = (string(*)())dlsym(handle, "GetError");
    last_error = (getError) ? getError() : "unknown";
}

CodecInfoKeeper::CodecInfoKeeper()
{
    uncompressed_FillPlugins(video_codecs);

    if (getenv("AVIPLUGIN_PATH"))
	plugin_def_path=getenv("AVIPLUGIN_PATH");

    struct dirent *dp;
#if HAVE_SCANDIR
    struct dirent **namelist;
    int n;
    n = scandir(plugin_def_path, &namelist, 0, alphasort);
    if(n>0)
    while(n--)
    {
	dp = namelist[n];
#else  // !HAVE_SCANDIR
    DIR *dir = opendir(plugin_def_path);
    if (dir != NULL) while ((dp = readdir(dir)) != NULL)
    {
#endif // HAVE_SCANDIR
	char* name=dp->d_name;
//	cerr<<"Checking "<<endl;
//	cerr<<name<<endl;
	if(strlen(name)<4)continue;
//	if(!n)continue;
	if(strcmp(name+strlen(name)-3, ".la"))continue;
	string lfn = string(plugin_def_path)+"/"+name;
	string dllname="";
	string temp;
	ifstream file(lfn.c_str());
	while (getline(file, temp))
	{
//	    file=getline(file, temp);
	    if(temp.substr(0, 8)=="dlname='")
	    {
		dllname=temp.substr(8);
		dllname.erase(dllname.size()-1);
		break;
	    }
	}
	if (!dllname.size())
	{
	    cerr<<"WARNING: plugin "<<lfn<<" is invalid"<<endl;
	    continue;
	}
	string fullname=string(plugin_def_path) + "/" + dllname;

	static const struct plugin_map {
	    const char* name;
            void (*fill)(vector<CodecInfo>& ci);
	} pm[] = {
	    { "libffmpeg.", libffmpeg_FillPlugins },
	    { "libwin32.", libwin32_FillPlugins },
	    { "libaudiodec.", libaudiodec_FillPlugins },
	    { "libmp3lame_audioenc.", libmp3lame_FillPlugins },
	    { "libmp3lamebin_audioenc.", libmp3lamebin_FillPlugins },
	    { "libmpeg_audiodec.", libmpeg_audiodec_FillPlugins },
	    { 0, 0 }
	};

	/**
	 * This chunk of code eliminates the need to open several shared
	 * libraries ( some 5 Megabytes of data ) while starting every app
	 */
	const plugin_map* c = pm;
	for (; c->name; c++)
	{
	    if (dllname.substr(0, strlen(c->name)) == c->name)
	    {
		vector<CodecInfo> ci;
		c->fill(ci);
                PluginAddList(ci, fullname);
		break;
	    }
	}
	if (c->name)
            continue; // found in the table above

	// third party plugin
	void* handle = PluginOpen(fullname.c_str());
	if (!handle)
	    continue;
	const vector<CodecInfo>& (*vRegisterPlugin)()=
	    (const vector<CodecInfo>& (*)())dlsym(handle, "RegisterPlugin");
	if (vRegisterPlugin)
	{
	    vector<CodecInfo> ci = vRegisterPlugin();
	    PluginAddList(ci, fullname);
	}
        else
	    cerr<<"WARNING: plugin "<<fullname<<" has incompatible format"<<endl;

	dlclose(handle);
    }
#if !HAVE_SCANDIR
    if (dir != NULL)
	closedir(dir);
#endif
}


namespace Creators {
string GetError()
{
    return last_error;
}


IVideoDecoder* CreateVideoDecoder(const BITMAPINFOHEADER& bh, int depth, int flip)
{
    vector<CodecInfo>::iterator it;
    for(it=video_codecs.begin(); it!=video_codecs.end(); it++)
    {
	if (!(it->direction & CodecInfo::Decode))
	    continue;
	vector<fourcc_t>::const_iterator iv;
	for (iv = it->fourcc_array.begin(); iv != it->fourcc_array.end(); iv++)
	{
	    //cout << "Create2 " << it->kind  << endl;
	    Debug printf("Kind: 0x%x  %.4s    %.4s =  0x%x\n", it->kind, (const char*) &(*iv), (const char*) &(bh.biCompression), bh.biCompression);
	    if (unsigned(bh.biCompression) == *iv)
	    {

		IVideoDecoder* (*pCreateVideoDecoder)(const CodecInfo&, const BITMAPINFOHEADER&, int);

		switch(it->kind)
		{
   		case CodecInfo::Source:
		    return new Unc_Decoder(*it, bh, flip);
		default:
		    void* handle = PluginOpen(it->modulename.c_str());
		    if (!handle)
			continue;
		    pCreateVideoDecoder =
			(IVideoDecoder* (*)(const CodecInfo&, const BITMAPINFOHEADER&, int))
			dlsym(handle, "CreateVideoDecoder");
		    if (pCreateVideoDecoder)
		    {
			IVideoDecoder* result
			    = pCreateVideoDecoder(*it, bh, flip);
			if (!result
			    && (fourcc_t)bh.biCompression != it->fourcc)
			{
			    // try again with default codec for this
                            // fourcc array
			    printf("Trying to use %.4s instead of %.4s\n",
				   (const char*)&it->fourcc,
				   (const char*)&bh.biCompression);
			    BITMAPINFOHEADER dbh(bh);
			    dbh.biCompression = it->fourcc;
			    result = pCreateVideoDecoder(*it, dbh, flip);
			}

			if (result)
			{
			    it->handle = handle;
			    return result;
			}
			PluginGetError(handle);
		    }
		    dlclose(handle);
		}//switch
	    }//if
	}
    }//for

    cout << "ERROR" << endl;
    strstream err;
    char s[5];
    *(int*)s = bh.biCompression;
    s[4]=0;
    err << "Unknown codec "<< hex << bh.biCompression << dec << " = '"
	<< s << "'!" << ends;
    last_error = err.str();
    return 0;
}

IVideoEncoder* CreateVideoEncoder(fourcc_t compressor, const BITMAPINFOHEADER& bh, const char* cname)
{
    vector<CodecInfo>::iterator it;
    for (it = video_codecs.begin(); it != video_codecs.end(); it++)
    {
	if (!(it->direction & CodecInfo::Encode))
	    continue;
	vector<fourcc_t>::const_iterator iv;
	for (iv = it->fourcc_array.begin(); iv != it->fourcc_array.end(); iv++)
	{
	    Debug printf("Check dir:%d  0x%x (%.4s)  %.4s    cname:(%s)\n", it->direction,
			 compressor, (const char*) &compressor,
			 (const char*)&(*iv), cname);
	    if (compressor == *iv && (cname == NULL || strcmp(it->text.c_str(), cname) == 0))
	    {
		void* handle;
		IVideoEncoder* (*pCreateVideoEncoder)(const CodecInfo&, fourcc_t, const BITMAPINFOHEADER&);
		switch(it->kind)
		{
		case CodecInfo::Source:
		    return new Unc_Encoder(*it, compressor, bh);
		default:
		    handle = PluginOpen(it->modulename.c_str());
		    if (!handle)
			continue;
		    pCreateVideoEncoder=
			(IVideoEncoder* (*)(const CodecInfo&, fourcc_t, const BITMAPINFOHEADER&))
			dlsym(handle, "CreateVideoEncoder");
		    if (pCreateVideoEncoder)
		    {
			IVideoEncoder* result =
			    pCreateVideoEncoder(*it, compressor, bh);
			if (result)
			{
			    it->handle = handle;
			    return result;
			}
			PluginGetError(handle);
		    }
		    dlclose(handle);
		}
	    }
	}
    }

    strstream err;
    err.setf(ios::hex, ios::basefield);
    char s[5];
    *(fourcc_t*)s = compressor;
    s[4] = 0;
    err << "Unknown codec " << compressor << " = '" << s << "'!" << endl;
    last_error = err.str();
    return 0;
}

IVideoEncoder* CreateVideoEncoder(const VideoEncoderInfo& info)
{
    const char* cname = info.cname.c_str();
    if (strlen(cname) == 0)
        cname = 0;
    IVideoEncoder* en = CreateVideoEncoder(info.compressor, info.header, cname);
    if (en)
    {
	en->SetQuality(info.quality);
	en->SetKeyFrame(info.keyfreq);
    }
    return en;
}

IAudioEncoder* CreateAudioEncoder(fourcc_t compressor, const WAVEFORMATEX* format)
{
    vector<CodecInfo>::iterator it;
    for (it = audio_codecs.begin(); it != audio_codecs.end(); it++)
    {
	if (!(it->direction & CodecInfo::Encode))
	    continue;
	vector<fourcc_t>::const_iterator iv;
	for (iv = it->fourcc_array.begin(); iv != it->fourcc_array.end(); iv++)
	{
	    void* handle;
	    IAudioEncoder* (*pCreateAudioEncoder)(const CodecInfo&, fourcc_t, const WAVEFORMATEX*);
	    if (compressor == *iv)
	    {
		switch(it->kind)
		{
		default:
		    handle = PluginOpen(it->modulename.c_str());
		    if (!handle)
			continue;
		    pCreateAudioEncoder =
			(IAudioEncoder* (*)(const CodecInfo&, fourcc_t, const WAVEFORMATEX*))
			dlsym(handle, "CreateAudioEncoder");
		    if (pCreateAudioEncoder)
		    {
			IAudioEncoder* result =
			    pCreateAudioEncoder(*it, compressor, format);
			if (result)
			{
			    it->handle = handle;
			    return result;
			}
			PluginGetError(handle);
		    }
		    dlclose(handle);
		}
	    }
	}
    }
    strstream err;
    err<<"No audio decoder for ID "<<format->wFormatTag<<"!"<<ends;
    last_error=err.str();
    return 0;
}

IAudioDecoder* CreateAudioDecoder(const WAVEFORMATEX* format)
{
    vector<CodecInfo>::iterator it;
    for (it = audio_codecs.begin(); it!=audio_codecs.end(); it++)
    {
	if (!(it->direction&CodecInfo::Decode))
	    continue;

	vector<fourcc_t>::const_iterator iv;
	for (iv = it->fourcc_array.begin(); iv!=it->fourcc_array.end(); iv++)
	{
	    //cout << "Audio search " << format->wFormatTag << "  for: " << *iv << endl;
	    if (unsigned(format->wFormatTag)==*iv)
	    {
		void* handle;
		IAudioDecoder* (*pCreateAudioDecoder)(const CodecInfo& info, const WAVEFORMATEX* fmt);
		switch(it->kind)
		{
		default:
		    handle = PluginOpen(it->modulename.c_str());
		    if (!handle)
			continue;
		    pCreateAudioDecoder=
			(IAudioDecoder* (*)(const CodecInfo& info, const WAVEFORMATEX* fmt))dlsym(handle, "CreateAudioDecoder");
		    if (pCreateAudioDecoder)
		    {
			IAudioDecoder* result =
			    pCreateAudioDecoder(*it, format);
			if (result)
			{
			    it->handle = handle;
			    return result;
			}
			PluginGetError(handle);
		    }
		    dlclose(handle);
		    continue;
		}
	    }
	}
    }

    strstream err;
    err<<"No audio decoder for ID "<<format->wFormatTag<<"!"<<ends;
    last_error=err.str();
    return 0;
}

int GetCodecAttr(const CodecInfo& info, const char* attribute, int& value)
{
    int result = -1;
    void* handle = PluginOpen(info.modulename.c_str());
    if (handle)
    {
	int (*GetAttrInt)(const CodecInfo&, const char*, int&)
	    = (int (*)(const CodecInfo&, const char*, int&))dlsym(handle, "GetAttrInt");
	if (GetAttrInt)
	    result=GetAttrInt(info, attribute, value);
	dlclose(handle);
    }
    return result;
}

int SetCodecAttr(const CodecInfo& info, const char* attribute, int value)
{
    int result = -1;
    void* handle = PluginOpen(info.modulename.c_str());
    if (handle)
    {
	int (*SetAttrInt)(const CodecInfo&, const char*, int) =
	    (int (*)(const CodecInfo&, const char*, int))dlsym(handle, "SetAttrInt");
	if (SetAttrInt)
	    result=SetAttrInt(info, attribute, value);
	dlclose(handle);
    }
    return result;
}

int GetCodecAttr(const CodecInfo& info, const char* attribute, char* value, int size)

{
    int result = -1;
    void* handle = PluginOpen(info.modulename.c_str());
    if (handle)
    {
	int (*GetAttrString)(const CodecInfo&, const char*, char*, int) =
	    (int (*)(const CodecInfo&, const char*, char*, int))dlsym(handle,"GetAttrString");

	if (GetAttrString)
	    result = GetAttrString(info, attribute, value, size);
	dlclose(handle);
    }
    return result;
}

int SetCodecAttr(const CodecInfo& info, const char* attribute, const char* value)
{
    int result = -1;
    void* handle = PluginOpen(info.modulename.c_str());
    if (handle)
    {
	int (*SetAttrString)(const CodecInfo&, const char*, const char*) =
	    (int (*)(const CodecInfo&, const char*, const char*))dlsym(handle,"SetAttrString");

	if (SetAttrString)
	    result = SetAttrString(info, attribute, value);
	dlclose(handle);
    }
    return result;
}

void FreeAudioDecoder(IAudioDecoder* decoder)
{
    if (decoder)
    {
	const CodecInfo& info = decoder->GetCodecInfo();
	Debug cout << "FreeAudioDecoder() " << info.text << endl;
	delete decoder;
        if (info.handle)
	    dlclose(info.handle);
    }
}

void FreeAudioEncoder(IAudioEncoder* encoder)
{
    if (encoder)
    {
	const CodecInfo& info = encoder->GetCodecInfo();
	Debug cout << "FreeAudioEncoder() " << info.text << endl;
	delete encoder;
        if (info.handle)
	    dlclose(info.handle);
    }
}

void FreeVideoDecoder(IVideoDecoder* decoder)
{
    if (decoder)
    {
	const CodecInfo& info = decoder->GetCodecInfo();
	Debug cout << "FreeVideoDecoder() " << info.text << endl;
	delete decoder;
        if (info.handle)
	    dlclose(info.handle);
    }
}

void FreeVideoEncoder(IVideoEncoder* encoder)
{
    if (encoder)
    {
	const CodecInfo& info = encoder->GetCodecInfo();
	Debug cout << "FreeVideoEncoder() " << info.text << endl;
	delete encoder;
        if (info.handle)
	    dlclose(info.handle);
    }
}

static int sortCodecInfoList(vector<CodecInfo>& codecs, const char* orderlist)
{
    // simple stupid bubble sort - this code is not supposed
    // to be executed very often...
    //
    // only CodecInfos' fount in the list will be switched
    // rest of them will stay in the unspecified order
    if (!orderlist)
	return 0;
    int changed = 0;
    int len = strlen(orderlist);
    //cout << "Codec size " << codecs.size() << endl;
    //cout << "Sort for list: " << orderlist << endl;
    for (unsigned i = 0; i < codecs.size(); i++)
    {
	//cout << "Search for " << codecs[i].text.c_str() << endl;
        const char* p = strstr(orderlist, codecs[i].text.c_str());
	if (!p) // tagged to the first position
	    p = orderlist + len; // last position
	int s = 0;
	for (unsigned j = i + 1; j < codecs.size(); j++)
	{
	    //cout << "    Compare with " << codecs[j].text.c_str() << endl;
	    const char* r = strstr(orderlist, codecs[j].text.c_str());
	    //cout << " ptr: " << (void*) r << "     p: " << (void*) p << endl;
	    if (r && r < p)
	    {
		//cout << "Found and smaller " << endl;
		s = j;
                p = r;
	    }
	}

	if (s > 0)
	{
	    CodecInfo tmp;
	    tmp = codecs[s];
	    codecs[s] = codecs[i];
	    codecs[i] = tmp;
            changed++;
	}
    }
    return changed;
}

int SortVideoCodecs(const char* orderlist)
{
    return sortCodecInfoList(video_codecs, orderlist);
}

int SortAudioCodecs(const char* orderlist)
{
    return sortCodecInfoList(audio_codecs, orderlist);
}
};
