/*
 * MusE FLUID Synth softsynth plugin
 *
 * Copyright (C) 2002 Robert Ham (node@users.sourcforge.net)
 *
 * $Id: fluidsynthgui.cpp,v 1.3 2003/11/04 20:31:53 lunar_shuttle Exp $
 *
 */

#include <iostream>
#include <qtooltip.h>
#include <qspinbox.h>
#include <qcombobox.h>
#include <qapplication.h>
#include <qlistbox.h>
#include <qcheckbox.h>
#include <qslider.h>
#include <qlineedit.h>
#include <qfiledialog.h>
#include <qlistview.h>
#include "fluidsynthgui.h"
#include "debug.h"

/*** FLUIDSynthGui ***/
FLUIDSynthGui::FLUIDSynthGui () {
	connect(Reverb, SIGNAL (toggled (bool)), SLOT(toggleReverb (bool)));
	connect(Chorus, SIGNAL (toggled (bool)), SLOT(toggleChorus (bool)));
	connect(Gain, SIGNAL (valueChanged (int)), SLOT(changeGain (int)));
	connect(ReverbRoomSize, SIGNAL (valueChanged (int)), SLOT(changeReverbRoomSize (int)));
	connect(ReverbDamping, SIGNAL (valueChanged (int)), SLOT(changeReverbDamping (int)));
	connect(ReverbWidth, SIGNAL (valueChanged (int)), SLOT(changeReverbWidth (int)));
	connect(ReverbLevel, SIGNAL (valueChanged (int)), SLOT(changeReverbLevel (int)));
	connect(ChorusNumber, SIGNAL (valueChanged (int)), SLOT(changeChorusNumber (int)));
	connect(ChorusType, SIGNAL (activated (int)), SLOT(changeChorusType (int)));
	connect(ChorusSpeed, SIGNAL (valueChanged (int)), SLOT(changeChorusSpeed (int)));
	connect(ChorusDepth, SIGNAL (valueChanged (int)), SLOT(changeChorusDepth (int)));
	connect(ChorusLevel, SIGNAL (valueChanged (int)), SLOT(changeChorusLevel (int)));

	connect (Push, SIGNAL (clicked ()), SLOT(pushClicked ()));
	connect (Pop,  SIGNAL (clicked ()), SLOT(popClicked ()));
	connect (File, SIGNAL (clicked ()), SLOT(fileClicked ()));

	connect(channelListView, SIGNAL(pressed(QListViewItem*,const QPoint&,int)),
		this, SLOT(channelItemClicked(QListViewItem*,const QPoint&,int)));
	connect(sfListView, SIGNAL(pressed(QListViewItem*,const QPoint&,int)),
		this, SLOT(sfItemClicked(QListViewItem*,const QPoint&,int)));

	_notifier = new QSocketNotifier(0, QSocketNotifier::Read);
	connect(_notifier, SIGNAL(activated(int)), SLOT(readData(int)));

	//Setup the ListView
	sfListView->setColumnWidthMode(MUSE_FLUID_ID_COL,QListView::Maximum);
	sfListView->setColumnWidthMode(MUSE_FLUID_SFNAME_COL,QListView::Maximum);
	channelListView->setColumnWidthMode(MUSE_FLUID_CHANNEL_COL,QListView::Maximum);
	channelListView->setColumnWidthMode(MUSE_FLUID_SF_ID_COL,QListView::Maximum);
	sfListView->setColumnAlignment(MUSE_FLUID_ID_COL,AlignHCenter);
	sfListView->setSorting(MUSE_FLUID_ID_COL,true);
	channelListView->setColumnAlignment(MUSE_FLUID_CHANNEL_COL,AlignHCenter);

	_currentlySelectedFont = -1; //No selected font to start with
	requestAllParameters (); //The GUI-process is killed every time the window is shut, need to get all parameters from the synth


	const char* fontnames[] = { "Sans", "Helvetica", "Courier" };
	const char* fontnames2[] = { "Helvetica", "Sans", "Arial", "Courier" };

#define FNSIZE 3
#define FN2SIZE 4
	//Check fonts in labels:
	assignSaneTextFont((QWidget*)loadedFontsLabel,10, fontnames, FNSIZE);
	assignSaneTextFont((QWidget*)fontSetupLabel,10, fontnames, FNSIZE);
	assignSaneTextFont((QWidget*)sfListView,10, fontnames2, FN2SIZE);
	assignSaneTextFont((QWidget*)channelListView,10, fontnames2, FN2SIZE);
	assignSaneTextFont((QWidget*)Push,10, fontnames2, FN2SIZE);
	assignSaneTextFont((QWidget*)Pop,10, fontnames2, FN2SIZE);
	assignSaneTextFont((QWidget*)Filename,8, fontnames2, FN2SIZE);
	assignSaneTextFont((QWidget*)File,10, fontnames2, FN2SIZE);
	assignSaneTextFont((QWidget*)Reverb,10, fontnames2, FN2SIZE);
	assignSaneTextFont((QWidget*)Chorus,10, fontnames2, FN2SIZE);
#undef FN2SIZE
#undef FNSIZE
   ReverbFrame->setEnabled (true);
   ChorusFrame->setEnabled (true);
}

void FLUIDSynthGui::assignSaneTextFont(QWidget* widget, int pointsize, const char** fontlist, int nr_of_fonts)
{
	QFont font = widget->font();
	//First, try the first alternative, 10:
	QFont f (fontlist[0],pointsize);
	widget->setFont(f);

	//Test the rest:
	if (widget->font().pointSize() != pointsize)
	{
		int i=1;
		while (i < nr_of_fonts && widget->font().pointSize() != pointsize)
		{
			QFont f ( fontlist[i], pointsize);
			widget->setFont(f);
			i++;
		}
	}
}

void FLUIDSynthGui::pushClicked () {
	const QString& fns = Filename->text();
	if (fns.isEmpty()) return;
	const char * fn = fns.latin1();

	int datalen = strlen (fn) + 3;
	unsigned char data [datalen];
	data[0] = MUSE_FLUID_SOUNDFONT_PUSH;
	data[1] = MUSE_FLUID_UNSPECIFIED_ID; //This makes the client choose next available external id
	memcpy (data + 2, fn, strlen(fn) + 1 ); //Store filename
	sendSysex (data, datalen);
	data[0] = MUSE_FLUID_GUI_REQ_SOUNDFONTS; //For simplicity's sake, just get all the soundfont data again.
	sendSysex (data, 1);

	return;
}

void FLUIDSynthGui::popClicked() {
	unsigned char data [2];
	data[0] = MUSE_FLUID_SOUNDFONT_POP;
	data[1] = _currentlySelectedFont;
	sendSysex(data,2);
}

void FLUIDSynthGui::fileClicked () {
	QString lastdir;
	if (_lastDir != "")
		lastdir = _lastDir;
	else
		lastdir = QString::null;
	QString filename = QFileDialog::getOpenFileName(lastdir, QString("*.[Ss][Ff]2"), this,
									"Load Soundfont dialog", "Choose soundfont");
	if (filename != QString::null)
	{
		int lastslash = filename.findRev('/');
		QString dir = filename.left(lastslash);
		std::cerr << "Last dir is now " << dir << std::endl;
		int datalen = strlen(dir) + 2;
		unsigned char data[datalen];
		data[0] = MUSE_FLUID_GUI_LASTDIR_CHANGE;
		memcpy(data+1, dir.latin1(), strlen(dir) + 1);
		sendSysex(data,datalen);
		Filename->setText (filename);
	}
}

void FLUIDSynthGui::readData (int fd) {
	unsigned char buffer[512];
	int n = ::read(fd, buffer, 512);
	dataInput(buffer, n);
}


FLUIDSynthGui::~FLUIDSynthGui () {
	delete _notifier;
}

void FLUIDSynthGui::toggleReverb (bool on) {
	Reverb->setEnabled (on);
	sendParameterChange(MUSE_FLUID_PARAMETER_REVERB,
				"on", ((int) on)*128);
}

void FLUIDSynthGui::toggleChorus (bool on) {
	Chorus->setEnabled (on);
	sendParameterChange(MUSE_FLUID_PARAMETER_CHORUS,
				"on", ((int) on)*128);
}

void FLUIDSynthGui::changeGain (int value) {
	int datalen = 1 + sizeof (double);
	unsigned char * data = new unsigned char [datalen];
	*data = MUSE_FLUID_GAIN_SET;
	double * dp = (double *) (data + 1);
	*dp = ( ((double)value) / ((double)128.0)) * ((double)10.0);
	sendSysex (data, datalen);
	delete data;
}

void FLUIDSynthGui::changeReverbRoomSize (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_REVERB,
				"roomsize", value);
}

void FLUIDSynthGui::changeReverbDamping (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_REVERB,
				"damping", value);
}

void FLUIDSynthGui::changeReverbWidth (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_REVERB,
				"width", value);
}

void FLUIDSynthGui::changeReverbLevel (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_REVERB,
				"level", value);
}

void FLUIDSynthGui::changeChorusNumber (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_CHORUS,
				"number", value);
}

void FLUIDSynthGui::changeChorusType (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_CHORUS,
				"type", value);
}

void FLUIDSynthGui::changeChorusSpeed (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_CHORUS,
				"speed", value); //TODO: Right now illegal values may be sent.
				//Make sure they stay within fluidsynths legal boundaries (0.29-5Hz) dunno what that is in doubles
				//This might be the case for the other chorus parameters as well
}

void FLUIDSynthGui::changeChorusDepth (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_CHORUS,
				"depth", value);
}

void FLUIDSynthGui::changeChorusLevel (int value) {
	sendParameterChange(MUSE_FLUID_PARAMETER_CHORUS,
				"level", value);
}


void FLUIDSynthGui::sysexReceived(unsigned char const * data, int len) {
	unsigned char * decdata = new unsigned char [len / 2];
	for (int i = 0; i < len / 2; i++) {
		decdata[i] = 0xf0 & (data[i * 2] << 4);
		decdata[i] |= (0xf & (data[(i * 2) + 1]));
	}
	dealWithSysex (decdata, len / 2);
	delete decdata;
}

void FLUIDSynthGui::dealWithSysex (unsigned char const * data, int /*datalen*/)
{
	char * cp;
	double * dp;
	switch (*data) {
	case MUSE_FLUID_CLIENT_SEND_PARAMETER:
		cp = (char *) (data + 2);
		dp = (double *) (data + strlen (cp) + 3);
		setParameter ((int) *(data+1), cp, *dp);
		break;
	case MUSE_FLUID_GAIN_GET:
		dp = (double *) (data + 1);
		Gain->setValue ((int) (*dp * 12.8));
		break;
	case MUSE_FLUID_CLIENT_SEND_SOUNDFONTS:
	{
		int count = (int) *(data + 1); //Number of elements
		cp = (char *) data + 2;		 //Point to beginning of first chunk

		int			chunk_len;
		int 			filename_len;
		sfListView->clear();		 //Clear the listview
		_soundfontStack.clear();

		while (count)
		{
			FLUIDSynthGui_soundfont font;
			filename_len	= strlen(cp) + 1;
			font._name		= QString(cp);
			font._id		= *(cp + filename_len);
			chunk_len 		= filename_len + MUSE_FLUID_SFDATALEN;
			_soundfontStack.push_front(font);
			cp += chunk_len;	//Move to next chunk
			count--;
		}
		updateSoundfontListView();
		break;
	}
	case MUSE_FLUID_CLIENT_SEND_CHANNELINFO:
	{
		cp = (char *) data +1;
		int	chunk_len = 2;
		channelListView->clear();

		for (int i=0; i<MUSE_FLUID_MAX_NR_OF_CHANNELS; i++)
		{
			QString chanstr, sfidstr;
			if (*cp == MUSE_FLUID_UNSPECIFIED_FONT)
				sfidstr = "unspecified";
			else {
				//sfidstr = QString("%1").arg((int)*cp,1);
				sfidstr = getSoundFontName((int)*cp);
			}
			chanstr = QString("%1").arg((int)*(cp+1)+1,1);
			if (chanstr.length()==1)
				chanstr = "0" + chanstr;

			QListViewItem* qlvNewItem = new QListViewItem(channelListView);
			qlvNewItem->setText(MUSE_FLUID_CHANNEL_COL, chanstr);
			qlvNewItem->setPixmap(MUSE_FLUID_SF_ID_COL, buttondown_xpm);
			qlvNewItem->setText(MUSE_FLUID_SF_ID_COL, sfidstr);
			channelListView->insertItem(qlvNewItem);
			cp += chunk_len;
		}
	}
	break;
	case MUSE_FLUID_CLIENT_LASTDIR_CHANGE:
	{
		if (*(char*)(data+1) != MUSE_FLUID_UNSPECIFIED_LASTDIR)
			_lastDir = QString((char*)(data+1));
		else
			_lastDir="";
	}

	default:
	break;
	}
}
void FLUIDSynthGui::updateSoundfontListView()
{
	//std::cerr << "Num of fonts on stack: " << _soundfontStack.size() << std::endl;
	sfListView->clear(); //Clear the listview
	for (std::list<FLUIDSynthGui_soundfont>::iterator it = _soundfontStack.begin();
		it != _soundfontStack.end();
		it++)
	{
		QListViewItem* qlvNewItem = new QListViewItem(sfListView);
		QString qsid	= QString("%1").arg(it->_id);
		qlvNewItem->setText(MUSE_FLUID_ID_COL, qsid);
		qlvNewItem->setText(MUSE_FLUID_SFNAME_COL, QString(it->_name));
		sfListView->insertItem(qlvNewItem);
	}
	sfListView->sort();
}
int FLUIDSynthGui::getSoundFontId(QString q)
{
	int id = -1;
	for (std::list<FLUIDSynthGui_soundfont>::iterator it = _soundfontStack.begin();
		it != _soundfontStack.end();
		it++)
	{
		if (q == it->_name)
			id = it->_id;
	}
	return id;
}

QString FLUIDSynthGui::getSoundFontName(int id)
{
	QString name = NULL;
	for (std::list<FLUIDSynthGui_soundfont>::iterator it = _soundfontStack.begin();
		it != _soundfontStack.end();
		it++)
	{
		if (id == it->_id)
			name = it->_name;
	}
	return name;
}


void FLUIDSynthGui::channelItemClicked(QListViewItem* item, const QPoint&, int col)
{
	if (col == MUSE_FLUID_SF_ID_COL)
	{
		QPopupMenu* popup = new QPopupMenu(this);
		QPoint ppt = channelListView->itemRect(item).bottomLeft();
		QListView* listView = item->listView();
		ppt += QPoint(listView->header()->sectionPos(col), listView->header()->height());
		ppt  = listView->mapToGlobal(ppt);

		int i = 0;
		for (std::list<FLUIDSynthGui_soundfont>::reverse_iterator it = _soundfontStack.rbegin();
			it != _soundfontStack.rend();
			it++)
		{
			i++;
			popup->insertItem(it->_name,i);
		}
		int lastindex = i + 1;
		popup->insertItem("unspecified",lastindex);
		int index 	= popup->exec(ppt, 0);
		if (index !=-1)
			{
				int sfid;
				QString fontname;
				if (index == lastindex)
				{
					sfid = MUSE_FLUID_UNSPECIFIED_FONT;
					fontname = "unspecified";	//Actually, it's not possible to reset fluid-channels as for now,
				}						//so this is just a dummy
				else
				{
					sfid = getSoundFontId(popup->text(index));
					fontname = getSoundFontName(sfid);
				}
				int channel	= atoi(item->text(MUSE_FLUID_CHANNEL_COL)) - 1;
				sendChannelChange((unsigned char) sfid, (unsigned char) channel);
				item->setText(MUSE_FLUID_SF_ID_COL,fontname);
			}
		delete popup;
	}
}

void FLUIDSynthGui::sfItemClicked(QListViewItem* item, const QPoint&, int /*col*/)
{
	if (item != 0)
	{
		_currentlySelectedFont = atoi(item->text(MUSE_FLUID_ID_COL));
		Pop->setEnabled(true);
	}
	else
	{
		_currentlySelectedFont = -1;
		Pop->setEnabled(false);
	}

}

// Tell the client to set a soundfont to a specific fluid channel
void FLUIDSynthGui::sendChannelChange(unsigned char font_id, unsigned char channel)
{
	unsigned char data[3];
	data[0] = MUSE_FLUID_GUI_SOUNDFONT_CHANNEL_SET;
	data[1] = font_id;
	data[2] = channel;
	sendSysex(data, 3);
}

void FLUIDSynthGui::eventReceived (int /*a*/, int /*b*/, int /*c*/) {
}

void FLUIDSynthGui::requestAllParameters () {
	unsigned char data[1];

	//data[0] = MUSE_FLUID_ADVGUI_GET;
	//sendSysex (data, 1);
	sendParameterRequest (MUSE_FLUID_PARAMETER_REVERB, "on");
	sendParameterRequest (MUSE_FLUID_PARAMETER_REVERB, "roomsize");
	sendParameterRequest (MUSE_FLUID_PARAMETER_REVERB, "damping");
	sendParameterRequest (MUSE_FLUID_PARAMETER_REVERB, "width");
	sendParameterRequest (MUSE_FLUID_PARAMETER_REVERB, "level");
	sendParameterRequest (MUSE_FLUID_PARAMETER_CHORUS, "on");
	sendParameterRequest (MUSE_FLUID_PARAMETER_CHORUS, "number");
	sendParameterRequest (MUSE_FLUID_PARAMETER_CHORUS, "type");
	sendParameterRequest (MUSE_FLUID_PARAMETER_CHORUS, "speed");
	sendParameterRequest (MUSE_FLUID_PARAMETER_CHORUS, "depth");
	sendParameterRequest (MUSE_FLUID_PARAMETER_CHORUS, "level");
	data[0] = MUSE_FLUID_GAIN_GET;
	sendSysex (data, 1);
	data[0] = MUSE_FLUID_GUI_REQ_SOUNDFONTS;
	sendSysex (data, 1);
}

bool FLUIDSynthGui::sendParameterRequest (int parameterSet, const char * parameter) {
  size_t parameterMem = strlen (parameter) + 1;
  int datalen = 2 + parameterMem;
  unsigned char * data = new unsigned char [datalen];
  *data = MUSE_FLUID_GUI_REQ_FXPARAMETER_GET;
  *(data + 1) = (char) parameterSet;
  memcpy (data + 2, parameter, parameterMem);
  if (!sendSysex (data, datalen)) {
    std::cerr << DEBUG_ARGS << "error sending sysex data" << std::endl;
    delete data;
    return false;
  }
  delete data;
  return true;
}

void FLUIDSynthGui::setParameter (int parameterSet, const char * parameter, double value) {
  int ival = (int) (value * 128);
  std::string ps (parameter);
  if (parameterSet == MUSE_FLUID_PARAMETER_REVERB) {
           if (ps == "roomsize") {
      ReverbRoomSize->setValue (ival);
    } else if (ps == "damping") {
      ReverbDamping->setValue (ival);
    } else if (ps == "width") {
      ReverbWidth->setValue (ival);
    } else if (ps == "level") {
      ReverbLevel->setValue (ival);
    } else if (ps == "on") {
      Reverb->setChecked (ival);
    }
  } else {
           if (ps == "number") {
      ChorusNumber->setValue (ival);
    } else if (ps == "type") {
      ChorusType->setCurrentItem (ival);
    } else if (ps == "speed") {
      ChorusSpeed->setValue (ival);
    } else if (ps == "depth") {
      ChorusDepth->setValue (ival);
    } else if (ps == "level") {
      ChorusLevel->setValue (ival);
    } else if (ps == "on") {
      Chorus->setChecked (ival);
    }
  }
}

//Sends parameter to reverb or chorus
bool FLUIDSynthGui::sendParameterChange (int parameterSet, const char * parameter, int value) {
  size_t parameterMem = strlen (parameter) + 1;
  int datalen = 2 + parameterMem + sizeof (double);
  unsigned char * data = new unsigned char [datalen];
  *data = (unsigned char) MUSE_FLUID_GUI_REQ_FXPARAMETER_SET;
  *(data + 1) = (unsigned char) parameterSet;
  memcpy (data + 2, parameter, parameterMem);
  double * dp = (double *) (data + 2 + parameterMem);
  *dp = ((double) value) / ((double) 128.0);
  if (!sendSysex (data, datalen)) {
    std::cerr << DEBUG_ARGS << "error sending sysex data" << std::endl;
    delete data;
    return false;
  }
  delete data;
  return true;
}

bool FLUIDSynthGui::sendSysex (const unsigned char * data, int datalen) {
  putchar (0xf0); // tell MidiRawIn::channelStatus that we're sending sysex

  const unsigned char * p = data;
  int i = datalen;
  while (i > 0) {
    putchar ((*p >> 4) & 0xf);
    putchar (*p & 0xf);
    ++p;
    --i;
  }

  putchar (0xf7); // we're done sending

  fflush (stdout);
  return true;
}


//---------------------------------------------------------
//   main
//---------------------------------------------------------

/*QString museProject;
QString museGlobalShare;
QString museUser;*/


int main(int argc, char* argv[])
{
/*
  museUser = getenv("MUSEHOME");
  if (museUser == 0)
    museUser = getenv("HOME");
  museGlobalShare = getenv("MUSE");
  if (museGlobalShare == 0) {
    museGlobalShare = "/usr/muse";
    if (access(museGlobalShare.latin1(), R_OK) != 0) {
      museGlobalShare = "/usr/local/muse";
      if (access(museGlobalShare.latin1(), R_OK) != 0)
        museGlobalShare = museUser;
    }
  }*/
	char * instanceName = argv[1];
	QApplication app (argc, argv, true);
	QWidget* w = new FLUIDSynthGui ();
	if (argc > 1)
	w->setCaption(QString(instanceName));
	w->show();
	app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
	qApp->exec();
}
