#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

typedef struct
{
	pthread_mutex_t* m;
	pthread_cond_t* c;
   pthread_t t;
	int member_index;
   int team_index;
   int teams;
   int* alive;
   int* game_team;
	int* counter_array;
	void* rvalue;
} thread_cont;

void print_team_counter(int* array, int teams)
{
   putchar('[');
   int i;

   for(i = 0; i < teams - 1; ++i)
      printf("%d,", array[i]);

   // last field value
   printf("%d", array[teams - 1]);

   puts("]");
}

void* game(void* data)
{
   thread_cont* container = (thread_cont*) data;
   
   int chosen_team;

   // critical section
   pthread_mutex_lock(container->m);

   // sleeping before being waked up
   while(*container->game_team != container->team_index)
      pthread_cond_wait(container->c, container->m);

   //sleep(1);

   --container->counter_array[container->team_index];
   --(*container->alive);

   printf("Team: %d, member: %d has been selected to play, total alive: %d, ", container->team_index, container->member_index, *container->alive);
   print_team_counter(container->counter_array, container->teams);

   if(*container->alive > 0)
   {
      // choosing my oponent to play
      do
      {
         chosen_team = lrand48() % container->teams;
      }
      while(container->counter_array[chosen_team] == 0);

      printf("\t I am chosing to play with team #%d !\n", chosen_team);
      *container->game_team = chosen_team;

      // sending broadcast
      pthread_cond_broadcast(container->c);
   }
   else
      printf("\tI am last, so I die without playing !\n");

   pthread_mutex_unlock(container->m);

   return (void*)container->member_index;
}

int main(int argc, char** argv)
{
   // validating options
   if(argc != 3)
   {
      fputs("Usage: team <teams> <members>\n", stderr);
      exit(EXIT_FAILURE);
   }

   int teams = atoi(argv[1]);
   int members = atoi(argv[2]);

   if(teams == 0 || members == 0)
   {
      fputs("Cannot launch team runner with zero in team or member count (or nonnumeric argument) !\n", stderr);
      exit(EXIT_FAILURE);
   }

   // init of mutex and cond variable
	pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
	pthread_cond_t conditional;
	pthread_cond_init(&conditional, NULL);

   // random seed init
	time_t t;
	time(&t);
	srand48((long)t);
   int alive = teams * members;
   int team_number = -1; // variable for game choosing
   
   int i, j;

   // array of threads containers
   int thread_cont_size = sizeof(thread_cont);
   thread_cont* container_array = (thread_cont*) malloc(teams * members * thread_cont_size);
   if(container_array == NULL)
   {
      fputs("Cannot malloc memory for array of info for threads !\n", stderr);
      exit(EXIT_FAILURE);
   }

   // array for teams
   int* team_array = malloc(teams * sizeof(int));
   if(team_array == NULL)
   {
      fputs("Cannot malloc memory for array of team counter !\n", stderr);
      exit(EXIT_FAILURE);
   }

   for(i = 0; i < teams; ++i)
      team_array[i] = members;

   // filling init array
   for(i = 0; i < teams; ++i)
      for(j = 0; j < members; ++j)
      {
         int index = members * i + j;
         container_array[index].member_index = j;
         container_array[index].team_index = i;
         container_array[index].m = &mutex;
         container_array[index].c = &conditional;
         container_array[index].teams = teams;
         container_array[index].game_team = &team_number;
         container_array[index].counter_array = team_array;
         container_array[index].alive = &alive;
      }

   // creating threads
   for(i = 0; i < teams; ++i)
      for(j = 0; j < members; ++j)
      {
         int index = members * i + j;
         pthread_create(&container_array[index].t, NULL, game, &container_array[index]);
      }

   // sending initial broadcast
   team_number = lrand48() % teams; // variable for game choosing
   printf("Main thread selected to play team #%d !\n", team_number);
   pthread_cond_broadcast(&conditional);

   // joining threads
   for(i = 0; i < teams; ++i)
      for(j = 0; j < members; ++j)
      {
         int index = members * i + j;
         pthread_join(container_array[index].t, &container_array[index].rvalue);
      }

	pthread_mutex_destroy(&mutex);

   free(container_array);
   free(team_array);

	return 0;
}