/*
 *  Hamlib Interface - provides registering for dynamically loadable backends.
 *  Copyright (c) 2000-2015 by Stephane Fillod
 *
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/**
 * \brief Dynamic registration of backends
 * \file register.c
 *
 * doc todo: Let's explain what's going on here!
 */

#include "hamlib/config.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "register.h"

#include "hamlib/rig.h"

//! @cond Doxygen_Suppress
#ifndef PATH_MAX
#  define PATH_MAX       1024
#endif

#define RIG_BACKEND_MAX 50

#define DEFINE_INITRIG_BACKEND(backend) \
    int MAKE_VERSIONED_FN(PREFIX_INITRIG, ABI_VERSION, backend(void *be_handle)); \
    rig_model_t MAKE_VERSIONED_FN(PREFIX_PROBERIG, ABI_VERSION, backend(hamlib_port_t *port, rig_probe_func_t cfunc, rig_ptr_t data))

#define RIG_FUNCNAMA(backend) MAKE_VERSIONED_FN(PREFIX_INITRIG, ABI_VERSION, backend)
#define RIG_FUNCNAMB(backend) MAKE_VERSIONED_FN(PREFIX_PROBERIG, ABI_VERSION, backend)

#define RIG_FUNCNAM(backend) RIG_FUNCNAMA(backend),RIG_FUNCNAMB(backend)

/*
 * RIG_BACKEND_LIST is defined here, please keep it up to date,
 *  i.e. each time you implement a new backend.
 */
DEFINE_INITRIG_BACKEND(dummy);
DEFINE_INITRIG_BACKEND(yaesu);
DEFINE_INITRIG_BACKEND(guohetec);
DEFINE_INITRIG_BACKEND(kenwood);
DEFINE_INITRIG_BACKEND(icom);
DEFINE_INITRIG_BACKEND(icmarine);
DEFINE_INITRIG_BACKEND(pcr);
DEFINE_INITRIG_BACKEND(aor);
DEFINE_INITRIG_BACKEND(jrc);
DEFINE_INITRIG_BACKEND(uniden);
DEFINE_INITRIG_BACKEND(drake);
DEFINE_INITRIG_BACKEND(lowe);
DEFINE_INITRIG_BACKEND(racal);
DEFINE_INITRIG_BACKEND(wj);
DEFINE_INITRIG_BACKEND(skanti);
DEFINE_INITRIG_BACKEND(tentec);
DEFINE_INITRIG_BACKEND(alinco);
DEFINE_INITRIG_BACKEND(kachina);
DEFINE_INITRIG_BACKEND(tapr);
DEFINE_INITRIG_BACKEND(flexradio);
DEFINE_INITRIG_BACKEND(rft);
DEFINE_INITRIG_BACKEND(kit);
DEFINE_INITRIG_BACKEND(tuner);
DEFINE_INITRIG_BACKEND(rs);
DEFINE_INITRIG_BACKEND(prm80);
DEFINE_INITRIG_BACKEND(adat);
DEFINE_INITRIG_BACKEND(dorji);
DEFINE_INITRIG_BACKEND(barrett);
DEFINE_INITRIG_BACKEND(elad);
DEFINE_INITRIG_BACKEND(codan);
DEFINE_INITRIG_BACKEND(gomspace);
DEFINE_INITRIG_BACKEND(mds);
DEFINE_INITRIG_BACKEND(anytone);
DEFINE_INITRIG_BACKEND(motorola);
DEFINE_INITRIG_BACKEND(commradio);
DEFINE_INITRIG_BACKEND(simplecat);
DEFINE_INITRIG_BACKEND(harris);
//! @endcond

#ifdef HAVE_WINRADIO
//! @cond Doxygen_Suppress
DEFINE_INITRIG_BACKEND(winradio);
//! @endcond
#endif


/**
 *  \def rig_backend_list
 *  \brief Static list of rig models.
 *
 *  This is a NULL terminated list of available rig backends. Each entry in
 *  the list consists of two fields: The branch number, which is an integer,
 *  and the branch name, which is a character string.
 */
static struct
{
    int be_num;
    const char *be_name;
    int (* be_init_all)(void *handle);
    rig_model_t (* be_probe_all)(hamlib_port_t *, rig_probe_func_t, rig_ptr_t);
} rig_backend_list[RIG_BACKEND_MAX] =
{
    { RIG_DUMMY, RIG_BACKEND_DUMMY, RIG_FUNCNAMA(dummy) },
    { RIG_YAESU, RIG_BACKEND_YAESU, RIG_FUNCNAM(yaesu) },
    { RIG_GUOHETEC, RIG_BACKEND_GUOHETEC, RIG_FUNCNAM(guohetec) },
    { RIG_KENWOOD, RIG_BACKEND_KENWOOD, RIG_FUNCNAM(kenwood) },
    { RIG_ICOM, RIG_BACKEND_ICOM, RIG_FUNCNAM(icom) },
    { RIG_ICMARINE, RIG_BACKEND_ICMARINE, RIG_FUNCNAMA(icmarine) },
    { RIG_PCR, RIG_BACKEND_PCR, RIG_FUNCNAMA(pcr) },
    { RIG_AOR, RIG_BACKEND_AOR, RIG_FUNCNAMA(aor) },
    { RIG_JRC, RIG_BACKEND_JRC, RIG_FUNCNAMA(jrc) },
    { RIG_UNIDEN, RIG_BACKEND_UNIDEN, RIG_FUNCNAM(uniden) },
    { RIG_DRAKE, RIG_BACKEND_DRAKE, RIG_FUNCNAM(drake) },
    { RIG_LOWE, RIG_BACKEND_LOWE, RIG_FUNCNAM(lowe) },
    { RIG_RACAL, RIG_BACKEND_RACAL, RIG_FUNCNAMA(racal) },
    { RIG_WJ, RIG_BACKEND_WJ, RIG_FUNCNAMA(wj) },
    { RIG_SKANTI, RIG_BACKEND_SKANTI, RIG_FUNCNAMA(skanti) },
#ifdef HAVE_WINRADIO
    { RIG_WINRADIO, RIG_BACKEND_WINRADIO, RIG_FUNCNAMA(winradio) },
#endif /* HAVE_WINRADIO */
    { RIG_TENTEC, RIG_BACKEND_TENTEC, RIG_FUNCNAMA(tentec) },
    { RIG_ALINCO, RIG_BACKEND_ALINCO, RIG_FUNCNAMA(alinco) },
    { RIG_KACHINA, RIG_BACKEND_KACHINA, RIG_FUNCNAMA(kachina) },
    { RIG_TAPR, RIG_BACKEND_TAPR, RIG_FUNCNAMA(tapr) },
    { RIG_FLEXRADIO, RIG_BACKEND_FLEXRADIO, RIG_FUNCNAMA(flexradio) },
    { RIG_RFT, RIG_BACKEND_RFT, RIG_FUNCNAMA(rft) },
    { RIG_KIT, RIG_BACKEND_KIT, RIG_FUNCNAMA(kit) },
    { RIG_TUNER, RIG_BACKEND_TUNER, RIG_FUNCNAMA(tuner) },
    { RIG_RS, RIG_BACKEND_RS, RIG_FUNCNAMA(rs) },
    { RIG_PRM80, RIG_BACKEND_PRM80, RIG_FUNCNAMA(prm80) },
    { RIG_ADAT, RIG_BACKEND_ADAT, RIG_FUNCNAM(adat) },
    { RIG_DORJI, RIG_BACKEND_DORJI, RIG_FUNCNAMA(dorji) },
    { RIG_BARRETT, RIG_BACKEND_BARRETT, RIG_FUNCNAMA(barrett) },
    { RIG_ELAD, RIG_BACKEND_ELAD, RIG_FUNCNAMA(elad) },
    { RIG_CODAN, RIG_BACKEND_CODAN, RIG_FUNCNAMA(codan) },
    { RIG_GOMSPACE, RIG_BACKEND_GOMSPACE, RIG_FUNCNAMA(gomspace) },
    { RIG_MDS, RIG_BACKEND_MDS, RIG_FUNCNAMA(mds) },
    { RIG_ANYTONE, RIG_BACKEND_ANYTONE, RIG_FUNCNAMA(anytone) },
    { RIG_MOTOROLA, RIG_BACKEND_MOTOROLA, RIG_FUNCNAMA(motorola) },
    { RIG_COMMRADIO, RIG_BACKEND_COMMRADIO, RIG_FUNCNAMA(commradio) },
    { RIG_SIMPLECAT, RIG_BACKEND_SIMPLECAT, RIG_FUNCNAMA(simplecat) },
    { RIG_HARRIS, RIG_BACKEND_HARRIS, RIG_FUNCNAMA(harris) },
    { 0, NULL }, /* end */
};


/*
 * This struct to keep track of known rig models.
 * It is chained, and used in a hash table, see below.
 */
//! @cond Doxygen_Suppress
struct rig_list
{
    struct rig_caps *caps;
    struct rig_list *next;
};
//! @endcond


// This size has to be > than the max# of rigs for any manufacturer
// A fatal error will occur when running rigctl if this value is too small
//! @cond Doxygen_Suppress
#define RIGLSTHASHSZ 65535
#define HASH_FUNC(a) ((a)%RIGLSTHASHSZ)
//! @endcond


/*
 * The rig_hash_table is a hash table pointing to a list of next==NULL
 *  terminated caps.
 */
static struct rig_list *rig_hash_table[RIGLSTHASHSZ] = { NULL, };


static int rig_lookup_backend(rig_model_t rig_model);


/*
 * Basically, this is a hash insert function that doesn't check for dup!
 */
//! @cond Doxygen_Suppress
int HAMLIB_API rig_register(struct rig_caps *caps)
{
    int hval;
    struct rig_list *p;

    //rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (!caps)
    {
        return -RIG_EINVAL;
    }

#if 0
    rig_debug(RIG_DEBUG_ERR,
              "%s: rig_register (%u)\n",
              __func__,
              caps->rig_model);
#endif

    p = (struct rig_list *)calloc(1, sizeof(struct rig_list));

    if (!p)
    {
        return -RIG_ENOMEM;
    }

    hval = HASH_FUNC(caps->rig_model);

    if (rig_hash_table[hval])
    {
        printf("Hash collision!!! Fatal error!!\n");
        exit(1);
    }

    p->caps = caps;
    // p->handle = NULL;
    p->next = rig_hash_table[hval];
    rig_hash_table[hval] = p;

    return RIG_OK;
}
//! @endcond

/*
 * Get rig capabilities.
 * ie. rig_hash_table lookup
 */

//! @cond Doxygen_Suppress
struct rig_caps *HAMLIB_API rig_get_caps(rig_model_t rig_model)
{
    struct rig_list *p;

    for (p = rig_hash_table[HASH_FUNC(rig_model)]; p; p = p->next)
    {
        if (p->caps->rig_model == rig_model)
        {
            return p->caps;
        }
    }

    return NULL;    /* sorry, caps not registered! */
}
//! @endcond

/*
 * lookup for backend index in rig_backend_list table,
 * according to BACKEND_NUM
 * return -1 if not found.
 */
//! @cond Doxygen_Suppress
static int rig_lookup_backend(rig_model_t rig_model)
{
    int i;

    for (i = 0; i < RIG_BACKEND_MAX && rig_backend_list[i].be_name; i++)
    {
        if (RIG_BACKEND_NUM(rig_model) ==
                rig_backend_list[i].be_num)

        {
            return i;
        }
    }

    return -1;
}
//! @endcond

/*
 * rig_check_backend
 * check the backend declaring this model has been loaded
 * and if not loaded already, load it!
 * This permits seamless operation in rig_init.
 */
//! @cond Doxygen_Suppress
int HAMLIB_API rig_check_backend(rig_model_t rig_model)
{
    const struct rig_caps *caps;
    int be_idx;
    int retval;
//    int i;

    /* already loaded ? */
    caps = rig_get_caps(rig_model);

    if (caps)
    {
        return RIG_OK;
    }

#if 0 // this stopped a 2nd rig_init call with a valid model to fail -- reversing

    // hmmm...no caps so did we already load the rigs?
    for (n = 0, i = 0; i < RIGLSTHASHSZ; i++)
    {
        if (rig_hash_table[i]) { ++n; }
    }


    if (n > 1)
    {
        rig_debug(RIG_DEBUG_ERR, "%s: rig model %u not found and rig count=%d\n",
                  __func__, rig_model, n);
        return -RIG_ENAVAIL;
    }

#endif

    be_idx = rig_lookup_backend(rig_model);

    /*
     * Never heard about this backend family!
     */
    if (be_idx == -1)
    {
        rig_debug(RIG_DEBUG_VERBOSE,
                  "rig_check_backend: unsupported backend %u for model %u\n",
                  RIG_BACKEND_NUM(rig_model),
                  rig_model);
        return -RIG_ENAVAIL;
    }

    // do we need to load the backend?
//    if (rig_backend_list[be_idx].be_init_all == 0)
    {
        retval = rig_load_backend(rig_backend_list[be_idx].be_name);
    }
#if 0
    else
    {
        retval = RIG_OK;
    }

#endif

    return retval;
}
//! @endcond



//! @cond Doxygen_Suppress
int HAMLIB_API rig_unregister(rig_model_t rig_model)
{
    int hval;
    struct rig_list *p, *q;

    hval = HASH_FUNC(rig_model);
    q = NULL;

    for (p = rig_hash_table[hval]; p; p = p->next)
    {
        if (p->caps->rig_model == rig_model)
        {
            if (q == NULL)
            {
                rig_hash_table[hval] = p->next;
            }
            else
            {
                q->next = p->next;
            }

            free(p);
            return RIG_OK;
        }

        q = p;
    }

    return -RIG_EINVAL; /* sorry, caps not registered! */
}
//! @endcond

/*
 * rig_list_foreach
 * executes cfunc on all the elements stored in the rig hash list
 */
//! @cond Doxygen_Suppress
int HAMLIB_API rig_list_foreach(int (*cfunc)(const struct rig_caps *,
                                rig_ptr_t),
                                rig_ptr_t data)
{
    struct rig_list *p;
    int i;

    if (!cfunc)
    {
        return -RIG_EINVAL;
    }

    for (i = 0; i < RIGLSTHASHSZ; i++)
    {
        struct rig_list *next = NULL;

        for (p = rig_hash_table[i]; p; p = next)
        {
            next = p->next;       /* read before call in case it is unregistered */

            if ((*cfunc)(p->caps, data) == 0)
            {
                return RIG_OK;
            }
        }
    }

    return RIG_OK;
}
//! @endcond

/*
 * rig_list_foreach_model
 * executes cfunc on all the elements stored in the rig hash list
 */
//! @cond Doxygen_Suppress
int HAMLIB_API rig_list_foreach_model(int (*cfunc)(const rig_model_t rig_model,
                                      rig_ptr_t),
                                      rig_ptr_t data)
{
    struct rig_list *p;
    int i;

    if (!cfunc)
    {
        return -RIG_EINVAL;
    }

    for (i = 0; i < RIGLSTHASHSZ; i++)
    {
        struct rig_list *next = NULL;

        for (p = rig_hash_table[i]; p; p = next)
        {
            next = p->next;       /* read before call in case it is unregistered */

            if ((*cfunc)(p->caps->rig_model, data) == 0)
            {
                return RIG_OK;
            }
        }
    }

    return RIG_OK;
}
//! @endcond

//! @cond Doxygen_Suppress
static int dummy_rig_probe(const hamlib_port_t *p,
                           rig_model_t model,
                           rig_ptr_t data)
{
    rig_debug(RIG_DEBUG_TRACE, "Found rig, model %u\n", model);

    return RIG_OK;
}
//! @endcond


/*
 * rig_probe_first
 * called straight by rig_probe
 */
//! @cond Doxygen_Suppress
rig_model_t rig_probe_first(hamlib_port_t *p)
{
    int i;
    rig_model_t model;

    for (i = 0; i < RIG_BACKEND_MAX && rig_backend_list[i].be_name; i++)
    {
        if (rig_backend_list[i].be_probe_all)
        {
            model = (*rig_backend_list[i].be_probe_all)(p, dummy_rig_probe,
                    (rig_ptr_t)NULL);

            /* stop at first one found */
            if (model != RIG_MODEL_NONE)
            {
                return model;
            }
        }
    }

    return RIG_MODEL_NONE;
}
//! @endcond


/*
 * rig_probe_all_backends
 * called straight by rig_probe_all
 */
//! @cond Doxygen_Suppress
int rig_probe_all_backends(hamlib_port_t *p,
                           rig_probe_func_t cfunc,
                           rig_ptr_t data)
{
    int i;

    for (i = 0; i < RIG_BACKEND_MAX && rig_backend_list[i].be_name; i++)
    {
        if (rig_backend_list[i].be_probe_all)
        {
            (*rig_backend_list[i].be_probe_all)(p, cfunc, data);
        }
    }

    return RIG_OK;
}
//! @endcond


//! @cond Doxygen_Suppress
int rig_load_all_backends()
{
    int i;

    memset(rig_hash_table, 0, sizeof(rig_hash_table));

    for (i = 0; i < RIG_BACKEND_MAX && rig_backend_list[i].be_name; i++)
    {
        rig_load_backend(rig_backend_list[i].be_name);
    }

    return RIG_OK;
}
//! @endcond


//! @cond Doxygen_Suppress
typedef int (*backend_init_t)(rig_ptr_t);
//! @endcond


/*
 * rig_load_backend
 */
//! @cond Doxygen_Suppress
int HAMLIB_API rig_load_backend(const char *be_name)
{
    int i;
    backend_init_t be_init;

    for (i = 0; i < RIG_BACKEND_MAX && rig_backend_list[i].be_name; i++)
    {
        if (!strcmp(be_name, rig_backend_list[i].be_name))
        {
            be_init = rig_backend_list[i].be_init_all ;

            if (be_init)
            {
                return (*be_init)(NULL);
            }
            else
            {
                return -RIG_EINVAL;
            }
        }
    }

    return -RIG_EINVAL;
}
//! @endcond
