/*
 * ZamSynth polyphonic synthesiser
 * Copyright (C) 2014  Damien Zammit <damien@zamaudio.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or 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.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */

#include "ZamSynthPlugin.hpp"
#include "ZamSynthUI.hpp"

START_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------

ZamSynthUI::ZamSynthUI()
    : UI()
{
    // background
    fImgBackground = Image(ZamSynthArtwork::zamsynthData, ZamSynthArtwork::zamsynthWidth, ZamSynthArtwork::zamsynthHeight, GL_BGR);

    fDragging = false;
    fDragValid = false;

    // knob
    Image knobImage(ZamSynthArtwork::knobData, ZamSynthArtwork::knobWidth, ZamSynthArtwork::knobHeight);

    // button
    Image smoothrImage(ZamSynthArtwork::smoothrData, ZamSynthArtwork::smoothrWidth, ZamSynthArtwork::smoothrHeight);
    Image smoothyImage(ZamSynthArtwork::smoothyData, ZamSynthArtwork::smoothyWidth, ZamSynthArtwork::smoothyHeight);

    // toggle
    Image toggleonImage(ZamSynthArtwork::toggleonData, ZamSynthArtwork::toggleonWidth, ZamSynthArtwork::toggleonHeight);
    Image toggleoffImage(ZamSynthArtwork::toggleoffData, ZamSynthArtwork::toggleoffWidth, ZamSynthArtwork::toggleoffHeight);

    // knob

    fKnobGain = new ImageKnob(this, knobImage);
    fKnobGain->setAbsolutePos(284.75, 240);
    fKnobGain->setRange(-30.f, 30.0f);
    fKnobGain->setDefault(0.0f);
    fKnobGain->setRotationAngle(240);
    fKnobGain->setCallback(this);

    fKnobSpeed = new ImageKnob(this, knobImage);
    fKnobSpeed->setAbsolutePos(284.75, 92.5);
    fKnobSpeed->setRange(1.f, 20.0f);
    fKnobSpeed->setDefault(10.0f);
    fKnobSpeed->setStep(1.0f);
    fKnobSpeed->setRotationAngle(240);
    fKnobSpeed->setCallback(this);

    // button
    fButtonSmooth = new ImageButton(this, smoothrImage, smoothrImage, smoothyImage);
    fButtonSmooth->setAbsolutePos(265, 165);
    fButtonSmooth->setCallback(this);

    // drawing area
    fCanvasArea.setPos(10,10);
    fCanvasArea.setSize(AREAHEIGHT,AREAHEIGHT);
    for (int i = 0; i < AREAHEIGHT; i++) {
        wave_y[i] = -(AREAHEIGHT*(sin(2.*i*M_PI/AREAHEIGHT)-1.0))/2.;
        env_y[i] = -(2*AREAHEIGHT*(sin(2.*i*M_PI/AREAHEIGHT/2.)-1.0))/2. < AREAHEIGHT / 2. ? -(2*AREAHEIGHT*(sin(2.*i*M_PI/AREAHEIGHT/2.)-1.0))/2. : AREAHEIGHT / 2.;
    }

    // toggle
    fToggleGraph = new ImageToggle(this, toggleonImage, toggleoffImage);
    fToggleGraph->setAbsolutePos(300, 33);
    fToggleGraph->setCallback(this);
    fToggleGraph->setValue(0.f);

    // set default values
    d_programChanged(0);
}

void ZamSynthUI::d_stateChanged(const char* key, const char* value)
{
        if (strcmp(key, "waveform") == 0) {
	        char* tmp;
	        int i = 0;
	        char tmpbuf[4*AREAHEIGHT+1] = {0};
	        snprintf(tmpbuf, 4*AREAHEIGHT, "%s", value);
	        tmp = strtok(tmpbuf, " ");
	        while ((tmp != NULL) && (i < AREAHEIGHT)) {
	                wave_y[i] = AREAHEIGHT-((float)atoi(tmp));
	                i++;
	                //printf("reload dsp wave_y[%d]=%.2f ", i, wave_y[i]);
	                tmp = strtok(NULL, " ");
	        }
	} else if (strcmp(key, "envelope") == 0) {
	        char* tmp;
	        int i = 0;
	        char tmpbuf[4*AREAHEIGHT+1] = {0};
	        snprintf(tmpbuf, 4*AREAHEIGHT, "%s", value);
	        tmp = strtok(tmpbuf, " ");
	        while ((tmp != NULL) && (i < AREAHEIGHT)) {
	                env_y[i] = AREAHEIGHT-((float)atoi(tmp));
	                i++;
	                //printf("reload dsp env_y[%d]=%.2f ", i, env_y[i]);
	                tmp = strtok(NULL, " ");
	        }
	}
}

// -----------------------------------------------------------------------
// DSP Callbacks

void ZamSynthUI::d_parameterChanged(uint32_t index, float value)
{
	switch (index)
	{
	case ZamSynthPlugin::paramGain:
		fKnobGain->setValue(value);
		break;
	case ZamSynthPlugin::paramSpeed:
		fKnobSpeed->setValue(value);
		break;
	case ZamSynthPlugin::paramGraph:
		fToggleGraph->setValue(value);
		break;
	}
}

void ZamSynthUI::d_programChanged(uint32_t index)
{
    if (index != 0)
        return;

    fKnobGain->setValue(0.0f);
    fKnobSpeed->setValue(10.0f);
}

// -----------------------------------------------------------------------
// Widget Callbacks

void ZamSynthUI::imageKnobDragStarted(ImageKnob* knob)
{
    if (knob == fKnobGain)
        d_editParameter(ZamSynthPlugin::paramGain, true);
    else if (knob == fKnobSpeed)
        d_editParameter(ZamSynthPlugin::paramSpeed, true);
}

void ZamSynthUI::imageKnobDragFinished(ImageKnob* knob)
{
    if (knob == fKnobGain)
        d_editParameter(ZamSynthPlugin::paramGain, false);
    else if (knob == fKnobSpeed)
        d_editParameter(ZamSynthPlugin::paramSpeed, false);
}

void ZamSynthUI::imageKnobValueChanged(ImageKnob* knob, float value)
{
    if (knob == fKnobGain)
        d_setParameterValue(ZamSynthPlugin::paramGain, value);
    else if (knob == fKnobSpeed)
        d_setParameterValue(ZamSynthPlugin::paramSpeed, value);
}

void ZamSynthUI::imageButtonClicked(ImageButton*, int)
{
	float wavesmooth[AREAHEIGHT];
	float xs[AREAHEIGHT];
	int i;
	for (i = 0; i < AREAHEIGHT; i++) {
		xs[i] = i;
	}

	float *gr;
	gr = (fToggleGraph->getValue() == 1.f) ? env_y : wave_y;

	gaussiansmooth(wavesmooth, xs, gr, AREAHEIGHT, 4);
	memcpy(gr, wavesmooth, AREAHEIGHT*sizeof(float));

	char tmp[4*AREAHEIGHT+1] = {0};
	for(i = 0; i < AREAHEIGHT; i++) {
		char wavestr[5] = {0};
		snprintf(wavestr, sizeof(wavestr), "%03d ", (int) (fCanvasArea.getHeight()-gr[i]));
		strcat(tmp, wavestr);
	}

	if (fToggleGraph->getValue() == 1.f)
		d_setState("envelope", tmp);
	else
		d_setState("waveform", tmp);
}

void ZamSynthUI::imageToggleClicked(ImageToggle*, int)
{
	float toggle = fToggleGraph->getValue();
	fToggleGraph->setValue(toggle);
	d_setParameterValue(ZamSynthPlugin::paramGraph, toggle);
}

void ZamSynthUI::gaussiansmooth(float* smoothed, float* xs, float* ys, int n, int radius)
{
	int i,j;
	float numer;
	float denom;
	float kernel;
	for (i = 0; i < n; i++) {
		numer = 0.f;
		denom = 0.f;
		for (j = 0; j < n; j++) {
			kernel = expf(-(i - xs[j])*(i - xs[j]) / (2. * radius));
			numer += kernel * ys[j];
			denom += kernel;
		}
		smoothed[i] = numer / denom;
	}
}

bool ZamSynthUI::onMouse(const MouseEvent& ev)
{
    if (ev.button != 1)
        return false;

    if (ev.press)
    {
        if (! fCanvasArea.contains(ev.pos)) {
            //fDragValid = false;
            return false;
	}

        fDragging = true;
        fDragValid = true;
        return true;
    }
    else if (fDragging)
    {
        fDragging = false;
        return true;
    }

    return false;
}

bool ZamSynthUI::onMotion(const MotionEvent& ev)
{
    if (! fDragging)
        return false;
    if (! fDragValid)
    {
        fDragValid = true;
    }

    int x = ev.pos.getX();
    int y = ev.pos.getY();

    if (x > fCanvasArea.getWidth()+10)
    x = fCanvasArea.getWidth()+10;
    if (x < 10) x = 10;
    if (y < 10) y = 10;

    float *gr;
    if (fToggleGraph->getValue() == 0.f) {
	gr = wave_y;
	if (y > fCanvasArea.getHeight()+10)
		y = fCanvasArea.getHeight()+10;
    } else {
    	gr = env_y;
	if (y > fCanvasArea.getHeight() / 2. + 10)
		y = fCanvasArea.getHeight() / 2. + 10;
    }

    if (gr[x-10] != (y-10)) {
	char tmp[4*AREAHEIGHT+1] = {0};
	int i;
	for(i = 0; i < AREAHEIGHT; i++) {
		char wavestr[5] = {0};
		snprintf(wavestr, sizeof(wavestr), "%03d ", (int) (fCanvasArea.getHeight()-gr[i]));
		strcat(tmp, wavestr);
	}

        gr[x-10] = y-10;

	if (gr == env_y)
		d_setState("envelope",tmp);
	else
		d_setState("waveform",tmp);

	repaint();
    }

    return true;
}


void ZamSynthUI::onDisplay()
{
	fImgBackground.draw();

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

    glLineWidth(2);
    float *gr;
    gr = (fToggleGraph->getValue() == 1.f) ? env_y : wave_y;

    int i;
        glColor4f(0.235f, 1.f, 0.235f, 1.0f);
        for (i = 2; i < AREAHEIGHT; ++i) {
            glBegin(GL_LINES);
                    glVertex2i(i-1+fCanvasArea.getX(), gr[i-1]+fCanvasArea.getY());
                    glVertex2i(i+fCanvasArea.getX(), gr[i]+fCanvasArea.getY());
            glEnd();
        }
    // reset color
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
// -----------------------------------------------------------------------

UI* createUI()
{
    return new ZamSynthUI();
}

// -----------------------------------------------------------------------

END_NAMESPACE_DISTRHO
