/*
 * Soubor:  sprava.c
 * Datum:   06.08.2012
 * Autor:   Stanislav Bárta, xbarta29@stud.fit.vutbr.cz
 * Projekt: IM modul
 * Popis:   soubor obsahujici funkce pro spravu sezeni
 */

//#include "xmpp.h"
#include "sprava.h"

/*
 * Funkce vypocita index do tabulky sezeni podle ipv4 adres a portu
 */
unsigned int hash_function_ipv4(struct in_addr ip1, int port1, struct in_addr ip2, int port2){
	unsigned int index = ((unsigned int)(ip1.s_addr) * 59) ^ (unsigned int)(ip2.s_addr) ^ ((unsigned int)(port1) << 16) ^ (unsigned int)(port2);
	return index % TABLE_SIZE;
}

/*
 * Funkce vypocita index do tabulky sezeni podle ipv6 adres a portu
 */
unsigned int hash_function_ipv6(struct in6_addr ip1, int port1, struct in6_addr ip2, int port2){

	unsigned int ip1_sum = 0, ip2_sum = 0;
	for(int i = 0; i < 16; i++){
		ip1_sum += ip1.s6_addr[i];
		ip2_sum += ip2.s6_addr[i];
	}
	
	unsigned int index = ((unsigned int)(ip1_sum) * 59) ^ (unsigned int)(ip2_sum) ^ ((unsigned int)(port1) << 16) ^ (unsigned int)(port2);
	return index % TABLE_SIZE;
}

/*
 * V tabulce seznamu sezeni najde aktualni sezeni a to vrati i s indexem do tabulky
 */
struct polozka_tabulky ziskat_sezeni(struct seznam_sezeni *Seznam, struct data_packetu obsah)
{
	struct polozka_tabulky polozka;
	struct sezeni *aktualni_sezeni = NULL;
	unsigned int index;

	if(obsah.smer == odchozi)
	{
		if(obsah.verze_ip == IPverze4)
		{
			index = hash_function_ipv4(obsah.ipv4_zdroje, obsah.port_zdroje, obsah.ipv4_cile, obsah.port_cile);
			aktualni_sezeni = zapocate_sezeni_IPv4(&Seznam[index], obsah.port_zdroje, obsah.ipv4_zdroje);
		}
		else if(obsah.verze_ip == IPverze6)
		{
			index = hash_function_ipv6(obsah.ipv6_zdroje, obsah.port_zdroje, obsah.ipv6_cile, obsah.port_cile);
			aktualni_sezeni = zapocate_sezeni_IPv6(&Seznam[index], obsah.port_zdroje, obsah.ipv6_zdroje);
		}

		// sezeni jeste nebylo vytvoreno
		if(aktualni_sezeni == NULL)
		{
			// vytvori se nove sezeni a nastavi se mu odpovidajici udaje
			vloz_prvni_sezeni(&Seznam[index]);
			Seznam[index].Prvni->data.transportni_protokol = obsah.transportni_protokol;
			if(obsah.verze_ip == IPverze4)
			{
				Seznam[index].Prvni->data.typ_ip = IPverze4;
				Seznam[index].Prvni->data.ipv4_adresa = obsah.ipv4_zdroje;
				Seznam[index].Prvni->data.ipv4_adresa_2 = obsah.ipv4_cile;
			}
			else if(obsah.verze_ip == IPverze6)
			{
				Seznam[index].Prvni->data.typ_ip = IPverze6;
				Seznam[index].Prvni->data.ipv6_adresa = obsah.ipv6_zdroje;
				Seznam[index].Prvni->data.ipv6_adresa_2 = obsah.ipv6_cile;
			}

			Seznam[index].Prvni->data.port_2 = obsah.port_cile;
			Seznam[index].Prvni->data.port = obsah.port_zdroje;
			aktualni_sezeni = &(Seznam[index].Prvni->data);


			aktualni_sezeni->protokol = obsah.typ;
		}
	}
	else if(obsah.smer == prichozi)
	{
		if(obsah.verze_ip == IPverze4)
		{
			index = hash_function_ipv4(obsah.ipv4_cile, obsah.port_cile, obsah.ipv4_zdroje, obsah.port_zdroje);
			aktualni_sezeni = zapocate_sezeni_IPv4(&Seznam[index], obsah.port_cile, obsah.ipv4_cile);
		}
		else if(obsah.verze_ip == IPverze6)
		{
			index = hash_function_ipv6(obsah.ipv6_cile, obsah.port_cile, obsah.ipv6_zdroje, obsah.port_zdroje);
			aktualni_sezeni = zapocate_sezeni_IPv6(&Seznam[index], obsah.port_cile, obsah.ipv6_cile);
		}

		// sezeni jeste nebylo vytvoreno
		if(aktualni_sezeni == NULL)
		{
			// vytvori se nove sezeni a nastavi se mu odpovidajici udaje
			vloz_prvni_sezeni(&Seznam[index]);
			Seznam[index].Prvni->data.transportni_protokol = obsah.transportni_protokol;
			if(obsah.verze_ip == IPverze4)
			{
				Seznam[index].Prvni->data.typ_ip = IPverze4;
				Seznam[index].Prvni->data.ipv4_adresa = obsah.ipv4_cile;
				Seznam[index].Prvni->data.ipv4_adresa_2 = obsah.ipv4_zdroje;
			}
			else if(obsah.verze_ip == IPverze6)
			{
				Seznam[index].Prvni->data.typ_ip = IPverze6;
				Seznam[index].Prvni->data.ipv6_adresa = obsah.ipv6_cile;
				Seznam[index].Prvni->data.ipv6_adresa_2 = obsah.ipv6_zdroje;
			}
			Seznam[index].Prvni->data.port_2 = obsah.port_zdroje;
			Seznam[index].Prvni->data.port = obsah.port_cile;
			aktualni_sezeni = &(Seznam[index].Prvni->data);

			aktualni_sezeni->protokol = obsah.typ;
		}
	}
	polozka.sezeni = aktualni_sezeni;
	polozka.index = index;

	return polozka;
}


/*
 * inicializuje seznam sezeni pro pozdejsi alokace novych polozek
 */
void inicializace_seznamu_sezeni(struct seznam_sezeni *Seznam)
{
	for(int i = 0; i < TABLE_SIZE; i++){
		Seznam[i].Prvni = NULL;
	}
}

/*
 * smazani seznamu sezeni
 */
void smazani_seznamu_sezeni(struct seznam_sezeni *Seznam)
{
	for(int i = 0; i < TABLE_SIZE; i++){
		struct sezeni_ptr *ptr = Seznam[i].Prvni;

		while(ptr != NULL)
		{
			Seznam[i].Prvni = ptr->dalsi;
			smazani_sezeni(&(ptr->data));
			free(ptr);
			ptr = Seznam[i].Prvni;
		}
	}
}

/*
 * do seznamu sezeni vlozi novou polozku na prvni misto
 */
void vloz_prvni_sezeni(struct seznam_sezeni *Seznam)
{
	struct sezeni_ptr *ptr = NULL;
	ptr = (struct sezeni_ptr *) malloc(sizeof(struct sezeni_ptr));
	ptr->dalsi = Seznam->Prvni;
	Seznam->Prvni = ptr;

	inicializace_sezeni(&(ptr->data));
}

/*
 * se seznamu sezeni odebere polozku ktera ma jako data rusene sezeni
 */
void odeber_sezeni(struct seznam_sezeni *Seznam, struct sezeni *Sezeni)
{
	struct sezeni_ptr *ptr = Seznam->Prvni;

	// kontrola jestli neni maza polozka prvni v seznamu
	if(&(ptr->data) == Sezeni)
	{
		struct sezeni_ptr *mazana_polozka = Seznam->Prvni;

		// upravime ukazatel na prvniho
		Seznam->Prvni = Seznam->Prvni->dalsi;

		// smazani sezeni
		smazani_sezeni(&(mazana_polozka->data));
		free(mazana_polozka);
		return;
	}

	//projdeme dalsi polozky
	while(ptr != NULL)
	{
		// predchozi uz sme zkontorlovali, podivame se jestli je dale co kontrolovat
		if(ptr->dalsi == NULL)
		{
			return;
		}
		// obsah nasledujici polozky seznamu je mazane sezeni
		if(&(ptr->dalsi->data) == Sezeni)
		{
			struct sezeni_ptr *mazana_polozka = ptr->dalsi;
			// nastavime ukazatel aby preskocil mazanou polozku
			ptr->dalsi = ptr->dalsi->dalsi;

			// smazani sezeni
			smazani_sezeni(&(mazana_polozka->data));
			free(mazana_polozka);

			return;
		}
		ptr = ptr->dalsi;
	}
}

/*
 * inicializace struktury noveho sezeni
 */
void inicializace_sezeni(struct sezeni *Sezeni)
{
	Sezeni->aktivni = 0;
	Sezeni->uzivatel_pripojen.tv_sec = 0;
	Sezeni->uzivatel_pripojen.tv_usec = 0;
	Sezeni->transportni_protokol = 0; 
	Sezeni->typ_ip = 0;
	Sezeni->ipv4_adresa.s_addr = 0;
	Sezeni->ipv4_adresa_2.s_addr = 0;
	for(int i = 0; i < 16; i++){
		Sezeni->ipv6_adresa.s6_addr[i] = 0;
		Sezeni->ipv6_adresa_2.s6_addr[i] = 0;
	}
	Sezeni->port = 0;
	Sezeni->port_2 = 0;
	Sezeni->protokol = NEZNAME;
	Sezeni->ID_uzivatele = NULL;
	Sezeni->server = NULL;
	Sezeni->aktualni_stav = 0; // nepripojen
	Sezeni->potreba_dokoncit_prichozi = NULL;
	Sezeni->ocekavat_dalsi_prichozi = 0;
	Sezeni->potreba_dokoncit_odchozi = NULL;
	Sezeni->ocekavat_dalsi_odchozi = 0;
	Sezeni->ukonceno = 0;
}

/*
 * vymaze alokovane polozky v seznamu sezeni
 */
void smazani_sezeni(struct sezeni *Sezeni)
{
	if(Sezeni->ID_uzivatele != NULL)
	{
		free(Sezeni->ID_uzivatele);
	}
	if(Sezeni->server != NULL)
	{
		free(Sezeni->server);
	}
	if(Sezeni->potreba_dokoncit_prichozi != NULL)
	{
		free(Sezeni->potreba_dokoncit_prichozi);
	}
	if(Sezeni->potreba_dokoncit_odchozi != NULL)
	{
		free(Sezeni->potreba_dokoncit_odchozi);
	}
}

/*
 * vrati ukazatel na polozku seznamu sezeni ktera odpovida zadane IP adrese a portu
 */
struct sezeni *zapocate_sezeni_IPv4(struct seznam_sezeni *Seznam, int port, struct in_addr ip)
{
	struct sezeni_ptr *pomocny = Seznam->Prvni;
	while(pomocny != NULL)
	{

		if(pomocny->data.port == port && pomocny->data.ipv4_adresa.s_addr == ip.s_addr)
		{
			return &(pomocny->data);
		}
		else
		{
			pomocny = pomocny->dalsi;
		}
	}
	// zadne odpovidajici sezeni neni v seznamu
	return NULL;
}

/*
 * vrati ukazatel na polozku seznamu sezeni ktera odpovida zadane IP adrese a portu
 */
struct sezeni *zapocate_sezeni_IPv6(struct seznam_sezeni *Seznam, int port, struct in6_addr ip)
{
	struct sezeni_ptr *pomocny = Seznam->Prvni;
	while(pomocny != NULL)
	{

		if(pomocny->data.port == port && porovnani_ipv6_adres(&(pomocny->data.ipv6_adresa), &ip))
		{
			return &(pomocny->data);
		}
		else
		{
			pomocny = pomocny->dalsi;
		}
	}
	// zadne odpovidajici sezeni neni v seznamu
	return NULL;
}

/*
 * porovnani dvou ipv6 adres. Hodnota 1 indikuje, ze jsou stejne
 */
int porovnani_ipv6_adres(struct in6_addr *ip1, struct in6_addr *ip2)
{
	for(int i = 0; i < 16; i++)
	{
		// adresy se lisi
		if(ip1->s6_addr[i] != ip2->s6_addr[i])
		{
			return 0;
		}
	}

	// cyklus probehl a nedoslo k neshode -> adresy jsou stejne
	return 1;
}

