#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/table_container.h>
#include <net-snmp/agent/agent_sysORTable.h>
#include <net-snmp/agent/sysORTable.h>

#include "sysORTable.h"
#include "system_mib.h"

#include <net-snmp/net-snmp-features.h>

netsnmp_feature_require(table_container);


/** Typical data structure for a row entry */
typedef struct sysORTable_entry_s {
    netsnmp_index            oid_index;
    oid                      sysORIndex;
    const struct sysORTable* data;
} sysORTable_entry;

/*
 * column number definitions for table sysORTable
 */
#define COLUMN_SYSORINDEX	1
#define COLUMN_SYSORID		2
#define COLUMN_SYSORDESCR	3
#define COLUMN_SYSORUPTIME	4

static netsnmp_container *table = NULL;
static u_long             sysORLastChange;
static oid                sysORNextIndex = 1;

/** create a new row in the table */
static void
register_foreach(const struct sysORTable* data, void* dummy)
{
    sysORTable_entry *entry;

    sysORLastChange = data->OR_uptime;

    entry = SNMP_MALLOC_TYPEDEF(sysORTable_entry);
    if (!entry) {
	snmp_log(LOG_ERR,
		 "could not allocate storage, sysORTable is inconsistent\n");
    } else {
	const oid firstNext = sysORNextIndex;
	netsnmp_iterator* it = CONTAINER_ITERATOR(table);

	do {
	    const sysORTable_entry* value;
	    const oid cur = sysORNextIndex;

	    if (sysORNextIndex == SNMP_MIN(MAX_SUBID, 2147483647UL))
		sysORNextIndex = 1;
	    else
		++sysORNextIndex;

	    for (value = (sysORTable_entry*)it->curr(it);
		 value && value->sysORIndex < cur;
		 value = (sysORTable_entry*)ITERATOR_NEXT(it)) {
	    }

	    if (value && value->sysORIndex == cur) {
		if (sysORNextIndex < cur)
		    it->reset(it);
	    } else {
		entry->sysORIndex = cur;
		break;
	    }
	} while (firstNext != sysORNextIndex);

	ITERATOR_RELEASE(it);

	if(firstNext == sysORNextIndex) {
            snmp_log(LOG_ERR, "Failed to locate a free index in sysORTable\n");
            free(entry);
	} else {
	    entry->data = data;
	    entry->oid_index.len = 1;
	    entry->oid_index.oids = &entry->sysORIndex;

	    CONTAINER_INSERT(table, entry);
	}
    }
}

static int
register_cb(int major, int minor, void* serv, void* client)
{
    DEBUGMSGTL(("mibII/sysORTable/register_cb",
                "register_cb(%d, %d, %p, %p)\n", major, minor, serv, client));
    register_foreach((struct sysORTable*)serv, NULL);
    return SNMP_ERR_NOERROR;
}

/** remove a row from the table */
static int
unregister_cb(int major, int minor, void* serv, void* client)
{
    sysORTable_entry *value;
    netsnmp_iterator* it = CONTAINER_ITERATOR(table);

    DEBUGMSGTL(("mibII/sysORTable/unregister_cb",
                "unregister_cb(%d, %d, %p, %p)\n", major, minor, serv, client));
    sysORLastChange = ((struct sysORTable*)(serv))->OR_uptime;

    while ((value = (sysORTable_entry*)ITERATOR_NEXT(it)) && value->data != serv);
    ITERATOR_RELEASE(it);
    if(value) {
	CONTAINER_REMOVE(table, value);
	free(value);
    }
    return SNMP_ERR_NOERROR;
}

/** handles requests for the sysORTable table */
static int
sysORTable_handler(netsnmp_mib_handler *handler,
                   netsnmp_handler_registration *reginfo,
                   netsnmp_agent_request_info *reqinfo,
                   netsnmp_request_info *requests)
{
    netsnmp_request_info *request;

    DEBUGMSGTL(("mibII/sysORTable/sysORTable_handler",
                "sysORTable_handler called\n"));

    if (reqinfo->mode != MODE_GET) {
	snmp_log(LOG_ERR,
		 "Got unexpected operation for sysORTable\n");
	return SNMP_ERR_GENERR;
    }

    /*
     * Read-support (also covers GetNext requests)
     */
    request = requests;
    while(request && request->processed)
	request = request->next;
    while(request) {
	sysORTable_entry *table_entry;
	netsnmp_table_request_info *table_info;

	if (NULL == (table_info = netsnmp_extract_table_info(request))) {
	    snmp_log(LOG_ERR,
		     "could not extract table info for sysORTable\n");
	    snmp_set_var_typed_value(
		    request->requestvb, SNMP_ERR_GENERR, NULL, 0);
	} else if(NULL == (table_entry = (sysORTable_entry *)
			   netsnmp_container_table_extract_context(request))) {
	    switch (table_info->colnum) {
	    case COLUMN_SYSORID:
	    case COLUMN_SYSORDESCR:
	    case COLUMN_SYSORUPTIME:
		netsnmp_set_request_error(reqinfo, request,
					  SNMP_NOSUCHINSTANCE);
		break;
	    default:
		netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
		break;
	    }
	} else {
	    switch (table_info->colnum) {
	    case COLUMN_SYSORID:
		snmp_set_var_typed_value(
			request->requestvb, ASN_OBJECT_ID,
			(const u_char*)table_entry->data->OR_oid,
                        table_entry->data->OR_oidlen * sizeof(oid));
		break;
	    case COLUMN_SYSORDESCR:
		snmp_set_var_typed_value(
			request->requestvb, ASN_OCTET_STR,
			(const u_char*)table_entry->data->OR_descr,
			strlen(table_entry->data->OR_descr));
		break;
	    case COLUMN_SYSORUPTIME:
		snmp_set_var_typed_integer(
			request->requestvb, ASN_TIMETICKS,
                        table_entry->data->OR_uptime);
		break;
	    default:
		netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
		break;
	    }
	}
	do {
	    request = request->next;
	} while(request && request->processed);
    }
    return SNMP_ERR_NOERROR;
}

static netsnmp_handler_registration *sysORLastChange_reg;
static netsnmp_watcher_info sysORLastChange_winfo;
static netsnmp_handler_registration *sysORTable_reg;
static netsnmp_table_registration_info *sysORTable_table_info;

/** Initializes the sysORTable module */
void
init_sysORTable(void)
{
    const oid sysORLastChange_oid[] = { 1, 3, 6, 1, 2, 1, 1, 8 };
    const oid sysORTable_oid[] = { 1, 3, 6, 1, 2, 1, 1, 9 };

    sysORTable_table_info =
        SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);

    table = netsnmp_container_find("sysORTable:table_container");

    if (sysORTable_table_info == NULL || table == NULL) {
        SNMP_FREE(sysORTable_table_info);
        CONTAINER_FREE(table);
        return;
    }
    table->container_name = strdup("sysORTable");

    netsnmp_table_helper_add_indexes(sysORTable_table_info,
                                     ASN_INTEGER, /** index: sysORIndex */
                                     0);
    sysORTable_table_info->min_column = COLUMN_SYSORID;
    sysORTable_table_info->max_column = COLUMN_SYSORUPTIME;

    sysORLastChange_reg =
        netsnmp_create_handler_registration(
            "mibII/sysORLastChange", NULL,
            sysORLastChange_oid, OID_LENGTH(sysORLastChange_oid),
            HANDLER_CAN_RONLY);
    netsnmp_init_watcher_info(
	    &sysORLastChange_winfo,
            &sysORLastChange, sizeof(u_long),
            ASN_TIMETICKS, WATCHER_FIXED_SIZE);
    netsnmp_register_watched_scalar(sysORLastChange_reg,
				    &sysORLastChange_winfo);

    sysORTable_reg =
        netsnmp_create_handler_registration(
            "mibII/sysORTable", sysORTable_handler,
            sysORTable_oid, OID_LENGTH(sysORTable_oid), HANDLER_CAN_RONLY);
    netsnmp_container_table_register(sysORTable_reg, sysORTable_table_info,
                                     table, TABLE_CONTAINER_KEY_NETSNMP_INDEX);

    sysORLastChange = netsnmp_get_agent_uptime();

    /*
     * Initialise the contents of the table here
     */
    netsnmp_sysORTable_foreach(&register_foreach, NULL);

    /*
     * Register callbacks
     */
    snmp_register_callback(SNMP_CALLBACK_APPLICATION,
                           SNMPD_CALLBACK_REG_SYSOR, register_cb, NULL);
    snmp_register_callback(SNMP_CALLBACK_APPLICATION,
                           SNMPD_CALLBACK_UNREG_SYSOR, unregister_cb, NULL);

#ifdef USING_MIBII_SYSTEM_MIB_MODULE
    if (++system_module_count == 3)
        REGISTER_SYSOR_TABLE(system_module_oid, system_module_oid_len,
                             "The MIB module for SNMPv2 entities");
#endif
}

void
shutdown_sysORTable(void)
{
#ifdef USING_MIBII_SYSTEM_MIB_MODULE
    if (system_module_count-- == 3)
        UNREGISTER_SYSOR_TABLE(system_module_oid, system_module_oid_len);
#endif

    snmp_unregister_callback(SNMP_CALLBACK_APPLICATION,
                             SNMPD_CALLBACK_UNREG_SYSOR, unregister_cb, NULL,
                             1);
    snmp_unregister_callback(SNMP_CALLBACK_APPLICATION,
                             SNMPD_CALLBACK_REG_SYSOR, register_cb, NULL, 1);

    if (table)
        CONTAINER_CLEAR(table, netsnmp_container_simple_free, NULL);
    netsnmp_container_table_unregister(sysORTable_reg);
    sysORTable_reg = NULL;
    table = NULL;
    netsnmp_table_registration_info_free(sysORTable_table_info);
    sysORTable_table_info = NULL;
    netsnmp_unregister_handler(sysORLastChange_reg);
    sysORLastChange_reg = NULL;
}
