//  Car.h - a body with wheels.
//
//  Copyright (C) 2001 Sam Varner
//
//  This file is part of Vamos Automotive Simulator.
//
//  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

#ifndef _CAR_H_
#define _CAR_H_

#include <vamos/geometry/Three_Vector.h>
#include <vamos/body/Body.h>
#include <vamos/body/Drivetrain.h>
#include <vamos/body/Fuel_Tank.h>
#include <vamos/body/Particle.h>
#include <vamos/body/Wheel.h>

#include <simgear/xml/easyxml.hxx>

#include <vector>
#include <utility>

class slSample;

namespace Vamos_Body
{
  class Car_Reader;

  //* A class that handles gradual application of a control that's
  // operated by a button, such as the clutch.  If you're using a
  // keyboard instead of a joystick, it also handles steering, gas and
  // brake.
  class Key_Control
  {
	// True if the next target is not set until the current one is
	// reached. 
	bool m_block;

	// True if a target is waiting.
	bool m_target_pending;
	
	// The current setting of this control.
	double m_value;

	// The desired setting of this control.
	double m_target;
	double m_next_target;

	// How fast `m_value' approaches `m_target'.
	double m_rate;
	double m_next_rate;

	// How long to wait before starting to change the setting.
	double m_delay;
	double m_next_delay;

	// The time needed for the control to reach its target.
	double m_time;
	double m_next_time;

  public:
	//** Constructor
	Key_Control (bool block = false);
	
	// Set the target setting of this control.  NEW_TARGET is the
	// desired setting.  TIME is how long it should take for the
	// setting to go from 0.0 to 1.0 after waiting for DELAY.
	// `m_rate' is calculated in this function.
	void target (double new_target, double time = 0.0, double delay = 0.0);

	// Update the setting of this control.  The setting move toward
	// `m_target' by the ammount `m_rate' * TIME.
	double update (double time);

	// Return the current value of this control.
	double value () const { return m_value; }

	// Go immediately to the target.
	void end ();
  };


  //* A body with wheels.
  class Car : public Body
  {
	friend class Car_Reader;

  private:
	std::string m_data_dir;
	std::string m_car_file;

  protected:
	// A pointer to the car's drivetrain.
	Drivetrain* mp_drivetrain;

	// A pointer to the car's fuel tank.
	Fuel_Tank* mp_fuel_tank;

	// The fraction of braking torque applied to the front wheels.
	double m_front_brake_bias;

	// The maximum angle for the steered wheels.
	double m_max_steer_angle;

	// The sum of the sliding speeds of the tires.
	double m_slide;

	// True if a shift has been requested but not yet made due to the
	// delay `m_shift_delay'.
	bool m_shift_pending;

	// The amount of time elapsed after the shift request.
	double m_shift_timer;

	// How long to wait between getting a shift request and actually
	// shifting. 
	double m_shift_delay;

	// The gear to shift to when `m_shift_timer' reaches
	// `m_shift_delay'. 
	int m_new_gear;

	// The control that gradually applies steering when the keyboard
	// is used.
	Key_Control m_steer_key_control;

	// The control that gradually applies the throttle when the
	// keyboard is used.
	Key_Control m_gas_key_control;

	// The control that gradually applies braking when the keyboard
	// is used.
	Key_Control m_brake_key_control;

	// The control that gradually applies the clutch when the keyboard
	// is used.
	Key_Control m_clutch_key_control;

	// A pointer to the frontmost particle of the car.
	Particle* mp_front_particle;

	// The total distance traveled since the start of the simulation.
	double m_distance_traveled;

	std::vector <Wheel*> m_wheels;

  public:
	//** Constructor
	Car (const Vamos_Geometry::Three_Vector& pos);

	//** Destructor
	virtual ~Car ();

	void read (std::string data_dir = "", std::string car_file = "");

	// Advance the car in time by TIME.
	virtual void propagate (double time);
  
	// Change the steering angle to ANGLE with a time constant of TIME.
	void steer (double angle, double time = 0.0);

	// Change the throttle to FACTOR with a time constant of TIME.
	void gas (double factor, double time = 0.0);

	// Change the brakes to FACTOR with a time constant of TIME.
	void brake (double factor, double time = 0.0);

	// Shift to the next lower gear after a delay of DELAY.  The gear
	// to be chosen is returned.
	int shift_down (double delay = 0.0);

	// Shift to the next higher gear after a delay of DELAY.  The gear
	// to be chosen is returned.
	int shift_up (double delay = 0.0);

	// Shift to GEAR after a delay of DELAY.  The gear to be chosen is
	// returned.
	int shift (int gear, double delay = 0.0);

	// Engage the clutch with a time constant of TIME.
	void engage_clutch (double time);

	// Disengage the clutch with a time constant of TIME.
	void disengage_clutch (double time);

	// Start the engine.
	void start ();
  
	// Set the front brake bias to BIAS.
	void brake_bias (double bias);

	// Set the largest possible steering angle.
	void max_steer_angle (double degree_angle) 
	{ m_max_steer_angle = degree_angle; }
	
	// Return the sum of the sliding speeds of the tires.
	double slide () const { return m_slide; }

	// Return the pointer to the WHEEL_INDEXth wheel.
	Wheel* wheel (int wheel_index) const;

	// Return the pointer to the front-most particle.
	Particle* front_particle () const { return mp_front_particle; }

	// Return a pointer to the engine.
	Engine* engine () { return mp_drivetrain->engine (); }

	// Return a pointer to the transmission.
	Transmission* transmission () { return mp_drivetrain->transmission (); }

	// Return a pointer to the fuel tank.
	Fuel_Tank* fuel_tank () { return mp_fuel_tank; }

	// Return the last gear to be selected.  The shift may not have
	// occurred yet due to the shift delay spicfied in the call to
	// `shift_up ()', `shift_down ()', or `shift ()'.
	int gear () const { return m_new_gear; }

	//** Enhance Body's reset methods by re-initializing the engine
	// and gearbox.

	// Restore the initial conditions.
	void reset ();

	// Restore the initial conditions and then set the position to
	// POSITION and the orientation to ORIENTATION.
	void reset (const Vamos_Geometry::Three_Vector& position, 
				const Vamos_Geometry::Three_Matrix& orientation);

	// Return the total distance traveled since the start of the
	// simulation.
	double distance_traveled () const { return m_distance_traveled; }

	void drivetrain (Drivetrain* drive);

	//* Methods to be defined in derived classes.

	// Render the car according to its current position and
	// orientation.
	virtual void draw () {};

	// Perform the transformations for the driver's view.
	virtual void view () {};

	// Make the display list for the car.
	virtual void build () {};

	// Draw the gauges and readouts.
	virtual void draw_dashboard () {};

	// Return the sound parameters.
	virtual double engine_pitch () { return 0.0; }
	virtual double engine_volume () { return 0.0; }
  };

  class Car_Reader : public XMLVisitor
  {
	void startElement (const char* element, 
					   const XMLAttributes& attributes);
	void endElement (const char* element);
	void data (const char* str, int length);

	std::string m_tag;
	std::string m_name;

	std::vector <double> m_doubles;
	std::vector <std::string> m_strings;
	std::vector <Vamos_Geometry::Three_Vector> m_vectors;
	std::vector <std::pair <int, double> > m_gears;
	std::vector <bool> m_bools;

	std::vector <double> m_long_parameters;
	std::vector <double> m_trans_parameters;
	std::vector <double> m_align_parameters;

	std::string m_data_dir;
	Vamos_Body::Car* mp_car;

	enum Classes
	  {
		CONTACT_POINT,
		AERODYNAMIC_DEVICE,
		SUSPENSION,
		TIRE,
		BRAKES,
		WHEEL,
		NONE
	  };
	
	Classes m_class;

	Engine* mp_engine;
	Clutch* mp_clutch;
	Transmission* mp_transmission;
	Differential* mp_differential;

  public:
	Car_Reader (std::string data_dir, 
				std::string track_file, 
				Vamos_Body::Car* car);
  };

  class XML_Parser_Error
  {
	std::string m_error;
  public:
	XML_Parser_Error (std::string error) : m_error (error) {};
	std::string message () const { return m_error; }
  };

  class Bad_Definition_File
  {
	std::string m_error;
  public:
	Bad_Definition_File (std::string error) : m_error (error) {};
	std::string message () const { return m_error; }
  };
}

#endif // !_CAR_H_
