/*
 * Filename: mpa-ctrl.c
 */

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

#include <termios.h> 
#include <unistd.h>  // for STDIN_FILENO

#include <pthread.h>

#include "prg_io_nonblock.h"
#include "thread_barrier.h"

#define PERIOD_STEP 10
#define PERIOD_MAX 2000
#define PERIOD_MIN 10

#define READ_TIMOUT_MS 500 //timeout for reading from pipe, quite high but due to 5 seconds alarm it is ok

enum {
   ERROR_FILE_IO = 100
};

typedef struct {
   // share data among threads, access using critical section (mtx (un)lock)
   bool quit;
   int fd_in; // file descriptor
   int fd_out; // file descriptor
   int counter;
   char sent;
   char received;
   pthread_mutex_t *mtx; // use pointer to avoid addressing operator
   pthread_cond_t *cond; // use pointer to avoid addressing operator
} data_t;

void call_termios(int reset);

// possible thread functions 
void* in_pipe_thread(void*);
void* keyboard_thread(void*);
void* output_thread(void*);

// - main ---------------------------------------------------------------------
int main(int argc, char *argv[])
{
   pthread_mutex_t mtx; // use local auto variables to avoid dynamic allocation
   pthread_cond_t cond; // use local auto variables to avoid dynamic allocation
   data_t data = { 
      .quit = false, .fd_in = -1, .fd_out = -1, .counter = 0, .sent = '?', .received = '?',
      .mtx = &mtx, .cond = &cond // has to be initiated by pthread_*_init() !
   };

   const char *in = argc > 1 ? argv[1] : "/tmp/mpa-ctrl.in";
   const char *out = argc > 2 ? argv[2] : "/tmp/mpa-alrm.in";

   fprintf(stderr, "DEBUG(%s) io_open_read(%s) ... ", __func__, in);
   data.fd_in = io_open_read(in);
   fprintf(stderr, "done\n");

   fprintf(stderr, "DEBUG(%s) io_open_write(%s)", __func__, out);
   data.fd_out = io_open_write(out); // it is open once another process open the pipe for reading
   fprintf(stderr, "done\n");

   if (data.fd_in == -1) {
      fprintf(stderr, "ERROR(%s) Cannot open named pipe port %s\n", __func__, in);
      exit(ERROR_FILE_IO);
   }
   if (data.fd_out == -1) {
      fprintf(stderr, "ERROR(%s) Cannot open named pipe port %s\n", __func__, out);
      exit(ERROR_FILE_IO);
   }

   enum { IN_PIPE, KEYBOARD, OUTPUT, NUM_THREADS };
   const char *threads_names[] = { "Input", "Keyboad", "Output" };

   void* (*thr_functions[])(void*) = { in_pipe_thread, keyboard_thread, output_thread};

   pthread_t threads[NUM_THREADS];
   pthread_mutex_init(&mtx, NULL); // initialize mutex with default attributes
   pthread_cond_init(&cond, NULL); // initialize mutex with default attributes

   call_termios(0);

   barrier_init(NUM_THREADS + 1); // +1 for the main thread
   for (int i = 0; i < NUM_THREADS; ++i) {
      int r = pthread_create(&threads[i], NULL, thr_functions[i], &data);
      fprintf(stderr, "INFO(%s) Create thread '%s' %s\n", __func__, threads_names[i], ( r == 0 ? "OK" : "FAIL") );
   }
   barrier(); // wait for start of the all threads
   fprintf(stderr, "INFO(%s) barrier -- all threads started\n", __func__);

   int *ex;
   for (int i = 0; i < NUM_THREADS; ++i) {
      fprintf(stderr, "INFO(%s) Call join to the thread %s\n", __func__, threads_names[i]);
      int r = pthread_join(threads[i], (void*)&ex);
      fprintf(stderr, "INFO(%s) Joining the thread %s has been %s - exit value %i\n", __func__, threads_names[i], (r == 0 ? "OK" : "FAIL"), *ex);
   }

   io_close(data.fd_in);
   io_close(data.fd_out);

   call_termios(1); // restore terminal settings
   return EXIT_SUCCESS;
}

// - function -----------------------------------------------------------------
void call_termios(int reset)
{
   static struct termios tio, tioOld;
   tcgetattr(STDIN_FILENO, &tio);
   if (reset) {
      tcsetattr(STDIN_FILENO, TCSANOW, &tioOld);
   } else {
      tioOld = tio; //backup 
      cfmakeraw(&tio);
      tio.c_oflag |= OPOST;
      tcsetattr(STDIN_FILENO, TCSANOW, &tio);
   }
}

// - function -----------------------------------------------------------------
void *in_pipe_thread(void *d)
{  // in_pipe_thread template 
   data_t *data = (data_t*)d;
   static int r = 0;
   
   fprintf(stderr, "DEBUG(%s): barrier\n", __func__);
   barrier();
   fprintf(stderr, "DEBUG(%s): barrier passed\n", __func__);

   pthread_mutex_lock(data->mtx);
   bool q = data->quit;
   pthread_mutex_unlock(data->mtx);
   while (!q) {
      unsigned char c;
      int r = io_getc_timeout(data->fd_in, READ_TIMOUT_MS, &c);
      fprintf(stderr, "DEBUG(%s) io_getc_timeout() r: %i c: %i\n", __func__, r, c);
      pthread_mutex_lock(data->mtx);
      q = data->quit;
      pthread_mutex_unlock(data->mtx);
      if (r != 1) { // check if quit was initiated 
	 continue;
      }
   } //end while
   fprintf(stderr, "DEBUG(%s) end while loop\n", __func__);
   return &r;
}

// - function -----------------------------------------------------------------
void *keyboard_thread(void *d)
{  // keyboard_thread template 
   data_t *data = (data_t*)d;
   static int r = 0;

   fprintf(stderr, "DEBUG(%s): barrier\n", __func__);
   barrier();
   fprintf(stderr, "DEBUG(%s): barrier passed\n", __func__);

   bool error = false; // detect error on pipe_out
   char c;
   while (( c = getchar()) != 'q' && !error) {
      // place key handlers here
      pthread_mutex_lock(data->mtx); // lock the mutex to change the shared cond variable
      pthread_cond_broadcast(data->cond); // just to update the outpuw
      pthread_mutex_unlock(data->mtx);

   }
   pthread_mutex_lock(data->mtx);
   data->quit = true;
   if (io_putc(data->fd_out, 'b') != 1) {
      fprintf(stderr, "DEBUG(%s) Cannot send command to alarm program, exit program\n", __func__);
      r = 1;
   }
   pthread_cond_broadcast(data->cond);
   pthread_mutex_unlock(data->mtx);
   return &r;
}

// - function -----------------------------------------------------------------
void *output_thread(void *d)
{  // output_thread template 
   data_t *data = (data_t*)d;
   static int r = 0;

   fprintf(stderr, "DEBUG(%s): barrier\n", __func__);
   barrier();
   fprintf(stderr, "DEBUG(%s): barrier passed\n", __func__);
   bool q = false;
   while (!q) {
      pthread_mutex_lock(data->mtx);
      pthread_cond_wait(data->cond, data->mtx); // wait for next event
      q = data->quit;
      printf("\rAlarm counter: %10i   Received: %c   Sent: %c", data->counter, data->received, data->sent);
      fflush(stdout);
      pthread_mutex_unlock(data->mtx);
   } //end while
   return &r;
}

/* end of mpa-ctrl.c */
