#include <vamos/body/Gauge.h>

#include <algorithm>
#include <iostream>
#include <functional>

using namespace Vamos_Geometry;

namespace Vamos_Body
{

//* Class Facade
Facade::Facade (double x, double y, double above, double width, double height,
				std::string image) :
  Gl_Texture_Image (image, true, true),
  m_x (x),
  m_y (y),
  m_above (above),
  m_width (width),
  m_height (height),
  m_list_index (glGenLists (1))
{
  build_facade ();
}

Facade::Facade (double center_x, double center_y, double above, double radius,
				std::string image) :
  Gl_Texture_Image (image, true, true)
{
  m_above = above;
  m_width = 2.0 * radius * width_pixels () / height_pixels ();
  m_height = 2.0 * radius;
  m_x = center_x - m_width / 2.0;
  m_y = center_y - m_height / 2.0;
  m_list_index = glGenLists (1);
  build_facade ();
}

void
Facade::build_facade ()
{
  clamp_to_edge ();
  glNewList (m_list_index, GL_COMPILE);
  build_image ();
  glEndList ();
}

void
Facade::build_image ()
{
  activate ();

  glColor3d (1.0, 1.0, 1.0);
  glBegin (GL_QUADS);
  glTexCoord2d (0.0, 0.0);
  glVertex3d (-m_above, -m_x, m_y + m_height);
  glTexCoord2d (1.0, 0.0);
  glVertex3d (-m_above, -m_x - m_width, m_y + m_height);
  glTexCoord2d (1.0, 1.0);
  glVertex3d (-m_above, -m_x - m_width, m_y);
  glTexCoord2d (0.0, 1.0);
  glVertex3d (-m_above, -m_x, m_y);
  glEnd ();
}

void
Facade::rotate (double degrees) const
{
  glRotated (degrees, 1.0, 0.0, 0.0);
}

void
Facade::draw () const 
{
  glCallList (m_list_index);
}

//* Class Scaler
Scaler::Scaler (double min_in, double min_out, double max_in, double max_out) :
  m_minimum_input (min_in),
  m_maximum_input (max_in),
  m_offset (min_out),
  m_factor ((max_out - min_out) / (max_in - min_in))
{
}

double
Scaler::scale (double value_in)
{
  value_in = std::max (m_minimum_input, value_in);
  value_in = std::min (m_maximum_input, value_in);
  return m_offset + (value_in - m_minimum_input) * m_factor;
}

//* Class Dial
Dial::Dial (double center_x, double center_y, double above, double radius, 
			double min, double min_angle, double max, double max_angle,
			std::string face_image, std::string needle_image) : 
  m_above (above),
  m_scaler (min, min_angle, max, max_angle),
  mp_face (0),
  m_center_x (center_x),
  m_center_y (center_y)
{
  if (face_image != "")
	{
	  mp_face = new Facade (center_x, center_y, above, radius, face_image); 
	}
  if (needle_image != "")
	{
	  mp_needle = new Facade (0.0, 0.0, above + 0.01, radius, 
							  needle_image); 
	}
}

Dial::~Dial ()
{
  delete mp_needle;
  delete mp_face;
}

void
Dial::set (double value)
{
  m_angle = m_scaler.scale (value);
}

void
Dial::draw () const
{
  glPushMatrix ();
  mp_face->draw ();
  glTranslated (0.0, -m_center_x, m_center_y);
  mp_needle->rotate (m_angle);
  mp_needle->draw ();
  glPopMatrix ();
}

//* Class LED_Gauge
LED_Gauge::LED_Gauge (double x, double y, double above, double width, 
					  int elements, double min, double redline, 
					  std::string image, bool on_wheel) :
  m_x (x),
  m_y (y),
  m_above (above),
  m_width (width),
  m_elements (elements),
  m_min (min),
  m_range (redline - min),
  m_leds_on (0),
  m_list_index (glGenLists (1))
{
  m_on_steering_wheel = on_wheel;
  mp_leds = new Gl_Texture_Image (image, true, true);

  m_height = (0.5 * m_width * mp_leds->height_pixels ()) 
	/ mp_leds->width_pixels ();

  glNewList (m_list_index, GL_COMPILE);

  mp_leds->activate ();
	  
  glTranslated (-m_above, -m_x, m_y);

  glColor3d (1.0, 1.0, 1.0);
  glBegin (GL_QUADS);
  glTexCoord2d (0.0, 0.5);
  glVertex3d (0.0, 0.0, 0.0);
  glTexCoord2d (1.0, 0.5);
  glVertex3d (0.0, -m_width, 0.0);
  glTexCoord2d (1.0, 1.0);
  glVertex3d (0.0, -m_width, m_height);
  glTexCoord2d (0.0, 1.0);
  glVertex3d (0.0, 0.0, m_height);
  glEnd ();

  glEndList ();
}

LED_Gauge::~LED_Gauge ()
{
  delete mp_leds;
}

void
LED_Gauge::set (double value)
{
  m_leds_on = int ((value - m_min)*(m_elements - 1)/m_range + 1.0);
  m_leds_on = std::max (m_leds_on, 0);
  m_leds_on = std::min (m_leds_on, m_elements);
}

void
LED_Gauge::draw () const
{
  glPushMatrix ();

  // Draw the LEDs all off...
  glCallList (m_list_index);

  // ...then draw the ones that are on over top.
  double frac = double (m_leds_on) / m_elements;

  mp_leds->activate ();
	  
  glColor3d (1.0, 1.0, 1.0);
  glBegin (GL_QUADS);
  glTexCoord2d (0.0, 0.5);
  glVertex3d (0.0, 0.0, 0.0);
  glTexCoord2d (frac, 0.5);
  glVertex3d (0.0, -m_width * frac, 0.0);
  glTexCoord2d (frac, 0.0);
  glVertex3d (0.0, -m_width * frac, m_height);
  glTexCoord2d (0.0, 0.0);
  glVertex3d (0.0, 0.0, m_height);
  glEnd ();

  glPopMatrix ();
}

//* Class Digital Gauge
Digital_Gauge::Digital_Gauge (double x, double y, double above,
							  double width, double height, size_t places,
							  std::string digits, bool on_wheel) :
  m_x (x),
  m_y (y),
  m_above (above),
  m_width (width),
  m_height (height),
  m_places (places)
{
  m_on_steering_wheel = on_wheel;
  m_digits.resize (places);
  mp_digits = new Gl_Texture_Image (digits, true, true);
}

Digital_Gauge::~Digital_Gauge ()
{
  delete mp_digits;
}

void
Digital_Gauge::set (double value)
{
  int n = int (value);
  int denom = 1;
  int sub = 0;
  for (size_t index = 0; index < m_places; index++)
	{
	  int m = denom * 10;
	  int place = (n % m) / denom; 
	  m_digits [m_places - 1 - index] = place;
	  denom = m;
	  sub += place;
	}
}

void
Digital_Gauge::draw () const
{
  mp_digits->activate ();

  bool nonzero = false;
  for (size_t i = 0; i < m_places; i++)
	{
	  int n = m_digits [i];
	  if ((!nonzero) && (n == 0) && (i != (m_places - 1)))
		{
		  continue;
		}
	  nonzero = true;

	  double tex_x1 =  n * 0.1;
	  double tex_x2 = tex_x1 + 0.1;

	  double x1 = i * m_width / m_places;
	  double x2 = x1 + m_width / m_places;

	  glColor3d (1.0, 1.0, 1.0);
	  glBegin (GL_QUADS);
	  glTexCoord2d (tex_x1, 1.0);
	  glVertex3d (-m_above, -m_x - x1, m_y);
	  glTexCoord2d (tex_x2, 1.0);
	  glVertex3d (-m_above, -m_x - x2, m_y);
	  glTexCoord2d (tex_x2, 0.0);
	  glVertex3d (-m_above, -m_x - x2, m_y + m_height);
	  glTexCoord2d (tex_x1, 0.0);
	  glVertex3d (-m_above, -m_x - x1, m_y + m_height);
	  glEnd ();
	}
}

//* Class Steering_Wheel
Steering_Wheel::Steering_Wheel (double center_x, double center_y, 
								double above, double radius, 
								double min, double min_angle, 
								double max, double max_angle,
								std::string image) :
  Facade (0.0, 0.0, above, radius, image),
  m_center_x (center_x),
  m_center_y (center_y),
  m_scaler (min, min_angle, max, max_angle)
{
}

void
Steering_Wheel::set (double value)
{
  m_angle = m_scaler.scale (value);
}

void
Steering_Wheel::draw () const
{
  glTranslated (0.0, -m_center_x, m_center_y);
  rotate (m_angle);
  Facade::draw ();
}

//* Class Gear_Indicator
Gear_Indicator::Gear_Indicator (double x, double y, double above,
								double width, double height, 
								int numbers, std::string image,
								bool on_wheel) :
  m_number_width (1.0 / numbers),
  mp_numbers (0),
  m_x (x),
  m_y (y),
  m_above (above),
  m_width (width),
  m_height (height)
{
  m_on_steering_wheel = on_wheel;
  if (image != "")
	{
	  mp_numbers = new Gl_Texture_Image (image, true, true);
	}
}

Gear_Indicator::~Gear_Indicator ()
{
  delete mp_numbers;
}

void
Gear_Indicator::draw () const
{
  mp_numbers->activate ();

  double x1 = m_number_width * (m_gear + 1);
  double x2 = x1 + m_number_width;

  glColor3d (1.0, 1.0, 1.0);
  glBegin (GL_QUADS);
  glTexCoord2d (x2, 1.0);
  glVertex3d (-m_above, -m_x, m_y);
  glTexCoord2d (x1, 1.0);
  glVertex3d (-m_above, -m_x + m_width, m_y);
  glTexCoord2d (x1, 0.0);
  glVertex3d (-m_above, -m_x + m_width, m_y + m_height);
  glTexCoord2d (x2, 0.0);
  glVertex3d (-m_above, -m_x, m_y + m_height);
  glEnd ();
}


//* Class Gear_Shift
Gear_Shift::Gear_Shift (double x, double y, double z, 
						double width, double height,
						const Vamos_Geometry::Three_Vector& rotation,
						const std::vector <Vamos_Geometry::Two_Point>& 
						positions,
						std::string plate_image, std::string stick_image) :
  Gear_Indicator (x, y, z, width, height, 0, "", false),
  m_rotation (rotation),
  m_positions (positions),
  m_top_gear (m_positions.size () - 2),
  m_list_index (glGenLists (2))
{
  mp_gate = new Gl_Texture_Image (plate_image, true, true);
  mp_stick = new Gl_Texture_Image (stick_image, true, true);

  m_stick_width = 
	m_width * mp_stick->width_pixels () / mp_gate->width_pixels ();
  m_stick_height = 
	m_height * mp_stick->height_pixels () / mp_gate->height_pixels ();

  glNewList (m_list_index, GL_COMPILE);

  mp_gate->activate ();

  glRotated (rotation [0], 0.0, -1.0, 0.0);
  glRotated (rotation [1], 0.0, 0.0, 1.0);
  glRotated (rotation [2], 1.0, 0.0, 0.0);
  glTranslated (-m_above, -m_x, m_y);

  glColor3d (1.0, 1.0, 1.0);
  glBegin (GL_QUADS);
  glTexCoord2d (0.0, 0.0);
  glVertex3d (0.0, 0.0, 0.0);
  glTexCoord2d (1.0, 0.0);
  glVertex3d (0.0, -m_width, 0.0);
  glTexCoord2d (1.0, 1.0);
  glVertex3d (0.0, -m_width, m_height);
  glTexCoord2d (0.0, 1.0);
  glVertex3d (0.0, 0.0, m_height);
  glEnd ();

  glTranslated (0.0, (-m_width + m_stick_width) / 2.0, m_height / 2.0);
  glEndList ();

  glNewList (m_list_index + 1, GL_COMPILE);

  mp_stick->activate ();

  glRotated (-rotation [0], 0.0, -1.0, 0.0);
  glRotated (-rotation [1], 0.0, 0.0, 1.0);
  glRotated (-rotation [2], 1.0, 0.0, 0.0);

  glColor3d (1.0, 1.0, 1.0);
  glBegin (GL_QUADS);
  glTexCoord2d (0.0, 1.0);
  glVertex3d (0.0, 0.0, 0.0);
  glTexCoord2d (1.0, 1.0);
  glVertex3d (0.0, -m_stick_width, 0.0);
  glTexCoord2d (1.0, 0.0);
  glVertex3d (0.0, -m_stick_width, m_stick_height);
  glTexCoord2d (0.0, 0.0);
  glVertex3d (0.0, 0.0, m_stick_height);
  glEnd ();

  glEndList ();
}

Gear_Shift::~Gear_Shift ()
{
  delete mp_stick;
  delete mp_gate;
}

void
Gear_Shift::set (int gear)
{
  m_gear = std::min (m_top_gear, gear);
}

void
Gear_Shift::draw () const
{
  glPushMatrix ();
  glCallList (m_list_index);
  glTranslated (0.0, -m_positions [m_gear + 1].x, m_positions [m_gear + 1].y);
  glCallList (m_list_index + 1);
  glPopMatrix ();
}


//* Class Dashboard
Dashboard::Dashboard (double x, double y, double z, double tilt) :
  m_x (x),
  m_y (y),
  m_z (z),
  m_tilt (tilt),
  mp_tachometer (0),
  mp_speedometer (0),
  mp_fuel_gauge (0),
  mp_gear_indicator (0),
  mp_steering_wheel (0)
{
}

Dashboard::~Dashboard ()
{
  delete mp_steering_wheel;
  delete mp_gear_indicator;
  delete mp_fuel_gauge;
  delete mp_speedometer;
  delete mp_tachometer;
  for (std::vector <Facade*>::iterator it = ma_facades.begin ();
	   it != ma_facades.end ();
	   it++)
	{
	  delete *it;
	}
}

void
Dashboard::add_tachometer (Gauge* tachometer)
{
  delete mp_tachometer;
  mp_tachometer = tachometer;
}

void
Dashboard::add_speedometer (Gauge* speedometer)
{
  delete mp_speedometer;
  mp_speedometer = speedometer;
}

void
Dashboard::add_fuel_gauge (Gauge* fuel_gauge)
{
  delete mp_fuel_gauge;
  mp_fuel_gauge = fuel_gauge;
}

void
Dashboard::add_gear_indicator (Gear_Indicator* gear_indicator)
{
  delete mp_gear_indicator;
  mp_gear_indicator = gear_indicator;
}

void
Dashboard::add_steering_wheel (Steering_Wheel* steering_wheel)
{
  delete mp_steering_wheel;
  mp_steering_wheel = steering_wheel;
}

void
Dashboard::add_facade (Facade* facade)
{
  ma_facades.push_back (facade);
}

void
Dashboard::set_tachometer (double rpm)
{
  if (mp_tachometer != 0)
	{
	  mp_tachometer->set (rpm);
	}
}

void
Dashboard::set_speedometer (double speed)
{
  if (mp_speedometer != 0)
	{
	  mp_speedometer->set (speed);
	}
}

void
Dashboard::set_fuel_gauge (double fuel)
{
  if (mp_fuel_gauge != 0)
	{
	  mp_fuel_gauge->set (fuel);
	}
}

void
Dashboard::set_gear_indicator (int gear)
{
  if (mp_gear_indicator != 0)
	{
	  mp_gear_indicator->set (gear);
	}
}

void
Dashboard::set_steering_wheel (double angle)
{
  if (mp_steering_wheel != 0)
	{
	  mp_steering_wheel->set (angle);
	}
}

void
Dashboard::draw () const
{
  glTranslated (m_x, m_y, m_z);

  std::for_each (ma_facades.begin (), ma_facades.end (),
 				 std::mem_fun (&Facade::draw));

  glRotated (m_tilt, 0.0, 1.0, 0.0);

  if ((mp_tachometer != 0) && !mp_tachometer->on_steering_wheel ())
	{
	  mp_tachometer->draw ();
	}
  if ((mp_speedometer != 0) && !mp_speedometer->on_steering_wheel ())
	{
	  mp_speedometer->draw ();
	}
  if ((mp_fuel_gauge != 0) && !mp_fuel_gauge->on_steering_wheel ())
	{
	  mp_fuel_gauge->draw ();
	}
  if ((mp_gear_indicator != 0) && !mp_gear_indicator->on_steering_wheel ())
	{
	  mp_gear_indicator->draw ();
	}
  if (mp_steering_wheel != 0)
	{
	  mp_steering_wheel->draw ();
	}

  glDisable (GL_DEPTH_TEST);
  if ((mp_tachometer != 0) && mp_tachometer->on_steering_wheel ())
	{
	  mp_tachometer->draw ();
	}
  if ((mp_speedometer != 0) && mp_speedometer->on_steering_wheel ())
	{
	  mp_speedometer->draw ();
	}
  if ((mp_fuel_gauge != 0) && mp_fuel_gauge->on_steering_wheel ())
	{
	  mp_fuel_gauge->draw ();
	}
  if ((mp_gear_indicator != 0) && mp_gear_indicator->on_steering_wheel ())
	{
	  mp_gear_indicator->draw ();
	}
  glEnable (GL_DEPTH_TEST);
}

} // namespace Vamos_Body
