/* vim: set noexpandtab shiftwidth=8 cino=:
 ***************************************************************************
 *            controls.cc
 *
 *  Sat Jun  4 08:18:38 2005
 *  Copyright  2005  Joe Venzon
 *  joe@venzon.net
 ****************************************************************************/

/*
 *  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 Library 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 "controls.h"

void CONTROL::Dump()
{
	cout << "Name: " << controlname << endl;
	cout << "Type: " << (int) controltype << " = ";
	if (controltype == Joy)
	{
		cout << "Joy" << endl;
		cout << "Joy number: " << joynum << endl;
		cout << "Input type: ";
		if (joytype == Button)
		{
			cout << "Button" << endl;
			cout << "Button number: " << joybutton << endl;
			cout << "One time: " << onetime << endl;
		}
		else if (joytype == Axis)
		{
			cout << "Axis" << endl;
			cout << "Axis number: " << joyaxis << endl;
			cout << "Axis type: " << joyaxistype << endl;
		}
		else
			cout << "Unknown" << endl;
	}
	else if (controltype == Key)
	{
		cout << "Key" << endl;
		cout << "Keycode: " << keycode << endl;
		cout << "One time: " << onetime << endl;
	}
	else if( controltype == Mouse )
	{
		cout << "Mouse" << endl;
		if( mousetype == MButton )
		{
			cout << "Button: " << mbutton << endl;
			cout << "One time: " << onetime << endl;
		}
		else if( mousetype == Motion )
		{
			cout << "Direction: " << mdir << endl;
		}
		else
		{
			cout << "unknown" << endl;
		}
	}
	else
		cout << "Unknown" << endl;
}


void GAMECONTROLS::LoadControls(string fname)
{
	UpdateSettings();

	error_log.open((settings.GetSettingsDir() + "/logs/controls.log").c_str());
	
	LoadCalibration();
	
	ifstream controlfile(fname.c_str());
	
	if (!controlfile)
	{
		cerr << "Couldn't open control file: " << fname << endl;
		Clear();
		return;
	}
	
	//first pass to get the number of lines
	string input;
	input = utility.sGetLine(controlfile);
	int numcontrols = 0;
	while (input != "!!!END OF FILE!!!")
	{
		input = utility.sGetLine(controlfile);
		if(input.find("joysticktype") == string::npos && input.find("!!!END OF FILE!!!") == string::npos)
			numcontrols++;
	}
	
	//delete [] controls;
	controls.clear();
	
	//controls = new CONTROL [numcontrols];
	
	controlfile.close();
	controlfile.open(fname.c_str());
	controlfile.clear();
	
	//second pass to actually read the file
	
	int i;
	for (i = 0; i < numcontrols; i++)
	{
		input = utility.sGetParam(controlfile);
		if (input == "joysticktype")
		{

			joysticktype = utility.sGetParam(controlfile);
			//input = utility.sGetParam(controlfile);
			compensation = utility.sGetParam(controlfile);
			if (joysticktype != "wheel" && joysticktype != "joystick")
			{
				cout << "invalid joystick type in " + settings.GetSettingsDir() + "/controls: " << joysticktype << endl;
				joysticktype = "joystick";
			}
			if (compensation != "off" && compensation != "low" && compensation != "med" && compensation != "high" && compensation != "900to200" && compensation != "speed1" && compensation != "speed2")
			{
				cout << "invalid joystick touchiness compensation in " + settings.GetSettingsDir() + "/controls: " << compensation << endl;
				compensation = "off";
			}
			i--;
		}
		else
		{
			CONTROL newctrl;
			newctrl.SetName(input);
			//controls[i].SetName(utility.sGetParam(controlfile));
			newctrl.SetType(utility.sGetParam(controlfile));
			if (newctrl.GetType() == Joy)
			{
				newctrl.SetJoyNum(utility.iGetParam(controlfile));
				newctrl.SetJoyType(utility.sGetParam(controlfile));
				if (newctrl.GetJoyType() == Button)
				{
					newctrl.SetJoyButton(utility.iGetParam(controlfile));
					newctrl.SetOneTime(utility.bGetParam(controlfile));
					
					//if (controls[i].GetOneTime())
						newctrl.SetJoyPushDown(utility.bGetParam(controlfile));
				}
				else if (newctrl.GetJoyType() == Axis)
				{
					newctrl.SetJoyAxis(utility.iGetParam(controlfile));
					string axis_type = utility.sGetParam(controlfile);
					if (axis_type == "positive")
						newctrl.SetJoyAxisType(Positive);
					else if (axis_type == "negative")
						newctrl.SetJoyAxisType(Negative);
					else if (axis_type == "both")
						newctrl.SetJoyAxisType(Both);
					else
					{
						ControlParseError(i, controlfile);
						i = numcontrols; //exit loop
					}
					float ftemp = utility.fGetParam(controlfile);
					newctrl.SetDeadzone(ftemp);
					ftemp = utility.fGetParam(controlfile);
					newctrl.SetExponent(ftemp);
					ftemp = utility.fGetParam(controlfile);
					newctrl.SetGain(ftemp);
				}
				else
				{
					ControlParseError(i, controlfile);
					i = numcontrols; //exit loop
				}
			}
			else if (newctrl.GetType() == Key)
			{
				newctrl.SetKeyCode(keyman.GetKey(utility.sGetParam(controlfile)));
				newctrl.SetOneTime(utility.bGetParam(controlfile));
				newctrl.SetKeyPushDown(utility.bGetParam(controlfile));
			}
			else if( newctrl.GetType() == Mouse )
			{
				newctrl.SetMouseType( utility.sGetParam( controlfile ) );
				if( newctrl.GetMouseType() == MButton )
				{
					newctrl.SetMouseButton( utility.sGetParam( controlfile ) );
					newctrl.SetOneTime( utility.bGetParam( controlfile ) );
					newctrl.SetMousePushDown( utility.bGetParam( controlfile ) );
				}
				else if( newctrl.GetMouseType() == Motion )
				{
					newctrl.SetMouseDirection( utility.sGetParam( controlfile ) );
				}
				else
				{
					ControlParseError( i, controlfile );
					i = numcontrols; // exit loop
				}
			}
			else
			{
				ControlParseError(i, controlfile);
				i = numcontrols; //exit loop
			}
			
			controls.push_back(newctrl);
		}
	}

/*
	//for debugging
	for( list<CONTROL>::iterator i = controls.begin(); i != controls.end(); ++i )
	{
		i->Dump();
		cout << endl;
	}
*/
}

void GAMECONTROLS::ControlParseError(int line, ifstream & ffrom)
{
	error_log << "Error parsing control number " << line << endl;
	ffrom.unget();
	ffrom.unget();
	ffrom.unget();
	ffrom.unget();
	ffrom.unget();
	string errorword;
	ffrom >> errorword;
	ffrom.seekg(0, ios_base::beg);
	string errorline;
	int i;
	for (i = 0; i <= line; i++)
		errorline = utility.sGetLine(ffrom);
	error_log << "The line was: " << errorline << endl;
	//error_log << "The error is in the vicinity of: " << errorword << endl;
}

GAMECONTROLS::GAMECONTROLS()
{
	Clear();
}

void GAMECONTROLS::Clear()
{
	controls.clear();
	//numcontrols = 0;
	
	joysticktype = "joystick";
	compensation = "off";
	
	int i;
	for (i = 0; i < MAX_JOYSTICKS; i++)
	{
		int m;
		for (m = 0; m < MAX_AXES; m++)
		{
			calibration[i][m][0] = -1;
			calibration[i][m][1] = 1;
		}
	}
}

extern bool verbose_output;
GAMECONTROLS::~GAMECONTROLS()
{
	if (verbose_output)
		cout << "gamecontrols deinit" << endl;
	
	//delete [] controls;
	controls.clear();
	error_log.close();
}

/*list <CONTROL> & GAMECONTROLS::GetControls()
{
	return controls;
}*/

int GAMECONTROLS::GetNumControls()
{
	//return numcontrols;
	return controls.size();
}

void GAMECONTROLS::SetControls(CONTROL * newcontrols, int newnum)
{
	/*delete [] controls;
	controls = newcontrols;
	numcontrols = newnum;*/
	
	controls.clear();
	for (int i = 0; i < newnum; i++)
		controls.push_back(newcontrols[i]);
}

void GAMECONTROLS::WriteControlFile()
{
	WriteControlFile(settings.GetSettingsDir() + "/controls");
}

void GAMECONTROLS::WriteControlFile(string fname)
{
	CONFIGFILE c;
	
	ofstream controlfile(fname.c_str());
	
	if (!controlfile)
		error_log << "Couldn't open control file for writing: " << fname << endl;
	
	controlfile << "#Generated by VDrift, feel free to edit manually" << endl << endl;

	controlfile << "# joystick type:  wheel or joystick, touchiness compensation" << endl;	
	controlfile << "joysticktype " << joysticktype << " " << compensation << endl << endl;


	// save settings in config file
	settings.Set( "joystick.type", joysticktype );
	settings.Set( "joystick.touchcomp", compensation );

	controlfile << "# for a joystick button:" << endl << "# [function name] joy [joystick number] button [button number] [true for not held down, false for held down] [true to trigger when button is pressed, false to trigger when button is released]" << endl << endl;

	controlfile << "# for a joystick axis:" << endl << "# [function name] joy [joystick number] axis [axis number] [true for positive axis, false for negative axis] [deadzone] [exponent] [gain]" << endl << endl;

	controlfile << "# for a keyboard key:" << endl << "# [fuction name] key [key name as defined in lists/keys] [true for keys that aren't held down, false for keys that are]  [true to trigger when key is pressed, false to trigger when key is released]" << endl << endl;

	//int i;
	//for (i = 0; i < numcontrols; i++)
	for (ControlIteratorReset(); ControlIteratorGetControl() != NULL; ControlIteratorIncrement())
	{
		CONTROL & curctrl = *(ControlIteratorGetControl());
		controlfile << curctrl.GetName() << " ";
		if (curctrl.GetType() == Joy)
		{
			controlfile << "joy ";
			controlfile << curctrl.GetJoyNum() << " ";
			
			if (curctrl.GetJoyType() == Axis)
			{
				controlfile << "axis ";
				controlfile << curctrl.GetJoyAxis() << " ";
				switch (curctrl.GetJoyAxisType()) {
					case Positive:
						controlfile << "positive";
						break;
					case Negative:
						controlfile << "negative";
						break;
					case Both:
						controlfile << "both";
						break;
				}
				controlfile << " " << curctrl.GetDeadzone() << " " << curctrl.GetExponent() << " " << curctrl.GetGain();
			}
			else if (curctrl.GetJoyType() == Button)
			{
				controlfile << "button ";
				controlfile << curctrl.GetJoyButton() << " ";
				if (curctrl.GetOneTime())
					controlfile << "true ";
				else
					controlfile << "false ";
				if (curctrl.GetJoyPushDown())
					controlfile << "true";
				else
					controlfile << "false";
			}
		}
		else if (curctrl.GetType() == Key)
		{
			controlfile << "key ";
			controlfile << keyman.GetKeyName(curctrl.GetKeyCode()) << " ";
			if (curctrl.GetOneTime())
				controlfile << "true ";
			else
				controlfile << "false ";
			if (curctrl.GetKeyPushDown())
				controlfile << "true";
			else
				controlfile << "false";
		}
		else if( curctrl.GetType() == Mouse )
		{
			controlfile << "mouse ";
			if( curctrl.GetMouseType() == MButton )
			{
				controlfile << "button ";
				controlfile << curctrl.GetMouseButton() << " ";
				if( curctrl.GetOneTime() )
					controlfile << "true ";
				else
					controlfile << "false ";
				if( curctrl.GetMousePushDown() )
					controlfile << "true ";
				else
					controlfile << "false ";
			}
			else if( curctrl.GetMouseType() == Motion )
			{
				string direction;
				int mdir = curctrl.GetMouseDirection();
				if( mdir == Up )
					direction = "up";
				else if( mdir == Down )
					direction = "down";
				else if( mdir == Left )
					direction = "left";
				else if( mdir == Right )
					direction = "right";

				controlfile << "motion " << direction;
			}
		}
		controlfile << endl;
	}
	
	controlfile.close();
}

void GAMECONTROLS::LoadCalibration()
{
	ifstream cf;
	cf.open((settings.GetSettingsDir() + "/calibration").c_str());
	if (cf)
	{
		int i, m;
		for (i = 0; i < MAX_JOYSTICKS; i++)
		{
			for (m = 0; m < MAX_AXES; m++)
			{
				calibration[i][m][0] = utility.fGetParam(cf);
				calibration[i][m][1] = utility.fGetParam(cf);
				
				if (calibration[i][m][0] > -0.1)
					calibration[i][m][0] = -0.1;
				if (calibration[i][m][1] < 0.1)
					calibration[i][m][1] = 0.1;
			}
		}
	
		cf.close();
	}
}

void GAMECONTROLS::WriteCalibration()
{
	ofstream cf;
	cf.open((settings.GetSettingsDir() + "/calibration").c_str());
	if (cf)
	{
		cf << "# VDrift joystick calibration file.\n#Each line is an axis with the format [min] [max]" << endl << endl;
		int i, m;
		for (i = 0; i < MAX_JOYSTICKS; i++)
		{
			for (m = 0; m < MAX_AXES; m++)
				cf << calibration[i][m][0] << " " << calibration[i][m][1] << endl;
		}
		
		cf.close();
	}
	else
		cout << "Error writing calibration file to " + settings.GetSettingsDir() + "/calibration" << endl;
}

float GAMECONTROLS::GetCalibration(int joynum, int joyaxis, bool max)
{
	float c;
	
	if (joynum >= MAX_JOYSTICKS || joyaxis >= MAX_AXES)
	{
		if (max)
			return 1;
		else
			return -1;
	}

	if (max)
		c = calibration[joynum][joyaxis][1];
	else
		c = calibration[joynum][joyaxis][0];
	
	return c;
}

void GAMECONTROLS::InitJoy()
{
	int j;
	for (j = 0; j < SDL_NumJoysticks(); j++)
	{
		js[j] = SDL_JoystickOpen(j);
	}
}

void GAMECONTROLS::DeinitJoy()
{
	int j;
	for (j = 0; j < SDL_NumJoysticks(); j++)
		SDL_JoystickClose(js[j]);
}

void GAMECONTROLS::MinimizeCalibration()
{
	int i;
	for (i = 0; i < MAX_JOYSTICKS; i++)
	{
		int m;
		for (m = 0; m < MAX_AXES; m++)
		{
			calibration[i][m][0] = -0.1;
			calibration[i][m][1] = 0.1;
		}
	}
}

void GAMECONTROLS::SetCalibrationPoint(int joynum, int joyaxis, float val)
{
	if (val < calibration[joynum][joyaxis][0])
		calibration[joynum][joyaxis][0] = val;
	if (val > calibration[joynum][joyaxis][1])
		calibration[joynum][joyaxis][1] = val;
}

void GAMECONTROLS::UpdateSettings()
{
	bool got_type = settings.Get( "joystick.type", joysticktype );
	bool got_comp = settings.Get( "joystick.touchcomp", compensation );

	if( !got_type || ( joysticktype != "wheel" && joysticktype != "joystick" ) )
	{
		cout << "controls: invalid joystick type, default to joystick." << endl;
		joysticktype = "joystick";
	}

	if( !got_comp || ( compensation != "off" && compensation != "low" && compensation != "med" && compensation != "high" && compensation != "900to200" && compensation != "speed1" && compensation != "speed2") )
	{
		cout << "controls: invalid joystick touchiness compensation, default to off." << endl;
		compensation = "off";
	}
}

void GAMECONTROLS::DeleteControlsWithAction(string actionstr)
{
	list <CONTROL>::iterator i;
	bool found;
	bool done = false;
	
	while( !done )
	{
		found = false;
		for( i = controls.begin(); i != controls.end(); ++i )
		{
			if( i->GetName() == actionstr )
			{
				found = true;
				break;
			}
		}

		if( found )
		{
			controls.erase( i );
		}
		else if( i == controls.end() )
		{
			done = true;
		}
	}
}

void GAMECONTROLS::AddControl(CONTROL & other)
{
	controls.push_back(other);
}

float GAMECONTROLS::ApplyCalibration(int joynum, int joyaxis, float val)
{
	float max = GetCalibration(joynum,joyaxis,true);
	float min = GetCalibration(joynum,joyaxis,false);
	float range = max - min;
	float calgain = 2.0 / range;
	float offset = 1.0 - max * calgain;
	val = val * calgain + offset;
	
	//clip
	if (val < -1)
		val = -1;
	if (val > 1)
		val = 1;
	
	return val;
}

float GAMECONTROLS::ApplyDeadzone(float dz, float val)
{
	if (utility.dabs(val) < dz)
		val = 0;
	else
	{
		if (val < 0)
			val = (val + dz)*(1.0/(1.0-dz));
		else
			val = (val - dz)*(1.0/(1.0-dz));
	}
	
	return val;
}

float GAMECONTROLS::ApplyGain(float gain, float val)
{
	val *= gain;
	if (val < -1.0)
		val = -1.0;
	if (val > 1.0)
		val = 1.0;
	
	return val;
}

float GAMECONTROLS::ApplyExponent(float exponent, float val)
{
	val = pow(val, exponent);
	if (val < -1.0)
		val = -1.0;
	if (val > 1.0)
		val = 1.0;
	
	return val;
}
