#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif

#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/hardware/sensors.h>
#include "hardware/sensors/hw_sensors.h"
#include "ucd-snmp/lmsensorsMib.h"

struct reg_and_table {
    netsnmp_handler_registration *reg;
    netsnmp_table_registration_info *table;
};

static netsnmp_container *sensorContainer;
static struct reg_and_table temp_reg;
static struct reg_and_table fan_reg;
static struct reg_and_table volt_reg;
static struct reg_and_table misc_reg;

static const oid lmTempSensorsTable_oid[]   = {1,3,6,1,4,1,2021,13,16,2};
static const oid lmFanSensorsTable_oid[]    = {1,3,6,1,4,1,2021,13,16,3};
static const oid lmVoltSensorsTable_oid[]   = {1,3,6,1,4,1,2021,13,16,4};
static const oid lmMiscSensorsTable_oid[]   = {1,3,6,1,4,1,2021,13,16,5};
            /* All the tables have the same length root OID */
const size_t lmSensorsTables_oid_len = OID_LENGTH(lmMiscSensorsTable_oid);


/*
 * Common initialisation code, used for setting up all four tables
 */
static struct reg_and_table
initialize_lmSensorsTable(const char *tableName, const oid *tableOID,
                          netsnmp_container_op *filter, int mult )
{
    netsnmp_handler_registration    *reg;
    netsnmp_table_registration_info *table_info;
    netsnmp_cache     *cache;
    netsnmp_container *container;
    struct reg_and_table res;

    memset(&res, 0, sizeof(res));

    /*
     * Ensure the HAL sensors module has been initialised,
     *   and retrieve the main sensors container.
     * This table will then be registered using a filter on this container.
     */
    sensorContainer = get_sensor_container();
    if ( !sensorContainer ) {
        init_hw_sensors( );
        sensorContainer = get_sensor_container();
    }
    container = netsnmp_container_find("sensorTable:table_container");
    container->insert_filter = filter;
    netsnmp_container_add_index( sensorContainer, container );


    /*
     * Create a basic registration structure for the table
     */
    reg = netsnmp_create_handler_registration(
               tableName, lmSensorsTables_handler,
               tableOID,  lmSensorsTables_oid_len, HANDLER_CAN_RONLY
              );

    /*
     * Register the table using the filtered container
     * Include an indicator of any scaling to be applied to the sensor value
     */
    reg->my_reg_void = (void *)(uintptr_t)mult;
    table_info = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info );
    netsnmp_table_helper_add_indexes(table_info, ASN_INTEGER, 0);
    table_info->min_column = COLUMN_LMSENSORS_INDEX;
    table_info->max_column = COLUMN_LMSENSORS_SIGNED;
    if (netsnmp_container_table_register(reg, table_info, container, 0) !=
        SNMPERR_SUCCESS) {
        snmp_log(LOG_ERR, "Failed to register the sensors container table\n");
        return res;
    }

    /*
     * If the HAL sensors module was configured as an on-demand caching
     *  module (rather than being automatically loaded regularly),
     *  then ensure this table makes use of that cache.
     */
    cache = get_sensor_cache();
    if ( cache ) {
        netsnmp_inject_handler_before( reg, netsnmp_cache_handler_get( cache ),
                                            "table_container");
    }

    res.reg = reg;
    res.table = table_info;
    return res;
}


/*
 *  Container filters for the four tables
 *
 *  Used to ensure that sensor entries appear in the appropriate table.
 */
static int _sensor_filter_temp( netsnmp_container *c, const void *v )
{
    const netsnmp_sensor_info *sp = (const netsnmp_sensor_info *)v;
    /* Only matches temperature sensors */
    return (( sp->type == NETSNMP_SENSOR_TYPE_TEMPERATURE ) ? 0 : 1 );
}

static int _sensor_filter_fan( netsnmp_container *c, const void *v )
{
    const netsnmp_sensor_info *sp = (const netsnmp_sensor_info *)v;
    /* Only matches fan sensors */
    return (( sp->type == NETSNMP_SENSOR_TYPE_RPM ) ? 0 : 1 );
}

static int _sensor_filter_volt( netsnmp_container *c, const void *v )
{
    const netsnmp_sensor_info *sp = (const netsnmp_sensor_info *)v;
    /* Only matches voltage sensors (AC or DC) */
    return ((( sp->type == NETSNMP_SENSOR_TYPE_VOLTAGE_DC ) ||
             ( sp->type == NETSNMP_SENSOR_TYPE_VOLTAGE_AC )) ? 0 : 1 );
}

static int _sensor_filter_misc( netsnmp_container *c, const void *v )
{
    const netsnmp_sensor_info *sp = (const netsnmp_sensor_info *)v;
    /* Matches everything except temperature, fan or voltage sensors */
    return ((( sp->type == NETSNMP_SENSOR_TYPE_TEMPERATURE ) ||
             ( sp->type == NETSNMP_SENSOR_TYPE_RPM         ) ||
             ( sp->type == NETSNMP_SENSOR_TYPE_VOLTAGE_DC  ) ||
             ( sp->type == NETSNMP_SENSOR_TYPE_VOLTAGE_AC  )) ? 1 : 0 );
}

/* Initialize the LM Sensors MIB module */
void
init_lmsensorsMib(void)
{
    DEBUGMSGTL(("ucd-snmp/lmsensorsMib","Initializing LM-SENSORS-MIB tables\n"));

    /*
     * Initialize the four LM-SENSORS-MIB tables
     *
     * They are almost identical, so we can use the same registration code.
     */
    temp_reg = initialize_lmSensorsTable("lmTempSensorsTable",
                   lmTempSensorsTable_oid, _sensor_filter_temp,
                   1000);  /* MIB asks for mC */
    fan_reg = initialize_lmSensorsTable("lmFanSensorsTable",
                   lmFanSensorsTable_oid, _sensor_filter_fan,  1);
    volt_reg = initialize_lmSensorsTable("lmVoltSensorsTable",
                   lmVoltSensorsTable_oid, _sensor_filter_volt,
                   1000);  /* MIB asks for mV */
    misc_reg = initialize_lmSensorsTable("lmMiscSensorsTable",
                   lmMiscSensorsTable_oid, _sensor_filter_misc, 1);
}

void
shutdown_lmsensorsMib(void)
{
    netsnmp_table_registration_info_free(misc_reg.table);
    netsnmp_unregister_table(misc_reg.reg);
    netsnmp_table_registration_info_free(volt_reg.table);
    netsnmp_unregister_table(volt_reg.reg);
    netsnmp_table_registration_info_free(fan_reg.table);
    netsnmp_unregister_table(fan_reg.reg);
    netsnmp_table_registration_info_free(temp_reg.table);
    netsnmp_unregister_table(temp_reg.reg);
}

/*
 * Handle requests for any of the four lmXxxxSensorsTables 
 *
 * This is possible because all the table share the
 *  same structure and behaviour.
 */
int
lmSensorsTables_handler(
    netsnmp_mib_handler               *handler,
    netsnmp_handler_registration      *reginfo,
    netsnmp_agent_request_info        *reqinfo,
    netsnmp_request_info              *requests) {

    netsnmp_request_info       *request;
    netsnmp_table_request_info *table_info;
    netsnmp_sensor_info        *sensor_info;
    int mult  = (uintptr_t)reginfo->my_reg_void;

    DEBUGMSGTL(( "ucd-snmp/lmsensorsMib","lmSensorsTables_handler - root: "));
    DEBUGMSGOID(("ucd-snmp/lmsensorsMib", reginfo->rootoid, reginfo->rootoid_len));
    DEBUGMSG((   "ucd-snmp/lmsensorsMib",", mode %d\n", reqinfo->mode ));
    /*
     * This is a read-only table, so we only need to handle GET requests.
     *    (The container helper converts GETNEXT->GET requests automatically).
     */
    switch (reqinfo->mode) {
    case MODE_GET:
        for (request=requests; request; request=request->next) {
            sensor_info = (netsnmp_sensor_info *)
                            netsnmp_container_table_extract_context(request);
            if ( !sensor_info ) {
                netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHINSTANCE);
                continue;
            }
    
            table_info   =  netsnmp_extract_table_info(request);
            switch (table_info->colnum) {
            case COLUMN_LMSENSORS_INDEX:
                snmp_set_var_typed_integer( request->requestvb, ASN_INTEGER,
                                            sensor_info->idx.oids[0]);
                break;
            case COLUMN_LMSENSORS_DEVICE:
                if ( sensor_info->descr[0] != '\0' ) {
                    snmp_set_var_typed_value( request->requestvb, ASN_OCTET_STR,
                                              sensor_info->descr, strlen(sensor_info->descr));
                } else {
                    snmp_set_var_typed_value( request->requestvb, ASN_OCTET_STR,
                                              sensor_info->name,  strlen(sensor_info->name));
                }
                break;
            case COLUMN_LMSENSORS_VALUE:
                /* Multiply the value by the appropriate scaling factor for this table */
                snmp_set_var_typed_integer( request->requestvb, ASN_GAUGE,
                                            (int)(mult*sensor_info->value));
                break;
            case COLUMN_LMSENSORS_SIGNED:
		/* Only avalable for temperature table */
	        if (reginfo->rootoid[9] != 2)
		    netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
		else
                    /* Multiply the value by the appropriate scaling factor for this table */
                    snmp_set_var_typed_integer( request->requestvb, ASN_INTEGER,
                                                (int)(mult*sensor_info->value));
                break;
            default:
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHOBJECT);
                break;
            }
        }
        break;

    }
    return SNMP_ERR_NOERROR;
}
