/*
**	flrec (c) 2004 Matteo Lucarelli <matteo@matteolucarelli.net>
**	See the file LICENSE included in this package for license terms.
**
**	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:
**
**		Free Software Foundation, Inc.
**		59 Temple Place - Suite 330
**		Boston, MA 02111-1307
**		USA
*/

#define FLREC_SETTINGS_HELP "This seems to be the first time you start flrec.\nPlease check settings!"

#include <getopt.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libgen.h>
#include <unistd.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdlib.h>

#include "flrecUImain.h"
#include "flrecUIsettings.h"
#include "flrecUIeffects.h"

/////////////////////////////////////////////////////////////////////////////////////////////////////
// define and constant

#define FLRECVERSION "0.13"
#define MAX_SOX_ARG 128
#define MAX_ARG_DIM 1024

const int f_val_count=6;
const char* f_val_name[]={"mp3","wav","vorbis","au","raw","cdr"}; // how sox calls the formats
const char* f_val_ext[]={"mp3","wav","ogg","au","raw","cda"};	  // extentions (in lower case)

const int m_val_count=3;
const char* m_val_num[]={"1","2","4"};
const char* m_val_str[]={"mono","stereo","4 channels"};

const int ds_val_count=4;
const char* ds_val[]={"-b","-w","-l","-d"};
const char* ds_val_str[]={"8-bit","16-bit","32-bit","64-bit"};

const int s_val_count=10;
const char* s_val_h[]={"8000","11025","16000","18900","22050","32000","37800","44100","48000","96000"};
const char* s_val_k[]={"8.","11.","16.","18.9","22.05","32.","37.8","44.1","48.","96."};

flrecUImain *UImain;
flrecUIsettings *UIsettings;
flrecUIeffects *UIeffects;

pid_t pid_mixer=0;
pid_t pid_sox=0;
bool quietmode=true;

/////////////////////////////////////////////////////////////////////////////////////////////////////
// service functions

// onmain: true=UImain,false=UIeffects
void adapt_menus_to_file(Fl_File_Input *f_file,Fl_Choice *mn_sample,Fl_Choice *mn_mode,Fl_Choice *mn_format)
{
	bool found;
	int i,ret;
	char cmd[1024];
	unsigned int j;
	const char* ext;
	char ext_tl[16];
	struct stat buf;

	found=false;
	for (i=0;i<(mn_format->size()-1);i++){
		if (strlen(f_file->value())>=strlen(mn_format->text(i))){
			ext=f_file->value()+strlen(f_file->value())-strlen(mn_format->text(i));
			strncpy(ext_tl,ext,16);

			// tolower ext_tl
			for (j=0;j<strlen(ext_tl);j++) *(ext_tl+j)=tolower(*(ext_tl+j));

			if (!strcmp(mn_format->text(i),ext_tl)){
				mn_format->value(i);
				found=true;
				break;
			}
		}
	}

	if (!found){
		mn_format->color(FL_RED);
		mn_format->redraw();
	}else{
		mn_format->color(FL_WHITE);
		mn_format->redraw();
	}

	// if file exisists and is a regular file
	if (!((stat(f_file->value(),&buf)==0)&&(S_ISREG(buf.st_mode)))) return;

	snprintf(cmd,1024,"file \"%s\" > /tmp/flrec.file",f_file->value());
	system(cmd);

	// sample rate
	found=false;
	for (i=(s_val_count-1);i>=0;i--){
		ret=system("grep -i khz /tmp/flrec.file &>/dev/null");

		if (ret==0) snprintf(cmd,1024,"grep -i \"%s\" /tmp/flrec.file &>/dev/null",s_val_k[i]);
		else snprintf(cmd,1024,"grep -i %s /tmp/flrec.file &>/dev/null",s_val_h[i]);

		ret=system(cmd);
		if (ret==0){
			mn_sample->value(i);
			found=true;
			break;
		}
	}

	if (!found){
		mn_sample->color(FL_RED);
		mn_sample->redraw();
	}else{
		mn_sample->color(FL_WHITE);
		mn_sample->redraw();
	}

	// mode
	found=false;
	for (i=0;i<m_val_count;i++){
		snprintf(cmd,1024,"grep -i \"%s\" /tmp/flrec.file &>/dev/null",m_val_str[i]);
		ret=system(cmd);
		if (ret==0){
			mn_mode->value(i);
			found=true;
			break;
		}
	}
	if (!found){
		mn_mode->color(FL_RED);
		mn_mode->redraw();
	}else{
		mn_mode->color(FL_WHITE);
		mn_mode->redraw();
	}

	system("rm /tmp/flrec.file");
}

// parse "sox -h" output for formats support
// (unsupported are removed from menus)
void adapt_menus_to_sox_h()
{
	int i,ret;
	char cmd[128];

	system("sox -h &> /tmp/flrec.soxh");

	for(i=0;i<f_val_count;i++){
		snprintf(cmd,128,"grep %s /tmp/flrec.soxh &> /dev/null",f_val_name[i]);
		ret=system(cmd);

		if (ret!=0){
			UImain->mn_format->mode(i,FL_MENU_INACTIVE);
			UIeffects->mn_format->mode(i,FL_MENU_INACTIVE);
			if (!quietmode) printf("flrec: %s unsupported\n",f_val_name[i]);
		}
		else if (!quietmode) printf("flrec: %s supported\n",f_val_name[i]);
	}

	system("rm /tmp/flrec.soxh");
}

// parse volume level
// from sox stat output
float read_sox_stat_vol(const char* file)
{
	float ret=0;
	char tmp[1024];
	FILE* fp;

	snprintf(tmp,1024,"sox %s -e stat -v &> /tmp/flrec.soxstat",file);
	system(tmp);

	fl_filename_expand(tmp,1024,"/tmp/flrec.soxstat");
	fp=fopen(tmp,"r");
	fscanf(fp,"%f",&ret);
	fclose(fp);

	system("rm /tmp/flrec.soxstat");
	return ret;
}

void launchmixer()
{
	if (pid_mixer!=0) return;

	// execute mixer cmd
	pid_mixer=fork();

	if (pid_mixer==0) {

		int i,argc=0;
		char* argv[128];
		char *stop;
		char *start=new char[1024];
		for (i=0;i<1024;i++) *(start+i)=0;
		strncpy(start,UIsettings->set_mix->value(),1024);

		// separate command string in single argument
		// (avoiding multiple spaces)
		while ((stop=strstr(start," "))&&(argc<128)){
			if (start!=stop){
				argv[argc]=new char[128];
				*stop=0;
				strncpy(argv[argc],start,128);
				argc++;
			}
			start=stop+1;
		}

		if (*start!=0){
			argv[argc]=new char[128];
			strncpy(argv[argc++],start,128);
		}
		argv[argc++]=NULL;

		if (!quietmode) printf("flrec: lauching %s\n",argv[0]);
		execvp(argv[0],argv);

		// if we're here there is an error!
		fprintf(stderr,"flrec: cannot excute %s\n",argv[0]);
		exit(0); // exit from child
	}
}

void autonum_file()
{
	char name[1024];
	char ext[16];
	char num[9];
	char tmp[16];
	int i,n=0;

	strncpy(name,UImain->f_fileinput->value(),1023);
	
	// isolate extension (if exists)
	*ext=0;
	for (i=strlen(name)-1;i>=0;i--){
		if (*(name+i)=='.'){
			strncpy(ext,name+i,15);
			*(name+i)=0;
			break;
		}
		if (*(name+i)=='/') break;
	}

	i=strlen(name);

	// search for numebers
	while((*(name+i-1)<58)&&(*(name+i-1)>47)) i--;

	n=atoi(name+i);
	
	// build format string
	snprintf(tmp,16,"%%0%ii",(strlen(name)-i));

	*(name+i)=0;

	snprintf(num,9,tmp,++n);
	strncat(name,num,1023);
	strncat(name,ext,1023);

	UImain->f_fileinput->value(name);
}

////////////////////////////////////////////////////////////////////////////////////////////////
// sox management

int stop_sox()
{
	if (pid_sox==0) return -1;

	kill(pid_sox,SIGCONT);
	//kill(pid_sox,SIGTERM);
	kill(pid_sox,SIGINT);
	return 0;
}

int pause_sox()
{
	if (pid_sox==0) return -1;

	kill(pid_sox,SIGSTOP);
	return 0;
}

int resume_sox()
{
	if (pid_sox==0) return -1;

	kill(pid_sox,SIGCONT);
	return 0;
}

void addfilemain(char** argv,int* pargc)
{
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"-r");
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%s",s_val_h[UImain->mn_sample->value()]);
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"-c");
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%s",m_val_num[UImain->mn_mode->value()]);
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"-t");
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%s",f_val_name[UImain->mn_format->value()]);
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%s",UImain->f_fileinput->value());
}

void addfileout(char** argv,int* pargc)
{
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"-r");
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%s",s_val_h[UIeffects->mn_sample->value()]);
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"-c");
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%s",m_val_num[UIeffects->mn_mode->value()]);
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"-t");
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%s",f_val_name[UIeffects->mn_format->value()]);
	snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%s",UIeffects->f_output->value());
}

void addeffects(char** argv,int* pargc)
{
	if (UIeffects->m_mode==0) return;
	if ((UIeffects->m_mode==1)&&(UImain->m_Status!=1)) return;
	if ((UIeffects->m_mode==2)&&(UImain->m_Status!=2)) return;
	if ((UIeffects->m_mode==3)&&(UImain->m_Status!=2)) return;

	// volume
	if (UIeffects->b_volume->value()==1){
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"vol");
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_volume->value());
	}

	// speed
	if (UIeffects->b_speed->value()==1){
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"speed");
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_speed->value());
	}

	// reverb
	if (UIeffects->b_reverb->value()==1){
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"reverb");
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_rev_int->value());
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_rev_damp->value());
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_rev_room->value());
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"100");
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_rev_delay->value());
	}

	// echo
	if (UIeffects->b_echo->value()==1){
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"echo");
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"1");
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_echo_level->value());
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_echo_delay->value());
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_echo_decay->value());
	}

	// reverse
	if (UIeffects->b_reverse->value()==1){
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"reverse");
	}

	// Earwax
	if (UIeffects->b_earwax->value()==1){
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"earwax");
	}

	// pitch
	if (UIeffects->b_pitch->value()==1){
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"key");
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_pitch_shift->value());
	}

	// stretch
	if (UIeffects->b_stretch->value()==1){
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"tempo");
		snprintf(argv[(*pargc)++],MAX_ARG_DIM,"%f",UIeffects->s_stretch_factor->value());
	}
	
}

int start_sox()
{
	char* argv[MAX_SOX_ARG];
	int i,argc=0;

	for (i=0; i<MAX_SOX_ARG; i++) argv[i]=new char[MAX_ARG_DIM];

	if (UImain->m_Status==1) strncpy(argv[argc++],"rec",MAX_ARG_DIM);
	else if ((UImain->m_Status==2)&&(UIeffects->m_mode!=3)) strncpy(argv[argc++],"play",MAX_ARG_DIM);
	else strncpy(argv[argc++],"sox",MAX_ARG_DIM);
	
	if (quietmode) strncpy(argv[argc++],"-q",MAX_ARG_DIM);
	
	addfilemain(argv,&argc);
	if (UIeffects->m_mode==3) addfileout(argv,&argc);

	addeffects(argv,&argc);

	argv[argc]=NULL;

	if (!quietmode){
		printf("flrec: executing \"");
		for (i=0; i<argc; i++) printf("%s ",argv[i]);
		printf("\"\n");
	}
	
	pid_sox=fork();
	if (pid_sox==0){
	 	execvp(argv[0],argv);
		// if we're here there is an error!
		fprintf(stderr,"flrec: cannot excute %s\n",argv[0]);
		exit(0); // exit from child
	}

	for (i=0; i<MAX_SOX_ARG; i++) delete argv[i];
	return 0;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
// callbacks and signal handlers

// Main window status cb
// 0stop 1rec 2ply 3pau 4prec 5pply
int UImainStCb(int status)
{
	static int prev_status=0;

	if (!quietmode) printf("flrec: MainUI status:%i\n",status);

	switch (status){
		case 0:
			stop_sox();
			break;
		case 1:
		case 2:
			// if operation is a copy
			if ((UIeffects->m_mode==3)&&(status==2)){
				// if files are different
				if (strcmp(UIeffects->f_output->value(),UImain->f_fileinput->value()))
					UImain->w_main->cursor(FL_CURSOR_WAIT);
				else{
					UImain->setstatus(0,true);
					fl_message("Please select different output file");
					break;
				}
			}

			if (pid_sox!=0) resume_sox();
			else{
				// file autonumbering
				if ((status==1)&&(UIsettings->set_pam->value()==1)&&(prev_status==4))
					autonum_file();
				start_sox();
			}
			break;
		case 3:
			break;
		case 4:
		case 5:
			if (pid_sox!=0){
				// for file autonumbering
				if ((status==4)&&(UIsettings->set_pam->value()==1)) stop_sox();
				else pause_sox();
			}
			break;
	}

	prev_status=status;
	return 0;
}

// Effects window command cb
// 0newfile 1maxvol 2swfile 3fileup 4filedown
void UIeffectsCmdCb(int command)
{
	if (!quietmode) printf("flrec: EffectsUI command:%i\n",command);

	switch (command){
		case 0:
			adapt_menus_to_file(UIeffects->f_output, UIeffects->mn_sample, UIeffects->mn_mode, UIeffects->mn_format);
			break;
		case 1:
			UIeffects->s_volume->value(read_sox_stat_vol(UImain->f_fileinput->value()));
			break;
		case 2:
			char tmp[1024];
			strncpy(tmp,UIeffects->f_output->value(),1024);
			UIeffects->f_output->value(UImain->f_fileinput->value());
			UImain->f_fileinput->value(tmp);
			adapt_menus_to_file(UImain->f_fileinput,UImain->mn_sample,UImain->mn_mode,UImain->mn_format);
			adapt_menus_to_file(UIeffects->f_output,UIeffects->mn_sample,UIeffects->mn_mode,UIeffects->mn_format);
			break;
		case 3:
			UImain->f_fileinput->value(UIeffects->f_output->value());
			adapt_menus_to_file(UImain->f_fileinput,UImain->mn_sample,UImain->mn_mode,UImain->mn_format);
			break;
		case 4:
			UIeffects->f_output->value(UImain->f_fileinput->value());
			adapt_menus_to_file(UIeffects->f_output,UIeffects->mn_sample,UIeffects->mn_mode,UIeffects->mn_format);
			break;
	}
}

// Main Window command cb
// 0newfile 1mixer 2settings 3effects 4SIGTERM
int UImainCmdCb(int command)
{
	if (!quietmode) printf("flrec: MainUI command:%i\n",command);

	switch (command){
		case 0:
			adapt_menus_to_file(UImain->f_fileinput,UImain->mn_sample,UImain->mn_mode,UImain->mn_format);
			break;
		case 1:
			launchmixer();
			break;
		case 2:
			UIsettings->loadsettings();
			UIsettings->w_main->show();
			break;
		case 3:
			if (UIeffects->w_main->visible()) UIeffects->w_main->hide();
			else UIeffects->w_main->show();
			break;
		case 4:
			stop_sox();
			if (pid_mixer!=0) kill(pid_mixer,SIGTERM);
			exit(0);
			break;
	}
	return 0;
}

// system signal cb
void sighandler(int sig)
{
	// SIGALARM is for UImain timer
	if (sig==SIGALRM) UImain->clicktimer();
	
	// use SIGALARM to test if dezombie eventually too-rapidly-died processes 
	// (to fixme: atomize fork for sox)
	if ((sig==SIGCHLD)||(sig==SIGALRM)){

		// comes from mixer
		if ((pid_mixer!=0)&&(waitpid(pid_mixer,NULL,WNOHANG)!=0)){
			if (!quietmode) printf("flrec: SIGCHLD from mixer\n");
			pid_mixer=0;
		}
		// comes from sox
		if ((pid_sox!=0)&&(waitpid(pid_sox,NULL,WNOHANG)!=0)){
			if (!quietmode) printf("flrec: SIGCHLD from sox\n");
			pid_sox=0;

			// if is not a pause with autonumbering
			if (!((UImain->m_Status==4)&&(UIsettings->set_pam->value()==1)))
				UImain->setstatus(0,true);

			// if operation were a copy
			if (UIeffects->m_mode==3){
				UIeffects->setmode(0);
				UImain->w_main->cursor(FL_CURSOR_DEFAULT);
			}
		}
	}

}

//////////////////////////////////////////////////////////////////////////////////////////////////////
// the main

void print_usage (int exit_code)
{
	printf("Usage: flrec [OPTIONS] [FILENAME]\n"
	       "Fast Light RECorder is a sox frontend\n"
	       "check for last version on www.matteolucarelli.net\n"
	       "  -h  --help        Display this usage information\n"
	       "  -v  --version     Display version informations\n"
	       "  -q  --quiet       Don't print messages on console (default)\n"
	       "  -m  --messages    Print messages on console (default)\n"
	       "  -r  --record      Start recording (must supply a FILENAME)\n"
	       "  -p  --play        Start playing (must supply a FILENAME)\n"
	       "Supplying FILENAME will set it in the dialog\n"
	);
	exit (exit_code);
}

int main(int argc, char **argv)
{
	bool firstime;
	bool startp=false;
	bool startr=false;
	char filename[MAX_ARG_DIM];
	*filename=0;

	int next_option;
	const char* const short_options = "hvmrp";
	const struct option long_options[] = {
		{ "help",      0, NULL, 'h' },
		{ "version",   0, NULL, 'v' },
		{ "messages",  0, NULL, 'm' },
		{ "record",    0, NULL, 'r' },
		{ "play",      0, NULL, 'p' },
		{ NULL,        0, NULL, 0   }};

	do {
		next_option = getopt_long (argc, argv, short_options,long_options, NULL);
		switch (next_option)
		{
			case 'h':
				print_usage(0);
				break;
			case 'v':
				printf("flrec-v%s (c)2004 Matteo Lucarelli\n",FLRECVERSION);
				exit(0);
				break;
			case 'm':
				quietmode=false;
				break;
			case 'p':
				startp=true;
				break;
			case 'r':
				startr=true;
				break;
			case '?': 
				print_usage(1); 
				break;
		}
	}
	while (next_option != -1);
	
	if (optind < argc) snprintf(filename,MAX_ARG_DIM,argv[optind]);

	// control options
	if (startp&&startr){ 
		fprintf(stderr,"flrec: Cannot start --play AND --record!\n");
		exit(1);
	}
	if ((startr||startp)&&(*filename==0)){ 
		fprintf(stderr,"flrec: you must supply FILENAME\n");
		exit(1);
	}

	// create settings interface
	UIsettings=new flrecUIsettings();
	firstime=!(UIsettings->loadsettings());
	UIsettings->savesettings();

	// create effects interface
	UIeffects=new flrecUIeffects(UIeffectsCmdCb);
	UIeffects->f_output->value(UIsettings->set_sd->value());
	UIeffects->fillmenus(s_val_h,s_val_count,m_val_str,m_val_count,f_val_ext,f_val_count);

	// create user interface
	UImain=new flrecUImain(UImainStCb,UImainCmdCb);
	UImain->fillmenus(s_val_h,s_val_count,m_val_str,m_val_count,f_val_ext,f_val_count);
	UImain->f_fileinput->value(UIsettings->set_sd->value());

	adapt_menus_to_sox_h();
	if (*filename!=0) UImain->f_fileinput->value(filename);
	adapt_menus_to_file(UImain->f_fileinput,UImain->mn_sample,UImain->mn_mode,UImain->mn_format);
	adapt_menus_to_file(UIeffects->f_output, UIeffects->mn_sample, UIeffects->mn_mode, UIeffects->mn_format);
	
	// register signal handler
	signal(SIGCHLD,sighandler);
	signal(SIGALRM,sighandler);

	// set a SIGALRM every second
	struct itimerval value;
	value.it_interval.tv_sec=1;
	value.it_interval.tv_usec=0;
	value.it_value.tv_sec=1;
	value.it_value.tv_usec=0;
	setitimer(ITIMER_REAL,&value,0);
	
	if (startp){
		UImain->setstatus(2,true);
		UImainStCb(2);
	}
	
	if (startr){
		UImain->setstatus(1,true);
		UImainStCb(1);
	}

	UImain->show();

	if (firstime){
		UIsettings->w_main->show();
		fl_message(FLREC_SETTINGS_HELP);
	}

	return Fl::run();
}
