/***************************************************************************
                          audio_encoderPlugin.cpp  -  description
                             -------------------

    copyright            : (C) 2008 by mean
    email                : fixounet@free.fr
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "ADM_default.h"
#include "ADM_dynamicLoading.h"
#include "ADM_edit.hxx"
#include "BVector.h"
#include "audioencoderInternal.h"
#include <string>
extern ADM_Composer *video_body;
static AUDIOENCODER ADM_encoderByName(const char *name);

/**
    \class ADM_AudioEncoderLoader
    \brief Helper class to load plugins
*/
class ADM_AudioEncoderLoader : public ADM_LibWrapper
{

  public:
    int initialised;
    ADM_audioEncoder *encoderBlock;

    ADM_AudioEncoderLoader(const char *file) : ADM_LibWrapper()
    {
        ADM_audioEncoder *e;
        ADM_audioEncoder *(*getInfo)(void);
        initialised = loadLibrary(file) && getSymbols(1, &getInfo, "getInfo");
        encoderBlock = NULL;
        if (initialised)
        {
            e = getInfo();
            if (e->apiVersion != ADM_AUDIO_ENCODER_API_VERSION)
            {
                e = NULL;
                initialised = 0;
            }
            else
            {
                printf("[AudioEncoder] Loaded %s version %02d.%02d.%02d wavTag :0x%x\n", e->codecName, e->major,
                       e->minor, e->patch, e->wavTag);
                encoderBlock = new ADM_audioEncoder;
                *encoderBlock = *e;
                encoderBlock->opaque = (void *)this;
            }
        }
        else
        {
            printf("Symbol loading failed for %s\n", file);
        }
    }
    ADM_AudioEncoderLoader(const char *name, const char *menuName) : ADM_LibWrapper()
    {
        initialised = false;
        encoderBlock = new ADM_audioEncoder;
        memset(encoderBlock, 0, sizeof(*encoderBlock));
        encoderBlock->codecName = name;
        encoderBlock->menuName = menuName;
        encoderBlock->opaque = (void *)this;
    }
    ~ADM_AudioEncoderLoader()
    {
        if (encoderBlock)
            delete encoderBlock;
        encoderBlock = NULL;
    }
};

static BVector<ADM_AudioEncoderLoader *> ListOfAudioEncoderLoader;

/**
        \fn ADM_ae_getPluginNbEncoders
        \brief Returns the number of av filter plugins except one
*/
uint32_t ADM_ae_getPluginNbEncoders(void)
{
    return ListOfAudioEncoder.size() - 1;
}
/**
    \fn     ADM_ae_getAPluginEncoderInfo
    \brief  Get Infos about the encoder #th plugin (plugin display)
*/
bool ADM_ae_getAPluginEncoderInfo(int filter, const char **name, uint32_t *major, uint32_t *minor, uint32_t *patch)
{
    filter++;
    ADM_assert(filter < ListOfAudioEncoder.size());
    *major = ListOfAudioEncoder[filter]->major;
    *minor = ListOfAudioEncoder[filter]->minor;
    *patch = ListOfAudioEncoder[filter]->patch;
    *name = ListOfAudioEncoder[filter]->description;
    return true;
}
/**
    \fn tryLoadingFilterPlugin
    \brief Try loading the file given as argument as an audio device plugin

*/
#define Fail(x)                                                                                                        \
    {                                                                                                                  \
        printf("%s:" #x "\n", file);                                                                                   \
        goto er;                                                                                                       \
    }
static bool tryLoadingFilterPlugin(const char *file)
{
    ADM_AudioEncoderLoader *dll = new ADM_AudioEncoderLoader(file);
    if (!dll->initialised)
        Fail(CannotLoad);

    ListOfAudioEncoderLoader.append(dll);
    ListOfAudioEncoder.append(dll->encoderBlock); // will be destroyed when Loader is destroyed
    ADM_info("[AudioEncoder] Registered filter %s as  %s\n", file, dll->encoderBlock->description);
    return true;
    // Fail!
er:
    delete dll;
    return false;
}
/**
 * 	\fn ADM_ae_loadPlugins
 *  \brief load all audio encoder plugins
 */
uint8_t ADM_ae_loadPlugins(const char *path)
{
    // FIXME Factorize

    std::vector<std::string> files;
    // Add the copy encoder
    ADM_AudioEncoderLoader *copy = new ADM_AudioEncoderLoader("copy", "Copy");
    ListOfAudioEncoderLoader.append(copy);
    ListOfAudioEncoder.append(copy->encoderBlock);
    //
    printf("[ADM_ae_plugin] Scanning directory %s\n", path);

    if (!buildDirectoryContent(path, &files, SHARED_LIB_EXT))
    {
        printf("[ADM_ae_plugin] Cannot open plugin directory\n");
        return 0;
    }

    for (int i = 0; i < files.size(); i++)
        tryLoadingFilterPlugin(files.at(i).c_str());

    printf("[ADM_ae_plugin] Scanning done\n");
    int nb = ListOfAudioEncoder.size();
    for (int i = 1; i < nb; i++)
        for (int j = i + 1; j < nb; j++)
        {
            ADM_audioEncoder *a, *b;
            a = ListOfAudioEncoder[i];
            b = ListOfAudioEncoder[j];
            if (strcmp(a->menuName, b->menuName) > 0)
            {
                ListOfAudioEncoder[j] = a;
                ListOfAudioEncoder[i] = b;
            }
        }
    return 1;
}
/**
    \fn ADM_ae_cleanup
*/
bool ADM_ae_cleanup(void)
{
    for (uint32_t i = 0; i < ListOfAudioEncoderLoader.size(); i++)
    {
        ADM_AudioEncoderLoader *a = ListOfAudioEncoderLoader[i];
        delete a;
        ListOfAudioEncoderLoader[i] = NULL;
    }

    ListOfAudioEncoderLoader.clear();
    return true;
}

/**
    \fn ADM_encoderByName
    \brief Returns the Id of the given string

*/
AUDIOENCODER ADM_encoderByName(const char *name)
{
    if (!name)
        return (AUDIOENCODER)0;
    if (!strcasecmp(name, "copy"))
        return (AUDIOENCODER)0; // copy
    for (uint32_t i = 1; i < ListOfAudioEncoder.size(); i++)
    {
        if (!strcasecmp(name, ListOfAudioEncoder[i]->codecName))
        {
            return i;
        }
    }
    printf("[AudioEncoder] Encoder not found :%s\n", name);
    return (AUDIOENCODER)0;
}
/**
    \fn    ADM_audioEncoderById
    \brief Returns the name of a device from its Id
*/
static const char *ADM_audioEncoderById(AUDIOENCODER id)
{

    ADM_assert(id < ListOfAudioEncoder.size());
    return ListOfAudioEncoder[id]->codecName;
}
/**
    \fn audioEncoderGetNumberOfEncoders
*/
uint32_t audioEncoderGetNumberOfEncoders(void)
{
    return ListOfAudioEncoder.size();
}

/**
    \fn audioEncoderGetDisplayName
*/
const char *audioEncoderGetDisplayName(int dex)
{
#if 0
    EditableAudioTrack *ed=video_body->getEditableAudioTrackAt(dex);
    if(!ed)
    {
        ADM_warning("Cannot set codec for track %d\n",dex);
        return "None";
    }
     int t=ed->encoderIndex;
     ADM_assert(t<ListOfAudioEncoder.size());
     return ListOfAudioEncoder[t]->menuName;
#endif
    if (dex >= ListOfAudioEncoder.size())
    {
        return "None";
    }
    return ListOfAudioEncoder[dex]->menuName;
}
// ********************************************************************
// ********************************************************************
// ********************************************************************
// ********************************************************************

/**
    \fn audioPrintCurrentCodec
    \brief updates the UI with the current selected audio encoder
*/
void UI_setAudioCodec(int i);
void audioPrintCurrentCodec(int dex)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (ed)
        UI_setAudioCodec(ed->encoderIndex);
}

/**
    \fn AVDM_getCurrentAudioEncoder
    \brief
*/
AUDIOENCODER AVDM_getCurrentAudioEncoder(int dex)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (ed)
        return ed->encoderIndex;
    ADM_warning("Cant get current audio encoder for track %d\n", dex);
    return (AUDIOENCODER)0;
}
/**
    \fn audioCodecSelect
    \brief Update UI
*/
uint8_t DIA_audioCodec(int *codec);
void audioCodecSelect(void)
{
    // #warning FIXME
    // #warning FIXME
    // #warning FIXME
    // DIA_audioCodec( &currentEncoder );
    audioPrintCurrentCodec(0);
}
/**
    \fn     audioCodecSetByName
    \brief  only called by JS, we have to update UI as well
*/
uint8_t audioCodecSetByName(int dex, const char *name)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    ADM_info("Setting %s audio codec for track %d\n", name, dex);
    if (!ed)
    {
        ADM_warning("Cannot set codec for track %d\n", dex);
        return 0;
    }

    for (uint32_t i = 0; i < ListOfAudioEncoder.size(); i++)
    {
        if (!strcasecmp(name, ListOfAudioEncoder[i]->codecName) || !strcasecmp(name, ListOfAudioEncoder[i]->menuName))
        {

            ed->encoderIndex = i;
            if (!dex)
                audioPrintCurrentCodec(dex); // Update UI
            ADM_info("Audio codec %s set for track %d (index=%d)\n", name, dex, i);
            return 1;
        }
    }
    ADM_warning("\n Mmmm Select audio codec by name failed...(%s) for track %d\n", name, dex);
    return 0;
}
/**
    \fn audioCodecSetByIndex
    \brief To be used by UI code only!
*/
uint8_t audioCodecSetByIndex(int dex, int i)
{
    ADM_assert(i < ListOfAudioEncoder.size());
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot set codec for track %d\n", dex);
        return 0;
    }
    ed->encoderIndex = i;
    if (ed->encoderConf)
        delete ed->encoderConf;
    ed->encoderConf = NULL;
    printf("[AudioEncoder] Selected %s for index %d, tag 0x%x \n", ListOfAudioEncoder[i]->codecName, i,
           ListOfAudioEncoder[i]->wavTag);
    return 1;
}
/**
    \fn audioCodecGetName
    \brief Returns the current codec tagname
*/
const char *audioCodecGetName(int dex)
{

    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot get codec name for track %d\n", dex);
        return 0;
    }
    ADM_assert(ed->encoderIndex < ListOfAudioEncoder.size());
    return ListOfAudioEncoder[ed->encoderIndex]->codecName;
}

/**
    \fn audioProcessMode
    \brief
    @return 1 in process mode, 0 in copy mode
*/
uint32_t audioProcessMode(int dex)
{

    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot get audio codec mode for track %d\n", dex);
        return 0;
    }
    ADM_assert(ed->encoderIndex < ListOfAudioEncoder.size());

    if (!ed->encoderIndex)
        return 0;
    return 1;
}
/**
 * 	\fn getAudioOuputTag
 *  \brief Return the encoding of the currently selected codec
 *  Must be called only in process mode, else it is meaningless.
 */
uint32_t audioFilter_getOuputCodec(int dex)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot get audio output codec for track %d\n", dex);
        return 0;
    }
    return ListOfAudioEncoder[ed->encoderIndex]->wavTag;
}

/**
 * 	\fn audioFilter_getMaxChannels
 *  \brief Return the max # of channels a codec supports
 */
uint32_t audioFilter_getMaxChannels(int dex)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot get audio maxc channel for track %d\n", dex);
        return 9999;
    }
    ADM_assert(ed->encoderIndex < ListOfAudioEncoder.size());
    if (!ed->encoderIndex)
        return 99999;
    return ListOfAudioEncoder[ed->encoderIndex]->maxChannels;
}
/**
    \fn audioCodecConfigure
    \brief
*/
void audioCodecConfigure(int dex)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot get audio configure for track %d\n", dex);
        return;
    }
    int i = ed->encoderIndex;
    ADM_info("Track %d has %d audio encoder index\n", dex, i);
    if (ListOfAudioEncoder[i]->configure)
    {
        ListOfAudioEncoder[i]->configure(&(ed->encoderConf));
    }
    return;
}
/**
    \fn audioCodecConfigureCodecIndex
    \brief
*/
void audioCodecConfigureCodecIndex(int dex, CONFcouple **conf)
{
    if (dex >= ListOfAudioEncoder.size())
    {
        ADM_warning("Audio codec out of bound %d/%d\n", dex, ListOfAudioEncoder.size());
        return;
    }
    if (ListOfAudioEncoder[dex]->configure)
    {
        ListOfAudioEncoder[dex]->configure(conf);
    }
    return;
}

/**
        \fn audioEncoderCreate
        \brief Spawn an audio encoder
*/
ADM_AudioEncoder *audioEncoderCreate(EditableAudioTrack *ed, AUDMAudioFilter *filter, bool globalHeader)
{
    if (!ed)
    {
        ADM_warning("audioEncoderCreate : NULL track %d\n");
        return NULL;
    }
    ADM_assert(ed->encoderIndex < ListOfAudioEncoder.size());
    ADM_audioEncoder *enc = ListOfAudioEncoder[ed->encoderIndex];
    ADM_AudioEncoder *a = enc->create(filter, globalHeader, (ed->encoderConf));
    return a;
}
/**
        \fn audioEncoderCreate
        \brief Spawn an audio encoder
*/
ADM_AudioEncoder *audioEncoderCreate(int dex, AUDMAudioFilter *filter, bool globalHeader)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot create encoder for track %d\n", dex);
        return NULL;
    }
    return audioEncoderCreate(ed, filter, globalHeader);
}
/**
        \fn getAudioExtraConf
        \brief
*/

bool getAudioExtraConf(int dex, CONFcouple **couple)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot get audio bitrate for track %d\n", dex);
        return false;
    }
    int i = ed->encoderIndex;
    if (!i)
    {
        printf("[AudioEncoder] Cannot get conf on copy!\n");
        return false;
    }
    ADM_assert(i < ListOfAudioEncoder.size());
    *couple = CONFcouple::duplicate(ed->encoderConf); // duplicate ?
    return true;
}
/**
    \fn setAudioExtraConf
*/
bool setAudioExtraConf(int dex, CONFcouple *c)
{

    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot get audio bitrate for track %d\n", dex);
        return false;
    }
    if (!ed->encoderIndex)
    {
        printf("[AudioEncoder] Cannot set conf on copy! (track %d)\n", dex);
        return false;
    }
    ADM_assert(ed->encoderIndex < ListOfAudioEncoder.size());
    if (ed->encoderConf)
        delete ed->encoderConf;
    ed->encoderConf = NULL;
    if (c)
        ed->encoderConf = CONFcouple::duplicate(c);
    return true;
}
/**
        \fn audio_selectCodecByTag
        \brief Select the "best" encoder outputing tag codec
*/
uint8_t audio_selectCodecByTag(int dex, uint32_t tag)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot set audio from tag for track %d\n", dex);
        return 0;
    }
    int selected = -1, priority = -1;
    for (int i = 1; i < ListOfAudioEncoder.size(); i++)
    {
        ADM_audioEncoder *c = ListOfAudioEncoder[i];
        if (c->wavTag == tag)
        {
            if ((int)c->priority > priority)
            {
                selected = i;
                priority = c->priority;
            }
        }
    }
    if (selected != -1)
    {
        if (ed->encoderIndex != selected)
        {
            ed->encoderIndex = selected;
            if (ed->encoderConf)
                delete ed->encoderConf;
            ed->encoderConf = NULL;
        }
        if (!dex)
            UI_setAudioCodec((int)ed->encoderIndex);
        printf("[AudioEncoder] Selected %s for tag %d (%s)\n", ListOfAudioEncoder[ed->encoderIndex]->codecName, tag,
               "");
        return 1;
    }
    return 0;
}
/**
         \fn audioSetOption
         \brief Allow per codec switch
*/
uint8_t audioSetOption(int dex, const char *option, uint32_t value)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot set audio from tag for track %d\n", dex);
        return 0;
    }
    ADM_audioEncoder *c = ListOfAudioEncoder[ed->encoderIndex];
    if (!c->setOption)
        return 0;
    return c->setOption(&ed->encoderConf, option, value);
}
/**
         \fn audio_setCopyCodec
         \brief Set audio codec to copy
*/
uint8_t audio_setCopyCodec(int dex)
{
    EditableAudioTrack *ed = video_body->getEditableAudioTrackAt(dex);
    if (!ed)
    {
        ADM_warning("Cannot set audio from tag for track %d\n", dex);
        return 0;
    }
    ed->encoderIndex = 0;
    return 1;
}

//**
