/*
 * tccodecs.c -- codecs helper functions.
 * (C) 2005-2010 - Francesco Romani <fromani -at- gmail -dot- com>
 *
 * This file is part of transcode, a video stream processing tool.
 *
 * transcode 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.
 *
 * transcode is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "libtc.h"
#include "src/transcode.h"
#include "tccodecs.h"

#include <string.h>

/* internal usage only ***************************************************/

typedef struct {
    TCCodecID id;        /* a TC_CODEC_* value */
    const char *name;    /* usually != fourcc */
    const char *fourcc;  /* real-world fourcc */
    const char *comment;
    int multipass;       /* multipass capable */
    int flags;
} TCCodecInfo;


/*
 * this table is *always* accessed in RO mode, so there is no need
 * to protect it with threading locks
 */
static const TCCodecInfo tc_codecs_info[] = {
    /* video codecs */
    { TC_CODEC_RGB,        "rgb",         "RGB",
                           "RGB/BGR",                0, TC_VIDEO },
    { TC_CODEC_YUV420P,    "yuv420p",     "I420",
                           "YUV420P",                0, TC_VIDEO },
    { TC_CODEC_YUV422P,    "yuv422p",     "UYVY",
                           "YUV422P",                0, TC_VIDEO },
    { TC_CODEC_YUY2,       "yuy2",        "YUY2",
                           "YUY2",                   0, TC_VIDEO },
    // XXX: right fcc?
    { TC_CODEC_MPEG1VIDEO, "mpeg1video",  "mpg1",
                           "MPEG1 ES",               1, TC_VIDEO },
    { TC_CODEC_MPEG2VIDEO, "mpeg2video",  "mpg2",
                           "MPEG2 ES",               1, TC_VIDEO },
    { TC_CODEC_MPEG4VIDEO, "mpeg4video",  "mp4v", 
                           "MPEG4 ES",               1, TC_VIDEO },
    /* FIXME; set up `DIVX' fcc for backward compatibility? */ 
    { TC_CODEC_XVID,       "xvid",        "XVID",
                           "XviD",                   1, TC_VIDEO },
    { TC_CODEC_DIVX3,      "divx3",       "DIV3",
                           "DivX;-)",                1, TC_VIDEO },
    { TC_CODEC_DIVX4,      "divx4",       "DIVX",
                           "DivX 4.x",               1, TC_VIDEO },
    { TC_CODEC_DIVX5,      "divx5",       "DX50",
                           "DivX 5.x",               1, TC_VIDEO },
    { TC_CODEC_MJPEG,      "mjpeg",       "MJPG",
                           "MJPEG",                  0, TC_VIDEO },
    { TC_CODEC_LJPEG,      "ljpeg",       "LJPG",
                           "Lossless (motion) JPEG", 0, TC_VIDEO },
    { TC_CODEC_DV,         "dvvideo",     "DVSD",
                           "DigitalVideo",           0, TC_VIDEO },
    { TC_CODEC_LZO1,       "lzo1",        "LZO1",
                           "LZO v1",                 0, TC_VIDEO },
    { TC_CODEC_LZO2,       "lzo2",        "LZO2",
                           "LZO v2",                 0, TC_VIDEO },
    { TC_CODEC_MP42,       "msmpeg4v2",   "MP42",
                           "MS MPEG4 v2",            1, TC_VIDEO },
    { TC_CODEC_MP43,       "msmpeg4v3",   "MP43",
                           "MS MPEG4 v3",            1, TC_VIDEO },
    { TC_CODEC_RV10,       "realvideo10", "RV10",
                           "RealVideo (old)",        0, TC_VIDEO },
    { TC_CODEC_WMV1,       "wmv1",        "WMV1",
                           "WMV v1 (WMP7)",          1, TC_VIDEO },
    { TC_CODEC_WMV2,       "wmv2",        "WMV2",
                           "WMV v2 (WMP8)",          1, TC_VIDEO },
    { TC_CODEC_H264,       "h264",        "H264",
                           "h.264 (AVC)",            1, TC_VIDEO },
    { TC_CODEC_H263P,      "h263p",       "H263",
                           "h.263 plus",             1, TC_VIDEO },
    // XXX: right fcc?
    { TC_CODEC_H263I,      "h263",        "H263",
                           "h.263",                  0, TC_VIDEO },

    { TC_CODEC_HUFFYUV,    "huffyuv",     "HFYU",
                           "HuffYUV",                1, TC_VIDEO },
    // XXX: right fcc?
    { TC_CODEC_FFV1,       "ffv1",        "FFV1",
                           "FFV1 (experimental)",    1, TC_VIDEO },
    { TC_CODEC_ASV1,       "asusvideo1",  "ASV1",
                           "ASUS codec v1",          0, TC_VIDEO },
    { TC_CODEC_ASV2,       "asusvideo2",  "ASV2",
                           "ASUS codec v2",          0, TC_VIDEO },
    { TC_CODEC_PV3,        "pv3",         "PV3",
                           "PV3",                    0, TC_VIDEO }, /* XXX */
    { TC_CODEC_NUV,        "nuv",         "NUV",
                           "RTjpeg",                 0, TC_VIDEO }, /* XXX */

    /* FIXME: add more codec informations, on demand */

    /* audio codecs */
    { TC_CODEC_PCM,        "pcm",         NULL, 
                           "PCM",                    0, TC_AUDIO },
    { TC_CODEC_LPCM,       "lpcm",        NULL,
                           "LPCM",                   0, TC_AUDIO },
    { TC_CODEC_AC3,        "ac3",         NULL,
                           "AC3",                    0, TC_AUDIO },
    { TC_CODEC_MP3,        "mp3",         NULL,
                           "MPEG ES Layer 3",        0, TC_AUDIO },
    { TC_CODEC_MP2,        "mp2",         NULL,
                           "MPEG ES Layer 2",        0, TC_AUDIO },
    { TC_CODEC_AAC,        "aac",         NULL,
                           "AAC",                    0, TC_AUDIO },
    { TC_CODEC_VORBIS,     "vorbis",      NULL,
                           "ogg/vorbis",             0, TC_AUDIO },
    { TC_CODEC_VAG,        "vag",         NULL,
                           "PS-VAG",                 0, TC_AUDIO },
    /* FIXME: add more codec informations, on demand */

    /* miscelanous; XXX: drop from here */
    { TC_CODEC_MPEG,       "MPEG",        NULL,
                           "MPEG program stream",    0, TC_VIDEO|TC_AUDIO },
    { TC_CODEC_MPEG1,      "MPEG-1",      NULL,
                           "MPEG 1 program stream",  0, TC_VIDEO|TC_AUDIO },
    { TC_CODEC_MPEG2,      "MPEG-2",      NULL, 
                           "MPEG 2 program stream",  0, TC_VIDEO|TC_AUDIO },

    /* special codecs*/
    { TC_CODEC_ANY,        "everything",  NULL, 
                           NULL,                     0, 0 },
    { TC_CODEC_UNKNOWN,    "unknown",     NULL,
                           NULL,                     0, 0 },
    { TC_CODEC_ERROR,      "error",       NULL,
                           NULL,                     0, 0 }, // XXX
    /* this MUST be the last one */
};

/* compatibility */
int tc_translate_codec_id(TCCodecID codec)
{
    switch (codec) {
      case CODEC_AC3:    return TC_CODEC_AC3;
      case CODEC_MP3:    return TC_CODEC_MP3;
      case CODEC_MP2:    return TC_CODEC_MP2;
      case CODEC_PCM:    return TC_CODEC_PCM;
      case CODEC_LPCM:   return TC_CODEC_LPCM;
      case CODEC_VORBIS: return TC_CODEC_VORBIS;
      case CODEC_VAG:    return TC_CODEC_VAG;
      default:           return TC_CODEC_ERROR; /* can't happen */
    }
    return TC_CODEC_ERROR;
}


/*
 * TCCodecMatcher:
 *      generic codec finder function family.
 *      tell if a TCCodecInfo descriptor match certains given criterias
 *      using a function-dependent method.
 *      See also 'find_tc_codec' function.
 *
 * Parameters:
 *      info: a pointer to a TCCodecInfo descriptor to be examinated
 *  userdata: a pointer to data with function-dependent meaning
 * Return Value:
 *      TC_TRUE if function succeed,
 *      TC_FALSE otherwise.
 */
typedef int (*TCCodecMatcher)(const TCCodecInfo *info, const void *userdata);

/*
 * id_matcher:
 *      match a TCCodecInfo descriptor on codec's id.
 *      'userdata' must be an *address* of an uint32_t containing a TC_CODEC_*
 *      to match.
 *
 * Parameters:
 *      as for TCCodecMatcher
 * Return Value:
 *      as for TCCodecMatcher
 */
static int id_matcher(const TCCodecInfo *info, const void *userdata)
{
    if (info == NULL || userdata == NULL) {
        return TC_FALSE;
    }

    return (*(int*)userdata == info->id) ?TC_TRUE :TC_FALSE;
}

/*
 * name_matcher:
 *      match a TCCodecInfo descriptor on codec's name (note: note != fourcc).
 *      'userdata' must be the C-string to match.
 *      Note: ignore case.
 *
 * Parameters:
 *      as for TCCodecMatcher
 * Return Value:
 *      as for TCCodecMatcher
 */
static int name_matcher(const TCCodecInfo *info, const void *userdata)
{
    if (info == NULL || userdata == NULL) {
        return TC_FALSE;
    }
    if(!info->name || (strcasecmp(info->name, userdata) != 0)) {
        return TC_FALSE;
    }
    return TC_TRUE;
}

/*
 * find_tc_codec:
 *      find a TCCodecInfo descriptor matching certains given criterias.
 *      It scans the whole TCCodecInfos table applying the given
 *      matcher with the given data to each element, halting when a match
 *      is found
 *
 * Parameters:
 *      matcher: a TCCodecMatcher to be applied to find the descriptor.
 *     userdata: matching data to be passed to matcher together with a table
 *               entry.
 *
 * Return Value:
 *      >= 0: index of an entry in TCCodecInfo in table if an entry match
 *            the finding criteria
 *      TC_NULL_MATCH if no entry matches the given criteria
 */
static int find_tc_codec(const TCCodecInfo *infos,
                         TCCodecMatcher matcher,
                         const void *userdata)
{
    int found = TC_FALSE, i = 0;

    if (infos == NULL) {
        return TC_NULL_MATCH;
    }

    for (i = 0; infos[i].id != TC_CODEC_ERROR; i++) {
        found = matcher(&infos[i], userdata);
        if (found) {
            break;
        }
    }
    if (!found) {
        i = TC_NULL_MATCH;
    }

    return i;
}

/* public API ************************************************************/

const char* tc_codec_to_comment(TCCodecID codec)
{
    int idx = find_tc_codec(tc_codecs_info, id_matcher, &codec);

    if (idx == TC_NULL_MATCH) { /* not found */
        return "unknown";
    }
    return tc_codecs_info[idx].comment; /* can be NULL */
}


const char* tc_codec_to_string(TCCodecID codec)
{
    int idx = find_tc_codec(tc_codecs_info, id_matcher, &codec);

    if (idx == TC_NULL_MATCH) { /* not found */
        return NULL;
    }
    return tc_codecs_info[idx].name; /* can be NULL */
}

TCCodecID tc_codec_from_string(const char *codec)
{
    int idx = find_tc_codec(tc_codecs_info, name_matcher, codec);

    if (idx == TC_NULL_MATCH) { /* not found */
        return TC_CODEC_ERROR;
    }
    return tc_codecs_info[idx].id;
}

const char* tc_codec_fourcc(TCCodecID codec)
{
    int idx = find_tc_codec(tc_codecs_info, id_matcher, &codec);

    if (idx == TC_NULL_MATCH) { /* not found */
        return NULL;
    }
    return tc_codecs_info[idx].fourcc; /* can be NULL */
}

int tc_codec_description(TCCodecID codec, char *buf, size_t bufsize)
{
    int idx = find_tc_codec(tc_codecs_info, id_matcher, &codec);
    int ret;

    if (idx == TC_NULL_MATCH) { /* not found */
        return -1;
    }

    ret = tc_snprintf(buf, bufsize, "%-12s: (fourcc=%s multipass=%-3s) %s",
                      tc_codecs_info[idx].name,
                      tc_codecs_info[idx].fourcc,
                      tc_codecs_info[idx].multipass ?"yes" :"no",
                      tc_codecs_info[idx].comment);
    return ret;
}

int tc_codec_is_multipass(TCCodecID codec)
{
    int idx = find_tc_codec(tc_codecs_info, id_matcher, &codec);

    if (idx == TC_NULL_MATCH) { /* not found */
        return TC_FALSE;
    }
    return tc_codecs_info[idx].multipass;
}

/*************************************************************************/

/*
 * Local variables:
 *   c-file-style: "stroustrup"
 *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
 *   indent-tabs-mode: nil
 * End:
 *
 * vim: expandtab shiftwidth=4:
 */
