/*
 * ni-route.c
 * This file is part of NetworkInador
 *
 * Copyright (C) 2021 - Félix Arreola Rodríguez
 *
 * NetworkInador 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.
 *
 * NetworkInador 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 NetworkInador; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "ni-client.h"
#include "ni-route.h"
#include "ni-ip.h"

#include "network-inador-manager.h"

struct _NIRoutePrivate {
	NIClient *ni_client;
	
	guint family;
	gint family_size;
	guint type;
	uint32_t table;
	struct_addr dest;
	guint prefix;
	
	guint protocol, tos, scope;
	
	gboolean has_prefsrc;
	struct_addr prefsrc;
	guint priority;
	
	/* Múltiples GW */
	guint nh;
	NIRouteNH *nexthops;
};

enum {
	PROP_NI_CLIENT = 1,
	
	N_PROPERTIES
};

G_DEFINE_TYPE_WITH_PRIVATE (NIRoute, ni_route, G_TYPE_OBJECT)

static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };

enum {
	ROUTE_UPDATED,
	
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

static gboolean ni_route_process_route_response (NIClient *ni_client, unsigned char *buffer, guint size, gpointer data);

static void ni_route_constructed (GObject *obj) {
	NIRoute *ni_route;
	GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT);
	
	parent_class->constructed (obj);
	
	ni_route = NI_ROUTE (obj);
	
	if (ni_route->priv->ni_client != NULL) {
		/* Conectar la señal de datos */
		g_signal_connect (ni_route->priv->ni_client, "packet-read", G_CALLBACK (ni_route_process_route_response), ni_route);
	}
}

static void ni_route_dispose (GObject *obj) {
	NIRoute *ni_route;
	GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT);
	
	ni_route = NI_ROUTE (obj);
	
	g_signal_handlers_disconnect_by_func (ni_route->priv->ni_client, G_CALLBACK (ni_route_process_route_response), ni_route);
	
	parent_class->dispose (obj);
}

static void ni_route_finalize (GObject *obj) {
	NIRoute *ni_route;
	GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT);
	
	ni_route = NI_ROUTE (obj);
	
	/* Nada que hacer, por el momento */
	
	parent_class->finalize (obj);
}

static void ni_route_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
	NIRoute *ni_route = NI_ROUTE (object);
	g_return_if_fail (NI_IS_ROUTE (object));

	switch (prop_id) {
		case PROP_NI_CLIENT:
			ni_route->priv->ni_client = NI_CLIENT (g_value_get_object (value));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void ni_route_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
	NIRoute *ni_route = NI_ROUTE (object);
	g_return_if_fail (NI_IS_ROUTE (object));

	switch (prop_id) {
		case PROP_NI_CLIENT:
			g_value_set_object (value, ni_route->priv->ni_client);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
	}
}

static void ni_route_class_init (NIRouteClass *klass) {
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	
	object_class->set_property = ni_route_set_property;
	object_class->get_property = ni_route_get_property;
	object_class->constructed = ni_route_constructed;
	object_class->dispose = ni_route_dispose;
	object_class->finalize = ni_route_finalize;
	
	obj_properties[PROP_NI_CLIENT] = g_param_spec_object (
		"ni-client",
		"Network Inador Client",
		"The client object",
		NI_TYPE_CLIENT,
		G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
	
	g_object_class_install_properties (object_class, N_PROPERTIES, obj_properties);
	
	signals[ROUTE_UPDATED] = g_signal_new ("updated",
		G_TYPE_FROM_CLASS (klass),
		G_SIGNAL_RUN_LAST,
		0,
		NULL,
		NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE,
		0);
}

static void ni_route_init (NIRoute *ni_route) {
	NIRoutePrivate *priv = ni_route_get_instance_private (ni_route);
	ni_route->priv = priv;
	
	/* initialize all public and private members to reasonable default values.
	* They are all automatically initialized to 0 to begin with. */
	priv->ni_client = NULL;
	priv->family = AF_UNSPEC;
	priv->type = 0;
	priv->table = 0;
	priv->prefix = 0;
	
	priv->protocol = 0;
	priv->tos = 0;
	priv->scope = 0;
	
	priv->priority = 0;
	
	priv->has_prefsrc = FALSE;
	priv->nh = 0;
	priv->nexthops = NULL;
	
	memset (&priv->dest, 0, sizeof (priv->dest));
	memset (&priv->prefsrc, 0, sizeof (priv->prefsrc));
}

static gboolean ni_route_process_route_response (NIClient *ni_client, unsigned char *buffer, guint size, gpointer user_data) {
	/* Tengo que revisar si los datos son de mi interés, para absorber el evento yo */
	NIRoute *ni_route = (NIRoute *) user_data;
	uint8_t family, family_size;
	uint8_t type, protocol, tos, scope, prefix;
	uint32_t tabla, prioridad;
	uint8_t num_nexthops;
	gboolean has_prefsrc;
	int pos, g;
	NIRouteNH nexthops[16];
	struct_addr dest, prefsrc;
	int was_update = 0;
	
	memset (nexthops, 0, sizeof (nexthops));
	family = buffer[2];
	
	if (buffer[0] != NET_INADOR_TYPE_EVENT && buffer[1] != NET_INADOR_EVENT_ROUTE_ADDED) {
		return FALSE; /* No es para mí */
	}
	
	if (family == AF_INET) {
		family_size = sizeof (struct in_addr);
	} else if (family == AF_INET6) {
		family_size = sizeof (struct in6_addr);
	}
	
	type = buffer[3];
	
	memcpy (&tabla, &buffer[4], 4);
	prefix = buffer[8];
	protocol = buffer[9];
	tos = buffer[10];
	scope = buffer[11];
	
	has_prefsrc = FALSE;
	if (buffer[12] & 0x01) {
		has_prefsrc = TRUE;
	}
	
	num_nexthops = buffer[13];
	
	memcpy (&prioridad, &buffer[14], 4);
	
	memcpy (&dest, &buffer[18], family_size);
	
	pos = 18 + family_size;
	if (has_prefsrc) {
		memcpy (&prefsrc, &buffer[pos], family_size);
		pos += family_size;
	}
	
	/* Tengo suficientes datos, revisar si es mi ruta */
	if (ni_route->priv->family != family) return FALSE;
	if (ni_route->priv->tos != tos) return FALSE;
	if (ni_route->priv->table != tabla) return FALSE;
	if (ni_route->priv->priority != prioridad) return FALSE;
	
	if (memcmp (&ni_route->priv->dest, &dest, family_size) != 0 || ni_route->priv->prefix != prefix) {
		return FALSE;
	}
	
	/* Terminar de leer los otros datos */
	if (num_nexthops > 16) {
		num_nexthops = 16;
	}
	for (g = 0; g < num_nexthops; g++) {
		nexthops[g].has_gw = FALSE;
		if (buffer[pos] & 0x01) {
			/* Tiene GW */
			nexthops[g].has_gw = TRUE;
		}
		pos++;
		
		nexthops[g].flags = buffer[pos];
		pos++;
		
		nexthops[g].weight = buffer[pos];
		pos++;
		
		pos++; /* El byte ignorado */
		
		memcpy (&nexthops[g].out_index, &buffer[pos], 4);
		pos +=4;
		
		if (nexthops[g].has_gw) {
			memcpy (&nexthops[g].gw, &buffer[pos], family_size);
			pos += family_size;
		}
	}
	
	/* Es mi ruta, procesar como actualización */
	if (ni_route->priv->protocol != protocol) {
		was_update = 1;
		ni_route->priv->protocol = protocol;
	}
	
	if (ni_route->priv->tos != tos) {
		was_update = 1;
		ni_route->priv->tos = tos;
	}
	
	if (ni_route->priv->scope != scope) {
		was_update = 1;
		ni_route->priv->scope = scope;
	}
	
	if (ni_route->priv->has_prefsrc != has_prefsrc) {
		ni_route->priv->has_prefsrc = has_prefsrc;
		was_update = 1;
		if (has_prefsrc == FALSE) {
			memset (&ni_route->priv->prefsrc, 0, family_size);
		} else {
			memcpy (&ni_route->priv->prefsrc, &prefsrc, ni_route->priv->family_size);
		}
	} else if (ni_route->priv->has_prefsrc) {
		was_update = 1;
		memcpy (&ni_route->priv->prefsrc, &prefsrc, ni_route->priv->family_size);
	}
	
	if (ni_route->priv->nh != num_nexthops) {
		/* Liberar los next-hops anteriores y re-crear los nuevos */
		ni_route->priv->nh = num_nexthops;
		
		free (ni_route->priv->nexthops);
		
		ni_route->priv->nexthops = (NIRouteNH *) malloc (sizeof (NIRouteNH) * num_nexthops);
		for (g = 0; g < num_nexthops; g++) {
			/* Copiar cada next-hop */
			memcpy (&ni_route->priv->nexthops[g], &nexthops[g], sizeof (NIRouteNH));
		}
		was_update = 1;
	} else {
		for (g = 0; g < num_nexthops; g++) {
			/* Comparar brincos */
			if (memcmp (&ni_route->priv->nexthops[g], &nexthops[g], sizeof (NIRouteNH)) != 0) {
				was_update = 1;
				memcpy (&ni_route->priv->nexthops[g], &nexthops[g], sizeof (NIRouteNH));
			}
		}
	}
	
	/* Emitir mi señal de update */
	if (was_update) {
		g_signal_emit (ni_route, signals[ROUTE_UPDATED], 0);
	}
	
	/* Mi evento, ya lo procesé */
	return TRUE;
}

/* Los métodos getters */
NIClient *ni_route_get_client (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), NULL);
	
	return ni_route->priv->ni_client;
}

guint ni_route_get_route_type (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0);
	
	return ni_route->priv->type;
}

guint ni_route_get_route_tos (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0);
	
	return ni_route->priv->tos;
}

guint ni_route_get_family (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0);
	
	return ni_route->priv->family;
}

const struct_addr *ni_route_get_dest (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), NULL);
	
	return &ni_route->priv->dest;
}

guint ni_route_get_prefix (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0);
	
	return ni_route->priv->prefix;
}

uint32_t ni_route_get_table (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0);
	
	return ni_route->priv->table;
}

guint ni_route_get_num_nexthops (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0);
	
	return ni_route->priv->nh;
}

const NIRouteNH *ni_route_get_nexthops (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), NULL);
	
	return ni_route->priv->nexthops;
}

gboolean ni_route_has_prefsrc (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), FALSE);
	
	return ni_route->priv->has_prefsrc;
}

const struct_addr *ni_route_get_prefsrc (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), NULL);
	
	return &ni_route->priv->prefsrc;
}

guint ni_route_get_priority (NIRoute *ni_route) {
	g_return_val_if_fail (NI_IS_ROUTE (ni_route), 0);
	
	return ni_route->priv->priority;
}

/*
guint family;
	guint type;
	guint table;
	struct_addr dest;
	guint prefix;
	
	guint protocol, tos, scope;
	
	gboolean has_prefsrc;
	struct_addr prefsrc;
	guint priority;
*/
NIRoute *ni_route_new (NIClient *ni_client, int family, int type, uint32_t table, struct_addr *dest, int prefix, unsigned char protocol, unsigned char tos, unsigned char scope, struct_addr *prefsrc, int priority, int num_nexthops, NIRouteNH *nexthops) {
	NIRoute *ni_route;
	int g;
	
	g_return_val_if_fail (NI_IS_CLIENT (ni_client), NULL);
	g_return_val_if_fail (dest != NULL, NULL);
	g_return_val_if_fail (num_nexthops > 0, NULL);
	
	ni_route = g_object_new (NI_TYPE_ROUTE, "ni-client", ni_client, NULL);
	
	if (family == AF_INET) {
		ni_route->priv->family_size = sizeof (struct in_addr);
	} else if (family == AF_INET6) {
		ni_route->priv->family_size = sizeof (struct in6_addr);
	}
	
	/* Establecer y copiar los valores */
	ni_route->priv->family = family;
	ni_route->priv->type = type;
	ni_route->priv->table = table;
	ni_route->priv->prefix = prefix;
	
	memcpy (&ni_route->priv->dest, dest, ni_route->priv->family_size);
	
	ni_route->priv->protocol = protocol;
	ni_route->priv->tos = tos;
	ni_route->priv->scope = scope;
	ni_route->priv->priority = priority;
	
	if (prefsrc != NULL) {
		ni_route->priv->has_prefsrc = TRUE;
		
		memcpy (&ni_route->priv->prefsrc, prefsrc, ni_route->priv->family_size);
	}
	
	ni_route->priv->nh = num_nexthops;
	ni_route->priv->nexthops = (NIRouteNH *) malloc (sizeof (NIRouteNH) * num_nexthops);
	for (g = 0; g < num_nexthops; g++) {
		/* Copiar cada next-hop */
		memcpy (&ni_route->priv->nexthops[g], &nexthops[g], sizeof (NIRouteNH));
	}
	
	
	return ni_route;
}