//	Vamos Automotive Simulator
//  Copyright (C) 2001--2003 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 Kerb
  {
	std::vector <Vamos_Geometry::Two_Point> m_points;
	Vamos_Geometry::Linear_Interpolator m_profile;
	double m_start;
	double m_start_transition_length;
	double m_start_transition_width;
	double m_end;
	double m_end_transition_length;
	double m_end_transition_width;
	bool m_full_length;

  public:
	Kerb (const std::vector <Vamos_Geometry::Two_Point>& profile,
		  double start, 
		  double start_transition_length,
		  double start_transition_width,
		  double end, 
		  double end_transition_length,
		  double end_transition_width);

	void length (double length);

	double transition_start () const { return m_start; }
	double transition_end () const { return m_end; }
	double start () const { return m_start + m_start_transition_length; }
	double end () const { return m_end - m_end_transition_length; }
	double start_transition_width () const { return m_start_transition_width; }
	double end_transition_width () const { return m_end_transition_width; }
	double width () const;
	bool on_kerb (double dist) const;
	double elevation (double along, double from_inside);
	double angle (double along, double from_inside);
	const Vamos_Geometry::Two_Point& point (size_t substrip);
	size_t substrips () const { return m_points.size () - 1; }
  };

  class Braking_Marker
  {
	double m_distance;
	Vamos_Geometry::Two_Point m_offset;
	Vamos_Geometry::Side m_side;
	Vamos_Geometry::Gl_Texture_Image* mp_image;

  public:
	Braking_Marker (double distance,
					Vamos_Geometry::Two_Point size,
					Vamos_Geometry::Two_Point offset,
					Vamos_Geometry::Side side,
					std::string image_file);
	~Braking_Marker ();

	double distance () const { return m_distance; }
	Vamos_Geometry::Two_Point offset () const { return m_offset; }
	Vamos_Geometry::Side side () const { return m_side; }
	Vamos_Geometry::Gl_Texture_Image* image () { return mp_image; }
  };

  //===========================================================================
  class Road_Segment
  {
  public:
	struct Model_Info
	{
	  std::string file;
	  double scale;
	  Vamos_Geometry::Three_Vector translation;
	  Vamos_Geometry::Three_Vector rotation;
	};

  private:
	void increment_kerb_distance (const Kerb& kerb);

	std::vector <Model_Info> m_models;
	
  protected:
	enum 
	  { 
		LEFT_BARRIER, 
		LEFT_SHOULDER, 
		LEFT_KERB,
		TRACK, 
		RIGHT_KERB,
		RIGHT_SHOULDER,
		RIGHT_BARRIER
	  };

	GLuint* m_gl_texture_name;
	GLuint m_gl_list_id;
	std::vector <GLuint> m_scenery_lists;

	// The total length of the segment.
	double m_length;
	// The radius of curvature; 0.0 for a straight segment. 
	double m_radius;

    double m_start_skew;
    double m_end_skew;

	double m_arc;

	Vamos_Geometry::Two_Point m_center;

	// 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;

	Kerb* mp_left_kerb;
	Kerb* mp_right_kerb;
	
	// 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;

	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;

	std::vector <double> m_texture_offsets;

	double bank_height (double along, double from_center) const;

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

	struct Segment_Iterator
	{
	  Segment_Iterator (double res, size_t strips);

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

	  Vamos_Geometry::Three_Vector coordinates;
	  Vamos_Geometry::Two_Point texture_coordinates;
	  Vamos_Geometry::Three_Vector normal;

	  double resolution;
	  double distance;
	  double angle;
	  bool left_side;

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

	  // The current substrip.
	  size_t substrip;
	};

	Segment_Iterator* mp_iterator;

	Road_Segment (const Road_Segment& seg);

	void draw_strip (size_t i, double texture_offset);

	std::vector <Braking_Marker*> m_braking_markers;

  public:
	Road_Segment (double resolution, double length, 
                  double radius, double skew, 
				  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, 
				  Kerb* left_kerb, Kerb* right_kerb,
				  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,
				  const std::vector <Braking_Marker*>& braking_markers);

	~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 set_length (double new_length);

    void set_arc (double arc);
    double arc () const { return m_arc; }

	void scale (double factor);

	double end_angle () const { return m_start_angle + m_arc; }
	double coordinates (const Vamos_Geometry::Three_Vector& world_pos,
						Vamos_Geometry::Three_Vector& track_pos);

	Vamos_Geometry::Three_Vector next_vertex (int strip);
	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);
	double angle (double along) const;
	double bank (double along) const;
	Vamos_Geometry::Three_Vector normal (double along, double from_center) const;
	Vamos_Geometry::Three_Vector barrier_normal (double along, 
												 double from_center) const;

	void build (double start_distance,
                const Vamos_Geometry::Three_Vector& start_coords, 
                double start_angle,
                double start_bank,
                const std::vector <double>& texture_offsets);
	void build_elevation (Vamos_Geometry::Spline* elevation,
						  double start_distance);

	void add_model_info (const Model_Info& info);
	
	void draw () const;

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

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

    void set_start_skew (double skew) { m_start_skew = skew; }
    void set_end_skew (double skew) { m_end_skew = skew; }
    double skew () const { return m_start_skew; }

	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; }

	std::vector <double> texture_offsets () const { return m_texture_offsets; }
  };
}

#endif // not _ROAD_SEGMENT_H_
