/***************************************************************************
 *   Copyright (C) 2006 by Jonathan Duddington                             *
 *   jsd@clara.co.uk                                                       *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "wx/wx.h"
#include "wx/filename.h"
#include "wx/wfstream.h"

#include "main.h"
#include "speech.h"
#include "voice.h"
#include "spect.h"

#define SCROLLUNITS  20
#define WAVE_HEIGHT 160
#define WAVE_WIDTH  1000
#define N_WAV_MARKERS   200

extern wxString path_wave;
extern FILE *f_wave;
extern void analyse_cycle(short *w, int length, float *harmf, int options);

class WaveWindow: public wxScrolledWindow
{//======================================
public:
	WaveWindow(wxWindow *parent, unsigned char *data, int n_data, wxString fname);
	~WaveWindow();
   virtual void OnDraw(wxDC &dc);
	void SetData(int nx, unsigned char *data);
	int n_samples;
	short *samples;
	wxFileName filename;

private:
	void SetZoom(float newzoom);
	void OnMouse(wxMouseEvent& event);
	void OnKey(wxKeyEvent& event);
	void OnMenu(wxCommandEvent& event);
	void Analyse(void);
	void Play(void);

	unsigned char *wavdata;
	int rate;
	float zoom;
	int cursor;
	int prev_cursor;
	int select1;   // start of selection
	int select2;   // end of selection.  0=no selection
	int n_markers;
	int markers[N_WAV_MARKERS];

	DECLARE_EVENT_TABLE()
};


class WaveFrame: public wxFrame
{//============================
public:
	WaveFrame(wxWindow *parent, unsigned char *data, int n_data, wxString fname);
	~WaveFrame();
	WaveWindow *wavewindow;
};


static wxMenu *menu_wave;


BEGIN_EVENT_TABLE(WaveWindow,wxScrolledWindow)
	EVT_LEFT_DOWN(WaveWindow::OnMouse)
	EVT_RIGHT_DOWN(WaveWindow::OnMouse)
	EVT_KEY_DOWN(WaveWindow::OnKey)
	EVT_MENU(-1, WaveWindow::OnMenu)
END_EVENT_TABLE()


WaveFrame::WaveFrame(wxWindow *parent, unsigned char *data, int n_data, wxString fname):
    wxFrame(parent,-1,_T("Wave Frame"),wxPoint(200,200),wxSize(WAVE_WIDTH,WAVE_HEIGHT+20))
{//============================================================
	SetTitle(fname);
	wavewindow = new WaveWindow(this, data, n_data, fname);
	Show(true);
}


WaveFrame::~WaveFrame()
{//===================
	delete wavewindow;
}


WaveWindow::WaveWindow(wxWindow *parent, unsigned char *data, int n_data, wxString fname):
	wxScrolledWindow(parent,-1,wxDefaultPosition,wxDefaultSize,wxHSCROLL)
{//========================================================
	SetBackgroundColour(* wxWHITE);
	filename = wxFileName(fname);

	// get the 2-byte samples, after the 44 byte WAV header
	wavdata = data;
	samples = (short *)(&data[44]);
	n_samples = (n_data -44)/2;

	rate = (data[25]<<8) + data[24];
	n_markers = 0;
	cursor = 0;
	prev_cursor = 0;
	select1 = 0;
	select2 = 0;
	zoom = 0.5;

	SetZoom(0.5);
	Show(true);
}

WaveWindow::~WaveWindow()
{//======================
	if(wavdata != NULL)
		free(wavdata);
}

void WaveWindow::SetZoom(float newzoom)
{//====================================
	int width,h;
	int centre;
	int origin;

	GetClientSize(&width,&h);

	// find wave index at the centre of the window
	CalcUnscrolledPosition(width/2,0,&centre,&h);

	// find the scroll amount needed to keep this point in the centre with the new zoom
	centre = int((float)centre * newzoom/ zoom);
	origin = centre - width/2;

	zoom = newzoom;
	SetScrollbars(SCROLLUNITS,SCROLLUNITS, (int)(n_samples*zoom/SCROLLUNITS),6, origin/SCROLLUNITS,0);
	Refresh();
}  //  end of SpectDisplay::SetExtent




void WaveWindow::OnDraw(wxDC &dc)
{//============================
	wxRegion region;
	int ix;
	int x, y;
	int xx, yy;
	int x1,x2,y1,y2;
	int vX,vY,vW,vH;        // Dimensions of client area in pixels
	int y_zoom;
	int end;
	int start;
	int prev_x;
	wxRegionIterator upd(GetUpdateRegion()); // get the update rect list

	static wxBrush BRUSH_SELECTED(wxColour(255,250,220),wxSOLID);

	y_zoom = 0x10000 / WAVE_HEIGHT;

	if(select2 > select1)
	{
		// shade the selected region
		dc.SetPen(*wxTRANSPARENT_PEN);
		dc.SetBrush(BRUSH_SELECTED);
		dc.DrawRectangle(int(select1*zoom),0,int((select2-select1)*zoom),WAVE_HEIGHT);
	}

	while (upd)
	{
		vX = upd.GetX();
		vY = upd.GetY();
		vW = upd.GetW();
		vH = upd.GetH();

		CalcUnscrolledPosition(vX,vY,&x1,&y1);
		CalcUnscrolledPosition(vX+vW,vY+vH,&x2,&y2);
    // Repaint this rectangle

		dc.SetPen(*wxLIGHT_GREY_PEN);
		dc.DrawLine(x1,WAVE_HEIGHT/2, x2,WAVE_HEIGHT/2);

		end = int((float(x2) + 1)/zoom) + 1;
		if(end > n_samples)
			end = n_samples;
		start = int((float(x1) -1)/zoom) - 1;
		if(start < 0)
			start = 0;

		dc.SetPen(*wxBLACK_PEN);
		x = prev_x = -1;
		for(ix=start; ix<end; ix++)
		{
			xx = int(ix*zoom);
			yy = samples[ix]/y_zoom + WAVE_HEIGHT/2;
			if(x > prev_x)
				dc.DrawLine(x,y, xx,yy);
			x = xx;
			y = yy;
		}
		upd ++ ;
	}

	dc.SetPen(*wxRED_PEN);
	x = int(cursor * zoom);
	dc.DrawLine(x,0, x,WAVE_HEIGHT);

	dc.SetPen(*wxCYAN_PEN);
	for(ix=0; ix<n_markers; ix++)
	{
		x = int(float(markers[ix]) * zoom);
		dc.DrawLine(x,10, x,WAVE_HEIGHT-10);
	}

}  // end of WaveFile::OnDraw


void WaveWindow::OnMouse(wxMouseEvent& event)
{//========================================
	int  ix;

	if(event.RightDown())
	{
		PopupMenu(menu_wave);
		return;
	}

	if(samples == NULL) return;

	wxClientDC dc(this);
	PrepareDC(dc);

	wxPoint pt(event.GetLogicalPosition(dc));
	ix = int(float(pt.x) / zoom + 0.5);

	prev_cursor = cursor;
	cursor = ix;

	if(event.ShiftDown())
	{
		// set selection
		if(cursor > prev_cursor)
		{
			select1 = prev_cursor;
			select2 = cursor;
		}
		else
		{
			select1 = cursor;
			select2 = prev_cursor;
		}
	}
	else
	{
		// clear selection
		select2 = 0;
	}
	Refresh();
}  // end of WaveFile::OnMouse



void WaveWindow::OnMenu(wxCommandEvent& event)
{//=============================================
	int id;
	int code;
	wxKeyEvent keyevent;
	static int key[] = {0,WXK_F1,'M','C','Z','+','-'};

	// simulate a KeyEvent that corresponds to this menu entry
	id = event.GetId();
	code = key[id];
	keyevent.m_keyCode = code & 0xfff;
	if(code & 0x1000)
		keyevent.m_controlDown = TRUE;
	else
		keyevent.m_controlDown = FALSE;
	if(code & 0x2000)
		keyevent.m_shiftDown = TRUE;
	else
		keyevent.m_shiftDown = FALSE;
	OnKey(keyevent);
}


void WaveWindow::OnKey(wxKeyEvent& event)
{//====================================
	int ix;
	int key;
	int cursor1;
	int movement;
	wxRect r;

	movement = int(1.0/zoom);
	if(movement <= 0)
		movement = 1;
	cursor1 = cursor;
	key = event.GetKeyCode();

	switch(key)
	{
	case WXK_RIGHT:
		if(!event.ShiftDown())
			movement *= 6;
		cursor += movement;

		if(cursor >= n_samples)
			cursor = n_samples-1;
Refresh();
//		RefreshRect(wxRect((cursor1-1)*zoom-1,0,(movement+4)*zoom+1,WAVE_HEIGHT),true);
		break;

	case WXK_LEFT:
		if(!event.ShiftDown())
			movement *= 6;
		cursor -= movement;

		if(cursor < 0)
			cursor = 0;
Refresh();
//		RefreshRect(wxRect((cursor-1)*zoom-1,0,(movement+4)*zoom+1,WAVE_HEIGHT),true);
		break;

	case '=':
	case '+':
		SetZoom(zoom * 1.414);
		break;

	case '-':
	case '_':
		if((n_samples * zoom) < WAVE_WIDTH/2)
			return;
		SetZoom(zoom / 1.414);
		break;

	case 'C':
		if(n_markers <= 0) break;
		ix = markers[--n_markers];
		Refresh();
		RefreshRect(wxRect(int((ix-1)*zoom-1),0,4,WAVE_HEIGHT),true);
		break;

	case 'M':
		if(n_markers >= N_WAV_MARKERS-1) break;
		markers[n_markers++] = cursor;
		RefreshRect(wxRect(int((cursor-1)*zoom-1),0,4,WAVE_HEIGHT),true);
		break;

	case 'Z':
		Analyse();
		break;

	case WXK_F1:
		Play();
		break;

	default:
		event.Skip();
		break;
	}
}  // end of OnKey



void WaveWindow::Play(void)
{//========================
	int ix;
	short *p;
	int length;
	int value;
	char *fname_speech;

	fname_speech = WavFileName();
	if(OpenWaveFile(fname_speech,samplerate) != 0)
		return;

	// if there is a selection, play just that
	if(select2 > select1)
	{
		p = &samples[select1];
		length = select2 - select1;
	}
	else
	{
		p = &samples[0];
		length = n_samples;
	}

	for(ix=0; ix<length; ix++)
	{
		value = p[ix];
		fputc(value,f_wave);
		fputc(value>>8, f_wave);
	}

	CloseWaveFile(rate);
	PlayWavFile(fname_speech);
}


void WaveWindow::Analyse(void)
{//===========================
	int ix;
	int mk;
	int length;
	int start;
	int n_frames=0;
	int n_harm;
	int max_y=0;
	SpectFrame *sf;
	SpectSeq *seq;
	USHORT *spect;
	float hresult[256];
	SpectFrame *frames[N_WAV_MARKERS];

	for(mk=1; mk<n_markers; mk++)
	{
		start = markers[mk-1];
		length = markers[mk] - start;
		analyse_cycle(&samples[start],length, hresult, 1);

		frames[n_frames++] = sf = new SpectFrame;
		sf->time = (float)start/ rate;
		sf->pitch = (float)rate/length;
		sf->dx = sf->pitch;
		n_harm = int(9000/sf->pitch);
		spect = (USHORT *)malloc(n_harm * sizeof(USHORT));
		if(spect != NULL)
		{
			for(ix=0; ix<n_harm; ix++)
			{
				spect[ix] = int(hresult[ix+1] * 1.4);
				if(spect[ix] > max_y)
					max_y = spect[ix];
			}
			sf->nx = n_harm;
			sf->spect = spect;
		}
	}
	if((n_frames == 0) || ((seq = new SpectSeq(n_frames)) == NULL))
	{
		wxLogError(_T("No markers are set"));
		return;
	}

	for(ix=0; ix<n_frames; ix++)
	{
		seq->frames[ix] = frames[ix];
	}
	seq->duration = int(seq->frames[n_frames-1]->time * 1000);
	seq->amplitude = 100;
	seq->max_x = 9000;
	seq->max_y = max_y;

	wxFileOutputStream stream(_T("analysis"));
	seq->Save(stream,0);
	stream.Close();

	wxFileInputStream input(_T("analysis"));
	if(input.Ok() == FALSE)
	{
		wxLogError(_T("Failed to read analysis"));
		return;
	}

	seq->Load(input);
	seq->name = filename.GetName();
	seq->MakePitchenv(seq->pitchenv);

	// Make another frame, containing a canvas
	MyChild *subframe = new MyChild(myframe, filename.GetName(),
                                      wxPoint(10, 10), wxSize(300, 300),
                                      wxDEFAULT_FRAME_STYLE |
                                      wxNO_FULL_REPAINT_ON_RESIZE);

	// Give it a status line
	subframe->CreateStatusBar();

	int width, height;
	subframe->GetClientSize(&width, &height);
	SpectDisplay *canvas = new SpectDisplay(subframe, wxPoint(0, 0), wxSize(width, height), seq);
   currentcanvas = canvas;

	// Associate the menu bar with the frame
	subframe->SetMenuBar(MakeMenu(1));
	subframe->canvas = canvas;
	subframe->Show(TRUE);
}



void MyFrame::LoadWavFile()
{//========================
// Load a WAV file into a WAV display window
	FILE *f;
	int len;
	unsigned char *p;
	WaveFrame *w;
	wxString fname;
	char buf[80];

	fname = wxFileSelector(_T("Load WAV file"),path_wave,fname,_T(""),_T("*"),wxOPEN);
	if(fname.IsEmpty())
		return;

	strcpy(buf,fname.mb_str(wxConvLocal));
	len = GetFileLength(buf);

	f = fopen(buf,"rb");
	if(f==NULL)
		return;

	path_wave = wxString(wxFileName(fname).GetPath());

	p = (unsigned char *)malloc(len);
	if(p == NULL)
		return;
	fread(p,len,1,f);
	fclose(f);
	w = new WaveFrame(this, p, len, fname);
	len = 0;

}



void InitWaveDisplay(void)
{//=======================

	menu_wave = new wxMenu;
	menu_wave->Append(1,_T("Play	F1"));
	menu_wave->Append(2,_T("Set Marker	M"));
	menu_wave->Append(3,_T("Cancal Marker	C"));
	menu_wave->Append(4,_T("Analyse	Z"));
	menu_wave->Append(5,_T("Zoom In	+"));
	menu_wave->Append(6,_T("Zoom Out	-"));
}

