#include "buttons.h"

extern ssize_t mts_attr_show_gpio_pin(struct device *dev,
        struct device_attribute *attr,
        char *buf);
extern struct gpio_pin *gpio_pin_by_attr_name(const char *name);

static button_info_pt *buttons = NULL;

struct gpio_pin *gpio_pin_by_button_name(const char *button_name)
{
	button_info_pt *pbutton;

	for (pbutton = buttons; *pbutton != NULL; pbutton++) {
		if (!strcmp(pbutton[0]->name, button_name)) {
			return gpio_pin_by_attr_name(pbutton[0]->label_pin);
		}
	}

	log_error("Button named %s not found", button_name);
	return NULL;
}

static button_info_t *button_by_monitor_name(const char *label_monitor)
{
    button_info_pt *pbutton;

	for (pbutton = buttons; *pbutton != NULL; pbutton++) {
		if (!strcmp(pbutton[0]->label_monitor, label_monitor)) {
			return pbutton[0];
		}
	}

	log_error("Button with %s monitor label is not found", label_monitor);
	return NULL;
}

static button_info_t *button_by_monitor_intervals_name(const char *label_monitor_intervals)
{
	button_info_pt *pbutton;

	for (pbutton = buttons; *pbutton != NULL; pbutton++) {
		if (!strcmp(pbutton[0]->label_monitor_intervals, label_monitor_intervals)) {
			return pbutton[0];
		}
	}

	log_error("Button with %s monitor intervals label is not found", label_monitor_intervals);
	return NULL;
}

ssize_t mts_attr_show_button_monitor_intervals(struct device *dev, struct device_attribute *attr, char *buf)
{
	int ret;

	button_info_t *button = button_by_monitor_intervals_name(attr->attr.name);

	if (!button) {
		return -ENODEV;
	}

	mutex_lock(&mts_io_mutex);

	ret = sprintf(buf, "%d %d\n", button->short_interval / BUTTON_CHECK_PER_SEC, button->long_interval / BUTTON_CHECK_PER_SEC);

	mutex_unlock(&mts_io_mutex);

	return ret;
}

ssize_t mts_attr_store_button_monitor_intervals(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	int short_int;
	int long_int;
	button_info_t *button = button_by_monitor_intervals_name(attr->attr.name);

	if (sscanf(buf, "%i %i", &short_int, &long_int) != 2) {
		return -EINVAL;
	}

	if (!button) {
		return -ENODEV;
	}

	mutex_lock(&mts_io_mutex);

	button->short_interval = short_int * BUTTON_CHECK_PER_SEC;
	button->long_interval = long_int * BUTTON_CHECK_PER_SEC;

	mutex_unlock(&mts_io_mutex);

	return count;
}

ssize_t mts_attr_show_button_monitor(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int ret;

	button_info_t *button = button_by_monitor_name(attr->attr.name);

	if (!button) {
		return -ENODEV;
	}

	mutex_lock(&mts_io_mutex);

	ret = sprintf(buf, "%d %d %d %d\n", button->pid, button->short_signal, button->long_signal, button->extra_long_signal);

	mutex_unlock(&mts_io_mutex);

	return ret;
}

ssize_t mts_attr_store_button_monitor(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	pid_t pid;
	int short_signal;
	int long_signal;
	int extra_long_signal;
	int result = sscanf(buf, "%i %i %i %i", &pid, &short_signal, &long_signal, &extra_long_signal);

	button_info_t *button = button_by_monitor_name(attr->attr.name);

	if (!button) {
		return -ENODEV;
	}

	if (result < 3 || result > 4) {
		return -EINVAL;
	}

	if(result == 3) {
		mutex_lock(&mts_io_mutex);

		button->pid = pid;
		button->short_signal = short_signal;
		button->long_signal = long_signal;

		mutex_unlock(&mts_io_mutex);
	} else {
		mutex_lock(&mts_io_mutex);

		button->pid = pid;
		button->short_signal = short_signal;
		button->long_signal = long_signal;
		button->extra_long_signal = extra_long_signal;

		mutex_unlock(&mts_io_mutex);
	}

	return count;
}


static void button_worker(struct work_struct *ignored);

static DECLARE_DELAYED_WORK(button_work, button_worker);

static void button_worker(struct work_struct *ignored)
{
	struct gpio_pin *pin;
	struct pid *vpid;
	int button_pressed = 0;

	button_info_pt *pbutton;

	mutex_lock(&mts_io_mutex);

	for (pbutton = buttons; *pbutton != NULL; pbutton++) {
		button_pressed = 0;
	    vpid = NULL;

		pin = gpio_pin_by_button_name(pbutton[0]->name);

		if (pin) {
			button_pressed = !gpio_get_value(pin->pin.gpio);
		}

		if (pbutton[0]->pid > 0) {
			vpid = find_vpid(pbutton[0]->pid);
		}

		if (vpid) {
			if (button_pressed) {
			    pbutton[0]->pressed_count++;
			}
			else {
				// The button is not pressed
				if (pbutton[0]->pressed_count > 0 && pbutton[0]->pressed_count < pbutton[0]->short_interval) {
				    log_debug("Button %s short signal", pbutton[0]->name);
					kill_pid(vpid, pbutton[0]->short_signal, 1);
				} else if (pbutton[0]->pressed_count >= pbutton[0]->short_interval && pbutton[0]->pressed_count < pbutton[0]->long_interval) {
                    log_debug("Button %s long signal", pbutton[0]->name);
					kill_pid(vpid, pbutton[0]->long_signal, 1);
				}

				pbutton[0]->pressed_count = 0;
				pbutton[0]->sent_extra_long = false;
			}
			if (pbutton[0]->pressed_count >= pbutton[0]->long_interval && ! pbutton[0]->sent_extra_long) {
                log_debug("Button %s extra long signal", pbutton[0]->name);
				kill_pid(vpid, pbutton[0]->extra_long_signal, 1);
				pbutton[0]->sent_extra_long = true;
			}
		} else {
		    pbutton[0]->pressed_count = 0;
		}
	}

	mutex_unlock(&mts_io_mutex);

	schedule_delayed_work(&button_work, BUTTON_INTERVAL);
}

int set_buttons (button_info_pt* platform_buttons) {
    if (platform_buttons == NULL) {
        log_error("Null pointer error");
        return -EINVAL;
    }
    mutex_lock(&mts_io_mutex);
    if (buttons != NULL) {
        log_warning("Buttons structure was initialized more then once");
    }
    buttons = platform_buttons;
    mutex_unlock(&mts_io_mutex);
    return 0;
}

void init_buttons(void) {
    if (buttons == NULL) {
        log_error("Button structure hasn't been set. Use default");
        set_buttons(default_buttons);
    }
	button_worker(NULL);
}

void cleanup_buttons(void) {
	cancel_delayed_work_sync(&button_work);
}

// Reset button is common for all devices

button_info_t reset_button = {
		.name = "Reset Button",
		.label_pin = "reset",
		.label_monitor = "reset-monitor",
		.label_monitor_intervals = "reset-monitor-intervals",

		/* Signals */
		.short_signal = SIGUSR1,
		.long_signal  = SIGUSR2,
		.short_signal = SIGHUP,

		/* Intervals */
		.short_interval = BUTTON_HOLD_COUNT,
		.long_interval = BUTTON_LONG_HOLD_COUNT,
};

DEVICE_ATTR_MTS(dev_attr_reset_monitor_intervals,
		reset_button.label_monitor_intervals,
		mts_attr_show_button_monitor_intervals,
		mts_attr_store_button_monitor_intervals);

DEVICE_ATTR_MTS(dev_attr_reset_monitor,
		reset_button.label_monitor,
		mts_attr_show_button_monitor,
		mts_attr_store_button_monitor);

DEVICE_ATTR_RO_MTS(dev_attr_reset,
		reset_button.label_pin,
		mts_attr_show_gpio_pin);


button_info_pt default_buttons[] = {
        &reset_button,
        NULL,
};