//	Vamos Automotive Simulator
//  Copyright (C) 2001--2002 Sam Varner
//
//  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 _ROAD_SEGMENT_H_
#define _ROAD_SEGMENT_H_

#include <vamos/geometry/Conversions.h>
#include <vamos/geometry/Linear_Interpolator.h>
#include <vamos/geometry/Material.h>
#include <vamos/geometry/Spline.h>
#include <vamos/geometry/Three_Vector.h>
#include <vamos/geometry/Two_Point.h>

#include <GL/glu.h>

#include <string>
#include <vector>
#include <cmath>

namespace Vamos_Track
{
  class Not_On_This_Segment
  {
	// How far off the far end (positive) or near end (negative) of the 
	// current segment the point is during a call to track_coordinates.
	double m_difference;
  public:
	Not_On_This_Segment (double diff) : m_difference (diff) {};
	double difference () const { return m_difference; }
  };

  class Road_Segment_Interface
  {
  public:
	Road_Segment_Interface () {};
	virtual ~Road_Segment_Interface () {};

	// Return the length of the segment.
	virtual double length () const = 0;

	// Return the coordinates of the end of the segment.
	virtual Vamos_Geometry::Three_Vector end_coords () const = 0;

	// Return the angle between the world x-axis and the track x-axis
	// at the end of the segment.
	virtual double end_angle () const = 0;

	// Return the bank angle at the end of the segment.
	virtual double end_bank () const = 0;

	// Change the length of a segment.  The track must be re-built when this
	// is done.
	virtual void length (double new_length) = 0;

	virtual void scale (double factor) = 0;

	// Return the track-centered coodinates for WORLD_POS.
	virtual Vamos_Geometry::Three_Vector 
	coordinates (const Vamos_Geometry::Three_Vector& world_pos) = 0;

	// Return the angle between the world x-axis and the track x-axis
	// at a distance of ALONG on the segment.
	virtual double angle (double along) const = 0;

	// Return the bank at a distance of ALONG on the segment.
	virtual double bank (double along) const = 0;

	// Return the normal vector at a distance of ALONG on the segment,
	// at FROM_CENTER from the center of the segment.
	virtual Vamos_Geometry::Three_Vector 
	normal (double along, double from_center) const = 0;

	// Return the normal vector for the barrier closer to FROM_CENTER
	// at a distance of ALONG on the segment.
	virtual Vamos_Geometry::Three_Vector 
	barrier_normal (double along, double from_center) const = 0;

	// Construct the display list for the track.
	virtual void build (double start_distance,
						const Vamos_Geometry::Three_Vector& start_coords, 
						double start_angle,
						double start_bank) = 0;

	// Add the segments elevation points to ELEVATION.
	virtual void build_elevation (Vamos_Geometry::Spline* elevation,
								  double start_distance) = 0;

	// Render the track.
	virtual void draw () const = 0;

	// Return the distance from the center to the left barrier at a
	// distance of ALONG on the segment. 
	virtual double left_width (double along) = 0;

	// Return the distance from the center to the right barrier at a
	// distance of ALONG on the segment. 
	virtual double right_width (double along) = 0;

	// Return the distance from the center to the left edge of the
	// road at a distance of ALONG on the segment.
	virtual double left_road_width (double along) = 0;

	// Return the distance from the center to the right edge of the
	// road at a distance of ALONG on the segment.
	virtual double right_road_width (double along) = 0;

	// Force the segment to end at the world origin.
	virtual void last_segment (bool last) = 0;

	// Return the distance on the track where the segment starts.
	virtual double start_distance () const = 0;

	// Return the radius of curvature of the segment, or 0.0 for a
	// straight segment.
	virtual double radius () const = 0;

	// Set the radius of the segment.
	virtual void radius (double new_radius) = 0;

	virtual const Vamos_Geometry::Material_Handle left_material () const = 0;
	virtual const Vamos_Geometry::Material_Handle right_material () const = 0;
	virtual const Vamos_Geometry::Material_Handle 
	material_at (double along, double from_center) = 0;

	virtual double min_x () const = 0;
	virtual double max_x () const = 0;
	virtual double min_y () const = 0;
	virtual double max_y () const = 0;
  };

  class Road_Segment : public Road_Segment_Interface
  {
  protected:
	GLuint* m_gl_texture_name;
	GLuint m_gl_list_id;

	// The total length of the segment.
	double m_length;
	// The length of a subdivision of the segment.
	double m_resolution;

	// The distance from the center line to the walls.
	Vamos_Geometry::Linear_Interpolator* mp_left_width;
	Vamos_Geometry::Linear_Interpolator* mp_right_width;

	// The distance from the center line to the edges of the track.
	Vamos_Geometry::Linear_Interpolator* mp_left_road_width;
	Vamos_Geometry::Linear_Interpolator* mp_right_road_width;

	// The heights of the walls.
	double m_left_wall_height;
	double m_right_wall_height;

	// The endpoints of the segment.
	Vamos_Geometry::Three_Vector m_start_coords;
	Vamos_Geometry::Three_Vector m_end_coords;
	double m_start_angle;
	double m_start_distance;

	Vamos_Geometry::Three_Vector m_current_coords;
	double m_current_distance;
	double m_current_angle;
	bool m_on_left_side;

	// True when we're rendering the first subdivision.
	bool m_first;
	// True when we're rendering the last subdivision.
	bool m_last;

	std::vector <Vamos_Geometry::Two_Point> m_elevation_points;
	Vamos_Geometry::Spline* mp_elevation_curve;

	std::vector <Vamos_Geometry::Two_Point> m_bank_points;
	double m_start_bank;
	double m_end_bank;
	double m_bank_pivot;
	Vamos_Geometry::Spline* mp_bank_curve;

	std::vector <Vamos_Geometry::Material_Handle> m_materials;

	// True if this is the last segment of a circuit.
	bool m_last_segment;

	// bounding dimensions
	double m_min_x;
	double m_max_x;
	double m_min_y;
	double m_max_y;

	double bank_height (double along, double from_center) const;

	virtual void start (const Vamos_Geometry::Three_Vector& start_corrds, 
						double angle, 
						double start_bank);

  public:
	Road_Segment (double resolution, double length, 
				  const std::vector <Vamos_Geometry::Two_Point>& left_width, 
				  const std::vector <Vamos_Geometry::Two_Point>& right_width, 
				  const std::vector <Vamos_Geometry::Two_Point>& 
				  left_road_width, 
				  const std::vector <Vamos_Geometry::Two_Point>& 
				  right_road_width, 
				  double left_wall_height, double right_wall_height,
				  const std::vector <Vamos_Geometry::Two_Point>& 
				  elevation_points,
				  const std::vector <Vamos_Geometry::Two_Point>& bank_points,
				  double end_bank, double bank_pivot,
				  const std::vector <Vamos_Geometry::Material_Handle>& 
				  materials);
	Road_Segment (const Road_Segment& seg);
	virtual ~Road_Segment ();

	// Return the length of the segment.
	double length () const { return m_length; }

	// Change the length of a segment.  The track must be re-built when this
	// is done.
	void length (double new_length);

	virtual void scale (double factor);

	virtual double end_angle () const { return m_start_angle; }
	virtual Vamos_Geometry::Three_Vector 
	coordinates (const Vamos_Geometry::Three_Vector& world_pos) = 0;

	virtual Vamos_Geometry::Three_Vector next_vertex (int strip) = 0;
	Vamos_Geometry::Three_Vector end_coords () const { return m_end_coords; } 
	Vamos_Geometry::Three_Vector start_coords () const 
	{ return m_start_coords; } 

	double end_bank () const { return m_end_bank; }

	double elevation (double, double) const;
	virtual double angle (double along) const = 0;
	virtual double bank (double along) const { return 0; }
	virtual Vamos_Geometry::Three_Vector normal (double along, 
												 double from_center) const = 0;
	virtual Vamos_Geometry::Three_Vector 
	barrier_normal (double along, double from_center) const = 0;
	void build (double start_distance,
				const Vamos_Geometry::Three_Vector& start_coords, 
				double start_angle,
				double start_bank);
	void build_elevation (Vamos_Geometry::Spline* elevation,
						  double start_distance);

	void draw () const;

	const std::vector <Vamos_Geometry::Material_Handle>& materials () const 
	{ return m_materials; }

	double left_width (double dist);
	double right_width (double dist);
	double left_road_width (double dist);
	double right_road_width (double dist);
	void last_segment (bool last) { m_last_segment = last; }
	double start_distance () const { return m_start_distance; }
	virtual double radius () const { return 0.0; }
	virtual void radius (double new_radius) {};

	const Vamos_Geometry::Material_Handle left_material () const 
	{ return *(m_materials.begin ()); }
	const Vamos_Geometry::Material_Handle right_material () const 
	{ return *(m_materials.end () - 1); }
	const Vamos_Geometry::Material_Handle 
	material_at (double along, double from_center);

	double min_x () const { return m_min_x; }
	double max_x () const { return m_max_x; }
	double min_y () const { return m_min_y; }
	double max_y () const { return m_max_y; }
  };

  //===========================================================================
  class Straight_Road : public Road_Segment
  {
	Vamos_Geometry::Three_Vector next_vertex (int strip);

  public:
	Straight_Road (double resolution, double length, 
				   const std::vector <Vamos_Geometry::Two_Point>& left_width, 
				   const std::vector <Vamos_Geometry::Two_Point>& right_width, 
				   const std::vector <Vamos_Geometry::Two_Point>& 
				   left_road_width, 
				   const std::vector <Vamos_Geometry::Two_Point>& 
				   right_road_width, 
				   double left_wall_height, double right_wall_height,
				   const std::vector <Vamos_Geometry::Two_Point>& 
				   elevation_points,
				   const std::vector <Vamos_Geometry::Two_Point>& bank_points,
				   double end_bank, double bank_pivot,
				   const std::vector <Vamos_Geometry::Material_Handle>& 
				   materials);

	Vamos_Geometry::Three_Vector 
	coordinates (const Vamos_Geometry::Three_Vector& world_pos);
	double angle (double along) const { return m_start_angle; }
	Vamos_Geometry::Three_Vector normal (double, double) const;
	Vamos_Geometry::Three_Vector barrier_normal (double along, 
												 double from_center) const;
	void scale (double factor);
  };

  //===========================================================================
  class Arc_Road : public Road_Segment
  {
	double m_radius;
	double m_arc;

	double m_center_x;
	double m_center_y;

	void start (const Vamos_Geometry::Three_Vector& start_coords, 
				double angle,
				double start_bank);

	Vamos_Geometry::Three_Vector next_vertex (int strip);

  public:
	enum Arc_Type
	{
	  LENGTH_AND_RADIUS,
	  LENGTH_AND_ANGLE,
	  RADIUS_AND_ANGLE
	};

	Arc_Road (double resolution, double arg1, double arg2,
			  const std::vector <Vamos_Geometry::Two_Point>& left_width, 
			  const std::vector <Vamos_Geometry::Two_Point>& right_width, 
			  const std::vector <Vamos_Geometry::Two_Point>& left_road_width, 
			  const std::vector <Vamos_Geometry::Two_Point>& right_road_width, 
			  double left_wall_height, double right_wall_height,
			  const std::vector <Vamos_Geometry::Two_Point>& elevation_points,
			  const std::vector <Vamos_Geometry::Two_Point>& bank_points,
			  double end_bank, double bank_pivot,
			  const std::vector <Vamos_Geometry::Material_Handle>& materials,
			  Arc_Type type = LENGTH_AND_RADIUS);

	Vamos_Geometry::Three_Vector 
	coordinates (const Vamos_Geometry::Three_Vector& world_pos);

	double end_angle () const { return m_start_angle + m_arc; }
	double angle (double along) const;
	double bank (double along) const;
	Vamos_Geometry::Three_Vector barrier_normal (double along, 
												 double from_center) const;
	virtual double radius () const { return m_radius; }
	virtual void radius (double new_radius) { m_radius = new_radius; }
	Vamos_Geometry::Three_Vector normal (double, double) const;
	void scale (double factor);
  };
}

#endif
