/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+
#+     Glade / Gtk Programming
#+
#+     Copyright (C) 2019 by Kevin C. O'Kane
#+
#+     Kevin C. O'Kane
#+     kc.okane@gmail.com
#+     https://www.cs.uni.edu/~okane
#+     http://threadsafebooks.com/
#+
#+ This program is free software; you can redistribute it and/or modify
#+ it under the terms of the GNU General Public License as published by
#+ the Free Software Foundation; either version 2 of the License, or
#+ (at your option) any later version.
#+
#+ This program is distributed in the hope that it will be useful,
#+ but WITHOUT ANY WARRANTY; without even the implied warranty of
#+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#+ GNU General Public License for more details.
#+
#+ You should have received a copy of the GNU General Public License
#+ along with this program; if not, write to the Free Software
#+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#+
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

// March 29, 2020

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gtk/gtkx.h>

//------------------
//	Globals
//------------------

#define AIRPORTS 8000
#define AIRPORT_ROWS 6

GtkWidget	*window;	// main window
GtkWidget	*fixed;		// main container
GtkWidget	*scroll;	// scroll bar
GtkWidget	*text;		// results text view
GtkWidget	*view; 		// view port
GtkWidget	*grid; 		// grid for buttons
GtkEntry        *location;	// city serach entry
GtkWidget	*curWeather;	// get weather button
GtkBuilder	*builder; 	// XML interface

GtkWidget       *airportButton1[AIRPORT_ROWS]; // airport code
GtkWidget       *airportButton2[AIRPORT_ROWS]; // airport city
GtkWidget       *airportButton3[AIRPORT_ROWS]; // airport name
GtkWidget       *airportButton4[AIRPORT_ROWS]; // airport country

GtkTextBuffer   *textbuffer1;	// for results text
GtkAdjustment	*adjustment1;	// scroll bar data

char	weatherLocation[512] = "";	// text entered in search box
int	airportCount = 0;		// number of valid entries in airport[]
char	*airport[AIRPORTS] = {NULL};	// pointers to airport data
char    Code_Home[2048];		// file address of program directory

//	functions

void    airportData(char *, char *, char *, char *, char *);	// parse airport text
void    on_airport(GtkButton *);				// process button callback
void	init_airports();					// initialize database
void    css_set(GtkCssProvider *, GtkWidget *);			// paint css on widget
void    on_curWeather_clicked(GtkWidget *b);			// get weather button

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

	gtk_init(&argc, &argv); // init Gtk

//---------------------------------------------------------------------
// establish contact with xml code used to adjust widget settings
//---------------------------------------------------------------------
 
	builder = gtk_builder_new_from_resource ("/part1/part1.glade");
 
	window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));

	g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

        gtk_builder_connect_signals(builder, NULL);

	fixed		= GTK_WIDGET(gtk_builder_get_object(builder, "fixed"));
	scroll		= GTK_WIDGET(gtk_builder_get_object(builder, "scroll"));
	text		= GTK_WIDGET(gtk_builder_get_object(builder, "text"));
	view		= GTK_WIDGET(gtk_builder_get_object(builder, "view"));
	grid		= GTK_WIDGET(gtk_builder_get_object(builder, "grid"));
	curWeather	= GTK_WIDGET(gtk_builder_get_object(builder, "curWeather"));
        textbuffer1     = GTK_TEXT_BUFFER(gtk_builder_get_object(builder, "textbuffer1"));
        location        = GTK_ENTRY(gtk_builder_get_object(builder, "location"));
	adjustment1	= GTK_ADJUSTMENT(gtk_builder_get_object(builder, "adjustment1"));

//-----------------------------------------------------------------------------
//	gets the location in the file system of the currently running program
//-----------------------------------------------------------------------------

        readlink("/proc/self/exe", Code_Home, 2048); // where are we?

//---------------------------------------------------------------
//	Remove the last token from the above (name of program)
//---------------------------------------------------------------

        for (int i = strlen(Code_Home); i > 0; i--)
                if (Code_Home[i] == '/' ) {
                        Code_Home[i] = 0;
                        break;
                        }

	init_airports();

	if (airportCount == 0) { // oops
		printf("No airports.\n");
		return EXIT_FAILURE;
		}

//-------------------------------------------------
//	apply gtk.css theme to application window
//-------------------------------------------------

	GtkCssProvider  *cssProviderMain;

	cssProviderMain  = gtk_css_provider_new();

	gtk_css_provider_load_from_resource(cssProviderMain, "/part1/gtk.css");

	gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
		GTK_STYLE_PROVIDER(cssProviderMain),
		GTK_STYLE_PROVIDER_PRIORITY_USER);

	gtk_widget_set_events(GTK_WIDGET(view), GDK_SCROLL_MASK);

	gtk_widget_show_all(window);

	gtk_main();

	return EXIT_SUCCESS;
	}

//----------------------------------
//	scroll bar slider moved
//----------------------------------

void    on_scroll_value_changed(GtkRange *r) {

	int i,j;
        char tmp[2048], code[8], city[256], airprt[256], country[256];

//-------------------------------------------------
//	get scroll location
//	returns a gdouble which we convert to int
//-------------------------------------------------

        i = (int) gtk_range_get_value (r);

//-----------------------
//	show last page
//-----------------------

        if (i >= airportCount) i = airportCount - AIRPORT_ROWS; 

//--------------------------------------------------------
//	re-populate buttons with text for where we are
//--------------------------------------------------------

        for (j = i; j < i + AIRPORT_ROWS; j++) {

                if (j < airportCount) {

//-----------------------------------------------------------------
//			copy line of airport data because strtok()
//			function in airportData() alters string
//-----------------------------------------------------------------

                        strcpy(tmp, airport[j]);

//----------------------------------------
//			extract contents
//----------------------------------------

                        airportData(tmp, code, city, airprt, country); // extract parts from line

                        gtk_button_set_label (GTK_BUTTON( airportButton1[j - i] ), (const gchar* ) code);
                        gtk_button_set_label (GTK_BUTTON( airportButton2[j - i] ), (const gchar* ) city);
                        gtk_button_set_label (GTK_BUTTON( airportButton3[j - i] ), (const gchar* ) airprt);
                        gtk_button_set_label (GTK_BUTTON( airportButton4[j - i] ), (const gchar* ) country);

                        }

                else break;

                }

        }

//-------------------------------------------------------------------------------------
//	Initially read in the list of airports, create the buttons 
//	and put them into the grid. Done once.
//-------------------------------------------------------------------------------------

void	init_airports() { // airport codes

	char tmp[2048], code[8], city[256], airprt[256], country[256];

//--------------------------------------------------------------------------------------------
//	This assumes that the list of airports is in the same directory as the running pgm
//--------------------------------------------------------------------------------------------

	strcpy(tmp, Code_Home);
	strcat(tmp, "/airports.config");
		
	FILE *air = fopen(tmp, "r");

	if (! air) {
		airportCount = 0;
		}
	else {
		int row = 0;
		airportCount = 0;
		strcpy(tmp, "");

		GtkCssProvider  *cssProviderButton1;
		GtkCssProvider  *cssProviderButton2;
		GtkCssProvider  *cssProviderButton3;
		GtkCssProvider  *cssProviderButton4;

		cssProviderButton1 = gtk_css_provider_new();
		cssProviderButton2 = gtk_css_provider_new();
		cssProviderButton3 = gtk_css_provider_new();
		cssProviderButton4 = gtk_css_provider_new();

#define CODE_LEN 5 	// maximum length
#define CITY_LEN 25 	// maximum length
#define AIRPORT_LEN 40 	// maximum length
#define COUNTRY_LEN 13 	// maximum length

//-----------------------------------------------------------
//	Weather grid button css code.
//	Mono space used in order to retain constant width.
//	This sets the min size of the buttons - otherwise the
//	buttons will vary in size. Function airportData() 
//	truncates long data values according to the defined 
//	constants above.
//-----------------------------------------------------------

		gtk_css_provider_load_from_data(cssProviderButton1, 
			"* {padding-top: 2px; padding-bottom: 2px; padding-left: 2px; "
			" padding-right: 0px; min-width: 45px; font-family: Monospace; background: lightblue;}" , -1, NULL);

		gtk_css_provider_load_from_data(cssProviderButton2, 
			"* {padding-top: 2px; padding-bottom: 2px; padding-left: 2px; "
			" padding-right: 0px; min-width: 180px; font-family: Monospace;}" , -1, NULL);

		gtk_css_provider_load_from_data(cssProviderButton3, 
			"* {padding-top: 2px; padding-bottom: 2px; padding-left: 2px; "
			" padding-right: 0px; min-width: 280px; font-family: Monospace;}" , -1, NULL);

		gtk_css_provider_load_from_data(cssProviderButton4, 
			"* {padding-top: 2px; padding-bottom: 2px; padding-left: 2px; "
			" padding-right: 0px; min-width: 100px; font-family: Monospace;}" , -1, NULL);

//-----------------------------------------------
//	Read and parse weather codes
//	format: city$airportName$country$code
//-----------------------------------------------

			while (fgets(tmp, 2048, air)) {

				if (tmp[0] == '#' ) continue;

				tmp[strlen(tmp) - 1] = 0;

				airport[airportCount] = malloc (strlen(tmp) + 1);

				if (airport[airportCount] == 0) {
					printf("*** out of memory airport codes\n");
					airportCount = 0;
					return;
					}

				strcpy(airport[airportCount], tmp);

				if (airportCount < AIRPORT_ROWS ) { // populate visible (first) rows

//--------------------------------------------------------------------
//					parse line of airport data
//--------------------------------------------------------------------

					airportData(tmp, code, city, airprt, country);

//-----------------------------------------------------------------------------------------------
//					create grid row 'airportCount' in the grid (0, 1, 2, ...)
//-----------------------------------------------------------------------------------------------

					gtk_grid_insert_row (GTK_GRID(grid), airportCount);

//------------------------------------------------------------------------------
//					create initial buttons into this row
//------------------------------------------------------------------------------

					airportButton1[airportCount] = gtk_button_new_with_label (code);
					airportButton2[airportCount] = gtk_button_new_with_label (city);
					airportButton3[airportCount] = gtk_button_new_with_label (airprt);
					airportButton4[airportCount] = gtk_button_new_with_label (country);

//------------------------------------------------------------------
//					apply css code from above
//------------------------------------------------------------------

					css_set(cssProviderButton1, airportButton1[airportCount]);
					css_set(cssProviderButton2, airportButton2[airportCount]);
					css_set(cssProviderButton3, airportButton3[airportCount]);
					css_set(cssProviderButton4, airportButton4[airportCount]);

//--------------------------------------------------------------------------------------------------------------
//					text alignment: <horizontal, vertical>: 0.0 -> left/top; 0.5 -> center 
//--------------------------------------------------------------------------------------------------------------

					gtk_button_set_alignment (GTK_BUTTON(airportButton1[airportCount]), 0.0, 0.5);
					gtk_button_set_alignment (GTK_BUTTON(airportButton2[airportCount]), 0.0, 0.5); 
					gtk_button_set_alignment (GTK_BUTTON(airportButton3[airportCount]), 0.0, 0.5); 
					gtk_button_set_alignment (GTK_BUTTON(airportButton4[airportCount]), 0.0, 0.5); 

//-----------------------------------------------------------------------------------------------
//					Attach the buttons to row airportCount in the grid.
//					There are four buttons to the row.
//					Final 2 args are column/row span.
//-----------------------------------------------------------------------------------------------

					gtk_grid_attach (GTK_GRID(grid), airportButton1[airportCount], 1, airportCount, 1, 1 );
					gtk_grid_attach (GTK_GRID(grid), airportButton2[airportCount], 2, airportCount, 1, 1 );
					gtk_grid_attach (GTK_GRID(grid), airportButton3[airportCount], 3, airportCount, 1, 1 );
					gtk_grid_attach (GTK_GRID(grid), airportButton4[airportCount], 4, airportCount, 1, 1 );

//-------------------------------------------------------------------------------------------------------
//					Establish a callback function for the first button.
//					This code sets call back for the first button. only Thus they others
//					are inactive but you might want to have them do something.
//					To do so, add more similar callback functions.
//-------------------------------------------------------------------------------------------------------

					g_signal_connect(airportButton1[airportCount], 
						"clicked", G_CALLBACK(on_airport), NULL); 

					}

				airportCount++;

				if (airportCount >= AIRPORTS ) {
					printf("Too many airports - truncated\n");
					airportCount --;
					break;
					}
				}

			fclose(air);

//----------------------------------------------------------------------
//			Set the range of the slider to airportCount
//----------------------------------------------------------------------

			gtk_range_set_range (GTK_RANGE(scroll), 0, airportCount ); // range of slider
			}
		}


//---------------------------------------------------
//	Parse and extract a line of airport data
//---------------------------------------------------

void	airportData(char *tmp, char *code, char *city, char *airport, char *country) {

	char *p1;

//-----------------------------------------------------
//	pattern in tmp is: city$airport$country$code
//-----------------------------------------------------

	p1 = strtok(tmp,"$");

	if (p1 == NULL) {
		strcpy(city,"<missing>");
		strcpy(airport,"<missing>");
		strcpy(country,"<missing>");
		strcpy(airport,"<missing>");
		return;
		}

	strncpy(city, p1, 256);

	p1 = strtok(NULL,"$");

	if (p1 == NULL) {
		strcpy(city,"<missing>");
		strcpy(airport,"<missing>");
		strcpy(country,"<missing>");
		strcpy(airport,"<missing>");
		return;
		}

	strncpy(airport, p1, 256);

	p1 = strtok(NULL,"$");

	if (p1 == NULL) {
		strcpy(city,"<missing>");
		strcpy(airport,"<missing>");
		strcpy(country,"<missing>");
		strcpy(airport,"<missing>");
		return;
		}

	strncpy(country, p1, 256);

	p1 = strtok(NULL,"$");

	if (p1 == NULL) {
		strcpy(city,"<missing>");
		strcpy(airport,"<missing>");
		strcpy(country,"<missing>");
		strcpy(airport,"<missing>");
		return;
		}

	strncpy(code, p1, 8);

//------------------------------------------------------
//	truncate results to fit in a button.
//	this has no effect if the results are short
//------------------------------------------------------

	code[CODE_LEN] = 0;  // length restriction
	city[CITY_LEN] = 0;  // length restriction
	airport[AIRPORT_LEN] = 0;  // length restriction
	country[COUNTRY_LEN] = 0; // length restriction
	}


//---------------------------------------
//	paint css onto a widget
//---------------------------------------

void    css_set(GtkCssProvider * cssProvider, GtkWidget *g_widget) {

        GtkStyleContext *context;

        context = gtk_widget_get_style_context(g_widget);

        gtk_style_context_add_provider (context,
                GTK_STYLE_PROVIDER(cssProvider), GTK_STYLE_PROVIDER_PRIORITY_USER);

        }


//-----------------------------------------------------
//	Signal handler for weather button
//-----------------------------------------------------

void	on_airport(GtkButton *b) {

	char tmp[100];

	strcpy(tmp, gtk_button_get_label(b));

//------------------------------------
//	remove training blank(s)
//------------------------------------

	for (int i = 0; tmp[i] != 0; i++) if (tmp[i] == ' ') { tmp[i] = 0; break; }

//-----------------------------
//	Place value in button
//-----------------------------

	gtk_button_set_label(GTK_BUTTON(curWeather), tmp); 

//-------------------------------------------
//	Simulate click of curWeather button
//-------------------------------------------

	on_curWeather_clicked(curWeather);
	}

//--------------------------------------
//      Weather panel button clicked
//--------------------------------------

void    on_curWeather_clicked(GtkWidget *b) {

        FILE *top;
        char buf[24000];
        int i;

//------------------------------------------------------------------------
//	Remove any prior copies of the temporary file containing results
//------------------------------------------------------------------------

        system("rm /tmp/sgr-weather"); // remove any previous copies

//-----------------------------------------------------------------
//	Create text of command to get the weather data from NOAA
//-----------------------------------------------------------------

        sprintf(buf,
                "wget -t 5 -O /tmp/sgr-weather ftp://tgftp.nws.noaa.gov/data/observations/metar/decoded/%s.TXT",
                        gtk_button_get_label(GTK_BUTTON(curWeather))); // 5 tries - default is 20

//---------------------------------------------------
//	Execute command. Results to a file in /tmp
//---------------------------------------------------

        system(buf);

//----------------------------------------------------
//	Read the results an insert into textbuffer
//----------------------------------------------------

        top = fopen("/tmp/sgr-weather", "r");

        i = 0;
        if (top) {
                char cmd1[2048];
                int j;

                strcpy(buf,"");
                strcpy(cmd1,"");

//--------------------------------------------
//	read file and concatenate into buf
//	includes newline characters
//--------------------------------------------

                while(fgets(cmd1, 2048, top)) {

                        if (strncmp(cmd1,"ob:",3) == 0) continue; // ignore some lines
                        if (strncmp(cmd1,"cycle:",5) == 0) continue; // ignore some lines
                        strcat(buf, cmd1); // add line to buffer

                        }

//---------------------------------------------------
//	if buf has contents, insert into textbuffer
//---------------------------------------------------

                if (strlen(buf))
                        gtk_text_buffer_set_text (GTK_TEXT_BUFFER(textbuffer1), buf, -1);

                else    gtk_text_buffer_set_text (GTK_TEXT_BUFFER(textbuffer1),
                                "Airport not found or not responding.", -1);

                fclose (top);
                }

//----------------------------------
//	no file - insert message
//----------------------------------

        else {
                gtk_text_buffer_set_text (GTK_TEXT_BUFFER(textbuffer1),
                        "Airport not found or not responding.", -1);
                }
        }

//-------------------------------------------------------------
//      text has been entered into weather location entry box
//-------------------------------------------------------------

        void on_location_changed(GtkEntry  *entry, gchar *preedit, gpointer user_data) {

        int i = gtk_entry_get_text_length (entry);

//------------------------------------------
//	extract text currently in box
//------------------------------------------

        strncpy(weatherLocation, gtk_entry_get_text(entry), 512);

//------------------------------------------------------
//	look up text fragment - length limited compare
//------------------------------------------------------

        for (i = 0; i < airportCount; i++) { // start looking for it

                if (strncasecmp(airport[i], weatherLocation, strlen(weatherLocation)) == 0) {
                        char tmp[256];

//---------------------------------------------------
//			move scrollbar slider
//---------------------------------------------------

                        gtk_adjustment_set_value (GTK_ADJUSTMENT(adjustment1), i);
                        break;
                        }

                }
        }

gboolean on_view_scroll_event(GtkWidget *b, GdkEvent *e) { // weather viewport

	GdkScrollDirection direction;

	gdk_event_get_scroll_direction (e, &direction);

	int i = gtk_range_get_value (GTK_RANGE(scroll)); // place slider

	if (direction == GDK_SCROLL_UP) i++; else i--;

	if (i <= 0) i = 0;

	else if (i > airportCount) i = airportCount;

	gtk_range_set_value (GTK_RANGE(scroll), i); // move slider

	return TRUE;
	}

