/******************************************************************************
 *
 * Copyright (c) 2000	TOSHIYUKI ARAI. ALL RIGHTS RESERVED. 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *  
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *	TypeTrainer.cpp
 *
 *****************************************************************************/



#include <sol\stdio.h>

#include "TypeTrainer.h"
#include "menuid.h"

#include <sol\PaintDC.h>


int TypeTrainer::Off= 	0;
int TypeTrainer::On  =	1;
int TypeTrainer::Pause =0;
int TypeTrainer::Run   =1;

int TypeTrainer:: LowSpeed		= 1300;
int TypeTrainer::MiddleSpeed 	= 1000;
int TypeTrainer::HighSpeed		= 700;


TypeTrainer::TypeTrainer(Application& applet, const char* name, Args& args)
	:ApplicationView(applet, name, args)
{
	Args	ar;

	addCallback(XmNmenuCallback, NNew, this, (Callback)&TypeTrainer::clear, NULL);
	addCallback(XmNmenuCallback, NQuit, this, (Callback)&TypeTrainer::quit, NULL);
	addCallback(XmNmenuCallback, NLow, this, (Callback)&TypeTrainer::setSpeed, NULL);
	addCallback(XmNmenuCallback, NMiddle, this, (Callback)&TypeTrainer::setSpeed, NULL);
	addCallback(XmNmenuCallback, NHigh, this, (Callback)&TypeTrainer::setSpeed, NULL);
	addCallback(XmNmenuCallback, NLeft1, this, (Callback)&TypeTrainer::setFinger, NULL);
	addCallback(XmNmenuCallback, NLeft2, this, (Callback)&TypeTrainer::setFinger, NULL);
	addCallback(XmNmenuCallback, NLeft3, this,(Callback)&TypeTrainer::setFinger, NULL);
	addCallback(XmNmenuCallback, NLeft4, this, (Callback)&TypeTrainer::setFinger, NULL);
	addCallback(XmNmenuCallback, NRight1, this, (Callback)&TypeTrainer::setFinger, NULL);
	addCallback(XmNmenuCallback, NRight2, this,(Callback)&TypeTrainer::setFinger, NULL);
	addCallback(XmNmenuCallback, NRight3, this, (Callback)&TypeTrainer::setFinger, NULL);
	addCallback(XmNmenuCallback, NRight4, this,(Callback)&TypeTrainer::setFinger, NULL);

	ar.reset();
	ar.set(XmNx, 8);
	ar.set(XmNy, 8);
	ar.set(XmNwidth, 74);
	ar.set(XmNheight, 10);
	ar.toPixelUnit();
	speedLabel.create(this, "Speed : Middle",ar);

	ar.reset();

	ar.set(XmNx, 8);
	ar.set(XmNy, 28);
	ar.set(XmNwidth, 72);
	ar.set(XmNheight, 10);
	ar.toPixelUnit();
	scoreLabel.create(this, "Score", ar);

	ar.reset();
	ar.set(XmNx, 106);
	ar.set(XmNy, 8);
	ar.set(XmNwidth, 103);
	ar.set(XmNheight, 33);
	ar.toPixelUnit();
	scoreList.create(this, "", ar);
	scoreList.disable();

	ar.reset();
	ar.set(XmNheight, (-12));
	//ar.set(XmNweight,  FW_BOLD);
	ar.set(XmNfaceName, "Courier");
	font.create(ar);

	ar.reset();

	ar.set(XmNx, 8);
	ar.set(XmNy, 47);
	ar.set(XmNwidth, 202);
	ar.set(XmNheight, 15);
	ar.toPixelUnit();
	sourceText.create(this, "", ar);
	sourceText.setFont(font);

	ar.reset();
	ar.set(XmNx, 8);
	ar.set(XmNy, 65);
	ar.set(XmNwidth, 202);
	ar.set(XmNheight, 15);
	ar.toPixelUnit();

	userText.create(this, "", ar);
	userText.setFont(font);

	max = 30;
	machine = new char[max];
	user    = new char[max];
	sourceText.setBuffer(machine, max);
	userText  .setBuffer(user,    max);
	mode  = Pause;
	timer = Off;
	finger = 1;
	count  = 0;
	score  = 0;
	createKeyBoard(12, 88);

	interval = MiddleSpeed;
	
	timerId = 1;
	finger = -1;
	ar.reset();
	statusbar = new StatusBar(this, "", ar);
	setStatusBar(statusbar);
	statusbar -> setParts(1, sizeArray);
	statusbar -> setText(0,  0, //SBT_POPOUT, 
		"Select a finger from the menu, and hit enter key to start a session.");

	addEventHandler(WM_CHAR, this, (Handler)&TypeTrainer::charInput, null);

	addEventHandler(WM_TIMER, this, (Handler)&TypeTrainer::timeout, null);	
	
}


void TypeTrainer::createKeyBoard(int x, int y)
{
	char label[2];
	Args arg;
	static char* keyTop[] = {
		"1234567890-=",
		"QWERTYUIOP[]",
		"ASDFGHJKL;' ",
		"ZXCVBNM,./",
	};

	for(int j = 0; j<4; j++) {
		char* ptr = keyTop[j];
		int i = 0;
		int n = 0;
		while(*ptr) {
			sprintf(label, "%c", *ptr);
			n = *ptr - ' ';
			arg.reset();
			arg.set(XmNx, x+5*j+15*i);
			arg.set(XmNy, y+15*j);
			arg.set(XmNwidth, 13);
			arg.set(XmNheight, 13);
			arg.set(XmNlabelString, label);
			arg.toPixelUnit();
			keySet[n].create(this, label, arg);
			keySet[n].setFont(font);
			keySet[n].disable();
			i++;
			ptr++;
		}
	}
}


void TypeTrainer::setFinger(Action& param)
{
	int i, n;
	int	len =0;

	Event& event = param.getEvent();
	finger = event.getMenuId();
	finger = finger/100 - 1;

	char* keys = getActiveKeySet();
	if(keys) {
		len = strlen(keys);
	}


	for(i = 0; i<100; i++) {
		if (keySet[i].getWindow())
			keySet[i].disable();
	}

	for(i = 0; i<len; i++) {
		n = keys[i] - ' ';
		keySet[n].enable();
	}
}


char* TypeTrainer::getActiveKeySet()
{
	static char* subKeySet[] = {
		"1QAZ",		//	Left4
		"2WSX",		//	Left3
		"3EDC",		//	Left2
		"45RFVTGB",	//	Left1
		"67YHNUJM",	//	Right1
		"8IK,",		//	Right2
		"9OL.",		//	Right3
		"0P;/"		//	Right4
		};
	return subKeySet[finger];
}


TypeTrainer::~TypeTrainer()
{
	delete [] machine;
	delete [] user;
	killTimer(timerId);
}




void TypeTrainer::initialize()
{
	machineCounter = 0;
	userCounter    = 0;

	for(int i = 0; i<max; i++) {
		machine[i] = ' ';
		user[i]    = ' ';
	}

	sourceText.clear();
	userText  .clear();
	if(timer == Off) {
		setTimer(timerId, interval, NULL);
		timer = On;
	}
}


void TypeTrainer::scoreToListBox()
{
	char string[100];

	if(count != 0) {
		sprintf(string," No%3d     %3d", count, score);
		scoreList .addString(string);
		scoreLabel.setText("Score");
	}
}


long TypeTrainer::charInput(Event& event)
{
	WORD charCode = event.getWParam();
	Printf("Char %d \r\n", charCode);
	
	// finger is not selected.
	if (finger == -1) {
		MessageBeep(0);		
		return 1;
	}

	if(mode == Pause && charCode == 13) {
		mode = Run;
		scoreToListBox();
		count++;
		initialize();
	}
	
	else if(mode == Run) {
		manageCharInput(charCode);
	}

	return 1;
}


void TypeTrainer::manageCharInput(WORD charCode)
{
	int n;

	user[userCounter] = (unsigned char)charCode;

	if(userCounter >= 0 && machine[userCounter] != user[userCounter]) {
		MessageBeep(0);
	}
	userText.putChar(userCounter, (char)charCode);

	if(charCode >= 'a' && charCode <= 'z') {
		n = charCode - ('a'-'A') - ' ';
	}
	if(charCode > ' ' && charCode <'A') {
		n = charCode - ' ';
	}
	else {
		n = charCode - ' ';
	}
	if((n >= 0) && (n<100) && (keySet[n].getWindow())) {
		pressedKey = keySet[n].getWindow();
	}
}


void TypeTrainer::clear(Action& param)
{
	count = 0;
	scoreList .resetContent();
	sourceText.clear();
	userText  .clear();
	if (timer == On) {
		killTimer(timerId);
		timer = Off;
	}
}


void TypeTrainer::quit(Action& param)
{
	destroyWindow();
}


void TypeTrainer::setSpeed(Action& param)
{
	Event& event = param.getEvent();
	int id = event.getMenuId();

	switch(id) {
	case NLow:
		speedLabel.setText("Speed : Low");
		interval = LowSpeed;
		break;

	case NMiddle:
		speedLabel.setText("Speed : Middle");
		interval = MiddleSpeed;
		break;

	case NHigh:
		speedLabel.setText("Speed : High");
		interval = HighSpeed;
		break;

	default:
		break;
	}
}


int TypeTrainer::generateRandomKey()
{
	char* keys = getActiveKeySet();
	int len = strlen(keys);

	int r = rand() % len;
	if((r >= len) || (r<0) ) {
		return generateRandomKey();
	}

	int k = keys[r];
	int n = k  - ' ';

	if((k > ' ') && (k <= 'Z') && (keySet[n].getWindow() ) &&
	   (keySet[n].getWindow() != getPrevKey()) ) {
		if(k < 'A') {
			k = k;
		}
		else {
			k = k + ('a'- 'A');
		}
		machine[machineCounter] =(unsigned char)k;
		userCounter = machineCounter;
		sourceText.putChar(machineCounter, k);
		machineCounter++;
		return n;
	}
	else {
		return generateRandomKey();
	}
}


HWND TypeTrainer::getPrevKey()
{
	return prevKey;
}


void TypeTrainer::displayScore()
{
    	int total = 0;
	char scoreBuffer[100];

	for(int i = 0; i<max; i++) {
		if(machine[i] == user[i]) {
			total++;
		}
	}
	score = (total*100)/max;
	sprintf(scoreBuffer,"Score = %d",score);
	scoreLabel.setText(scoreBuffer);
}


void TypeTrainer::invertKey(HWND hwnd)
{
	if(hwnd != NULL) {
		RECT rect;
		::GetClientRect(hwnd, &rect);
		HDC hdc = GetDC(prevKey);
		InvertRect(hdc, &rect);
		ReleaseDC(hwnd, hdc);
	}
}


long TypeTrainer::timeout(Event& event)
{
	if(mode == Pause) {
		return 1;
	}

	if(mode == Run &&  machineCounter == max) {
		mode = Pause;
		displayScore();
		killTimer(timerId);
		timer = Off;
		invertKey(prevKey);
		prevKey = NULL;
		return 1;
	}
 	int n = generateRandomKey();

	invertKey(prevKey);
	prevKey = keySet[n].getWindow();
	invertKey(prevKey);
	return 1;
}


long TypeTrainer::size(Event& event) 
{
	ApplicationView::size(event);

	StatusBar* statusbar = getStatusBar();
	LPARAM s = event.getLParam();
	sizeArray[0] = -1;
	statusbar->setParts(1, sizeArray);

	return 0;
}
