// Master
// kompilacja: g++ master.cpp -lstdc++ -o masterProcess && ./masterProcess
/*
Jeden proces (master) oczekuje na pojawienie się podległych mu procesów (slaves). Za pomocą dodatkowego procesu możemy startować podległe procesy. Nowy proces wysyła masterowi sygnał, że się pojawił i poprzez kolejkę komunikatów przekazuje mu namiary na siebie. Od tego momentu master zaczyna komunikować się z nowopowstałym slave’wem periodycznie, według wzorca pytanie/odpowiedź poprzez named pipes, co slave uwidacznia.
*/

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/msg.h>

#define handle_error(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)

// prosta funkcja zwracajaca czas
struct timeval czas;
int ret; // zmienna przyjmujaca zwracana wartosc z paru funkcji w programie
char* getTime()
{
	ret = gettimeofday(&czas, NULL);
	char* buffer = (char *) malloc(sizeof(char) * 18);
	if(ret == 0)
	{
		sprintf(buffer, "%ld.%06ld:", czas.tv_sec, czas.tv_usec);
	}
	else
	{
		strcpy(buffer, "Error:");
	}
	return buffer;
}

// funkcja obslugujaca przychodzace sygnaly (tylko SIGINT)
// sygnaly sa zliczane, a nie zapamietywane jako 0 lub 1 ('czy przyszedl'),
// bo moze przyjsc kilka zanim zacznie sie ich obsluga w 'main'
int otrzymalSygnalow = 0;
static void obslugaSygnalu(int signum)
{
	otrzymalSygnalow += 1;
	printf("Otrzymano sygnal SIGINT\n");
}

// struktura do przesylania przez kolejke komunikatow (FIFO)
struct Komunikat
{
	// pierwszy w strukturze MUSI byc 'long int' [o dowolnej nazwie],
	// w nim bedzie przechowywany 'typ' komunikatu
	// w funkcji ktora pobieramy komunikaty:
	// msgrcv(kolejkaKomunikatowID, &komunikat, sizeof(struct Komunikat), 1, 0);
	// ta '1' to wlasnie typ komunikatu jaki chcemy otrzymac
	// (proces 'slave' przed wyslaniem komunikatu ustawia w strukturze typ na '1')
	long int typeKomunikatu;
	long int pid;
};

// limit obslugiwanych slave, tej wartosci uzywamy przy tworzeniu tablic z wejsciami i wyjsciami named-pipe
int iloscMaksymalnaSlave = 30;
// czas miedzy sprawdzeniami czy jest jakis sygnal
int czasPomiedzyObslugaSygnalow = 1000; // 0.001 sec - 1 ms
// czas pomiedzy obsluga sygnalow i periodyczna komunikacja w mikrosekundach
int czasPeriodycznejKomunikacji = 500000; // 0.5 sec

int main(int argc, char *argv[])
{
	int kolejkaKomunikatowID;
	int i; // iterator do 'for'
	Komunikat komunikat; // komunikat przesylany przez kolejke komunikatow
	char buffer [50]; // taki sam rozmiar jak w slave
	int iloscSlaveow = 0; // ilosc juz obslugiwanych slave
	int wejscie[iloscMaksymalnaSlave]; // named-pipe wejscia od slave
	int wyjscie[iloscMaksymalnaSlave]; // named-pipe wyjscia do slave
	int czasOdOstatniejPeriodycznejKomunikacji = 0; // zlicza czas od ostatniej periodycznej komunikacji z slave

	// ustawianie funkcji 'obslugaSygnalu' jako tej ktora obsluguje sygnal 'SIGINT' (ctrl+c)
	struct sigaction akcja;
	akcja.sa_handler = obslugaSygnalu;
	akcja.sa_flags = 0;
	sigaction(SIGINT, &akcja, NULL);
	// koniec ustawiania

	// tworze lub pobieram kolejke komunikatow o ID 123
	// jako, ze mogla juz istniec i cos w niej moglo byc to lepiej ja..
	if((kolejkaKomunikatowID = msgget(123, IPC_CREAT | 0660 )) == -1)
	{
		printf("Nie udalo sie utworzyc/pobrac ID kolejki o podanym kluczu.\n");
		return EXIT_FAILURE;
	}
	// ..usunac
	msgctl(kolejkaKomunikatowID, IPC_RMID, (struct msqid_ds *) NULL);
	// a potem stworzyc na nowo, teraz juz na 100% kolejka jest pusta
	if((kolejkaKomunikatowID = msgget(123, IPC_CREAT | 0660 )) == -1)
	{
		printf("Nie udalo sie utworzyc/pobrac ID kolejki o podanym kluczu.\n");
		return EXIT_FAILURE;
	}
	while(true)
	{
		// spimy okreslony czas
		usleep(czasPomiedzyObslugaSygnalow);
		czasOdOstatniejPeriodycznejKomunikacji += czasPomiedzyObslugaSygnalow;
		// sygnalow moglo przyjsc kilka lub w trakcie trwania petli mogl dojsc kolejny
		// wiec obslugujemy w petli 'while', a nie uzywajac 'if'
		while(otrzymalSygnalow > 0)
		{
   			printf("Obsluga sygnalu %d\n", iloscSlaveow);
			otrzymalSygnalow -= 1;
			// jesli limit slave przekroczony to wylecimy poza pamiec w tablicy!
			if(iloscSlaveow == iloscMaksymalnaSlave)
			{
   				printf("Przekroczono limit (%d) obslugiwanych slave!\n", iloscMaksymalnaSlave);
				// przerywamy obsluge 'sygnalu' - przechodzimy do komunikacji z juz znanymi slave
				break;
			}
			ret = msgrcv(kolejkaKomunikatowID, &komunikat, sizeof(struct Komunikat), 1, 0);
   			printf("Odczytano komunikat\n");
			if(ret == -1)
			{
				// -1 = wystapil blad podczas odczytu komunikatu [np. usunieto kolejke komunikatow]
				handle_error("Wystapil blad podczas odczytu komunikatu.");
			}
			else if(ret != sizeof(struct Komunikat))
			{
				// rozmiar komunikatu jest inny, niz rozmiar naszej struktury
				// ktos inny, niz slave wyslal cos do naszej kolejki komunikatow?
				printf("Pobrany komunikat ma inny rozmiar, niz rozmiar struktury.\n");
				return EXIT_FAILURE;
			}
			// przygotowujemy nazwe named-pipe ktory chcemy otworzyc
			sprintf(buffer, "%ld.to_master", komunikat.pid);
			printf("Otwieram named-pipe z przychodzacymi wiadomosciami %s\n", buffer);
			wejscie[iloscSlaveow] = open(buffer, O_RDONLY);
			if(wejscie[iloscSlaveow] == -1)
			{
				printf("Nie udalo sie otworzyc named-pipe odbierajacego.\n");
				return EXIT_FAILURE;
			}
			// przygotowujemy nazwe named-pipe ktory chcemy otworzyc
			sprintf(buffer, "%ld.to_slave", komunikat.pid);
			printf("Otwieram named-pipe z wychodzacymi wiadomosciami %s\n", buffer);
			wyjscie[iloscSlaveow] = open(buffer, O_WRONLY);
			if(wyjscie[iloscSlaveow] == -1)
			{
				printf("Nie udalo sie otworzyc named-pipe wysylajacego.\n");
				return EXIT_FAILURE;
			}
			printf("Dodano slave o ID %d\n", iloscSlaveow);
			iloscSlaveow += 1;
		}
		// nie za kazdym obrotem petli chcemy wysylac/odbierac komunikaty
		// sprawdzamy czy od ostatniej komunikacji z slaveami minelo dosc czasu
		if(czasOdOstatniejPeriodycznejKomunikacji > czasPeriodycznejKomunikacji)
		{
			// zerujemy licznik czasu
			czasOdOstatniejPeriodycznejKomunikacji = 0;
			// lecimy petla przez wszystkich slave
			for(i = 0; i < iloscSlaveow; i++)
			{
				// wypelniamy buffer trescia z wejscia od konkretnego slave
				// jesli slave przestanie pisac to 'read' stanie w miejscu :(
				ret = read(wejscie[i], buffer, sizeof(buffer));
				if(ret <= 0)
				{
					printf("Funkcja read zwrocila error dla slave %d.\n", i);
					perror(NULL);
					return EXIT_FAILURE;
				}
		   		printf("%s Odczytano %d znakow od slave ID %d tresc: %s\n", getTime(), ret, i, buffer);

				// wstawiamy do buffer wysylany tekst
				strcpy(buffer,"DZIALA!");
				// przepisujemy tresc z buffer na wyjscie do konkretnego slave
				ret = write(wyjscie[i], buffer, sizeof(buffer));
				if(ret == -1)
				{
					printf("Funkcja write zwrocila error dla slave %d.\n", i);
					perror(NULL);
					return EXIT_FAILURE;
				}
				printf("%s Wyslano %d znakow do slave ID %d tresc: %s\n", getTime(), ret, i, buffer);
			}
		}
	}
}