/*
 * tcmodchain.c -- simple module system explorer frontend
 * (C) 2006-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 <glob.h>
#include "tcstub.h"

#define EXE "tcmodchain"

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


void version(void)
{
    printf("%s v%s (C) 2006-2010 Transcode Team\n", EXE, VERSION);
}

enum {
    STATUS_DONE = -1, /* used internally */
    STATUS_OK = 0,
    STATUS_BAD_PARAM,
    STATUS_MODULE_ERROR,
    STATUS_MODULE_MISMATCH,
    STATUS_GLOB_FAILED,
};


#define MAX_MODS     (16)

typedef struct modrequest_ ModRequest;
struct modrequest_ {
    char **rawdata; /* main reference */

    const char *type; /* commodity */
    const char *name; /* commodity */

    TCModule module;
};


static void modrequest_init(ModRequest *modr);
static int modrequest_load(TCFactory factory,
                           ModRequest *modr, const char *str);
static int modrequest_scan(const char *modpath, const char *modstr,
                           glob_t *globbuf);
static int modrequest_fill(TCFactory factory, ModRequest *mods,
                           size_t maxmods, size_t *modnum, glob_t *globbuf);
#define modrequest_free(GLOBBUF)    globfree((GLOBBUF))
static int modrequest_unload(TCFactory factory, ModRequest *modr);

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

enum {
    TC_MODULE_DEMUXER  =  1, /* future */
    TC_MODULE_DECODER  =  2, /* future */
    TC_MODULE_FILTER   =  4, /* future */
    TC_MODULE_ENCODER  =  8,
    TC_MODULE_MUXER    = 16,

    TC_MODULE_FIXED    = 1024,
    TC_MODULE_TEMPLATE = 2048,
};


static uint32_t parse_modstr(const char *str)
{
    uint32_t ret = 0;
    if (str != NULL && strlen(str) > 0) {
        struct {
            const char *name;
            int kind;
            size_t off;
        } tags[] = {
            { "encode:", TC_MODULE_ENCODER, strlen("encode:") },
            { "multiplex:", TC_MODULE_MUXER, strlen("multiplex:") },
            { NULL, 0, 0 },
        };
        int i = 0;

        for (i = 0; tags[i].name != NULL; i++) {
            if (strncmp(str, tags[i].name, tags[i].off) == 0) {
                ret |= tags[i].kind;
                str += tags[i].off;
                break;
            }
        }

        /* found something supported/making sense */
        if (ret != 0 && (str != NULL && strlen(str) > 0)) {
            if (!strcmp(str, "*")) {
                ret |= TC_MODULE_TEMPLATE;
            } else {
                ret |= TC_MODULE_FIXED;
            }
        }
    }
    return ret;
}

static int parse_path(const char *fullpath, char *modstr, size_t buflen)
{
    const char *pc = NULL;
    char *pr = NULL;

    if (!fullpath || !modstr || (buflen < TC_BUF_MIN)) {
        return TC_ERROR;
    }

    pc = strrchr(fullpath, '/');
    if (pc == NULL || strlen(pc + 1) > buflen) {
        return TC_ERROR;
    }
    strlcpy(modstr, pc + 1, buflen);

    pr = strrchr(modstr, '.');
    if (pr == NULL) {
        return TC_ERROR;
    }
    *pr = '\0';

    pr = strchr(modstr, '_');
    if (pr == NULL) {
        return TC_ERROR;
    }
    *pr = ':';

    return TC_OK;
}


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

static void modrequest_init(ModRequest *modr)
{
    if (modr != NULL) {
        modr->rawdata = NULL;
        modr->type = NULL;
        modr->name = NULL;
        modr->module = NULL;
    }
}

static int modrequest_load(TCFactory factory,
                           ModRequest *modr, const char *str)
{
    size_t pieces = 0;

    if (factory == NULL || modr == NULL || str == NULL) {
        tc_log_warn(EXE, "wrong parameters for modrequest_load");
        return TC_ERROR;
    }

    modr->rawdata = tc_strsplit(str, ':', &pieces);
    if (modr->rawdata == NULL || pieces != 2) {
        tc_log_warn(EXE, "malformed module string: %s", str);
        return TC_ERROR;
    }
    modr->type = modr->rawdata[0];
    modr->name = modr->rawdata[1];

    modr->module = tc_new_module(factory, modr->type, modr->name, TC_NONE);
    if (modr->module == NULL) {
        tc_log_warn(EXE, "failed creation of module: %s", str);
        return TC_ERROR;
    }
    return TC_OK;
}

static int modrequest_unload(TCFactory factory, ModRequest *modr)
{
    if (factory == NULL || modr == NULL) {
        tc_log_warn(EXE, "wrong parameters for modrequest_load");
        return TC_ERROR;
    }

    tc_del_module(factory, modr->module);
    tc_strfreev(modr->rawdata);

    /* re-blank fields */
    modrequest_init(modr);

    return TC_OK;
}

static int modrequest_scan(const char *modpath, const char *modstr,
                           glob_t *globbuf)
{
    char path_model[PATH_MAX];
    char buf[TC_BUF_MIN];
    const char *pc = NULL;
    int err = 0;

    pc = strchr(modstr, ':');
    if (pc == NULL) {
        return 1;
    }
    if ((pc - modstr + 1) > sizeof(buf)) {
        return 2; /* XXX watch out here */
    }
    strlcpy(buf, modstr, pc - modstr + 1);

    tc_snprintf(path_model, sizeof(path_model), "%s/%s_*.so", modpath, buf);
    err = glob(path_model, GLOB_ERR, NULL, globbuf);

    if (err) {
        tc_log_error(EXE, "error while scanning for modules: %s",
                     (err == GLOB_NOSPACE)   ?"can't get enough memory" :
                     (err == GLOB_ABORTED)   ?"read error" :
                          /* GLOB_NOMATCH */ "no modules found");
        return -1;
    }
    return 0;
}

static int modrequest_fill(TCFactory factory, ModRequest *mods,
                           size_t maxmods, size_t *modnum, glob_t *globbuf)
{
    char modstr[TC_BUF_MIN];
    int i = 0, count = 0, lim = 0;

    if (!factory || !mods || !globbuf) {
        return TC_ERROR;
    }
    if (maxmods < globbuf->gl_pathc) {
        tc_log_warn(EXE, "found %u candidate modules, but "
                              "only %u allowed (dropping remaining)",
                    (unsigned)globbuf->gl_pathc, (unsigned)maxmods);
    }
    lim = TC_MIN(maxmods, globbuf->gl_pathc);

    for (i = 0; i < lim; i++) {
        int ret;

        ret = parse_path(globbuf->gl_pathv[i], modstr, TC_BUF_MIN);
        if (ret != 0) {
            tc_log_warn(EXE, "error while parsing '%s', skipping",
                                  globbuf->gl_pathv[i]);
            continue;
        }
        ret = modrequest_load(factory, &mods[i], modstr);
        if (ret != 0) {
            tc_log_warn(EXE, "error while loading '%s', skipping",
                                  modstr);
            continue;
        }
        count++;
    }

    if (modnum != NULL) {
        *modnum += count;
    }

    return TC_OK;
}

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

typedef struct cmdletdata_ CmdLetData;
struct cmdletdata_ {
    ModRequest mods[MAX_MODS];
    size_t modsnum;

    const char *modpath;
    TCFactory factory;
};

typedef  int (*CmdLet)(CmdLetData *cdata, int argc, char **argv);


#define CLEANUP(CDATA) do { \
    int i = 0; \
    for (i = 0; i < (CDATA)->modsnum; i++) { \
        modrequest_unload((CDATA)->factory, &((CDATA)->mods[i])); \
    } \
    (CDATA)->modsnum = 0; \
} while (0)
 
/* XXX */
static int check_module_pair(const ModRequest *head,
                             const ModRequest *tail,
                             const ModRequest *ref,
                             int verbose)
{
    int ret = 0;

    if (head->module == NULL || tail->module == NULL) {
        tc_log_error(EXE, "check_module_pair: missing module handle");
        return -1;
    }

    ret = tc_module_info_match(TC_CODEC_ANY,
                               tc_module_get_info(head->module),
                               tc_module_get_info(tail->module));
    if (verbose >= TC_DEBUG) {
        tc_log_info(EXE, "%s:%s | %s:%s [%s]",
                    head->type, head->name, tail->type, tail->name,
                    (ret == 1) ?"OK" :"MISMATCH"); 
    } else if (verbose >= TC_INFO) {
        if (ret == 1) {
            printf("%s\n", ref->name);
        }
    }
    return ret;
}

static int cmdlet_usage(CmdLetData *unused, int ac, char **av)
{
    version();
    printf("Usage: %s [options] module [module... [module...]]\n",
           EXE);
    printf("    -L                list mode (see manpage for details)\n");
    printf("    -C                check mode (see manpage for details)\n");
    printf("    -d verbosity      verbosity mode [1 == TC_INFO]\n");
    printf("    -m PATH           use PATH as module path\n");
    printf("    -v                show program version and exit\n");
    printf("    -h                show this help message\n");
    return STATUS_OK;
}


static int cmdlet_check(CmdLetData *cdata, int ac, char **av)
{
    int i = 0, matches = 0;
    int status = STATUS_OK; /* let's be optimisc, once in lifetime */

    if (ac < 2) {
        tc_log_error(EXE, "not enough arguments for `check' mode");
        return STATUS_BAD_PARAM;
    }

    for (i = 0; i < ac; i++) {
        modrequest_load(cdata->factory, &cdata->mods[cdata->modsnum], av[i]);
        cdata->modsnum++;
    }

    status = STATUS_OK;
    if (cdata->modsnum >= 2) {
        /* N modules, so N - 1 interfaces */
        for (i = 0; i < cdata->modsnum - 1; i++) {
            int ret = check_module_pair(&cdata->mods[i], &cdata->mods[i + 1],
                                        &cdata->mods[i],
                                        (verbose >= TC_INFO) ?TC_DEBUG :TC_QUIET);
            if (ret != -1) { /* no error */
                matches += ret;
            }
                
        }
        if (matches < cdata->modsnum - 1) {
            status = STATUS_MODULE_MISMATCH;
        }
    }

    if (verbose) {
        if (status == STATUS_OK) {
            tc_log_info(EXE, "module chain OK");
        } else {
            tc_log_info(EXE, "module chain ILLEGAL");
        }
    }

    CLEANUP(cdata);
    return status;
}

static int cmdlet_list(CmdLetData *cdata, int ac, char **av)
{
    glob_t globbuf;
    int ret, i = 0, fid = 0 /* fixed id */, tid = 0; /* template id */
    uint32_t modkind[2] = { 0, 0 };
    ModRequest fixed;

    if (ac != 2) {
        tc_log_error(EXE, "wrong number of arguments for `list' mode");
        return STATUS_BAD_PARAM;
    }
    /* we support only encoder|multiplexor, yet */
    modkind[0] = parse_modstr(av[0]);
    if (!(modkind[0] & TC_MODULE_ENCODER)) {
        tc_log_error(EXE, "unknown/unsupported module '%s'", av[0]);
        return STATUS_BAD_PARAM;
    }
    modkind[1] = parse_modstr(av[1]);
    if (!(modkind[1] & TC_MODULE_MUXER)) {
        tc_log_error(EXE, "unknown/unsupported module '%s'", av[1]);
        return STATUS_BAD_PARAM;
    }

    if ((modkind[0] & TC_MODULE_FIXED)
     && (modkind[1] & TC_MODULE_TEMPLATE)) {
        fid = 0;
        tid = 1;    
    } else if ((modkind[0] & TC_MODULE_TEMPLATE)
            && (modkind[1] & TC_MODULE_FIXED)) {
        fid = 1;
        tid = 0;
    } else {
        tc_log_error(EXE, "incorrect arguments,"
                          " maybe you want to use `check' mode?");
        return STATUS_BAD_PARAM;
    }

    modrequest_init(&fixed);
    ret = modrequest_load(cdata->factory, &fixed, av[fid]);
    if (ret != TC_OK) {
        return STATUS_MODULE_ERROR;
    }

    ret = modrequest_scan(cdata->modpath, av[tid], &globbuf);
    if (ret != 0) {
        return STATUS_GLOB_FAILED;
    }
    ret = modrequest_fill(cdata->factory, cdata->mods, MAX_MODS,
                          &cdata->modsnum, &globbuf);
    if (ret != TC_OK) {
        return STATUS_MODULE_ERROR;
    }

    for (i = 0; i < cdata->modsnum; i++) {
        const ModRequest *H = (tid == 0) ?(&cdata->mods[i]) :(&fixed);
        const ModRequest *T = (tid == 1) ?(&cdata->mods[i]) :(&fixed);
        check_module_pair(H, T, &(cdata->mods[i]),
                          (verbose == 0) ?TC_INFO :verbose);
    }

    CLEANUP(cdata);
    modrequest_free(&globbuf);
    ret = modrequest_unload(cdata->factory, &fixed);
    if (ret != TC_OK) {
        return STATUS_MODULE_ERROR;
    }
    return STATUS_OK;
}

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

int main(int argc, char *argv[])
{
    /* needed by filter modules */
    TCVHandle tcv_handle = tcv_init();
    CmdLet cmdlet = cmdlet_usage;
    int ch, ret, status, i = 0;

    CmdLetData cdata = {
        .modpath = MOD_PATH,
        .factory = NULL,
        .modsnum = 0,
    };

    ac_init(AC_ALL);
    tc_set_config_dir(NULL);
    libtc_init(&argc, &argv);

    filter[0].id = 0; /* to make gcc happy */
    for (i = 0; i < MAX_MODS; i++) {
        modrequest_init(&cdata.mods[i]);
    }

    while (1) {
        ch = getopt(argc, argv, "LCd:?vhm:");
        if (ch == -1) {
            break;
        }

        switch (ch) {
          case 'L':
            cmdlet = cmdlet_list;
            break;
          case 'C':
            cmdlet = cmdlet_check;
            break;
          case 'd':
            if (optarg[0] == '-') {
                cmdlet_usage(&cdata, argc, argv);
                return STATUS_BAD_PARAM;
            }
            verbose = atoi(optarg);
            break;
          case 'm':
            cdata.modpath = optarg;
            break;
          case 'v':
            version();
            return STATUS_OK;
          case '?': /* fallthrough */
          case 'h': /* fallthrough */
          default:
            cmdlet_usage(&cdata, argc, argv);
            return STATUS_OK;
        }
    }

    /* XXX: watch out here */
    argc -= optind;
    argv += optind;

    /* 
     * we can't distinguish from OMS and NMS modules at glance, so try
     * first using new module system
     */
    cdata.factory = tc_new_module_factory(cdata.modpath, verbose);

    status = cmdlet(&cdata, argc, argv);

    ret = tc_del_module_factory(cdata.factory); /* XXX: unchecked */
    tcv_free(tcv_handle);
    return status;
}

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

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