/*
 * (c) 2009 Andrzej Godziuk (GDR!) <gdr@go2.pl>
 * Development sponsored by CloudAccess.net (http://cloudaccess.net/)
 */

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

#include "mysql/mysql.h"

#include "base.h"
#include "log.h"
#include "buffer.h"

#include "plugin.h"

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

/**
 * This is a per-vhost bandwidth counter
 */



/* plugin config for all request/connections */

typedef struct {
	buffer *server;
	buffer *db;
	buffer *table;
	buffer *user;
	buffer *password;
	off_t  report_interval;
} plugin_config;

typedef struct {
	PLUGIN_DATA;

	plugin_config **config_storage;

	plugin_config conf;

	/* GDR! */
	array *in_traffic, *out_traffic;
	time_t last_exec;
} plugin_data;

typedef struct {
	size_t foo;
} handler_ctx;

static handler_ctx * handler_ctx_init() {
	handler_ctx * hctx;

	hctx = calloc(1, sizeof(*hctx));

	return hctx;
}

static void handler_ctx_free(handler_ctx *hctx) {

	free(hctx);
}

/* init the plugin data */
INIT_FUNC(mod_bw_counter_init) {
	plugin_data *p;

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

	p->in_traffic = array_init();
	p->out_traffic = array_init();
	p->last_exec = 0;

	return p;
}

/* detroy the plugin data */
FREE_FUNC(mod_bw_counter_free) {
	plugin_data *p = p_d;

	UNUSED(srv);

	if (!p) return HANDLER_GO_ON;

	if (p->config_storage) {
		size_t i;

		for (i = 0; i < srv->config_context->used; i++) {
			plugin_config *s = p->config_storage[i];

			if (!s) continue;

			buffer_free(s->server);
			buffer_free(s->db);
			buffer_free(s->table);
			buffer_free(s->user);
			buffer_free(s->password);

			free(s);
		}
		free(p->config_storage);
	}

	array_free(p->in_traffic);
	array_free(p->out_traffic);

	free(p);

	return HANDLER_GO_ON;
}

/* handle plugin config and check values */

SETDEFAULTS_FUNC(mod_bw_counter_set_defaults) {
	plugin_data *p = p_d;
	size_t i = 0;

	config_values_t cv[] = {
		{ "bw_counter.mysql_server",             	NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },  
		{ "bw_counter.mysql_db",         			NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },  
		{ "bw_counter.mysql_table",             	NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },  
		{ "bw_counter.mysql_user",             		NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },  
		{ "bw_counter.mysql_password",             	NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },  
		{ "bw_counter.report_interval",				NULL, T_CONFIG_SHORT,  T_CONFIG_SCOPE_SERVER },
		{ NULL,                         			NULL, T_CONFIG_UNSET,  T_CONFIG_SCOPE_UNSET  }
	};

	if (!p) return HANDLER_ERROR;

	p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));

	for (i = 0; i < srv->config_context->used; i++) {
		plugin_config *s;

		s = calloc(1, sizeof(plugin_config));
		s->server = buffer_init();
		s->db = buffer_init();
		s->table = buffer_init();
		s->user = buffer_init();
		s->password = buffer_init();
		s->report_interval = 30;

		cv[0].destination = s->server;
		cv[1].destination = s->db;
		cv[2].destination = s->table;
		cv[3].destination = s->user;
		cv[4].destination = s->password;
		cv[5].destination = &(s->report_interval);

		p->config_storage[i] = s;

		if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
			return HANDLER_ERROR;
		}
	}

	return HANDLER_GO_ON;
}

void mod_bw_counter_write_to_db(server *srv, plugin_data *p) {
	unsigned int i;
	MYSQL mysql;
	MYSQL_STMT *update, *insert;
	MYSQL_BIND bind[4];
	buffer *update_b, *insert_b;

	if(mysql_init(&mysql) == NULL)
	{
		log_error_write(srv, __FILE__, __LINE__, "s", "Failed to initate MySQL connection");
		return;
	}

	if (!mysql_real_connect(&mysql, p->config_storage[0]->server->ptr, p->config_storage[0]->user->ptr, p->config_storage[0]->password->ptr, p->config_storage[0]->db->ptr, 0, NULL, 0)) 
	{ 
		log_error_write(srv, __FILE__, __LINE__, "ss", "Failed to connect to MySQL: Error:", mysql_error(&mysql)); 
		return;
	}

	/* prepare UPDATE statement */
    update_b = buffer_init_string("UPDATE ");
	buffer_append_string_buffer(update_b, p->config_storage[0]->table);
	buffer_append_string_len(update_b, CONST_STR_LEN(" SET `in`=`in` + ?, `out`=`out`+? WHERE vhost=? AND day=CURDATE()"));
	
	update = mysql_stmt_init(&mysql);
	if(!update)
	{
		log_error_write(srv, __FILE__, __LINE__, "s", "mysql_stmt_init(), out of memory"); 
		mysql_close(&mysql);
		return;
	}

	if (mysql_stmt_prepare(update, update_b->ptr, strlen(update_b->ptr)))
	{
		log_error_write(srv, __FILE__, __LINE__, "sss", " mysql_stmt_prepare(), UPDATE failed ", mysql_stmt_error(update), update_b->ptr);
		mysql_close(&mysql);
		buffer_free(update_b);
		return;
	}
	buffer_free(update_b);

	/* prepare INSERT statement */
    insert_b = buffer_init_string("INSERT INTO ");
	buffer_append_string_buffer(insert_b, p->config_storage[0]->table);
	buffer_append_string_len(insert_b, CONST_STR_LEN("(`in`, `out`, `vhost`, `day`) VALUES(?, ?, ?, CURDATE())"));
	
	insert = mysql_stmt_init(&mysql);
	if(!insert)
	{
		log_error_write(srv, __FILE__, __LINE__, "s", "mysql_stmt_init(), out of memory"); 
		mysql_close(&mysql);
		return;
	}

	if (mysql_stmt_prepare(insert, insert_b->ptr, strlen(insert_b->ptr)))
	{
		log_error_write(srv, __FILE__, __LINE__, "sss", " mysql_stmt_prepare(), INSERT failed ", mysql_stmt_error(insert), insert_b->ptr);
		mysql_close(&mysql);
		buffer_free(insert_b);
		return;
	}
	buffer_free(insert_b);

	for(i = 0; i < p->in_traffic->used; i++)
	{
		data_count *di = (data_count*)p->in_traffic->data[i]; 
		data_count *dio = (data_count*)p->out_traffic->data[i]; 
		if(di->count > 0)
		{
			unsigned long len;
			/*
			buffer *q;
			q = buffer_init_string("UPDATE ");
			buffer_append_string_buffer(q, p->config_storage[0]->table);
			buffer_append_string_len(q, CONST_STR_LEN(" SET in=int+"));
			buffer_append_off_t(q, di->count);
			buffer_append_string_len(q, CONST_STR_LEN(" WHERE domain=\'"));
			buffer_append_string_buffer(q, di->key);
			buffer_append_string_len(q, CONST_STR_LEN("\'"));

			fprintf(stderr, "%s\n", q->ptr);
			*/
			memset(bind, 0, sizeof(bind));
			len = di->key->used - 1;

			bind[0].buffer_type = MYSQL_TYPE_LONG;
			bind[0].buffer = (char*)&(di->count);
			bind[0].is_null = 0;
			bind[0].length = 0;

			bind[1].buffer_type = MYSQL_TYPE_LONG;
			bind[1].buffer = (char*)&(dio->count);
			bind[1].is_null = 0;
			bind[1].length = 0;

			bind[2].buffer_type = MYSQL_TYPE_STRING;
			bind[2].buffer = (char*)(di->key->ptr);
			bind[2].is_null = 0;
			bind[2].length = &len;

			/* DEBUG-REMOVE! */
			if(strcmp(di->key->ptr, dio->key->ptr))
			{
				log_error_write(srv, __FILE__, __LINE__, "sss", " array indexes unequal - (in, out) ", di->key->ptr, dio->key->ptr);
			}

			if(mysql_stmt_bind_param(update, bind))
			{
				log_error_write(srv, __FILE__, __LINE__, "ss", " mysql_stmt_bind_param(), UPDATE failed ", mysql_stmt_error(update));
				continue;
			}

			if(mysql_stmt_execute(update))
			{
				log_error_write(srv, __FILE__, __LINE__, "ss", " mysql_stmt_execute(), UPDATE failed ", mysql_stmt_error(update));
				continue;
			}

			if(mysql_stmt_affected_rows(update) != 1)
			{
				/* Let's insert, then */
				if(mysql_stmt_bind_param(insert, bind))
				{
					log_error_write(srv, __FILE__, __LINE__, "ss", " mysql_stmt_bind_param(), INSERT failed ", mysql_stmt_error(insert));
					continue;
				}

				if(mysql_stmt_execute(insert))
				{
					log_error_write(srv, __FILE__, __LINE__, "ss", " mysql_stmt_execute(), INSERT failed ", mysql_stmt_error(insert));
					continue;
				}
			}
		}
		/* if insert/update succeeded */
		di->count = 0;
		dio->count = 0;
	}


	p->last_exec = time(0);
	mysql_close(&mysql);
}


CONNECTION_FUNC(mod_bw_counter_close_handler) {
	data_count *dc, *prev;
	plugin_data *p = p_d;

	if(con->server_name->used == 0 || (con->bytes_read == 0 && con->bytes_written == 0))
	{
		return HANDLER_GO_ON;
	}
#if 0
	b = buffer_init_buffer(con->server_name);
	buffer_append_string(b, " - ");
	buffer_append_off_t(b, con->bytes_read);
	buffer_append_string(b, "/");
	buffer_append_off_t(b, con->bytes_written);
	buffer_append_string(b, "\r\n");
#endif

	/* Update incoming data counters */
	/* Get vhost data, if existed */
	prev = array_get_element(p->in_traffic, con->server_name->ptr);

	if(prev)
	{
		/* Increase byte counter by current counter */
		prev->count += con->bytes_read;
	}
	else
	{
		/* Prepare keyval pair */
		dc = data_count_init();
		buffer_copy_string_buffer(dc->key, con->server_name);
		dc->count = con->bytes_read;

		/* Insert entry into array */
		array_replace(p->in_traffic, (data_unset *)dc);
	}

	/* Update outgoing data counters */
	/* Get vhost data, if existed */
	prev = array_get_element(p->out_traffic, con->server_name->ptr);

	if(prev)
	{
		/* Increase byte counter by current counter */
		prev->count += con->bytes_written;
	}
	else
	{
		/* Prepare keyval pair */
		dc = data_count_init();
		buffer_copy_string_buffer(dc->key, con->server_name);
		dc->count = con->bytes_written;

		/* Insert entry into array */
		array_replace(p->out_traffic, (data_unset *)dc);
	}

	if(time(0) - p->last_exec > p->config_storage[0]->report_interval)
	{
		mod_bw_counter_write_to_db(srv, p);
	}

//	fprintf(stderr, "host %s user %s pass %s db %s\n", p->config_storage[0]->server->ptr, p->config_storage[0]->user->ptr, p->config_storage[0]->password->ptr, p->config_storage[0]->db->ptr);
//	fprintf(stderr, b->ptr);

	return HANDLER_GO_ON;
}


/* this function is called at dlopen() time and inits the callbacks */

int mod_bw_counter_plugin_init(plugin *p) {
	p->version     = LIGHTTPD_VERSION_ID;
	p->name        = buffer_init_string("bw_counter");

	p->init        = mod_bw_counter_init;
	p->handle_connection_close  = mod_bw_counter_close_handler;
	p->connection_reset  = mod_bw_counter_close_handler;
	p->set_defaults  = mod_bw_counter_set_defaults;
	p->cleanup     = mod_bw_counter_free;

	p->data        = NULL;

	return 0;
}

