/*
 * Copyright (C) 2005-2009 Alex Murray <murray.alex@gmail.com>
 *
 * This program 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.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_REGEX_H
#include <regex.h>
#endif

#ifdef HAVE_SENSORS_SENSORS_H
#include <sensors/sensors.h>
#endif

#include "libsensors-plugin.h"

const gchar *plugin_name = "libsensors";

GHashTable *hash_table = NULL;

enum {
	LIBSENSORS_CHIP_PARSE_ERROR,
	LIBSENSORS_MISSING_FEATURE_ERROR,
	LIBSENSORS_REGEX_URL_COMPILE_ERROR,
	LIBSENSORS_CHIP_NOT_FOUND_ERROR
};

#if SENSORS_API_VERSION < 0x400 
#define LIBSENSORS_CONFIG_FILE "/etc/sensors.conf"
#define LIBSENSORS_ALTERNATIVE_CONFIG_FILE "/usr/local/etc/sensors.conf"
#endif

regex_t uri_re;

static char *get_chip_name_string(const sensors_chip_name *chip) {
	char *name;

#if SENSORS_API_VERSION < 0x400
	// taken from lm-sensors:prog/sensors/main.c:sprintf_chip_name
	switch (chip->bus) {
	case SENSORS_CHIP_NAME_BUS_ISA:
		name = g_strdup_printf ("%s-isa-%04x", chip->prefix, chip->addr);
		break;
	case SENSORS_CHIP_NAME_BUS_DUMMY:
		name = g_strdup_printf ("%s-%s-%04x", chip->prefix, chip->busname, chip->addr);
		break;
        case SENSORS_CHIP_NAME_BUS_PCI:
                name = g_strdup_printf ("%s-pci-%04x", chip->prefix, chip->addr);
                break;
	default:
		name = g_strdup_printf ("%s-i2c-%d-%02x", chip->prefix, chip->bus, chip->addr);
		break;
	}
#else
	// adapted from lm-sensors:prog/sensors/main.c:sprintf_chip_name 
        // in lm-sensors-3.0
#define BUF_SIZE 200
	name = g_malloc0(BUF_SIZE);
	if (sensors_snprintf_chip_name(name, BUF_SIZE, chip) < 0) {
		g_free(name);
                name = NULL;
        }
        
#endif

	return name;
}

#if SENSORS_API_VERSION < 0x400
static SensorType get_sensor_type (const char *name) {
        SensorType type = CURRENT_SENSOR;

	if (g_strrstr(name, "in")) {
                type = VOLTAGE_SENSOR;
	}
        else if (g_strrstr(name, "fan")) {
                type = FAN_SENSOR;
	}
        else if (g_strrstr(name, "temp")) {
                type = TEMP_SENSOR;
	} else {
                g_debug("sensor %s not recognised as either voltage, fan or temp sensor - assuming is a current sensor", name);
                type = CURRENT_SENSOR;
        }
        return type;
}


/* If a sensor is 'interesting' to us then return its label, otherwise NULL. */
static char *get_sensor_interesting_label (sensors_chip_name chip, int feature) {
	char *label;

	if (sensors_get_ignored(chip, feature) && 
            (sensors_get_label(chip, feature, &label) == 0)) {
                if (! (strcmp ("alarms", label) == 0 || strncmp ("sensor", label, 6) == 0)) {
                        return label;
                } else {
                        free (label);
                }
        }
        

	return NULL;
}

static const sensors_feature_data * get_sensor_min_max(const sensors_chip_name *chip, 
                               int *n1, int *n2,
                               int number, 
                               gdouble *low_value,
                               gdouble *high_value) {
	const sensors_feature_data *data;
	double value;
	
	/* The sub features are returned directly after the main feature by
	   sensors_get_all_features(), so no need to iterate over all features */
	while ((data = sensors_get_all_features (*chip, n1, n2)) != NULL &&
			data->mapping == number) {
		if ((data->mode & SENSORS_MODE_R) && 
		    (sensors_get_feature(*chip, data->number, &value) == 0) &&
                    data->name != NULL) {
			if (g_str_has_suffix(data->name, "_min")) {
                                
				*low_value = value;
                                
                                g_debug("overriding low value of sensor %s with value %f", data->name, value);
                                
                        } else if (g_str_has_suffix(data->name, "_max")) {
                                
				*high_value = value;
                                g_debug("overriding high value of sensor %s with value %f", data->name, value);
                                
                        }
                        
		}
	}
        return data;
}

#endif

static IconType get_sensor_icon (SensorType type) {
	switch (type) {
	case TEMP_SENSOR:
		return CPU_ICON;
	case FAN_SENSOR:
		return FAN_ICON;
	default:
		return GENERIC_ICON;
	}
}


#if SENSORS_API_VERSION < 0x400
static void check_sensor_with_data(GList **sensors,
                                   const char * const chip_name,
                                   const sensors_chip_name *chip,
                                   int *n1, int *n2,
                                   const sensors_feature_data *data) 
{
        char *label;
	double value;
        SensorsAppletSensorInfo *sensor_info = NULL;
        
        /* ... some of which are boring ... */
        if ((label = get_sensor_interesting_label (*chip, data->number))) {
                /* ... and some of which are actually sensors */
                if ((data->mode & SENSORS_MODE_R) && 
                    (data->mapping == SENSORS_NO_MAPPING) && 
                    (sensors_get_feature(*chip, data->number, &value) == 0) // make sure we can actually get a value for it
                        ) {
                        SensorType type;
                        gboolean visible;
                        IconType icon;
                        gdouble low_value, high_value;
			
                        gchar *url;
			
                        type = get_sensor_type (data->name);
                        visible = (type == TEMP_SENSOR ? TRUE : FALSE);
                        icon = get_sensor_icon(type);
                        
                        // the 'path' contains all the information we need to
                        // identify this sensor later
                        url = g_strdup_printf ("sensor://%s/%d", chip_name, data->number);
                        
                        // get low and high values
                        sensors_applet_plugin_default_sensor_limits(type, 
                                                                    &low_value,
                                                                    &high_value);
                        
                        data = get_sensor_min_max(chip, n1, n2, data->number,
                                                  &low_value, &high_value);                        
                        if (data != NULL) {
                                // try adding this one
                                                        // at this point we have called sensors_get_all_features() and stopped when we have a potential new sensor, so make sure we try this as well - do a recursive call
                                check_sensor_with_data(sensors, chip_name, chip, n1, n2, data);
                                        
                        }

                        g_hash_table_insert(hash_table, g_strdup(url), (void *)chip);
                        // the id identifies a particular sensor for the user;
                        // we default to the label returned by libsensors
                        sensors_applet_plugin_add_sensor_with_limits(sensors,
                                                                     url,
                                                                     label, 
                                                                     label,
                                                                     type, 
                                                                     visible,
                                                                     low_value, 
                                                                     high_value, 
                                                                     icon,
                                                                     DEFAULT_GRAPH_COLOR);                        
                        g_free(url);
                }
                free (label);
        }
        
}

#endif


static GList *libsensors_plugin_get_sensors(void) {
	const sensors_chip_name *chip_name;
	int i;
        GList *sensors = NULL;

#if SENSORS_API_VERSION < 0x400        
	FILE *file;
        g_debug("%s: using libsensors version < 4", __FUNCTION__);

	/* try to open config file, otherwise try alternate config
	 * file - if neither succeed, exit */
	if ((file = fopen (LIBSENSORS_CONFIG_FILE, "r")) == NULL) {
		if ((file = fopen (LIBSENSORS_ALTERNATIVE_CONFIG_FILE, "r")) == NULL) {
                        g_debug("%s: error opening libsensors config file... ", __FUNCTION__);
			return sensors;
		}
	}
	
	/* at this point should have an open config file, if is not
	 * valid, close file and return */
	if (sensors_init(file) != 0) {
		fclose(file);
                g_debug("%s: error initing libsensors from config file...", __FUNCTION__);
		return sensors;
	}
	fclose(file);

	/* libsensors exposes a number of chips -  ... */
	i = 0;
	while ((chip_name = sensors_get_detected_chips (&i)) != NULL) {
		char *chip_name_string;
		const sensors_feature_data *data;
		int n1 = 0, n2 = 0;
                
		chip_name_string = get_chip_name_string(chip_name);
		
		/* ... each of which has one or more 'features' ... */
		while ((data = sensors_get_all_features (*chip_name, &n1, &n2)) != NULL) { // error
                        // fill in list for us
                        check_sensor_with_data(&sensors, chip_name_string, 
                                               chip_name, &n1, &n2, data);
                }
		g_free (chip_name_string);
	}

#else
        g_debug("%s: using libsensors version >= 4", __FUNCTION__);
        
        int nr = 0;
        if (sensors_init(NULL) != 0) {
                g_debug("%s: error initing libsensors", __FUNCTION__);
                return sensors;
        }
	i = 0;
	while ((chip_name = sensors_get_detected_chips(NULL, &nr)))
        {
                char *chip_name_string, *label;                
                const sensors_subfeature *input_feature;
                const sensors_subfeature *low_feature;
                const sensors_subfeature *high_feature;
		const sensors_feature *main_feature;
                SensorType type;
		gint nr1 = 0;
                gdouble value, low, high;
                gchar *path;
                gboolean visible;
                IconType icon;
                
		chip_name_string = get_chip_name_string(chip_name);
                if (chip_name_string == NULL) {
                        g_debug("%s: %d: error getting name string for sensor: %s\n",
                                        __FILE__, __LINE__, chip_name->path);
                        continue;
                }
                
		while ((main_feature = sensors_get_features(chip_name, &nr1)))
                {
			switch (main_feature->type)
                        {
                        case SENSORS_FEATURE_IN:
				type = VOLTAGE_SENSOR;
				input_feature = sensors_get_subfeature(chip_name, 
                                                                    main_feature, 
                                                                    SENSORS_SUBFEATURE_IN_INPUT);
				low_feature = sensors_get_subfeature(chip_name, 
                                                                     main_feature, 
                                                                     SENSORS_SUBFEATURE_IN_MIN);
				high_feature = sensors_get_subfeature(chip_name, 
                                                                      main_feature, 
                                                                      SENSORS_SUBFEATURE_IN_MAX);
			  	break;
                        case SENSORS_FEATURE_FAN:
				type = FAN_SENSOR;
				input_feature = sensors_get_subfeature(chip_name, 
                                                                    main_feature, 
                                                                    SENSORS_SUBFEATURE_FAN_INPUT);
				low_feature = sensors_get_subfeature(chip_name, 
                                                                     main_feature, 
                                                                     SENSORS_SUBFEATURE_FAN_MIN);
                                // no fan max feature
				high_feature = NULL;
				break;
                        case SENSORS_FEATURE_TEMP:
				type = TEMP_SENSOR;
				input_feature = sensors_get_subfeature(chip_name, 
                                                                    main_feature, 
                                                                    SENSORS_SUBFEATURE_TEMP_INPUT);
				low_feature = sensors_get_subfeature(chip_name, 
                                                                     main_feature, 
                                                                     SENSORS_SUBFEATURE_TEMP_MIN);
				high_feature = sensors_get_subfeature(chip_name, 
                                                                      main_feature, 
                                                                      SENSORS_SUBFEATURE_TEMP_MAX);
				if (!high_feature)
					high_feature = sensors_get_subfeature(chip_name, 
									      main_feature, 
									      SENSORS_SUBFEATURE_TEMP_CRIT);
				break;
                        default:
                                g_debug("%s: %d: error determining type for: %s\n",
                                        __FILE__, __LINE__, chip_name_string);
				continue;
                        }
                        
			if (!input_feature)
                        {
                                g_debug("%s: %d: could not get input subfeature for: %s\n",
                                        __FILE__, __LINE__, chip_name_string);
				continue;
                        }
                        // if still here we got input feature so get label
                        label = sensors_get_label(chip_name, main_feature);
                        if (!label) 
                        {
                                g_debug("%s: %d: error: could not get label for: %s\n",
                                        __FILE__, __LINE__, chip_name_string);
                                continue;
                        }

                        g_assert(chip_name_string && label);
                        
                        icon = get_sensor_icon(type);
                        visible = (type == TEMP_SENSOR ? TRUE : FALSE);
                        sensors_applet_plugin_default_sensor_limits(type, 
                                                                    &low, 
                                                                    &high);                        
                        if (low_feature) {
                                sensors_get_value(chip_name, low_feature->number, &low);
                        }

                        if (high_feature) {
                                sensors_get_value(chip_name, high_feature->number, &high);
                        }
                        
                                
                        if (sensors_get_value(chip_name, input_feature->number, &value) < 0) {
                                g_debug("%s: %d: error: could not get value for input feature of sensor: %s\n",
                                        __FILE__, __LINE__, chip_name_string);
                                free(label);
                                continue;
                        }

                        g_debug("for chip %s (type %s) got label %s and value %f", chip_name_string, 
                                (type == TEMP_SENSOR ? "temp" :
                                 (type == FAN_SENSOR ? "fan" :
                                  (type == VOLTAGE_SENSOR ? "voltage" : "error"))), label, value);

                        path = g_strdup_printf ("sensor://%s/%d", chip_name_string, input_feature->number);
			g_hash_table_insert(hash_table, g_strdup(path), (void *)chip_name);
                        sensors_applet_plugin_add_sensor_with_limits(&sensors,
                                                                     path,
                                                                     label,
                                                                     label,
                                                                     type, 
                                                                     visible,
                                                                     low, 
                                                                     high,
                                                                     icon,
                                                                     DEFAULT_GRAPH_COLOR);
                }
                g_free(chip_name_string);
                
                
        }
#endif

        return sensors;
}

static gdouble libsensors_plugin_get_sensor_value(const gchar *path, 
						  const gchar *id, 
						  SensorType type,
						  GError **error) {
	gdouble result = 0;
	regmatch_t m[3];

	/* parse the uri into a (chip, feature) tuplet */
	if (regexec (&uri_re, path, 3, m, 0) == 0) {
		const sensors_chip_name *found_chip;
		int feature;
		int i;

		if ((found_chip = g_hash_table_lookup(hash_table,
						     path)) != NULL)
		{
			gdouble value;
			feature = atoi(path + m[2].rm_so);
#if SENSORS_API_VERSION < 0x400
			/* retrieve the value of the feature */
			if (sensors_get_feature (*found_chip, feature, &value) == 0) {
				result = value;
			} else {
				g_set_error (error, SENSORS_APPLET_PLUGIN_ERROR, LIBSENSORS_MISSING_FEATURE_ERROR, "Error retrieving sensor value");
			}
#else
			if (sensors_get_value(found_chip, feature, &value) >= 0) {
				result = value;
			} else {
				g_set_error (error, SENSORS_APPLET_PLUGIN_ERROR, LIBSENSORS_MISSING_FEATURE_ERROR, "Error retrieving sensor value");
			}
#endif
		} else {
			g_set_error (error, SENSORS_APPLET_PLUGIN_ERROR, LIBSENSORS_CHIP_NOT_FOUND_ERROR, "Chip not found");
		}
	} else {
		g_set_error (error, SENSORS_APPLET_PLUGIN_ERROR, LIBSENSORS_REGEX_URL_COMPILE_ERROR, "Error compiling URL regex");
	}
	return result;
}


static GList *libsensors_plugin_init() {
	/* compile the regular expressions */
	if (regcomp(&uri_re, "^sensor://([a-z0-9_-]+)/([0-9]+)$",
                    REG_EXTENDED | REG_ICASE) != 0) {
		g_debug("Error compiling regexp...not initing libsensors sensors interface");
		return NULL;
	}

	/* create hash table to associate path strings with sensors_chip_name
	 * pointers - make sure it free's the keys strings on destroy */
	hash_table = g_hash_table_new_full(g_str_hash,
					   g_str_equal,
					   g_free,
					   NULL);
	return libsensors_plugin_get_sensors();
}


const gchar *sensors_applet_plugin_name(void) 
{
        return plugin_name;
}

GList *sensors_applet_plugin_init(void) 
{
        return libsensors_plugin_init();
}

gdouble sensors_applet_plugin_get_sensor_value(const gchar *path, 
                                                const gchar *id, 
                                                SensorType type,
                                                GError **error) {
        return libsensors_plugin_get_sensor_value(path, id, type, error);
}
