/********************************************************************

PhatBeat - A simple beat-counter for producers, DJs, or anybody else.
Copyright (C) 2004 Jack Chatterton <chattj AT postreal.org>

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.

*********************************************************************/

// The X11 frontend for phatbeat. Uses SDL libraries, so no static
// linking please!

#include "BeatCounter.h"
using namespace std;
#include "phatx.h"
using namespace std;
#include "resize.h"
using namespace std;
#include "numbers.h"
using namespace std;
#include "portability.h"
using namespace std;

#include "SDL.h"
using namespace std;
#include "SDL_image.h"
using namespace std;

#include <math.h>
using namespace std;


// 100,000 ms in 1 second.
#define MS_DIVIDER .001

BeatCounter phat(1);

// Scale of all operations as compared with a height of 600
float scale;

// Values needed for actual operation of beatcounter.
// Also, values to check against changes, to speed up 
// drawing time.
float BPMDisplayVal, SPMDisplayVal;
float oldBPMDisplayVal, oldSPMDisplayVal;
int BeatsToCount, BeatsInMeasure;
int oldBeatsToCount, oldBeatsInMeasure;
bool resetNextTap = false;

// These two together will hold the interface's state
enum Button {	
	B_AC_DN,
	B_TAP_DN,
	B_BTC_UP_DN,
	B_BTC_DN_DN,
	B_BIM_UP_DN,
	B_BIM_DN_DN,
	B_ALL_UP,
	BUTTON_COUNT = 7 
};
int PressedButton;

// SDL surfaces we'll need.
SDL_Surface *renderme, *window, *black;
SDL_Surface *bkgnd, *tap_up, *tap_dn, *AC_up, *AC_dn, *uparrow_up, \
	*uparrow_dn, *dnarrow_up, *dnarrow_dn;
	
// Create the numbers. These must be global so continuity
// between different drawWindow calls can be preserved.
SDL_Surface *BPMimg, *secimg, *btcimg, *bimimg;

// Way simple routines to replace a constant with a scaled 
// constant, for display on (relative) screen.
int Rescale(int fixme) {
	return (int)ceil(fixme * scale);
}
float Rescale(float fixme) {
	return (fixme * scale);
}

// Create window with all state as needed, and display.
void drawWindow() {
	// Temporary rectangle to be used later.
	SDL_Rect temprect;

	
	if(BPMDisplayVal != oldBPMDisplayVal) {
		BPMimg = fourDigitNumber(BPMDisplayVal);
		temprect.w = Rescale(280);
		temprect.h = Rescale(120);
		BPMimg = Resize(BPMimg, &temprect);
	}
	oldBPMDisplayVal = BPMDisplayVal;
	
	if(SPMDisplayVal != oldSPMDisplayVal) {	
		secimg = fourDigitNumber(SPMDisplayVal);
		temprect.w = Rescale(140); 
		temprect.h = Rescale(60);
		secimg = Resize(secimg, &temprect);
	}
	oldSPMDisplayVal = SPMDisplayVal;

	if(BeatsToCount != oldBeatsToCount) {
		btcimg = twoDigitInteger(BeatsToCount);
		temprect.w = Rescale(70); 
		temprect.h = Rescale(60);
		btcimg = Resize(btcimg, &temprect);
	}
	oldBeatsToCount = BeatsToCount;
	
	if(BeatsInMeasure != oldBeatsInMeasure) {
		bimimg = twoDigitInteger(BeatsInMeasure);
		temprect.w = Rescale(70); 
		temprect.h = Rescale(60);
		bimimg = Resize(bimimg, &temprect);
	}
	oldBeatsInMeasure = BeatsInMeasure;
	
	// Place blackground	
	temprect.x = 0; temprect.y = 0;
	SDL_BlitSurface(black, NULL, window, &temprect);
	// Place BPM
	temprect.x = Rescale(60); 
	temprect.y = Rescale(40);
	SDL_BlitSurface(BPMimg, NULL, window, &temprect);
	// Place seconds
	temprect.x = Rescale(100); temprect.y = Rescale(200);
	SDL_BlitSurface(secimg, NULL, window, &temprect);
	// Place setting numbers, beats to count and beats in measure
	temprect.x = Rescale(40); temprect.y = Rescale(300);
	SDL_BlitSurface(btcimg, NULL, window, &temprect);
	temprect.y = Rescale(360);
	SDL_BlitSurface(bimimg, NULL, window, &temprect);
	
	// Add the background
	temprect.x = 0; temprect.y = 0;
	SDL_BlitSurface(bkgnd, NULL, window, &temprect);
	
	// Add main buttons
	temprect.x = Rescale(130); temprect.y = Rescale(440);
	if(PressedButton == B_AC_DN)
		SDL_BlitSurface(AC_dn, NULL, window, &temprect);
	else
		SDL_BlitSurface(AC_up, NULL, window, &temprect);
	temprect.x = Rescale(130); temprect.y = Rescale(490);
	if(PressedButton == B_TAP_DN)
		SDL_BlitSurface(tap_dn, NULL, window, &temprect);
	else
		SDL_BlitSurface(tap_up, NULL, window, &temprect);

	// Add up/dn buttons.
	temprect.x = Rescale(125); temprect.y = Rescale(300);
	if(PressedButton == B_BTC_UP_DN)
		SDL_BlitSurface(uparrow_dn, NULL, window, &temprect);
	else
		SDL_BlitSurface(uparrow_up, NULL, window, &temprect);
	temprect.y = Rescale(330);
	if(PressedButton == B_BTC_DN_DN)
		SDL_BlitSurface(dnarrow_dn, NULL, window, &temprect);
	else
		SDL_BlitSurface(dnarrow_up, NULL, window, &temprect);
	temprect.y = Rescale(360);
	if(PressedButton == B_BIM_UP_DN)
		SDL_BlitSurface(uparrow_dn, NULL, window, &temprect);
	else	
		SDL_BlitSurface(uparrow_up, NULL, window, &temprect);
	temprect.y = Rescale(390);
	if(PressedButton == B_BIM_DN_DN)
		SDL_BlitSurface(dnarrow_dn, NULL, window, &temprect);
	else
		SDL_BlitSurface(dnarrow_up, NULL, window, &temprect);
	
	// Show window
	SDL_Flip(window);
}

// All Clear event. Resets the beat counter and everything else.
void doAC() {
	phat = BeatCounter(BeatsInMeasure);
	resetNextTap = false;
	BPMDisplayVal = (float)BeatsToCount;
	SPMDisplayVal = 0.0;
	drawWindow();
}

// Tap event. Adds a new time to the beatcounter class, or generates
// the BPM and spm values, or does an AC event after those have
// been displayed.
void doTap() {
	if(resetNextTap) 
		doAC();
	else if(BPMDisplayVal >= 2) {
		phat.AddBeat((double)(SDL_GetTicks() * MS_DIVIDER));
		BPMDisplayVal--;
	}
	else {
		phat.AddBeat((double)(SDL_GetTicks() * MS_DIVIDER));
		BPMDisplayVal = (float)phat.getRA_BPM();
		SPMDisplayVal = (float)phat.getMeasureSecs();
		resetNextTap = true;
	}
	drawWindow();
}

// Mouse click event handler. This could go in the inputLoop, but 
// I'm doing it this way to add code whitespace. Still and all, 
// there is lots of ugly in this routine; maybe it would be better
// handled with SDL_Rects of some kind (ie if x,y are in SDL_Rect
// then do stuff).
void doMouseClick(Uint8 button, Uint16 x, Uint16 y) {
	int realx = (int)x, realy = (int)y;
	
	// Do nothing if user right clicks or etc.
	if (button != SDL_BUTTON_LEFT)
		return;
	
	if ((realx > Rescale(130)) && (realx < Rescale(280)) && (realy > Rescale(450)) && (realy < Rescale(500))) {
		PressedButton = B_AC_DN;
		doAC();
	}
	else if ((realx > Rescale(130)) && (realx < Rescale(280)) && (realy > Rescale(510)) && (realy < Rescale(600))) {
		PressedButton = B_TAP_DN;
		doTap();
	}
	else if ((realx > Rescale(130)) && (realx < Rescale(160)) && (realy > Rescale(300)) && (realy < Rescale(330))) {
		PressedButton = B_BTC_UP_DN;
		if(BeatsToCount < MAX_BEATS)
			BeatsToCount++;
		doAC();
	}
	else if ((realx > Rescale(130)) && (realx < Rescale(160)) && (realy > Rescale(330)) && (realy < Rescale(360))) {
		PressedButton = B_BTC_DN_DN;
		if(BeatsToCount > 4)
			BeatsToCount--;
		doAC();
	}
	else if ((realx > Rescale(130)) && (realx < Rescale(160)) && (realy > Rescale(360)) && (realy < Rescale(390))) {
		PressedButton = B_BIM_UP_DN;
		if(BeatsInMeasure < 99) 
			BeatsInMeasure++;
		doAC();
	}
	else if ((realx > Rescale(130)) && (realx < Rescale(160)) && (realy > Rescale(390)) && (realy < Rescale(420))) {
		PressedButton = B_BIM_DN_DN;
		if(BeatsInMeasure > 1)
			BeatsInMeasure--;
		doAC();
	}
	
	return;
}


// Main input loop. Looks for keyboard events, mouseclicks, etc.
bool inputLoop() {
	SDL_Event event;
	SDL_WaitEvent(&event);
	if(event.type == SDL_QUIT)
		return true;
	else if(event.type == SDL_KEYDOWN)
		switch(event.key.keysym.sym) {
			case SDLK_ESCAPE:
	   		case SDLK_q:				
				return true;
			case SDLK_a:
				PressedButton = B_AC_DN;
				doAC();
				break;
			default:
				PressedButton = B_TAP_DN;
				doTap();
		}
	else if(event.type == SDL_KEYUP) {
		PressedButton = B_ALL_UP;
		drawWindow();
	}
	else if(event.type == SDL_MOUSEBUTTONDOWN) 
		doMouseClick(event.button.button, event.button.x, event.button.y);
	else if(event.type == SDL_MOUSEBUTTONUP) {
		PressedButton = B_ALL_UP;
		drawWindow();
	}
	
	return false;
}

// Load  & resize phatbeat interface.
// Each 3 line segment first creates and loads a temporary
// surface, as well as what will be the final surface.
// To get the final surface's size, it uses the temporary
// surface's size multiplied by the scaling factor.
// After all of this, the img is resized (and blitted)
// to the final img.
void loadInterfaceImages() {
	SDL_Rect temprect;
	
	bkgnd = portable_IMG_Load("images/bkgnd_test5.png");
	temprect.w = Rescale(bkgnd->w);
	temprect.h = Rescale(bkgnd->h);
	bkgnd = Resize(bkgnd, &temprect);

	black = portable_IMG_Load("images/blackgnd.png");
	bkgnd = Resize(bkgnd, &temprect);
	
	uparrow_up = portable_IMG_Load("images/uparrow_up.png");
	temprect.w = Rescale(uparrow_up->w);
	temprect.h = Rescale(uparrow_up->h);
	uparrow_up = Resize(uparrow_up, &temprect);

	uparrow_dn = portable_IMG_Load("images/uparrow_dn.png");
	temprect.w = Rescale(uparrow_dn->w);
	temprect.h = Rescale(uparrow_dn->h);
	uparrow_dn = Resize(uparrow_dn, &temprect);
	
	dnarrow_up = portable_IMG_Load("images/dnarrow_up.png");
	temprect.w = Rescale(dnarrow_up->w);
	temprect.h = Rescale(dnarrow_up->h);
	dnarrow_up = Resize(dnarrow_up, &temprect);
	
	dnarrow_dn = portable_IMG_Load("images/dnarrow_dn.png");
	temprect.w = Rescale(dnarrow_dn->w);
	temprect.h = Rescale(dnarrow_dn->h);
	dnarrow_dn = Resize(dnarrow_dn, &temprect);
	
	tap_up = portable_IMG_Load("images/tap_up.png");
	temprect.w = Rescale(tap_up->w);
	temprect.h = Rescale(tap_up->h);
	tap_up = Resize(tap_up, &temprect);

	tap_dn = portable_IMG_Load("images/tap_dn.png");
	temprect.w = Rescale(tap_dn->w);
	temprect.h = Rescale(tap_dn->h);
	tap_dn = Resize(tap_dn, &temprect);
	
	AC_up = portable_IMG_Load("images/AC_up.png");
	temprect.w = Rescale(AC_up->w);
	temprect.h = Rescale(AC_up->h);
	AC_up = Resize(AC_up, &temprect);
	
	AC_dn = portable_IMG_Load("images/AC_dn.png");
	temprect.w = Rescale(AC_dn->w);
	temprect.h = Rescale(AC_dn->h);
	AC_dn = Resize(AC_dn, &temprect);
}
	

int main(int argc, char *argv[]) {
	
	// Originally the program was supposed to scale with the user's
	// monitor size, so it would appear the same everywhere. 
	// Unfortunately, a large window makes for slow blitting times
	// on some computers (Pentium 500MHz or slower, it seems) if
	// the window is too large.  Hence, this line constrains the
	// window to 200x300.
	scale = .55;


	// Make sure SDL is healthy.
	if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) < 0) {
		return -1;
	}

	// Have graphics code load its number images.
	loadNumberImages();	// from numbers.h
	loadInterfaceImages();	// from phatx.cpp

	// Create display surfaces.
	int xval = Rescale(400), yval = Rescale(620);
	window = SDL_SetVideoMode(xval, yval, 32, SDL_DOUBLEBUF|SDL_HWSURFACE|SDL_HWACCEL);

	// Set initial values.
	SPMDisplayVal = 0.0;
	BeatsToCount = 8;
	BeatsInMeasure = 4;
	BPMDisplayVal = (float) BeatsToCount;
	oldBPMDisplayVal = -1; oldSPMDisplayVal = -1;
	oldBeatsToCount = -1; oldBeatsInMeasure = -1;
	PressedButton = B_ALL_UP;
	phat = BeatCounter(BeatsInMeasure);
	
	// Draw the window.
	SDL_WM_SetCaption(" p h a t b e a t","p h a t b e a t");
	SDL_WM_SetIcon(portable_IMG_Load("images/icon.png"), NULL);
	drawWindow();

	// Enter input loop.
	while(!inputLoop());
		
	SDL_Quit();

	return 0;
}
