/* 
qrq - High speed morse trainer, similar to the DOS classic "Rufz"

Copyright (C) 2006-2019  Fabian Kurz and contributors

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., 51 Franklin
Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/ 
#if WIN32
#define WIN_THREADS
typedef int AUDIO_HANDLE;
#endif

#ifndef WIN_THREADS
#include <pthread.h>			/* CW output will be in a separate thread */
#endif
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>				/* basename */
#include <ctype.h>
#include <time.h> 
#include <limits.h> 			/* PATH_MAX */

#ifndef PATH_MAX				/* Not defined e.g. on GNU/hurd */
#define PATH_MAX 4096 
#endif

#include <dirent.h>
#include <math.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>			/* mkdir */
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#ifdef WIN32
#include <windows.h>
#endif

#define PI M_PI

#define SILENCE 0		/* Waveforms for the tone generator */
#define SINE 1
#define SAWTOOTH 2
#define SQUARE 3

#ifndef DESTDIR
#	define DESTDIR "/usr"
#endif

#ifndef VERSION
#  define VERSION "0.0.0"
#endif

#ifdef CA
#include "coreaudio.h"
typedef void *AUDIO_HANDLE;
#endif

#ifdef OSS
#include "oss.h"
#define write_audio(x, y, z) write(x, y, z)
#define close_audio(x) close(x)
typedef int AUDIO_HANDLE;
#endif

#ifdef PA
#include "pulseaudio.h"
typedef void *AUDIO_HANDLE;
#endif

/* callsign array will be dynamically allocated */
static char **calls = NULL;

const static char *codetable[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..",".---",
"-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",
".--","-..-","-.--","--..","-----",".----","..---","...--","....-",".....",
"-....", "--...","---..","----."};

/* List of available callbase files. Probably no need to do dynamic memory allocation for that list.... */

static char cblist[100][PATH_MAX];

static char mycall[15]="DJ1YFK";		/* mycall. will be read from qrqrc */
static char dspdevice[PATH_MAX]="/dev/dsp";	/* will also be read from qrqrc */
static int score = 0;					/* qrq score */
static int sending_complete;			/* global lock for "enter" while sending */
static int callnr = 0;					/* nr of actual call in attempt */
static int initialspeed=200;			/* initial speed. to be read from file*/
static int mincharspeed=0;				/* min. char. speed, below: farnsworth*/
static int speed=200;					/* current speed in cpm */
static int maxspeed=0;
static int freq=800;					/* current cw sidetone freq */
static int errornr=0;					/* number of errors in attempt */
static int p=0;							/* position of cursor, relative to x */
static int status=1;					/* 1= attempt, 2=config */
static int mode=1;						/* 0 = overwrite, 1 = insert */
static int j=0;							/* counter etc. */
static int constanttone=0;              /* if 1 don't change the pitch */
static int ctonefreq=800;               /* if constanttone=1 use this freq */
static int f6=0;						/* f6 = 1: allow unlimited repeats */
static int fixspeed=0;					/* keep speed fixed, regardless of err*/
static int unlimitedattempt=0;			/* attempt with all calls  of the DB */
static int attemptvalid=1;				/* 1 = not using any "cheats" */
static unsigned long int nrofcalls=0;	

long samplerate=44100;
static long long_i;
static int waveform = SINE;				/* waveform: (0 = none) */
static char wavename[10]="Sine    ";	/* Name of the waveform */
static double edge=2.0;						/* rise/fall time in milliseconds */
static int ed;							/* risetime, normalized to samplerate */

static short buffer[88200];
static int full_buf[882000];  /* 20 second max buffer */
static int full_bufpos = 0;

AUDIO_HANDLE dsp_fd;

static int display_toplist();
static int calc_score (char * realcall, char * input, int speed, char * output, int f6pressed);
static int update_score();
static int show_error (char * realcall, char * wrongcall); 
static int clear_display();
static int add_to_toplist(char * mycall, int score, int maxspeed);
static int read_config();
static int save_config();
static int tonegen(int freq, int length, int waveform);
static void *morse(void * arg); 
static int add_to_buf(void* data, int size);
static int readline(WINDOW *win, int y, int x, char *line, int i); 
static void thread_fail (int j);
static int check_toplist ();
static int find_files ();
static int statistics ();
static int read_callbase ();
static void find_callbases();
static void select_callbase ();
static void help ();
static void callbase_dialog();
static void parameter_dialog();
static int clear_parameter_display();
static void update_parameter_dialog();
static void start_summary_file();
static void close_summary_file();

#ifdef WIN_THREADS
HANDLE cwthread;
#else
pthread_t cwthread;				/* thread for CW output, to enable
								   keyboard reading at the same time */
pthread_attr_t cwattr;
#endif

char rcfilename[PATH_MAX]="";			/* filename and path to qrqrc */
char tlfilename[PATH_MAX]="";			/* filename and path to toplist */
char cbfilename[PATH_MAX]="";			/* filename and path to callbase */
char sumfilepath[PATH_MAX]="";			/* path where to save summary files for each attempt */

char destdir[PATH_MAX]="";

char summary[65536]="";                 /* detailled attempt summary, saved in a file */
int s_pos = 0;                          /* Position within summary */

/* create windows */
WINDOW *top_w;					/* actual score					*/
WINDOW *mid_w;					/* callsign history/mistakes	*/
WINDOW *conf_w;					/* parameter config display	*/
WINDOW *bot_w;					/* user input line				*/
WINDOW *inf_w;					/* info window for param displ	*/
WINDOW *right_w;				/* highscore list/settings		*/


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

  /* if built as osx bundle set DESTDIR to Resources dir of bundle */
#ifdef OSX_BUNDLE
  char tempdir[PATH_MAX]="";
  char* p_slash = strrchr(argv[0], '/');
  strncpy(tempdir, argv[0], p_slash - argv[0]);
  p_slash = strrchr(tempdir, '/');
  strncpy(destdir, tempdir, p_slash - tempdir);
  strcat(destdir, "/Resources");
#else
  strcpy(destdir, DESTDIR);
#endif

	char abort = 0;
	char tmp[80]="";
	char input[15]="";
	int i=0,j=0,k=0;						/* counter etc. */
	char previouscall[80]="";
	int previousfreq = 0;
	int f6pressed=0;

	if (argc > 1) {
		help();
	}
	
	(void) initscr();
	cbreak();
	noecho();
	curs_set(FALSE);
	keypad(stdscr, TRUE);
	scrollok(stdscr, FALSE);
	
	printw("qrq v%s - Copyright (C) 2006-2019 Fabian Kurz, DJ1YFK\n", VERSION);
	printw("This is free software, and you are welcome to redistribute it\n");
	printw("under certain conditions (see COPYING).\n");

	refresh();

	/* search for 'toplist', 'qrqrc' and callbase.qcb and put their locations
	 * into tlfilename, rcfilename, cbfilename */
	find_files();

	/* check if the toplist is in the suitable format. as of 0.0.7, each line
	 * is 31 characters long, with the added time stamp */
	check_toplist();

	/* buffer for audio */
	for (long_i=0;long_i<88200;long_i++) {
		buffer[long_i]=0;
	}
	
	/* random seed from time */
	srand( (unsigned) time(NULL) ); 

#ifndef WIN_THREADS
	/* Initialize cwthread. We have to wait for the cwthread to finish before
	 * the next cw output can be made, this will be done with pthread_join */
	pthread_attr_init(&cwattr);
	pthread_attr_setdetachstate(&cwattr, PTHREAD_CREATE_JOINABLE);
#endif
	
	/****** Reading configuration file ******/
	printw("\nReading configuration file qrqrc \n");
	read_config();

	attemptvalid = 1;
	if (f6 || fixspeed || unlimitedattempt) {
		attemptvalid = 0;	
	}

	/****** Reading callsign database ******/
	printw("\nReading callsign database... ");
	nrofcalls = read_callbase();

	printw("done. %d calls read.\n\n", nrofcalls);
	printw("Press any key to continue...");

	refresh();
	getch();

	erase();
	refresh();

	top_w = newwin(4, 60, 0, 0);
	mid_w = newwin(17, 60, 4, 0);
	conf_w = newwin(17, 60, 4, 0);
	bot_w = newwin(3, 60, 21, 0);
	inf_w = newwin(3, 60, 21, 0);
	right_w = newwin(24, 20, 0, 60);

	werase(top_w);
	werase(mid_w);
	werase(conf_w);
	werase(bot_w);
	werase(inf_w);
	werase(right_w);

	keypad(bot_w, TRUE);
	keypad(mid_w, TRUE);
	keypad(conf_w, TRUE);

#ifdef WIN_THREADS
	cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"QRQ",0, NULL);
#else
	/* no need to join here, this is the first possible time CW is sent */
	pthread_create(&cwthread, NULL, & morse, (void *) "QRQ");
#endif

/* very outter loop */
while (1) {	

/* status 1 = running an attempt of 50 calls */	
while (status == 1) {
	box(top_w,0,0);
	box(conf_w,0,0);
	box(mid_w,0,0);
	box(bot_w,0,0);
	box(inf_w,0,0);
	box(right_w,0,0);
	wattron(top_w,A_BOLD);
	mvwaddstr(top_w,1,1, "QRQ v");
	mvwaddstr(top_w,1,6, VERSION);
	wattroff(top_w, A_BOLD);
	mvwaddstr(top_w,1,11, " by Fabian Kurz, DJ1YFK");
	mvwaddstr(top_w,2,1, "Homepage and Toplist: http://fkurz.net/ham/qrq.html"
					"     ");

	clear_display();
	wattron(mid_w,A_BOLD);
	mvwaddstr(mid_w,1,1, "Usage:");
	mvwaddstr(mid_w,10,2, "F6                          F10       ");
	wattroff(mid_w, A_BOLD);
	mvwaddstr(mid_w,2,2, "After entering your callsign, 50 random callsigns");
	mvwaddstr(mid_w,3,2, "from a database will be sent. After each callsign,");
	mvwaddstr(mid_w,4,2, "enter what you have heard. If you copied correctly,");
	mvwaddstr(mid_w,5,2, "full points are credited and the speed increases by");
	mvwaddstr(mid_w,6,2, "2 WpM -- otherwise the speed decreases and only a ");
	mvwaddstr(mid_w,7,2, "fraction of the points, depending on the number of");
	mvwaddstr(mid_w,8,2, "errors is credited.");
	mvwaddstr(mid_w,10,2, "F6 repeats a callsign once, F10 quits.");
	mvwaddstr(mid_w,12,2, "Settings can be changed with F5 (or in qrqrc).");
#ifndef WIN32
	mvwaddstr(mid_w,14,2, "Score statistics (requires gnuplot) with F7.");
#endif

	wattron(right_w,A_BOLD);
	mvwaddstr(right_w,1, 6, "Toplist");
	wattroff(right_w,A_BOLD);

	display_toplist();

	p=0;						/* cursor to start position */
	wattron(bot_w,A_BOLD);
	mvwaddstr(bot_w, 1, 1, "Please enter your callsign:                      ");
	wattroff(bot_w,A_BOLD);
	
	wrefresh(top_w);
	wrefresh(mid_w);
	wrefresh(bot_w);
	wrefresh(right_w); 
	
	/* reset */
	maxspeed = errornr = score = 0;
	speed = initialspeed;
	
	/* prompt for own callsign */
	i = readline(bot_w, 1, 30, mycall, 1);

	/* F5 -> Configure sound */
	if (i == 5) {
		parameter_dialog();
		break;
	} 
	/* F6 -> play test CW */
	else if (i == 6) {
		freq = constanttone ? ctonefreq : 800;
#ifdef WIN_THREADS
		 WaitForSingleObject(cwthread,INFINITE);
		 CloseHandle(cwthread);
		 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"VVVTEST",0, NULL);
#else
		pthread_join(cwthread, NULL);
		j = pthread_create(&cwthread, NULL, &morse, (void *) "VVVTEST");	
		thread_fail(j);
#endif
		break;
	}
	else if (i == 7) {
#ifndef WIN32
		statistics();
#endif
		break;
	}

	if (strlen(mycall) == 0) {
		strcpy(mycall, "NOCALL");
	}
	else if (strlen(mycall) > 7) {		/* cut excessively long calls */
		mycall[7] = '\0';
	}
	
	clear_display();
	wrefresh(mid_w);
	
	/* update toplist (highlight may change) */
	display_toplist();

	mvwprintw(top_w,1,1,"                                      ");
	mvwprintw(top_w,2,1,"                                               ");
	mvwprintw(top_w,1,1,"Callsign:");
	wattron(top_w,A_BOLD);
	mvwprintw(top_w,1,11, "%s", mycall);
	wattroff(top_w,A_BOLD);
	update_score();
	wrefresh(top_w);


	/* Reread callbase */
	nrofcalls = read_callbase();

	/****** send 50 or unlimited calls, ask for input, score ******/
    start_summary_file();
	
	for (callnr=1; callnr < (unlimitedattempt ? nrofcalls : 51); callnr++) {
		/* Make sure to wait for the cwthread of the previous callsign, if
		 * necessary. */
#ifdef WIN_THREADS
		WaitForSingleObject(cwthread,INFINITE);
		CloseHandle(cwthread);
#else
		pthread_join(cwthread, NULL);
#endif	
		/* select an unused callsign from the calls-array */
		do {
			i = (int) ((float) nrofcalls*rand()/(RAND_MAX+1.0));
		} while (calls[i] == NULL);

		/* only relevant for callbases with less than 50 calls */
		if (nrofcalls == callnr) { 		/* Only one call left!" */
				callnr =  51; 			/* Get out after next one */
		}



		/* output frequency handling a) random b) fixed */
		if ( constanttone == 0 ) {
				/* random freq, fraction of samplerate */
				freq = (int) (samplerate/(50+(40.0*rand()/(RAND_MAX+1.0))));
		}
		else { /* fixed frequency */
				freq = ctonefreq;
		}

		mvwprintw(bot_w,1,1,"                                      ");
		mvwprintw(bot_w, 1, 1, "%3d/%s", callnr, unlimitedattempt ? "-" : "50");	
		wrefresh(bot_w);	
		tmp[0]='\0';

		/* starting the morse output in a separate process to make keyboard
		 * input and echoing at the same time possible */
		
		sending_complete = 0;	
#ifdef WIN_THREADS
		cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,calls[i],0, NULL);
#else
		j = pthread_create(&cwthread, NULL, morse, calls[i]);	
		thread_fail(j);		
#endif
		
		f6pressed=0;

		while (!abort && (j = readline(bot_w, 1, 8, input,1)) > 4) {/* F5..F10 pressed */

			switch (j) {
				case 6:		/* repeat call */
				if (f6pressed && (f6 == 0)) {
					continue;
				}
				f6pressed=1;
				/* wait for old cwthread to finish, then send call again */
			
#ifdef WIN_THREADS
			WaitForSingleObject(cwthread,INFINITE);
			CloseHandle(cwthread);
			cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,calls[i],0, NULL);
#else
			pthread_join(cwthread, NULL);
			j = pthread_create(&cwthread, NULL, &morse, calls[i]);	
			thread_fail(j);
#endif	
					break; /* 6*/
				case 7:		/* repeat _previous_ call */
					if (callnr > 1) {
						k = freq;
						freq = previousfreq;
#ifdef WIN_THREADS
			WaitForSingleObject(cwthread,INFINITE);
			CloseHandle(cwthread);
			cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,previouscall,0, NULL);
			WaitForSingleObject(cwthread,INFINITE);
			CloseHandle(cwthread);
#else
			pthread_join(cwthread, NULL);
			j = pthread_create(&cwthread, NULL, &morse, previouscall);	
			thread_fail(j);
			pthread_join(cwthread, NULL);
#endif	
						/* NB: We must wait for the CW thread before
						 * we set the freq back -- this blocks keyboard
						 * input, but in this case it shouldn't matter */
						freq = k;
					}
					break;
				case 10:	/* abort attempt */
					abort = 1;
					continue;
					break;
			}
					
		}

		
		if (abort) {
			abort = 0;
			input[0]='\0';
			break;
		}
		
		tmp[0]='\0';
		score += calc_score(calls[i], input, speed, tmp, f6pressed);
		update_score();
		if (strcmp(tmp, "-")) {			/* made an error */
				show_error(calls[i], tmp);
		}
		input[0]='\0';
		strncpy(previouscall, calls[i], 80);
		previousfreq = freq;
		calls[i] = NULL;
	}

    close_summary_file();

	/* attempt is over, send AR */
	callnr = 0;
	
#ifdef WIN_THREADS
		 WaitForSingleObject(cwthread,INFINITE);
		 CloseHandle(cwthread);
		 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"+",0, NULL);
#else
		pthread_join(cwthread, NULL);
		j = pthread_create(&cwthread, NULL, &morse, (void *) "+");	
		thread_fail(j);
#endif
	
	add_to_toplist(mycall, score, maxspeed);
	
	curs_set(0);
	wattron(bot_w,A_BOLD);
	mvwprintw(bot_w,1,1, "Attempt finished. Press any key to continue!");
	wattroff(bot_w,A_BOLD);
	wrefresh(bot_w);
	getch();
	mvwprintw(bot_w,1,1, "                                            ");
	curs_set(1);

	
} /* while (status == 1) */


} /* very outter loop */

	getch();
	endwin();
	delwin(top_w);
	delwin(bot_w);
	delwin(mid_w);
	delwin(right_w);
	getch();
	return 0;
}



/* (formerly status == 2). Change parameters */

void parameter_dialog () {

int j = 0;


update_parameter_dialog();

while ((j = getch()) != 0) {

	switch ((int) j) {
		case '+':							/* rise/falltime */
			if (edge <= 9.0) {
				edge += 0.1;
			}
			break;
		case '-':
			if (edge > 0.1) {
				edge -= 0.1;
			}
			break;
		case 'w':							/* change waveform */
			waveform = ((waveform + 1) % 3)+1;	/* toggle 1-2-3 */
			break;
		case 'k':							/* constanttone */
			if (ctonefreq >= 160) {
				ctonefreq -= 10;
			}
			else {
					constanttone = 0;
			}
			break;
		case 'l':
			if (constanttone == 0) {
				constanttone = 1;
			}
			else if (ctonefreq < 1600) {
				ctonefreq += 10;
			}
			break;
		case '0':
			if (constanttone == 1) {
				constanttone = 0;
			}
			else {
				constanttone = 1;
			}
			break;
		case 'f':
				f6 = (f6 ? 0 : 1);
			break;
		case 's':
				fixspeed = (fixspeed ? 0 : 1);
			break;
		case 'u':
				unlimitedattempt = (unlimitedattempt ? 0 : 1);
			break;
		case KEY_UP: 
			initialspeed += 10;
			break;
		case KEY_DOWN:
			if (initialspeed > 10) {
				initialspeed -= 10;
			}
			break;
		case KEY_RIGHT:
			mincharspeed += 10;
			break;
		case KEY_LEFT:
			if (mincharspeed > 10) {
				mincharspeed -= 10;
			}
			break;
		case 'c':
			readline(conf_w, 5, 25, mycall, 1);
			if (strlen(mycall) == 0) {
				strcpy(mycall, "NOCALL");
			}
			else if (strlen(mycall) > 7) {	/* cut excessively long calls */
				mycall[7] = '\0';
			}
			p=0;							/* cursor position */
			break;
#ifdef OSS
		case 'e':
			readline(conf_w, 12, 25, dspdevice, 0);
			if (strlen(dspdevice) == 0) {
				strcpy(dspdevice, "/dev/dsp");
			}
			p=0;							/* cursor position */
			break;
#endif
		case 'd':							/* go to database browser */
			if (!callnr) {					/* Only allow outside of attempt */
				curs_set(1);
				callbase_dialog();
			}
			break;
		case KEY_F(2):
			save_config();	
			mvwprintw(conf_w,15,39, "Config saved!");
			wrefresh(conf_w);
#ifdef WIN32
			Sleep(1000);
#else
			sleep(1);	
#endif
			break;
		case KEY_F(6):
			freq = constanttone ? ctonefreq : 800;
#ifdef WIN_THREADS
		 WaitForSingleObject(cwthread,INFINITE);
		 CloseHandle(cwthread);
		 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"TESTING",0, NULL);
#else
		pthread_join(cwthread, NULL);
		j = pthread_create(&cwthread, NULL, &morse, (void *) "TESTING");	
		thread_fail(j);
#endif
			break;
		case KEY_F(10):
		case KEY_F(3):
			curs_set(1);
			clear_parameter_display();
			wrefresh(conf_w);
			/* restore old windows */
			touchwin(mid_w);
			touchwin(bot_w);
			wrefresh(mid_w);
			wrefresh(bot_w);
			return;
	}

	speed = initialspeed;

	attemptvalid = 1;
	if (f6 || fixspeed || unlimitedattempt) {
		attemptvalid = 0;	
	}

	update_parameter_dialog();

} /* while 1 (return only by F3/F10) */

} /* parameter_dialog */


/* update_parameter_dialog 
 * repaints the whole config/parameter screen (F5) */


void update_parameter_dialog () {

	clear_parameter_display();
	switch (waveform) {
		case SINE:
			strcpy(wavename, "Sine    ");
			break;
		case SAWTOOTH:
			strcpy(wavename, "Sawtooth");
			break;
		case SQUARE:
			strcpy(wavename, "Square  ");
			break;
	}

	mvwaddstr(inf_w,1,1, "                                                         ");
	curs_set(0);
	wattron(conf_w,A_BOLD);
	mvwaddstr(conf_w,1,1, "Configuration:          Value                Change");
	mvwprintw(conf_w,14,2, "      F6                    F10            ");
	mvwprintw(conf_w,15,2, "      F2");
	wattroff(conf_w, A_BOLD);
	mvwprintw(conf_w,2,2, "Initial Speed:         %3d CpM / %3d WpM" 
					"    up/down", initialspeed, initialspeed/5);
	mvwprintw(conf_w,3,2, "Min. character Speed:  %3d CpM / %3d WpM" 
					"    left/right", mincharspeed, mincharspeed/5);
	mvwprintw(conf_w,4,2, "CW rise/falltime (ms): %1.1f           " 
					"       +/-", edge);
	mvwprintw(conf_w,5,2, "Callsign:              %-14s" 
					"       c", mycall);
	mvwprintw(conf_w,6,2, "CW pitch (0 = random): %-4d"
					"                 k/l or 0", (constanttone)?ctonefreq : 0);
	mvwprintw(conf_w,7,2, "CW waveform:           %-8s"
					"             w", wavename);
	mvwprintw(conf_w,8,2, "Allow unlimited F6*:   %-3s"
					"                  f", (f6 ? "yes" : "no"));
	mvwprintw(conf_w,9,2, "Fixed CW speed*:       %-3s"
					"                  s", (fixspeed ? "yes" : "no"));
	mvwprintw(conf_w,10,2, "Unlimited attempt*:    %-3s"
					"                  u", (unlimitedattempt ? "yes" : "no"));
	if (!callnr) {
		mvwprintw(conf_w,11,2, "Callsign database:     %-15s"
					"      d (%d)", basename(cbfilename),nrofcalls);
	}
#ifdef OSS
	mvwprintw(conf_w,12,2, "DSP device:            %-15s"
					"      e", dspdevice);
#endif
	mvwprintw(conf_w,14,2, "Press");
	mvwprintw(conf_w,14,11, "to play sample CW,");
	mvwprintw(conf_w,14,34, "to go back.");
	mvwprintw(conf_w,15,2, "Press");
	mvwprintw(conf_w,15,11, "to save config permanently.");
	mvwprintw(inf_w,1,1, "          * Makes scores ineligible for toplist");
	wrefresh(conf_w);
	wrefresh(inf_w);
	

} /* update_parameter_dialog */




void callbase_dialog () {

	clear_parameter_display();

	wattron(conf_w,A_BOLD);
	mvwaddstr(conf_w,1,1, "Change Callsign Database");
	wattroff(conf_w,A_BOLD);
#if WIN32
	mvwprintw(conf_w,3,1, ".qcb files found:");
#else
	mvwprintw(conf_w,3,1, ".qcb files found (in %s/share/qrq/ and ~/.qrq/):",destdir);
#endif

	/* populate cblist */	
	find_callbases();
	/* selection dialog */
	select_callbase();
	wrefresh(conf_w);

	nrofcalls = read_callbase();


	return;	/* back to config menu */
}














/* reads a callsign etc. in *win at y/x and writes it to *line */

static int readline(WINDOW *win, int y, int x, char *line, int capitals) {
	int c;						/* character we read */
	int i=0;

	if (strlen(line) == 0) {p=0;}	/* cursor to start if no call in buffer */
	
	if (mode == 1) { 
		mvwaddstr(win,1,55,"INS");
	}
	else {
		mvwaddstr(win,1,55,"OVR");
	}

	mvwaddstr(win,y,x,line);
	wmove(win,y,x+p);
	wrefresh(win);
	curs_set(TRUE);
	
	while (1) {
		c = wgetch(win);
		if (c == '\n' && sending_complete)
			break;

		if (((c > 64 && c < 91) || (c > 96 && c < 123) || (c > 47 && c < 58)
					 || c == '/' || c == ' ') && strlen(line) < 14) {
	
			line[strlen(line)+1]='\0';
			if (capitals) {
				c = toupper(c);
			}
			if (mode == 1) {						/* insert */
				for(i=strlen(line);i > p; i--) {	/* move all chars by one */
					line[i] = line[i-1];
				}
			} 
			line[p]=c;						/* insert into gap */
			p++;
		}
		else if ((c == KEY_BACKSPACE || c == 127 || c == 9 || c == 8)
						&& p != 0) {					/* BACKSPACE */
			for (i=p-1;i < strlen(line); i++) {
				line[i] =  line[i+1];
			}
			p--;
		}
		else if (c == KEY_DC && strlen(line) != 0) {		/* DELETE */ 
			p++;
			for (i=p-1;i < strlen(line); i++) {
				line[i] =  line[i+1];
			}
			p--;
		}
		else if (c == KEY_LEFT && p != 0) {
			p--;	
		}
		else if (c == KEY_RIGHT && p < strlen(line)) {
			p++;
		}
		else if (c == KEY_HOME) {
			p = 0;
		}
		else if (c == KEY_END) {
			p = strlen(line);
		}
		else if (c == KEY_IC) {						/* INS/OVR */
			if (mode == 1) { 
				mode = 0; 
				mvwaddstr(win,1,55,"OVR");
			}
			else {
				mode = 1;
				mvwaddstr(win,1,55,"INS");
			}
		}
		else if (c == KEY_PPAGE && callnr && !attemptvalid) {
			speed += 5;
			update_score();
			wrefresh(top_w);
		}
		else if (c == KEY_NPAGE && callnr && !attemptvalid) {
			if (speed > 20) speed -= 5;
			update_score();
			wrefresh(top_w);
		}
		else if (c == KEY_F(5)) {
			parameter_dialog();
		}
		else if (c == KEY_F(6)) {
			return 6;
		}
		else if (c == KEY_F(7)) {
			return 7;
		}
		else if (c == KEY_F(10)) {				/* quit */
			if (callnr) {						/* quit attempt only */
				return 10;
			} 
			/* else: quit program */
			endwin();
			printf("Thanks for using 'qrq'!\nYou can submit your"
					" highscore to http://fkurz.net/ham/qrqtop.php\n");
			/* make sure that no more output is running, then send 73 & quit */
			speed = 200; freq = 800;
#ifdef WIN_THREADS
		 WaitForSingleObject(cwthread,INFINITE);
		 CloseHandle(cwthread);
		 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"73",0, NULL);
		 WaitForSingleObject(cwthread,INFINITE);
		 CloseHandle(cwthread);
#else
		pthread_join(cwthread, NULL);
		j = pthread_create(&cwthread, NULL, &morse, (void *) "73");	
		thread_fail(j);
			/* make sure the cw thread doesn't die with the main thread */
			/* Exit the whole main thread */
			pthread_join(cwthread, NULL);
#endif
			exit(0);
		}
		
		mvwaddstr(win,y,x,"                ");
		mvwaddstr(win,y,x,line);
		wmove(win,y,x+p);
		wrefresh(win);
	}
	curs_set(FALSE);
	return 0;
}

/* Read toplist and diplay first 10 entries */
static int display_toplist () {
	FILE * fh;
	int i=0;
	char tmp[35]="";
	if ((fh = fopen(tlfilename, "a+")) == NULL) {
		endwin();
		fprintf(stderr, "Couldn't read or create file '%s'!", tlfilename);
		exit(EXIT_FAILURE);
	}
	rewind(fh);				/* a+ -> end of file, we want the beginning */
	(void) fgets(tmp, 34, fh);		/* first line not used */
	while ((feof(fh) == 0) && i < 20) {
		i++;
		if (fgets(tmp, 34, fh) != NULL) {
			tmp[17]='\0';
			if (strstr(tmp, mycall)) {		/* highlight own call */
				wattron(right_w, A_BOLD);
			}
			mvwaddstr(right_w,i+2, 2, tmp);
			wattroff(right_w, A_BOLD);
		}
	}
	fclose(fh);
	wrefresh(right_w);
	return 0;
}

/* calculate score depending on number of errors and speed.
 * writes the correct call and entered call with highlighted errors to *output
 * and returns the score for this call
 *
 * in training modes (unlimited attempts, f6, fixed speed), no points.
 * */
static int calc_score (char * realcall, char * input, int spd, char * output, int f6pressed) {
	int i,x,m=0;
    int score = 0;

	x = strlen(realcall);

	if (strcmp(input, realcall) == 0) {		 /* exact match! */
		output[0]='-';						/* * == OK, no mistake */
		output[1]='\0';	
		if (speed > maxspeed) {maxspeed = speed;}
		if (!fixspeed) speed += 10;
		if (attemptvalid) {
            score =  2*x*spd;						/* score */
		}
	}
	else {									/* assemble error string */
		errornr += 1;
		if (strlen(input) >= x) {x =  strlen(input);}
		for (i=0;i < x;i++) {
			if (realcall[i] != input[i]) {
				m++;								/* mistake! */
				output[i] = tolower(input[i]);		/* print as lower case */
			}
			else {
				output[i] = input[i];
			}
		}
		output[i]='\0';
		if ((speed > 29) && !fixspeed) {speed -= 10;}

		/* score when 1-3 mistakes was made */
		if ((m < 4) && attemptvalid) {
			score = (int) (2*x*spd)/(5*m);
		}
	}

    s_pos += sprintf(summary + s_pos, "%-16s %-16s %-16s %3d %3d %5d %c\r\n", realcall, input, output, spd, spd/5, score, f6pressed ? '*' : ' ');

    return score;
}

static void start_summary_file () {
    s_pos = 0;
    s_pos += sprintf(summary + s_pos, "QRQ attempt by %s.\r\n\r\n", mycall);
    s_pos += sprintf(summary + s_pos, "%-16s %-16s %-16s %-3s %-3s %-5s %s\r\n", "Sent call", "Input", "Difference", "CpM", "WpM", "Score", "F6");
    s_pos += sprintf(summary + s_pos, "--------------------------------------------------------------------\r\n");
}

static void close_summary_file () {
    FILE *fh;
    time_t t;
    struct tm *tmp;
    char time_fmt[256];
    char filename[PATH_MAX];

    t = time(NULL);
    tmp = localtime(&t);
    if (tmp == NULL) {
        return;
    }

    if (strftime(time_fmt, sizeof(time_fmt), "%Y%m%d_%H%M", tmp) == 0) {
        return;
    }

    s_pos += sprintf(summary + s_pos, "--------------------------------------------------------------------\r\n");
    s_pos += sprintf(summary + s_pos, "Score: %d, Max. speed (CpM/WpM): %d / %d\r\nSaved at: %s\r\n", score, maxspeed, maxspeed/5, time_fmt);

    snprintf(filename, PATH_MAX, "%s/%s-%s.txt", sumfilepath, mycall, time_fmt);

	if ((fh = fopen(filename, "w")) == NULL) {
		printf("Unable to open summary file (%s)!\r\n", filename);
		exit(EXIT_FAILURE);
	}

    fwrite(summary, 1, s_pos, fh);
    fclose(fh);
	
    for (int i = 12; i <= 15; i++) {
        mvwprintw(mid_w,i,2, "                                                         ");
    }

	mvwprintw(mid_w,13,1, " Written detailled summary of this attempt to:");
	mvwprintw(mid_w,14,2, filename);
    wrefresh(mid_w);

}

/* print score, current speed and max speed to window */
static int update_score() {
	mvwaddstr(top_w,1,20, "Score:                         ");
	mvwaddstr(top_w,2,20, "Speed:     CpM/    WpM, Max:    /  ");
	if (attemptvalid) {
		mvwprintw(top_w, 1, 27, "%6d", score);	
	}
	else {
		mvwprintw(top_w, 1, 27, "[training mode]", score);	
	}
	mvwprintw(top_w, 2, 27, "%3d", speed);	
	mvwprintw(top_w, 2, 35, "%3d", speed/5);	
	mvwprintw(top_w, 2, 49, "%3d", maxspeed);	
	mvwprintw(top_w, 2, 54, "%3d", maxspeed/5);	
	wrefresh(top_w);
	return 0;
}

/* display the correct callsign and what the user entered, with mistakes
 * highlighted. */
static int show_error (char * realcall, char * wrongcall) {
	int x=2;
	int y = errornr;
	int i;

	/* Screen is full of errors. Remove them and start at the beginning */
	if (errornr == 31) {	
		for (i=1;i<16;i++) {
			mvwaddstr(mid_w,i,2,"                                        "
							 "          ");
		}
		errornr = y = 1;
	}

	/* Move to second column after 15 errors */	
	if (errornr > 15) {
		x=30; y = (errornr % 16)+1;
	}

	mvwprintw(mid_w,y,x, "%-13s %-13s", realcall, wrongcall);
	wrefresh(mid_w);		
	return 0;
}

/* clear error display */
static int clear_display() {
	int i;
	for (i=1;i<16;i++) {
		mvwprintw(mid_w,i,1,"                                 "
										"                        ");
	}
	return 0;
}

/* clear parameter display */
static int clear_parameter_display() {
	int i;
	for (i=1;i<16;i++) {
		mvwprintw(conf_w,i,1,"                                 "
										"                        ");
	}
	return 0;
}


/* write entry into toplist at the right place 
 * going down from the top of the list until the score in the current line is
 * lower than the score made. then */

static int add_to_toplist(char * mycall, int score, int maxspeed) {
	FILE *fh;	
	char *part1, *part2;
	char tmp[35]="";
	char insertline[35]="DJ1YFK     36666 333 1111111111";		/* example */
						/* call       pts   max timestamp */
	int i=0, k=0, j=0;
	int pos = 0;		/* position where first score < our score appears */
	int timestamp = 0;
	int len = 32;		/* length of a line. 32 for unix, 33 for Win */

	/* For the training modes */
	if (score == 0) {
		return 0;
	}

	timestamp = (int) time(NULL);
	sprintf(insertline, "%-10s%6d %3d %10d", mycall, score, maxspeed, timestamp);
	
	if ((fh = fopen(tlfilename, "rb+")) == NULL) {
		printf("Unable to open toplist file %s!\n", tlfilename);
		exit(EXIT_FAILURE);
	}

	/* find out if we use CRLF or just LF */
	fgets(tmp, 35, fh);
	if (tmp[31] == '\r') {	/* CRLF */
		len = 33;
		strcat(insertline, "\r\n");
	}
	else {
		len = 32;
		strcat(insertline, "\n");
	}

	fseek(fh, 0, SEEK_END);
	j = ftell(fh);

	part1 = malloc((size_t) j);
	part2 = malloc((size_t) j + len);	/* one additional entry */

	rewind(fh);

	/* read whole toplist */
	fread(part1, sizeof(char), (size_t) j, fh);

	/* find first score below "score"; scores at positions 10 + (i*len) */

	do {
		for (i = 0 ; i < 6 ; i++) {	
			tmp[i] = part1[i + (10 + pos*len)];
		}
		k = atoi(tmp);
		pos++;
	} while (score < k);
 
	/* Found it! Insert own score here! */
	memcpy(part2, part1, len * (pos-1));
	memcpy(part2 + len * (pos - 1), insertline, len);
	memcpy(part2 + len * pos , part1 + len * (pos -1), j - len * (pos - 1));

	rewind(fh);
	fwrite(part2, sizeof(char), (size_t) j + len, fh);
	fclose(fh);

	free(part1);
	free(part2);

	return 0;

}


/* Read config file 
 *
 * TODO contains too much copypasta. write proper function to parse a key=value
 *
 * */

static int read_config () {
	FILE *fh;
	char tmp[80]="";
	int i=0;
	int k=0;
	int line=0;

	if ((fh = fopen(rcfilename, "r")) == NULL) {
		endwin();
		fprintf(stderr, "Unable to open config file %s!\n", rcfilename);
		exit(EXIT_FAILURE);
	}

	while ((feof(fh) == 0) && (fgets(tmp, 80, fh) != NULL)) {
		i=0;
		line++;
		tmp[strlen(tmp)-1]='\0';

		/* find callsign, speed etc. 
		 * only allow if the lines are beginning at zero, so stuff can be
		 * commented out easily; return value if strstr must point to tmp*/
		if(tmp == strstr(tmp,"callsign=")) {
			while (isalnum(tmp[i] = toupper(tmp[9+i]))) {
				i++;
			}
			tmp[i]='\0';
			if (strlen(tmp) < 8) {				/* empty call allowed */
				strcpy(mycall,tmp);
				printw("  line  %2d: callsign: >%s<\n", line, mycall);
			}
			else {
				printw("  line  %2d: callsign: >%s< too long. "
								"Using default >%s<.\n", line, tmp, mycall);
			}
		}
		else if (tmp == strstr(tmp,"initialspeed=")) {
			while (isdigit(tmp[i] = tmp[13+i])) {
				i++;
			}
			tmp[i]='\0';
			i = atoi(tmp);
			if (i > 9) {
				initialspeed = speed = i;
				printw("  line  %2d: initial speed: %d\n", line, initialspeed);
			}
			else {
				printw("  line  %2d: initial speed: %d invalid (range: 10..oo)."
								" Using default %d.\n",line,  i, initialspeed);
			}
		}
		else if (tmp == strstr(tmp,"mincharspeed=")) {
			while (isdigit(tmp[i] = tmp[13+i])) {
				i++;
			}
			tmp[i]='\0';
			if ((i = atoi(tmp)) > 0) {
				mincharspeed = i;
				printw("  line  %2d: min.char.speed: %d\n", line, mincharspeed);
			} /* else ignore */
		}
		else if (tmp == strstr(tmp,"dspdevice=")) {
			while (isgraph(tmp[i] = tmp[10+i])) {
				i++;
			}
			tmp[i]='\0';
			if (strlen(tmp) > 1) {
				strcpy(dspdevice,tmp);
				printw("  line  %2d: dspdevice: >%s<\n", line, dspdevice);
			}
			else {
				printw("  line  %2d: dspdevice: >%s< invalid. "
								"Using default >%s<.\n", line, tmp, dspdevice);
			}
		}
		else if (tmp == strstr(tmp, "risetime=")) {
			while (isdigit(tmp[i] = tmp[9+i]) || ((tmp[i] = tmp[9+i])) == '.') {
				i++;	
			}
			tmp[i]='\0';
			edge = atof(tmp);
			printw("  line  %2d: risetime: %f\n", line, edge);
		}
		else if (tmp == strstr(tmp, "waveform=")) {
			if (isdigit(tmp[i] = tmp[9+i])) {	/* read 1 char only */
				tmp[++i]='\0';
				waveform = atoi(tmp);
			}
			if ((waveform <= 3) && (waveform > 0)) {
				printw("  line  %2d: waveform: %d\n", line, waveform);
			}
			else {
				printw("  line  %2d: waveform: %d invalid. Using default.\n",
						 line, waveform);
				waveform = SINE;
			}
		}
		else if (tmp == strstr(tmp, "constanttone=")) {
			while (isdigit(tmp[i] = tmp[13+i])) {
				i++;    
			}
			tmp[i]='\0';
			k = 0; 
			k = atoi(tmp); 							/* constanttone */
			if ( (k*k) > 1) {
				printw("  line  %2d: constanttone: %s invalid. "
							"Using default %d.\n", line, tmp, constanttone);
			}
			else {
				constanttone = k ;
				printw("  line  %2d: constanttone: %d\n", line, constanttone);
			}
        }
        else if (tmp == strstr(tmp, "ctonefreq=")) {
			while (isdigit(tmp[i] = tmp[10+i])) {
            	i++;    
			}
			tmp[i]='\0';
			k = 0; 
			k = atoi(tmp);							/* ctonefreq */
			if ( (k > 1600) || (k < 100) ) {
				printw("  line  %2d: ctonefreq: %s invalid. "
					"Using default %d.\n", line, tmp, ctonefreq);
			}
			else {
				ctonefreq = k ;
				printw("  line  %2d: ctonefreq: %d\n", line, ctonefreq);
			}
		}
		else if (tmp == strstr(tmp, "f6=")) {
			f6=0;
			if (tmp[3] == '1') {
				f6 = 1;
			}
			printw("  line  %2d: unlimited f6: %s\n", line, (f6 ? "yes":"no"));
        }
		else if (tmp == strstr(tmp, "fixspeed=")) {
			fixspeed=0;
			if (tmp[9] == '1') {
				fixspeed = 1;
			}
			printw("  line  %2d: fixed speed:  %s\n", line, (fixspeed ? "yes":"no"));
        }
		else if (tmp == strstr(tmp, "unlimitedattempt=")) {
			unlimitedattempt=0;
			if (tmp[17] == '1') {
				unlimitedattempt= 1;
			}
			printw("  line  %2d: unlim. att.:  %s\n", line, (unlimitedattempt ? "yes":"no"));
        }
		else if (tmp == strstr(tmp,"callbase=")) {
			while (isgraph(tmp[i] = tmp[9+i])) {
				i++;
			}
			tmp[i]='\0';
			if (strlen(tmp) > 1) {
				strcpy(cbfilename,tmp);
				printw("  line  %2d: callbase:  >%s<\n", line, cbfilename);
			}
			else {
				printw("  line  %2d: callbase:  >%s< invalid. "
								"Using default >%s<.\n", line, tmp, cbfilename);
			}
		}
		else if (tmp == strstr(tmp,"samplerate=")) {
			while (isdigit(tmp[i] = tmp[11+i])) {
				i++;
			}
			tmp[i]='\0';
			samplerate = atoi(tmp);
			printw("  line  %2d: sample rate: %d\n", line, samplerate);
		}
	}

	fclose(fh);

	printw("Finished reading qrqrc.\n");
	return 0;
}


static void *morse(void *arg) { 
	char * text = arg;
	int i,j;
	int c, fulldotlen, dotlen, dashlen, charspeed, farnsworth, fwdotlen;
	const char *code;

#if WIN32 /* WinMM simple support by Lukasz Komsta, SP8QED */
	HWAVEOUT		h;
	WAVEFORMATEX	wf;
	WAVEHDR			wh;
	HANDLE			d;

	wf.wFormatTag = WAVE_FORMAT_PCM;
	wf.nChannels = 1;
	wf.wBitsPerSample = 16;
	wf.nSamplesPerSec = samplerate * 2;
	wf.nBlockAlign = wf.nChannels * wf.wBitsPerSample / 8;
	wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
	wf.cbSize = 0;
	d = CreateEvent(0, FALSE, FALSE, 0);
	if(waveOutOpen(&h, 0, &wf, (DWORD) d, 0, CALLBACK_EVENT) != MMSYSERR_NOERROR);

#else
	/* opening the DSP device */
	dsp_fd = open_dsp(dspdevice);
#endif
	/* set bufpos to 0 */

	full_bufpos = 0; 

	/* Some silence; otherwise the call starts right after pressing enter */
	tonegen(0, samplerate/4, SILENCE);

	/* Farnsworth? */
	if (speed < mincharspeed) {
			charspeed = mincharspeed;
			farnsworth = 1;
			fwdotlen = (int) (samplerate * 6/speed);
	}
	else {
		charspeed = speed;
		farnsworth = 0;
	}

	/* speed is in LpM now, so we have to calculate the dot-length in
	 * milliseconds using the well-known formula  dotlength= 60/(wpm*50) 
	 * and then to samples */

	dotlen = (int) (samplerate * 6/charspeed);
	fulldotlen = dotlen;
	dashlen = 3*dotlen;

	/* edge = length of rise/fall time in ms. ed = in samples */

	ed = (int) (samplerate * (edge/1000.0));

	/* the signal needs "ed" samples to reach the full amplitude and
	 * at the end another "ed" samples to reach zero. The dots and
	 * dashes therefore are becoming longer by "ed" and the pauses
	 * after them are shortened accordingly by "ed" samples */

	for (i = 0; i < strlen(text); i++) {
		c = text[i];
		if (isalpha(c)) {
			code = codetable[c-65];
		}
		else if (isdigit(c)) {
			code = codetable[c-22];
		}
		else if (c == '/') { 
			code = "-..-.";
		}
		else if (c == '+') {
			code = ".-.-.";
		}
        else if (c == ' ') {        /* space */
            code = " ";
        }
		else {						/* not supposed to happen! */
			code = "..--..";
		}
		
		/* code is now available as string with - and . */

		for (j = 0; j < strlen(code) ; j++) {
			c = code[j];
			if (c == '.') {
				tonegen(freq, dotlen + ed, waveform);
				tonegen(0, fulldotlen - ed, SILENCE);
			}
			else if (c == '-') {
				tonegen(freq, dashlen + ed, waveform);
				tonegen(0, fulldotlen - ed, SILENCE);
			}
            else {  /* space */
				tonegen(0, dotlen, SILENCE);
            }
		}
		if (farnsworth) {
			tonegen(0, 3*fwdotlen - fulldotlen, SILENCE);
		}
		else {
			tonegen(0, 2*fulldotlen, SILENCE);
		}
	}


#if !defined(PA) && !defined(CA)
	add_to_buf(buffer, 88200);
#endif

#if WIN32
	wh.lpData = (char*) &full_buf[0];
	wh.dwBufferLength = full_bufpos - 2;
	wh.dwFlags = 0;
	wh.dwLoops = 0;
	waveOutPrepareHeader(h, &wh, sizeof(wh));
	ResetEvent(d);
	waveOutWrite(h, &wh, sizeof(wh));
	if(WaitForSingleObject(d, INFINITE) != WAIT_OBJECT_0);
	waveOutUnprepareHeader(h, &wh, sizeof(wh));
	waveOutClose(h);
	CloseHandle(d);
#else
	write_audio(dsp_fd, &full_buf[0], full_bufpos);
	close_audio(dsp_fd);
#endif
	sending_complete = 1;
	return NULL;
}

static int add_to_buf(void* data, int size)
{
	memcpy(&full_buf[full_bufpos / sizeof(int)], data, size);
	full_bufpos += size;
	return 0;
}	

/* tonegen generates a sinus tone of frequency 'freq' and length 'len' (samples)
 * based on 'samplerate', 'edge' (rise/falltime) */

static int tonegen (int freq, int len, int waveform) {
	int x=0;
	int out;
	double val=0;

	for (x=0; x < len-1; x++) {
		switch (waveform) {
			case SINE:
				val = sin(2*PI*freq*x/samplerate);
				break;
			case SAWTOOTH:
				val=((1.0*freq*x/samplerate)-floor(1.0*freq*x/samplerate))-0.5;
				break;
			case SQUARE:
				val = ceil(sin(2*PI*freq*x/samplerate))-0.5;
				break;
			case SILENCE:
				val = 0;
		}


		if (x < ed) { val *= pow(sin(PI*x/(2.0*ed)),2); }	/* rising edge */

		if (x > (len-ed)) {								/* falling edge */
				val *= pow(sin(2*PI*(x-(len-ed)+ed)/(4*ed)),2); 
		}
		
		out = (int) (val * 32500.0);
#ifndef PA
		out = out + (out<<16);	/* stereo only for OSS & CoreAudio*/
#endif
		add_to_buf(&out, sizeof(out));
	}
	return 0;
}

/* Save config file
 *
 * Tries to keep the old format (including comments, etc.) and adds
 * config options that were not used yet in the file to the end 
 * */

static int save_config () {
	FILE *fh;
	char tmp[4096]="";
	char confopts[12][80] = {
		"\ncallsign=", 
		"\ncallbase=",
		"\ndspdevice=", 
		"\ninitialspeed=", 
		"\nmincharspeed=",
		"\nwaveform=", 
		"\nconstanttone=",
		"\nctonefreq=", 
		"\nfixspeed=", 
		"\nunlimitedattempt=", 
		"\nf6=", 
		"\nrisetime=" 
	};
	char *conf1;
	char *conf2;
	char *find, *findend;
	int i, len, conf1len, conf2len;
	int j;

	conf2 = malloc(1);

	if ((fh = fopen(rcfilename, "rb")) == NULL) {
		endwin();
		fprintf(stderr, "Unable to open config file '%s'!\n", rcfilename);
		exit(EXIT_FAILURE);
	}

	fseek(fh, 0, SEEK_END);
	j = (int) ftell(fh);
	conf1 = malloc((size_t) j+1);

	rewind(fh);
	i = fread(conf1, sizeof(char), (size_t) j, fh);
	conf1[j] = '\0';
	conf1len = j;

	fclose(fh);

	/* The whole config file is now in conf1 
	 *
	 * For each config option, search&replace it with the current value.
	 * Only accept key=value pairs if the key starts on pos 0 of the line
	 * */

	//endwin();
	for (i = 0; i < 12; i++) {
		/* assemble new string for this conf option*/
		switch (i) {
			case 0:
				sprintf(tmp, "%s%s ", confopts[i], mycall);
				break;
			case 1:
				sprintf(tmp, "%s%s ", confopts[i], cbfilename);
				break;
			case 2:
				sprintf(tmp, "%s%s ", confopts[i], dspdevice);
				break;
			case 3:
				sprintf(tmp, "%s%d ", confopts[i], initialspeed);
				break;
			case 4:
				sprintf(tmp, "%s%d ", confopts[i], mincharspeed);
				break;
			case 5:
				sprintf(tmp, "%s%d ", confopts[i], waveform);
				break;
			case 6:
				sprintf(tmp, "%s%d ", confopts[i], constanttone);
				break;
			case 7:
				sprintf(tmp, "%s%d ", confopts[i], ctonefreq);
				break;
			case 8:
				sprintf(tmp, "%s%d ", confopts[i], fixspeed);
				break;
			case 9:
				sprintf(tmp, "%s%d ", confopts[i], unlimitedattempt);
				break;
			case 10:
				sprintf(tmp, "%s%d ", confopts[i], f6);
				break;
			case 11:
				sprintf(tmp, "%s%f ", confopts[i], edge);
				break;
		}	

		/* Conf option already in rc-file? */
		if ((find = strstr(conf1, confopts[i])) != NULL) {
			/* determine length. */
			findend = find;
			findend++;	/* starts with \n, always skip it */
			while (!isspace(*findend++));
			len = findend - find;
			
			/* old size of conf1: conf1len (see above) 
			 * new size: conf1len - len + strlen(tmp)*/
			conf2len = conf1len - len + strlen(tmp);
			conf2 = realloc(conf2, (size_t) conf2len);
			memcpy(conf2, conf1, (find - conf1));
			memcpy(conf2 + (find - conf1), tmp, strlen(tmp));
			memcpy(conf2 + (find - conf1) + strlen(tmp), findend, 
					(size_t) conf2len - (find - conf1) - strlen(tmp));
		}
		/* otherwise, add to the end */
		else {
			/* CR LF or LF only? */
			if (strstr(conf1, "\r")) {
				strcat(tmp, "\r");
			}
			strcat(tmp, "\n");
			conf2len = conf1len + strlen(tmp) - 1;
			conf2 = realloc(conf2, conf2len);
			memcpy(conf2, conf1, conf1len-1);  // excl. \0
			memcpy(conf2 + conf1len - 1, tmp, strlen(tmp)); // (incl. \0)
		}
		conf1 = realloc(conf1, (size_t) conf2len);
		conf1len = conf2len;
		if (conf1 == NULL) {
			exit(0);
		}
		memcpy(conf1, conf2, conf1len);
	}

	if ((fh = fopen(rcfilename, "wb")) == NULL) {
		endwin();
		fprintf(stderr, "Unable to open config file '%s'!\n", rcfilename);
		exit(EXIT_FAILURE);
	}

	fwrite(conf1, conf1len, sizeof(char), fh); 
	fclose(fh);

	free(conf1);
	free(conf2);

	return 0;
}
		
static void thread_fail (int j) {
	if (j) {
		endwin();
		perror("Error: Unable to create cwthread!\n");
		exit(EXIT_FAILURE);
	}
}

/* Add timestamps to toplist file if not there yet */
static int check_toplist () {
	char line[80]="";
	char tmp[80]="";
	FILE *fh;
	FILE *fh2;

	if ((fh = fopen(tlfilename, "r+")) == NULL) {
		endwin();
		perror("Unable to open toplist file 'toplist'!\n");
		exit(EXIT_FAILURE);
	}

	fgets(tmp, 35, fh);
	
	rewind(fh);
	
	if (strlen(tmp) == 21) {
			printw("Toplist file in old format. Converting...");
			strcpy(tmp, "cp -f ");
			strcat(tmp, tlfilename);
			strcat(tmp, " /tmp/qrq-toplist");
			if (system(tmp)) {
					printw("Failed to copy to /tmp/qrq-toplist\n");
					getch();
					endwin();
					exit(EXIT_FAILURE);
			}

			fh2 = fopen("/tmp/qrq-toplist", "r+"); 		/* should work ... */

			while ((feof(fh2) == 0) && (fgets(line, 35, fh2) != NULL)) {
					line[20]=' ';
					strcpy(tmp, line);
					strcat(tmp, "1181234567\n");
					fputs(tmp, fh);
			}
			
			printw(" done!\n");
	
			fclose(fh2);

	}

	fclose(fh);

	return 0;
}



/* See where our files are. We need 'callbase.qcb', 'qrqrc' and 'toplist'.
 * The can be: 
 * 1) In the current directory -> use them
 * 2) In ~/.qrq/  -> use toplist and qrqrc from there and callbase from
 *    DESTDIR/share/qrq/
 * 3) in DESTDIR/share/qrq/ -> create ~/.qrq/ and copy qrqrc and toplist
 *    there.
 * 4) Nowhere --> Exit.*/
static int find_files () {
	
	FILE *fh;
	const char *homedir = NULL;
	char tmp_rcfilename[PATH_MAX] = "";
	char tmp_tlfilename[PATH_MAX] = "";
	char tmp_cbfilename[PATH_MAX] = "";

	printw("\nChecking for necessary files (qrqrc, toplist, callbase)...\n");
	
	if (((fh = fopen("qrqrc", "r")) == NULL) ||
		((fh = fopen("toplist", "r")) == NULL) ||
		((fh = fopen("callbase.qcb", "r")) == NULL)) {
		
		if ((homedir = getenv("HOME")) != NULL) {
    		printw("... not found in current directory. Checking %s/.qrq/...\n", homedir);
    		refresh();
	    	strcat(rcfilename, homedir);
		}
		else {
		    printw("... not found in current directory. Checking ./.qrq/...\n");
    		refresh();
	    	strcat(rcfilename, ".");
		}
				
		strcat(rcfilename, "/.qrq/qrqrc");
	
		/* check if there is ~/.qrq/qrqrc. If it's there, it's safe to assume
		 * that toplist also exists at the same place and callbase exists in
		 * DESTDIR/share/qrq/. */

		if ((fh = fopen(rcfilename, "r")) == NULL ) {
			printw("... not found in %s/.qrq/. Checking %s/share/qrq..."
							"\n", homedir, destdir);
			/* check for the files in DESTDIR/share/qrq/. if exists, copy 
			 * qrqrc and toplist to ~/.qrq/  */

			strcpy(tmp_rcfilename, destdir);
			strcat(tmp_rcfilename, "/share/qrq/qrqrc");
			strcpy(tmp_tlfilename, destdir);
			strcat(tmp_tlfilename, "/share/qrq/toplist");
			strcpy(tmp_cbfilename, destdir);
			strcat(tmp_cbfilename, "/share/qrq/callbase.qcb");

			if (((fh = fopen(tmp_rcfilename, "r")) == NULL) ||
				((fh = fopen(tmp_tlfilename, "r")) == NULL) ||
				 ((fh = fopen(tmp_cbfilename, "r")) == NULL)) {
				printw("Sorry: Couldn't find 'qrqrc', 'toplist' and"
			   			" 'callbase.qcb' anywhere. Exit.\n");
				getch();
				endwin();
				exit(EXIT_FAILURE);
			}
			else {			/* finally found it in DESTDIR/share/qrq/ ! */
				/* abusing rcfilename here for something else temporarily */
				printw("Found files in %s/share/qrq/."
						"\nCreating directory %s/.qrq/ and copy qrqrc and"
						" toplist there.\n", destdir, homedir);
				strcpy(rcfilename, homedir);
				strcat(rcfilename, "/.qrq/");
#ifdef WIN32
				j = mkdir(rcfilename);
#else
				j = mkdir(rcfilename,  0777);
#endif
				if (j && (errno != EEXIST)) {
					printw("Failed to create %s! Exit.\n", rcfilename);
					getch();
					endwin();
					exit(EXIT_FAILURE);
				}

				/* OK, now we created the directory, we can read in
				 * DESTDIR/local/, so I assume copying files won't cause any
				 * problem, with system()... */

				strcpy(rcfilename, "install -m 644 ");
				strcat(rcfilename, tmp_tlfilename);
				strcat(rcfilename, " ");
				strcat(rcfilename, homedir);
				strcat(rcfilename, "/.qrq/ 2> /dev/null");
				if (system(rcfilename)) {
					printw("Failed to copy toplist file: %s\n", rcfilename);
					getch();
					endwin();
					exit(EXIT_FAILURE);
				}
				strcpy(rcfilename, "install -m 644 ");
				strcat(rcfilename, tmp_rcfilename);
				strcat(rcfilename, " ");
				strcat(rcfilename, homedir);
				strcat(rcfilename, "/.qrq/ 2> /dev/null");
				if (system(rcfilename)) {
					printw("Failed to copy qrqrc file: %s\n", rcfilename);
					getch();
					endwin();
					exit(EXIT_FAILURE);
				}
				printw("Files copied. You might want to edit "
						"qrqrc according to your needs.\n", homedir);
				strcpy(rcfilename, homedir);
				strcat(rcfilename, "/.qrq/qrqrc");
				strcpy(tlfilename, homedir);
				strcat(tlfilename, "/.qrq/toplist");
				strcpy(cbfilename, tmp_cbfilename);
                strcpy(sumfilepath, homedir);
                strcat(sumfilepath, "/.qrq/Summary");
			} /* found in DESTDIR/share/qrq/ */
		}
		else {
			printw("... found files in %s/.qrq/.\n", homedir);
			strcpy(tlfilename, homedir);
			strcat(tlfilename, "/.qrq/toplist");
			strcpy(cbfilename, destdir);
			strcat(cbfilename, "/share/qrq/callbase.qcb");
            strcpy(sumfilepath, homedir);
            strcat(sumfilepath, "/.qrq/Summary");
		}
	}
	else {
		printw("... found in current directory.\n");
		strcpy(rcfilename, "qrqrc");
		strcpy(tlfilename, "toplist");
		strcpy(cbfilename, "callbase.qcb");
        strcpy(sumfilepath, "Summary");
	}
#ifdef WIN32
    mkdir(sumfilepath);
#else
    mkdir(sumfilepath, 0777);
#endif
	refresh();
	fclose(fh);
	return 0;
}


static int statistics () {
		char line[80]="";

		int time = 0;
		int score = 0;
		int count= 0;

		FILE *fh;
		FILE *fh2;
		
		if ((fh = fopen(tlfilename, "r")) == NULL) {
				fprintf(stderr, "Unable to open toplist.");
				exit(0);
		}
		
		if ((fh2 = fopen("/tmp/qrq-plot", "w+")) == NULL) {
				fprintf(stderr, "Unable to open /tmp/qrq-plot.");
				exit(0);
		}

		fprintf(fh2, "set yrange [0:]\nset xlabel \"Date/Time\"\n"
					"set title \"QRQ scores for %s. Press 'q' to "
					"close this window.\"\n"
					"set ylabel \"Score\"\nset xdata time\nset "
					" timefmt \"%%s\"\n "
					"plot \"-\" using 1:2 title \"\"\n", mycall);

		while ((feof(fh) == 0) && (fgets(line, 80, fh) != NULL)) {
				if ((strstr(line, mycall) != NULL)) {
					count++;
					sscanf(line, "%*s %d %*d %d", &score, &time);
					fprintf(fh2, "%d %d\n", time, score);
				}
		}

		if (!count) {
			fprintf(fh2, "0 0\n");
		}
		
		fprintf(fh2, "end\npause 10000");

		fclose(fh);
		fclose(fh2);

		system("gnuplot -p /tmp/qrq-plot 2> /dev/null &");
	return 0;
}


int read_callbase () {
	FILE *fh;
	int c,i;
	int maxlen=0;
	char tmp[80] = "";
	int nr=0;

	if ((fh = fopen(cbfilename, "r")) == NULL) {
		endwin();
		fprintf(stderr, "Error: Couldn't read callsign database ('%s')!\n",
						cbfilename);
		exit(EXIT_FAILURE);
	}

	/* count the lines/calls and lengths */
	i=0;
	while ((c = getc(fh)) != EOF) {
		i++;
		if (c == '\n') {
			nr++;
			maxlen = (i > maxlen) ? i : maxlen;
			i = 0;
		}
	}
	maxlen++;

	if (!nr) {
		endwin();
		printf("\nError: Callsign database empty, no calls read. Exiting.\n");
		exit(EXIT_FAILURE);
	}

	/* allocate memory for calls array, free if needed */
    if (calls) {
        for (i = 0; i < nrofcalls; i++) {
            free(calls[i]);
            calls[i] = NULL;
        }
        free(calls);
        calls = NULL;
    }

	if ((calls = (char **) malloc( (size_t) sizeof(char *)*nr )) == NULL) {
		fprintf(stderr, "Error: Couldn't allocate %d bytes!\n", 
						(int) sizeof(char)*nr);
		exit(EXIT_FAILURE);
	}
	
	/* Allocate each element of the array with size maxlen */
	for (c=0; c < nr; c++) {
		if ((calls[c] = (char *) malloc (maxlen * sizeof(char))) == NULL) {
			fprintf(stderr, "Error: Couldn't allocate %d bytes!\n", maxlen);
			exit(EXIT_FAILURE);
		}
	}

	rewind(fh);
	
	nr=0;
	while (fgets(tmp,maxlen,fh) != NULL) {
		for (i = 0; i < strlen(tmp); i++) {
				tmp[i] = toupper(tmp[i]);
		}
		tmp[i-1]='\0';				/* remove newline */
		if (tmp[i-2] == '\r') {		/* also for DOS files */
			tmp[i-2] = '\0';
		}
		strcpy(calls[nr],tmp);
		nr++;
		if (nr == c) 			/* may happen if call file corrupted */
				break;
	}
	fclose(fh);


	return nr;

}

void find_callbases () {
	DIR *dir;
	struct dirent *dp;
	char tmp[PATH_MAX];
	char path[3][PATH_MAX];
	int i=0,j=0,k=0;

#ifndef WIN32
		strcpy(path[0], getenv("PWD"));
		strcat(path[0], "/");
		strcpy(path[1], getenv("HOME"));
		strcat(path[1], "/.qrq/");
		strcpy(path[2], destdir);
		strcat(path[2], "/share/qrq/");
#else
		strcpy(path[0], "./");
		strcpy(path[1], getenv("APPDATA"));
		strcat(path[1], "/qrq/");
		strcpy(path[2], "c:\\");
#endif

	for (i=0; i < 100; i++) {
		strcpy(cblist[i], "");
	}

	/* foreach paths...  */
	for (k = 0; k < 3; k++) {

		if (!(dir = opendir(path[k]))) {
			continue;
		}
	
		while ((dp = readdir(dir))) {
			strcpy(tmp, dp->d_name);
			i = strlen(tmp);
			/* find *.qcb files ...  */
			if (i>4 && tmp[i-1] == 'b' && tmp[i-2] == 'c' && tmp[i-3] == 'q') {
				strcpy(cblist[j], path[k]);
				strcat(cblist[j], tmp);
				j++;
			}
		}
	} /* for paths */
}



void select_callbase () {
	int i = 0, j = 0, k = 0;
	int c = 0;		/* cursor position   */
	int p = 0;		/* page a 10 entries */
	char* cblist_ptr;


	curs_set(FALSE);

	/* count files */
	while (strcmp(cblist[i], "")) i++;

	if (!i) {
		mvwprintw(conf_w,10,4, "No qcb-files found!");
		wrefresh(conf_w);
#ifdef WIN32
		Sleep(1000);
#else
		sleep(1);
#endif
		return;
	}

	/* loop for key unput */
	while (1) {

	/* cls */
	for (j = 5; j < 16; j++) {
			mvwprintw(conf_w,j,2, "                                         ");
	}

	/* display 10 files, highlight cursor position */
	for (j = p*10; j < (p+1)*10; j++) {
		if (j <= i) {
				cblist_ptr = cblist[j];
				mvwprintw(conf_w,5+(j - p*10 ),2, "  %s       ", cblist_ptr);
		}
		if (c == j) {						/* cursor */
			mvwprintw(conf_w,5+(j - p*10),2, ">");
		}
	}
	
	wrefresh(conf_w);

	k = getch();

	switch ((int) k) {
		case KEY_UP:
			c = (c > 0) ? (c-1) : c;
			if (!((c+1) % 10)) {	/* scroll down */
				p = (p > 0) ? (p-1) : p;
			}
			break;
		case KEY_DOWN:
			c = (c < i-1) ? (c+1) : c;
			if (c && !(c % 10)) {	/* scroll down */
				p++;
			}
			break;
		case '\n':
			strcpy(cbfilename, cblist[c]);
			nrofcalls = read_callbase();
			return;	
			break;
	}

	wrefresh(conf_w);

	} /* while 1 */

	curs_set(TRUE);

}



void help () {
		printf("qrq v%s  (c) 2006-2019 Fabian Kurz, DJ1YFK. "
					"http://fkurz.net/ham/qrq.html\n", VERSION);
		printf("High speed morse telegraphy trainer, similar to"
					" RUFZ.\n\n");
		printf("This is free software, and you are welcome to" 
						" redistribute it\n");
		printf("under certain conditions (see COPYING).\n\n");
		printf("Start 'qrq' without any command line arguments for normal"
					" operation.\n\n");
#ifdef BUILD_INFO
        printf("Build info for this executable:\n%s\n", BUILD_INFO);
#endif
		exit(0);
}


/* vim: noai:ts=4:sw=4 
*/
