//============================================================================
//
//      A a n d e r a a   W e a t h e r   S t a t i o n
//
//	Reads data from Aanderaa box
//
//
//	Rev.	Date		  By	Description
//	-----------------------------------------------
//  0.1     9 Jun 2003	  OM	Test version
//  0.2     23 Aug 2003   OM    Cosmetic changes only
//
//      Compile like this: cc -o aanderaa aanderaa.c -lm -lpthread
//============================================================================



#define _GNU_SOURCE
#include <getopt.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <math.h>
#include <curses.h>
#include <signal.h>
#include <stdbool.h>
#include <time.h>

#include <sys/socket.h>  //UDP
#include <resolv.h>
#include <arpa/inet.h>
#include <ctype.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <complex.h>
#include "/home/linacq/OBS2003/shared_memory_common.h"

#define NO_OF_AANDERAA_CHANNELS 11
#define AANDERAA_STRING_LENGTH 65
#define AANDERAA_PARAMETER_FILE "aanderaa.conf"
#define AANDERAA_PORT "/dev/ttyC0"	// ttyCx = Cyclades 8-port card
#define ASCII 0
#define BINARY 1
#define LOGFILE "testlog.txt"		// log file name
#define BUFSIZE		1024
#define TRUE 1
#define FALSE 0
#define METERS_PER_SECOND  0.514444		// 1852 m (1 nm) / 3600 

void read_Aanderaa();
void shared_mem_read();
void initialize_serial_port(int FLAG, int baudrate, const char *com);
void ctrl_c_handler();


//	T y p e   d e c l a r a t i o n  

typedef struct arg_t  {
  const char *com;
  const char *profile;
} arguments;


typedef struct {	// For one line in Aanderaa parameter file
int ch;
char name[50];
int max;
int min;
int avr;
float A;
float B;
float C;
float D;
char units[12];
char format[5];
} parameter;


//	V a r i a b l e    d e c l a r a t i o n

pthread_t	shared_mem_thread;
arguments	shared_mem_arg;

parameter par_list[20];			// Array of parameters for each Aanderaa sensor
pthread_t	aanderaa_thread;
arguments	aanderaa_arg;
FILE *log_fd;
int k=0;
int aanderaa_data_ready=FALSE;
int shared_mem_data_ready=FALSE;

char aanderaa_string[250];
char raw_data_string[250];

char *str_ptr=aanderaa_string;

int running = 1;
void *shared_memory = (void *)0;
struct shared_use_st *shared_stuff;
int shmid;

char GPRMC_string[TEXT_SIZE];
char GPHDT_string[TEXT_SIZE];
int  GPRMC_flag = FALSE;
int  GPHDT_flag = FALSE;

int UDP_alive = FALSE;




//==============================================================
//	              M a i n   p r o g r a m
//=============================================================

int main (int argc, char *argv[]) {

	char buffer[BUFSIZE];
	struct sockaddr_in addr;
	int sd, addr_size, bytes_read;

	char parameter_string[128];
	FILE *fp;

	char test[50];
	int channel;
	int k=1;

	int raw[NO_OF_AANDERAA_CHANNELS];
	float converted[NO_OF_AANDERAA_CHANNELS];
	float Nexp2,Nexp3;
	int i;
	char *p;
	char test_string[TEXT_SIZE];
	char *sub_string;
	char GPRMC_time[25];
	char GPRMC_speed[25];
	char GPRMC_course[25];
	char GPRMC_date[25];
	char GPHDT_heading[25];
	
	int $1,$3,$4,$5;
	char $2[50],$10[12],$11[5];
	float $6,$7,$8,$9;

	char time_string[80];
	#define TIME_STRING_SIZE	25
	char date_str[TIME_STRING_SIZE], time_str[TIME_STRING_SIZE];
	char time_flag;			// If no UDP telegram: Local PC time. 'A'=valid UDP time 'L'=PC local time
	char true_wind_flag; 	// If UDP telegram: 'Y' else 'N'
	char output_string[255];
	time_t local_time;
	struct tm *time_ptr;

	double complex Rel_wind_vector;
	double complex True_wind_vector;
	double complex Ship_vector;
	double complex j;
	double pi, to_degrees, to_radians;
	double Re,Im;
	float rel_wind_speed, rel_wind_dir, true_wind_speed, true_wind_dir, ship_speed, ship_heading;

	
	// Some constants needed when converting relative wind speed/direction to true values, using ship's speed and heading
	pi = 4 * atan(1);		// Tips from Octave
	to_degrees = 180.0/pi;
	to_radians = pi/180.0;
	j = csqrt(-1);


	(void) signal(SIGINT, ctrl_c_handler);		// Install custom ctrl-c handler

	//--------------------------------------------------------------------------
	// SHARED MEMORY stuff
	//--------------------------------------------------------------------------

	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
	if (shmid == -1)
	{
		fprintf(stderr, "shmget failed\n");
		exit(EXIT_FAILURE);
	}

	// Attach: Make shared memory accessible to the program

	shared_memory = shmat(shmid, (void *)0, 0);
	if (shared_memory == (void *)-1)
	{
		fprintf(stderr, "shmat failed\n");
		exit(EXIT_FAILURE);
	}

	// SUCCESS, we got shared memory

	fprintf(stderr, "Shared memory ok, attached at %X\n", (int)shared_memory);
	shared_stuff = (struct shared_use_st *)shared_memory;

	// ---------------------------------------------------------------------------
	// Read Aanderaa parameter file and put parameters into data structure.
	// Assumes this parameter file layout:
	// Ch#  Description  Calculations:max,min,avr A B C D Units Format
	// If this pattern is broken the program will probably crash (due to sscanf) !
	//----------------------------------------------------------------------------

	//printf("Parsing parameter file ");
	if((fp = fopen(AANDERAA_PARAMETER_FILE, "r"))==NULL)
	{
		fprintf(stderr, "Cannot open Aanderaa parameter file: %s\n", AANDERAA_PARAMETER_FILE);
		exit(1);
	}
	// Read file line by line ...
	while((fgets(parameter_string, sizeof(parameter_string), fp))!=NULL)
	{
		if ((parameter_string[0] != '#') &&  (parameter_string[0] != ' ')) // Assumes comments start with '#' or ' ' in first coloumn
		{
			// printf(parameter_string);
			sscanf(parameter_string, "%d", &channel);  // Wish AWK was here ...
			channel--;
			sscanf(parameter_string, "%d %s %d %d %d %f %f %f %f %s %s", &$1, $2, &$3, &$4, &$5, &$6, &$7, &$8, &$9, $10, $11);
			par_list[channel].ch     = $1;
			strcpy(par_list[channel].name, $2);		// Strings treated this way
			par_list[channel].max    = $3;
			par_list[channel].min    = $4;
			par_list[channel].avr    = $5;
			par_list[channel].A      = $6;
			par_list[channel].B      = $7;
			par_list[channel].C      = $8;
			par_list[channel].D      = $9;
			strcpy(par_list[channel].units, $10);
			strcpy(par_list[channel].format, $11);
			//printf("Ch.%d: units = %s\n", channel, par_list[channel].units);
		}
	}
	
	fclose(fp);	// Close parameter file


	//----------------------------------------------------------
	// Configure serial ports
	//----------------------------------------------------------

    initialize_serial_port(ASCII, B300, AANDERAA_PORT);

	//------------------------------------------------------------------------------
	// Start threads
	//------------------------------------------------------------------------------

	aanderaa_arg.com = AANDERAA_PORT;
	aanderaa_arg.profile = "";
	pthread_create(&aanderaa_thread, NULL, (void*) read_Aanderaa, (void *) &aanderaa_arg);
	fprintf(stderr,"Thread collecting Aanderaa data .........: ");		// Thread will print 'OK'
	sleep(1);

	shared_mem_arg.com = 0;
	shared_mem_arg.profile = "";
	pthread_create(&shared_mem_thread, NULL, (void*) shared_mem_read, (void *) &shared_mem_arg);
	fprintf(stderr,"Thread collecting shared mem UDP data ...: ");		// Thread will print 'OK'


	//==================================================================================
	//                            M A I N   L O O P
	//==================================================================================


	while (running)
	{
		//--------------------------------------------------------------------------------
		//          P r o c e s s i n g   o f   N M E A   A S C I I   s t r i n g s
		//       r e c e i v e d   f r o m   U D P   v i a   s h a r e d   m e m o r y
		// Expect to get two sentences, shown by example:
		//		$GPRMC,194135,A,6817.26,N,01417.00,E,11.6,020.,070603,00.,E*74
		//		-- format: time (UTC) lat long speed(in knots) CMG date ??
		//		$GPHDT,24.3,T
		//		-- format: heading
		//--------------------------------------------------------------------------------

		// Parsing of $GPRMC telegram starts here (should check for correct number of fields, later ...)
		if (GPRMC_flag == TRUE)
		{
			if (strstr(GPRMC_string, "$GPRMC") != NULL)		// Just checking ...
			{
				//fprintf(stderr,GPRMC_string);
				strcpy(test_string, GPRMC_string);
				// Extract time (token=2), speed (8), course (9), date (10)
				sub_string = strtok(test_string, ",");
				i = 2; // Token counter
				while ((sub_string=strtok(NULL, ",")) != NULL)
				{
					switch (i)
					{
						case 2:		strcpy(GPRMC_time, sub_string);
									break;
						case 3:
						case 4:
						case 5:
						case 6:
						case 7:		break;
						case 8:		strcpy(GPRMC_speed, sub_string);
									break;
						case 9:		strcpy(GPRMC_course, sub_string);
									break;
						case 10:	strcpy(GPRMC_date, sub_string);
									break;
						default:	break;
					}
					i++;
				}
				//fprintf(stderr, "%s %s %s %s\n", GPRMC_time, GPRMC_date, GPRMC_speed, GPRMC_course);
				GPRMC_flag = FALSE;
			}
		}
		// Parsing of $GPHDT telegram starts here (should check for correct number of fields, later ...)
		if (GPHDT_flag == TRUE)		
		{
			if (strstr(GPHDT_string, "$GPHDT") != NULL)		// Just checking ...
			{
				//fprintf(stderr,GPHDT_string);
				strcpy(test_string, GPHDT_string);
				// Extract heading (token=2)
				sub_string = strtok(test_string, ",");
				i = 2; // Token counter
				while ((sub_string=strtok(NULL, ",")) != NULL)
				{
					switch (i)
					{
						case 2:		strcpy(GPHDT_heading, sub_string);
									break;
						default:	break;
					}
					i++;
				}
				//fprintf(stderr, "%s\n", GPRMC_heading);
				GPHDT_flag = FALSE;
			}
		}

		//----------------------------------------------------------------------------------------------------------
		//                   P r o c e s s i n g   o f   A a n d e r a a   d a t a 
		// NOTE: When time permits, allow sensor channel sequence to change by reading this information from
		// Aanderaa config file. Now I'm using arrays to store raw- and converted values, and indexing assumes
		// a certain sensor sequence. Could try to index via sensor name enumeration as an intermediate step.
		//----------------------------------------------------------------------------------------------------------

		if (aanderaa_data_ready == TRUE)	// Aanderaa thread will set this flag
		{
			// First convert ASCII raw data to integers, then put these into array raw[0..(n-1)],
			// where n = number of sensor channels.
			// If file format is changed program will probably go down in flames
			// as sscanf expects that input match format specifier. Someone must make it more robust ...

			sscanf(raw_data_string, "%d %d %d %d %d %d %d %d %d %d %d", &raw[0],&raw[1],&raw[2],&raw[3],&raw[4],&raw[5],&raw[6],&raw[7],&raw[8],&raw[9],&raw[10]);

			// Loop through all raw data integers and calculate physical parameter using this expression:
			//                  Value = A + (B*N) + (C*Nexp2) + (D*Nexp3)
			// where N = raw data reading and A, B, C, D are coefficients stored in the 'par_list[]' array.
			// Converted values stored in the 'converted[]' array.

			for (i=0; i<NO_OF_AANDERAA_CHANNELS; i++)
			{
				Nexp2 = raw[i] * raw[i];
				Nexp3 = Nexp2 * raw[i];
				converted[i] = par_list[i].A + (par_list[i].B * raw[i]) + (par_list[i].C * Nexp2) + (par_list[i].D * Nexp3);
				fprintf(stderr, "input: %d, output: %.1f %s\n", raw[i], converted[i], par_list[i].units);
			}

			// If we receive info on ship's heading via UDP and shared memory, calculate true wind speed
			// and direction.
			UDP_alive = TRUE;  // Testing ...
			
			if (UDP_alive == TRUE)
			{
				//----------------------------------------------------------------------------
				// Time- and date format in UDP telegrams:      235659    301202
				// Convert this to ease later handling of data: 23:56:59  2003-12-30
				// Using brute force:
				//----------------------------------------------------------------------------
				time_str[0] = GPRMC_time[0]; time_str[1] = GPRMC_time[1]; time_str[2] = ':';
				time_str[3] = GPRMC_time[2]; time_str[4] = GPRMC_time[3]; time_str[5] = ':';
				time_str[6] = GPRMC_time[4]; time_str[7] = GPRMC_time[5]; time_str[8] = '\0';
				
				date_str[0] = '2'; date_str[1] = '0';		// Oh no a year 2100 problem ...
				date_str[2] = GPRMC_date[4]; date_str[3] = GPRMC_date[5]; date_str[4] = '-';
				date_str[5] = GPRMC_date[2]; date_str[6] = GPRMC_date[3]; date_str[7] = '-';
				date_str[8] = GPRMC_date[0]; date_str[9] = GPRMC_date[1]; date_str[10] = '\0';
				time_flag   = 'A';
				//fprintf(stderr,"UDP time conversion ok\n");
				
				//--------------------------------------------------------------------------------------------	
				// Expressing vectors as complex numbers:
				// First setup relative wind vector. 'rel_wind_speed' is vector length, and 'rel_wind_dir' is
				// direction (this is 'module' and 'argument' in complex notation).
				// Transform to x,y values, then use these as real and imaginary part of the complex number.
				// Remember all angles expressed in radians.
				//--------------------------------------------------------------------------------------------
				
				rel_wind_speed = converted[1];							// This is fragile. And remember we index from 0.
				rel_wind_dir   = converted[2]*to_radians;
				ship_speed     = converted[3];							// Use speed info from Aanderaa system
				//ship_speed     = atof(GPRMC_speed)*METERS_PER_SECOND;	// Bad: no input validation
				ship_heading   = atof(GPHDT_heading);
				fprintf(stderr, "UDP telegrams: Ship speed: %.1f, heading: %.1f\n", ship_speed, ship_heading);
				
				Re = rel_wind_speed * cos(rel_wind_dir);
				Im = rel_wind_speed * sin(rel_wind_dir);
				Rel_wind_vector = Re + j*Im;
				// Setup 'Ship_vector' 
				Ship_vector     = ship_speed + j*0;
				//fprintf(stderr,"Vector setup complete\n");
				// Then get true wind vector:
				True_wind_vector = Rel_wind_vector - Ship_vector;
				true_wind_speed = cabs(True_wind_vector);
				true_wind_dir = carg(True_wind_vector)*to_degrees;
				true_wind_dir += ship_heading;
				fprintf(stderr,"Wind dir, before check: %.1f\n", true_wind_dir);
				if (true_wind_dir < 0.0) true_wind_dir = (360.0 + true_wind_dir); // We're actually subtracting here ..
				if (true_wind_dir > 360.0) true_wind_dir -= 360.0;		// Later versions: take CMG into account
				true_wind_flag = 'Y';
				//fprintf(stderr,"Vector calculations complete\n");
			}
			else	// No UDP data, use PC internal clock and signal this with time_flag, set 'true_wind_dir' and 'true_wind_speed' to 0.0
			{
				local_time = time(NULL);			// Would be nice if PC time was ntp'ed ...
				time_ptr = localtime(&local_time);
				//strftime(time_string, 100, "%T %F", time_ptr);
				strftime(time_str, TIME_STRING_SIZE, "%T", time_ptr);	// Like "23:50:52"
				strftime(date_str, TIME_STRING_SIZE, "%F", time_ptr);	// Like "2002-11-23"
				time_flag       = 'L';
				true_wind_speed = 0.0;
				true_wind_dir   = 0.0;
				true_wind_flag  = 'N';
			}
			//---------------------------------------------------------------------------------------------------------
			// Format output string. Format example:
			// $PSHWEA,MOSBY,19:41:35,2003-06-07,A,1003.8,8.9,85.2,4.3,11.8,350.7,A*<checksum, 1 byte>
			// Sensor sequence: Barometric pressure, air temp, rel. humidity, sea temp, true wind speed, true wind dir
			//---------------------------------------------------------------------------------------------------------

			sprintf(output_string, "$PSHWEA,MOSBY,%s,%s,%c,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%c*\n",time_str,date_str,time_flag,converted[5],converted[6],converted[7],converted[8],true_wind_speed,true_wind_dir,true_wind_flag);
			write(1, output_string, strlen(output_string));
			fprintf(stderr, output_string);
			//sprintf(output_string, "%s %.1f\n", time_string, converted[5]);
			//fprintf(stdout, output_string);
			//write(1, output_string, strlen(output_string));
			//fprintf(stderr,"\nRel. Wind dir.: %.0f, Rel. wind speed: %.1f\n", converted[2], converted[1]);
			//fprintf(stderr, "%s %s %s %s %s\n\n", GPRMC_time, GPRMC_date, GPRMC_speed, GPRMC_course, GPRMC_heading);
			aanderaa_data_ready = FALSE;
		}
		usleep(500000);
		//fprintf(stderr, "!");
	}


	//-------------------------------------------------------
	// TERMINATE PROGRAM, first clean up shared memory stuff
	//-------------------------------------------------------

	if (shmdt(shared_memory) == -1)		// Detach shared memory
	{
		fprintf(stderr, "\nshmdt failed\n");	// Didn't work
		exit(EXIT_FAILURE);
	}

	fprintf(stderr, "\nDetached shared memory\n");

	exit(EXIT_SUCCESS);
}


//==================================================================
//	T H R E A D   Aanderaa serial data
//==================================================================

void read_Aanderaa(arguments *arg)
{

	FILE *a_fd;
	char *bytes;

	a_fd = fopen(arg->com,"r");
	fprintf(stderr,"Aanderaa thread ok\n");
	while (1)
	{
		bytes = fgets(aanderaa_string, sizeof aanderaa_string, a_fd);
		//printf(">>\n");
		if (aanderaa_string[0] != '\x0a')   // Skip blank line; Aanderaa box terminates lines by <LF><LF>
		{
			if (strlen(aanderaa_string) != AANDERAA_STRING_LENGTH)  //Must ensure that string is complete
			{
				//printf("! %i char: %s\n", strlen(aanderaa_string), aanderaa_string);
			}
			else
			{
				strcpy(raw_data_string, aanderaa_string);	// aanderaa_string did not make it to main loop ....
				//printf(">> %s\n", aanderaa_string);
				aanderaa_data_ready = TRUE;  //Let Main loop handle this

			}
		}
    }
}

//=====================================================================================
//	T H R E A D   Read shared memory data, containing NMEA telegrams received via UDP.
//                Another program receives UDP and writes telegrams to shared memory
//=====================================================================================

void shared_mem_read(arguments *arg)
{
	fprintf(stderr,"Shared memory thread ok\n");
	while (1)
	{
		if (shared_stuff->GPHDT_ready == 1)			// Looking for $GPHDT telegram
		{
			strcpy(GPHDT_string, shared_stuff->UDP_GPHDT);
			shared_stuff->GPHDT_ready = 0;
			GPHDT_flag = TRUE;
		}

		if (shared_stuff->GPRMC_ready == 1)			// Looking for $GPRMC telegram
		{
			strcpy(GPRMC_string, shared_stuff->UDP_GPRMC);
			shared_stuff->GPRMC_ready = 0;
			GPRMC_flag = TRUE;
		}
		usleep(500000);
	}
	exit(0);
}

//=========================================================
//	I n i t i a l i z e   s e r i a l   p o r t s
//
//	Binary mode must be re-designed - allow setting of
//	VMIN & VTIME for each serial port ....
//=========================================================

void initialize_serial_port(int FLAG, int baudrate, const char *com) {
  // FLAG = 0, configure serial port com for ascii data
  // FLAG = 1, configure serial port com for binary stream
  int fd;
  struct termios options;
  fd = open(com, O_RDWR | O_NOCTTY );
  if (fd <0) {
     perror(com);
     exit(-1);
  }
  if (FLAG == ASCII) {
    tcgetattr(fd,&options);      /* Get current setting */
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE; /* Mask the character size bits */
    options.c_cflag |= CS8;    /* Select 8 data bits */
    options.c_lflag |= (ICANON | ECHO | ECHOE);
    cfsetispeed(&options, baudrate);
    cfsetospeed(&options, baudrate);
    tcsetattr(fd, TCSANOW, &options); /* Apply changes */
    /* Finished serial settings */
  }
 else if (FLAG == BINARY) {
    tcgetattr(fd,&options);      /* Get current setting */
    cfsetispeed(&options, B9600);
    cfsetospeed(&options, B9600);

	//  c_cflag section
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE; /* Mask the character size bits */
    options.c_cflag |= CS8;    /* Select 8 data bits */

    options.c_cc[VMIN] = 228;  // 228 = NORSTAR format, bygg om, tenk paa GUNCO ogsaa!!
    options.c_cc[VTIME] = 7;

    // c_lflag section
	options.c_lflag &= ~ICANON;
	options.c_lflag &= ~ECHO;
	options.c_lflag &= ~ECHOE;
	options.c_lflag &= ~ECHONL;
	options.c_lflag &= ~ISIG;
	
	// c_iflags section
	options.c_iflag |= IGNBRK;
	options.c_iflag &= ~IGNCR;
	options.c_iflag |= IGNPAR;
	options.c_iflag &= ~INLCR;
	options.c_iflag &= ~ICRNL;
	options.c_iflag &= ~INPCK;
	options.c_iflag &= ~PARMRK;
	options.c_iflag &= ~ISTRIP;
	options.c_iflag &= ~IXOFF;
	options.c_iflag &= ~IXON;

    tcsetattr(fd, TCSANOW, &options); /* Apply changes */

    /* Finished serial settings */
  }
  close(fd);
}

//------------------------------------------------------------------
//	CTRL-C  handler. Program can then stop in an orderly way.
//------------------------------------------------------------------

void ctrl_c_handler(int sig)		// Executed on first ctrl-c
{
	running = 0;			// Flag used in main loop
	(void) signal(SIGINT, SIG_DFL);	// Next ctrl-c will act as usual
}