/*
  Copyright (C) 2006-2011 Nasca Octavian Paul
  Author: Nasca Octavian Paul

  This program is free software; you can redistribute it and/or modify
  it under the terms of version 2 of the GNU General Public License 
  as published by the Free Software Foundation.

  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 (version 2) for more details.

  You should have received a copy of the GNU General Public License (version 2)
  along with this program; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
*/
#include <math.h>
#include <stdlib.h>
#include <FL/Fl.H>
#include "globals.h"
#include "Control.h"
#include "XMLwrapper.h"
using namespace std;

Control::Control(){
	player=new Player();
	player->start();

	wavinfo.samplerate=44100;
	wavinfo.nsamples=0;
	wavinfo.intype=FILE_WAV;
	wav32bit=false;

	process.bufsize=16384;
	process.stretch=4.0;
	process.onset_detection_sensitivity=0.0;

	seek_pos=0.0;
	window_type=W_HANN;
	info.render_percent=-1.0;
	info.cancel_render=false;
	volume=1.0;


	gui_sliders.fftsize_s=0.5;
	gui_sliders.stretch_s=0.5;
	gui_sliders.mode_s=0;


///#warning test
///	process.transient.enable=true;
};

Control::~Control(){
	//    delete player; face crash daca il las
};
	

bool Control::set_input_filename(string filename,FILE_TYPE intype){
	InputS *ai=NULL;
	if (intype==FILE_VORBIS) ai=new VorbisInputS;
	if (intype==FILE_MP3) ai=new MP3InputS;
	if (intype==FILE_WAV) ai=new AInputS;
	if (intype==FILE_FLAC) ai=new AInputS;
	if (!ai) return false;
	wavinfo.filename=filename;
	wavinfo.intype=intype;
	bool result=ai->open(wavinfo.filename);
	if (result) {
		wavinfo.samplerate=ai->info.samplerate;
		wavinfo.nsamples=ai->info.nsamples;
///		if (process.transient.enable) {
///			pre_analyse_whole_audio(ai);
///		};

		delete ai;
	}else{
		wavinfo.filename="";
		wavinfo.samplerate=0;
		wavinfo.nsamples=0;
		delete ai;
	};
	return result;
};

string Control::get_input_filename(){
	return wavinfo.filename;
};
string Control::get_input_filename_and_info(){
	int seconds=wavinfo.nsamples/wavinfo.samplerate;
	const int size=200;
	char tmp[size];tmp[size-1]=0;
	snprintf(tmp,size-1," ( samplerate=%d; duration=%02d:%02d:%02d )",wavinfo.samplerate,seconds/3600,(seconds/60)%60,seconds%60);

	string filename=wavinfo.filename;
	int len=filename.length();
	if (len>70)filename=filename.substr(0,25)+"..."+filename.substr(len-35);
	return filename+tmp;
};
/*string Control::get_recommanded_output_filename(){
  return "none";
  };
  */


std::string Control::get_stretch_info(){
	const int size=200;
	char tmp[size];tmp[size-1]=0;

	if (wavinfo.nsamples==0) return "Stretch: ";


	double realduration=wavinfo.nsamples/wavinfo.samplerate*process.stretch;

	if (realduration>(365.25*86400.0*1.0e12)){//more than 1 trillion years
		double duration=(realduration/(365.25*86400.0*1.0e12));//my
		snprintf(tmp,size,"Stretch: %.7gx (%g trillion years)",process.stretch,duration);
		return tmp;
	};
	if (realduration>(365.25*86400.0*1.0e9)){//more than 1 billion years
		double duration=(realduration/(365.25*86400.0*1.0e9));//my
		snprintf(tmp,size,"Stretch: %.7gx (%g billion years)",process.stretch,duration);
		return tmp;
	};
	if (realduration>(365.25*86400.0*1.0e6)){//more than 1 million years
		double duration=(realduration/(365.25*86400.0*1.0e6));//my
		snprintf(tmp,size,"Stretch: %.7gx (%g million years)",process.stretch,duration);
		return tmp;
	};
	if (realduration>(365.25*86400.0*2000.0)){//more than two millenniums
		int duration=(int)(realduration/(365.25*86400.0));//years
		int years=duration%1000;
		int milleniums=duration/1000;

		char stryears[size];stryears[0]=0;
		if (years!=0){
			if (years==1) snprintf(stryears,size," 1 year");
			else snprintf(stryears,size," %d years",years);
		};
		snprintf(tmp,size,"Stretch: %.7gx (%d millenniums%s)",process.stretch,milleniums,stryears);
		return tmp;
	};
	if (realduration>(365.25*86400.0)){//more than 1 year
		int duration=(int) (realduration/3600.0);//hours
		int hours=duration%24;
		int days=(duration/24)%365;
		int years=duration/(365*24);

		char stryears[size];stryears[0]=0;
		if (years==1) snprintf(stryears,size,"1 year ");
		else snprintf(stryears,size,"%d years ",years);

		char strdays[size];strdays[0]=0;
		if (days>0){
			if (days==1) snprintf(strdays,size,"1 day");
			else snprintf(strdays,size,"%d days",days);
		};	
		if (years>=10) hours=0;
		char strhours[size];strhours[0]=0;
		if (hours>0){
			snprintf(strhours,size," %d h",hours);
		};	

		snprintf(tmp,size,"Stretch: %.7gx (%s%s%s)",process.stretch,stryears,strdays,strhours);
		return tmp;
	}else{//less than 1 year
		int duration=(int)(realduration);//seconds

		char strdays[size];strdays[0]=0;
		int days=duration/86400;
		if (days>0){
			if (days==1) snprintf(strdays,size,"1 day ");
			else snprintf(strdays,size,"%d days ",duration/86400);
		};	
		REALTYPE stretch=process.stretch;
		if (stretch>=1.0){
			stretch=((int) (stretch*100.0))*0.01;
		};
		snprintf(tmp,size,"Stretch: %.7gx (%s%.2d:%.2d:%.2d)",
				stretch,strdays,(duration/3600)%24,(duration/60)%60,duration%60);
		return tmp;
	};
	return "";
};

string Control::get_fftsize_info(){
	const int size=200;
	char tmp[size];tmp[size-1]=0;

	string fftsizelabel;
	fftsizelabel+="Window size (samples): ";

	if (wavinfo.nsamples==0) return fftsizelabel;
	fftsizelabel+=getfftsizestr(process.bufsize);

	return fftsizelabel;
};

string Control::get_fftresolution_info(){
	string resolution="Resolution: ";
	if (wavinfo.nsamples==0) return resolution;

	//todo: unctime and uncfreq are correct computed? Need to check later.
	REALTYPE unctime=process.bufsize/(REALTYPE)wavinfo.samplerate*sqrt(2.0);
	REALTYPE uncfreq=1.0/unctime*sqrt(2.0);
	char tmp[100];
	snprintf(tmp,100,"%.5g seconds",unctime);resolution+=tmp;
	snprintf(tmp,100," (%.5g Hz)",uncfreq);resolution+=tmp;
	return resolution;
};


void Control::startplay(bool bypass){
	if ((!player->info.playing)||(player->info.samplerate!=wavinfo.samplerate)){
		stopplay();
		sleep(200);
#ifdef HAVE_JACK
		JACKaudiooutputinit(player,wavinfo.samplerate);
#else
		PAaudiooutputinit(player,wavinfo.samplerate);
#endif
	};
	if (wavinfo.filename!="") player->startplay(wavinfo.filename,seek_pos,process.stretch,process.bufsize,wavinfo.intype,bypass,&ppar,&bbpar);
//	sleep(100);
//	update_process_parameters();
};
void Control::stopplay(){
	player->stop();
	player->seek(0.0);
	seek_pos=0;
#ifdef HAVE_JACK
	JACKclose();
#else
	PAfinish();
#endif
};
void Control::pauseplay(){
	player->pause();
};

void Control::freezeplay(){
	player->freeze();
};

void Control::set_volume(REALTYPE vol){
	volume=vol;
	player->set_volume(vol);
};


void Control::set_seek_pos(REALTYPE x){
	seek_pos=x;
	player->seek(x);
};
REALTYPE Control::get_seek_pos(){
	if (player->getmode()==Player::MODE_PLAY) seek_pos=player->info.position;
	return seek_pos;
};


void Control::set_stretch_controls(double stretch_s,int mode,double fftsize_s,double onset_detection_sensitivity){
	gui_sliders.stretch_s=stretch_s;
	gui_sliders.mode_s=mode;
	gui_sliders.fftsize_s=fftsize_s;

	double stretch=1.0;
	switch(mode){
		case 0:
			stretch_s=pow(stretch_s,1.2);
			stretch=pow(10.0,stretch_s*4.0);
			break;
		case 1:
			stretch_s=pow(stretch_s,1.5);
			stretch=pow(10.0,stretch_s*18.0);
			break;
		case 2:
			stretch=1.0/pow(10.0,stretch_s*2.0);
			break;
	};


	fftsize_s=pow(fftsize_s,1.5);
	int bufsize=(int)(pow(2.0,fftsize_s*12.0)*512.0);

	bufsize=optimizebufsize(bufsize);

	process.stretch=stretch;
	process.bufsize=bufsize;
	process.onset_detection_sensitivity=onset_detection_sensitivity;

};

double Control::get_stretch_control(double stretch,int mode){
	double result=1.0;
	switch(mode){
		case 0:
			if (stretch<1.0) return -1;
			stretch=(log(stretch)/log(10))*0.25;
			result=pow(stretch,1.0/1.2);
			break;
		case 1:
			if (stretch<1.0) return -1;
			stretch=(log(stretch)/log(10))/18.0;
			result=pow(stretch,1.0/1.5);
			break;
		case 2:
			if (stretch>1.0) return -1;
			result=2.0/(log(stretch)/log(10));
			break;
	};
	return result;
};


void Control::update_player_stretch(){
	player->setrap(process.stretch);
	player->set_onset_detection_sensitivity(process.onset_detection_sensitivity);
};


int abs_val(int x){
	if (x<0) return -x;
	else return x;
};


int Control::get_optimized_updown(int n,bool up){
	int orig_n=n;
	while(true){
		n=orig_n;
#ifndef KISSFFT
		while (!(n%11)) n/=11;
		while (!(n%7)) n/=7;
#endif
		while (!(n%5)) n/=5;
		while (!(n%3)) n/=3;
		while (!(n%2)) n/=2;
		if (n<2) break;
		if (up) orig_n++;
		else orig_n--;
		if (orig_n<4) return 4;
	};
	return orig_n;
};
int Control::optimizebufsize(int n){
	int n1=get_optimized_updown(n,false);
	int n2=get_optimized_updown(n,true);
	if ((n-n1)<(n2-n)) return n1;
	else return n2;
};



void Control::set_window_type(FFTWindow window){
	window_type=window;
	if (player) player->set_window_type(window);
};


string Control::Render(string inaudio,string outaudio,FILE_TYPE outtype,FILE_TYPE intype,REALTYPE pos1,REALTYPE pos2){
	if (pos2<pos1){
		REALTYPE tmp=pos2;
		pos2=pos1;
		pos1=tmp;
	};
	InputS *ai=NULL;
	switch(intype){
		case FILE_VORBIS:ai=new VorbisInputS;
						 break;
		case FILE_MP3:ai=new MP3InputS;
					  break;
		default:ai=new AInputS;
	};
	AOutputS ao;
	VorbisOutputS vorbisout;
	info.cancel_render=false;
	if (!ai->open(inaudio)){
		return "Error: Could not open audio file (or file format not recognized) :"+inaudio;
	};
	BinauralBeats bb(ai->info.samplerate);
	bb.pars=bbpar;
	if (outtype==FILE_WAV) ao.newfile(outaudio,ai->info.samplerate,wav32bit);
	if (outtype==FILE_VORBIS) vorbisout.newfile(outaudio,ai->info.samplerate);

	ai->seek(pos1);

	int inbufsize=process.bufsize;

	if (inbufsize<32) inbufsize=32;
	short int *inbuf_i=new short int[inbufsize*8];
	int outbufsize;
	struct{
		REALTYPE *l,*r;
	}inbuf;
	ProcessedStretch *stretchl=new ProcessedStretch(process.stretch,inbufsize,window_type,false,ai->info.samplerate,1);
	ProcessedStretch *stretchr=new ProcessedStretch(process.stretch,inbufsize,window_type,false,ai->info.samplerate,2);
	stretchl->set_onset_detection_sensitivity(process.onset_detection_sensitivity);
	stretchr->set_onset_detection_sensitivity(process.onset_detection_sensitivity);
	stretchl->set_parameters(&ppar);
	stretchr->set_parameters(&ppar);

	outbufsize=stretchl->get_bufsize();
	int *outbuf=new int[outbufsize*2];

	int poolsize=stretchl->get_max_bufsize();

	inbuf.l=new REALTYPE[poolsize];
	inbuf.r=new REALTYPE[poolsize];
	for (int i=0;i<poolsize;i++) inbuf.l[i]=inbuf.r[i]=0.0;

	int readsize=0;
	const int pause_max_write=65536;
	int pause_write=0;
	bool firstbuf=true;
	while(!ai->eof){
		float in_pos=(REALTYPE) ai->info.currentsample/(REALTYPE)ai->info.nsamples;
		if (firstbuf){
			readsize=stretchl->get_nsamples_for_fill();
			firstbuf=false;
		}else{
			readsize=stretchl->get_nsamples(in_pos*100.0);
		};
		int readed=0;
		if (readsize!=0) readed=ai->read(readsize,inbuf_i);
		
		for (int i=0;i<readed;i++) {
			inbuf.l[i]=inbuf_i[i*2]/32768.0;
			inbuf.r[i]=inbuf_i[i*2+1]/32768.0;
		};
		REALTYPE onset_l=stretchl->process(inbuf.l,readed);
		REALTYPE onset_r=stretchr->process(inbuf.r,readed);
		REALTYPE onset=(onset_l>onset_r)?onset_l:onset_r;
		stretchl->here_is_onset(onset);
		stretchr->here_is_onset(onset);
		bb.process(stretchl->out_buf,stretchr->out_buf,outbufsize,in_pos*100.0);
		for (int i=0;i<outbufsize;i++) {
			stretchl->out_buf[i]*=volume;
			stretchr->out_buf[i]*=volume;
		};
		int nskip=stretchl->get_skip_nsamples();
		if (nskip>0) ai->skip(nskip);

		if (outtype==FILE_WAV){	
			for (int i=0;i<outbufsize;i++) {
				REALTYPE l=stretchl->out_buf[i],r=stretchr->out_buf[i];
				if (l<-1.0) l=-1.0;
				else if (l>1.0) l=1.0;
				if (r<-1.0) r=-1.0;
				else if (r>1.0) r=1.0;
				outbuf[i*2]=(int)(l*32767.0*65536.0);
				outbuf[i*2+1]=(int)(r*32767.0*65536.0);
			};
			ao.write(outbufsize,outbuf);
		};
		if (outtype==FILE_VORBIS) vorbisout.write(outbufsize,stretchl->out_buf,stretchr->out_buf);

		REALTYPE totalf=ai->info.currentsample/(REALTYPE)ai->info.nsamples-pos1;
		if (totalf>(pos2-pos1)) break;

		info.render_percent=(totalf*100.0/(pos2-pos1+0.001));
		pause_write+=outbufsize;
		if (pause_write>pause_max_write){
			float tmp=outbufsize/1000000.0;
			if (tmp>0.1) tmp=0.1;
			Fl::wait(0.01+tmp);
			pause_write=0;
			if (info.cancel_render) break;
		};
	};

	delete stretchl;
	delete stretchr;    
	delete []outbuf;
	delete []inbuf_i;
	delete []inbuf.l;
	delete []inbuf.r;

	info.render_percent=-1.0;
	return "";
};



string Control::getfftsizestr(int fftsize){
	int size=100;
	char tmp[size];tmp[size-1]=0;
	if (fftsize<1024.0) snprintf(tmp,size-1,"%d",fftsize);
	else if (fftsize<(1024.0*1024.0)) snprintf(tmp,size-1,"%.4gK",fftsize/1024.0);
	else if (fftsize<(1024.0*1024.0*1024.0)) snprintf(tmp,size-1,"%.4gM",fftsize/(1024.0*1024.0));
	else snprintf(tmp,size-1,"%.7gG",fftsize/(1024.0*1024.0*1024.0));
	return tmp;
};

void Control::update_process_parameters(){
	if (player) player->set_process_parameters(&ppar,&bbpar);
};

bool Control::save_parameters(const char *filename){
	XMLwrapper *xml=new XMLwrapper();
	
	xml->beginbranch("PAULSTRETCH");
		xml->beginbranch("STRETCH_PARAMETERS");
			xml->beginbranch("BASE");
				xml->addpar("bufsize",process.bufsize);
				xml->addparreal("stretch",process.stretch);
	
				xml->addparreal("fftsize_s",gui_sliders.fftsize_s);
				xml->addparreal("stretch_s",gui_sliders.stretch_s);
				xml->addpar("mode_s",gui_sliders.mode_s);
				
				xml->addpar("window_type",window_type);
				xml->addparreal("volume",volume);
				xml->addparreal("onset_detection_sensitivity",process.onset_detection_sensitivity);

			xml->endbranch();
			
			xml->beginbranch("PROCESS");
				ppar.add2XML(xml);
			xml->endbranch();

			xml->beginbranch("BINAURAL_BEATS");
				bbpar.add2XML(xml);
			xml->endbranch();

		xml->endbranch();
	xml->endbranch();

	int result=xml->saveXMLfile(filename);
	delete xml;
	return true;
};

bool Control::load_parameters(const char *filename){
	XMLwrapper *xml=new XMLwrapper();

	if (xml->loadXMLfile(filename)<0) {
		delete xml;
		return false;
	};


	if (xml->enterbranch("PAULSTRETCH")==0) {
		delete xml;
		return false;
	};

	if (xml->enterbranch("STRETCH_PARAMETERS")){
		if (xml->enterbranch("BASE")){
			process.bufsize=xml->getpar("bufsize",process.bufsize,16,2e9);
			process.stretch=xml->getparreal("stretch",process.stretch);
			gui_sliders.fftsize_s=xml->getparreal("fftsize_s",gui_sliders.fftsize_s);
			gui_sliders.stretch_s=xml->getparreal("stretch_s",gui_sliders.stretch_s);
			gui_sliders.mode_s=xml->getpar("mode_s",gui_sliders.mode_s,0,2);
			window_type=(FFTWindow)xml->getpar("window_type",window_type,0,4);
			process.onset_detection_sensitivity=xml->getparreal("onset_detection_sensitivity",0.0);
			volume=xml->getparreal("volume",1.0);
			xml->exitbranch();
		};

		if (xml->enterbranch("PROCESS")){
				ppar.getfromXML(xml);
			xml->exitbranch();
		};

		if (xml->enterbranch("BINAURAL_BEATS")){
				bbpar.getfromXML(xml);
			xml->exitbranch();
		};

		xml->exitbranch();
	};
	delete xml;

	set_stretch_controls(gui_sliders.stretch_s,gui_sliders.mode_s,gui_sliders.fftsize_s,process.onset_detection_sensitivity);
	
	set_window_type(window_type);
	set_volume(volume);
	update_process_parameters();
	return true;
};
	

