//  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 "../geometry/Conversions.h"
#include "../geometry/Linear_Interpolator.h"
#include "../geometry/Rectangle.h"
#include "../geometry/Spline.h"
#include "../geometry/Three_Vector.h"
#include "../geometry/Two_Vector.h"

#include <string>
#include <vector>
#include <complex>

namespace Vamos_Track
{
  typedef std::vector <Vamos_Geometry::Two_Vector> TPoints;

  //===========================================================================
  /// @class Kerb
  /// Rumble strips on the sides of the track.
  class Kerb
  {
  public:
    /// The constructor defines the geometry of the kerb.
    ///
    /// The kerb shape is defined by the profile.  Transition zones may be
    /// defined to make the kerb taper to the ground.  Transition zones do not
    /// add to the length of the kerb.  A zero-length transition zone leaves the
    /// kerb open on the end.  This is useful for connecting kerbs across
    /// segment boundaries.  The roughness of the kerb is defined by the
    /// material assigned to it in the track object.
    ///
    /// @param profile the shape of the kerb defined by a vector of Two_Vectors
    /// where x is the distance from the edge of the track and y is the height.
    /// @param start the distance from the start of the road segment to the
    /// start of the kerb.
    /// @param start_transition_length the distance from the start of the kerb
    /// to the end of the transition.
    /// @ param start_transition_width the width of the kerb where it meets the
    /// ground. 
    /// @param end the distance from the start of the road segment to the end of
    /// the kerb.
    /// @param end_transition_length the distance from the start of transition
    /// to the end of the kerb
    /// @ param end_transition_width the width of the kerb where it meets the
    /// ground. 
    Kerb (const TPoints& profile,
          double start, 
          double start_transition_length,
          double start_transition_width,
          double end, 
          double end_transition_length,
          double end_transition_width,
          bool full_length);

    /// Change the length of the kerb, perhaps because the length of its road
    /// segment has changed.
    /// @param length the new length.
    void set_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;

    /// @return true if 'dist' along the segment is between the start and end of
    /// the kerb.
    bool on_kerb (double dist) const;

    /// @param along distance along the segment.
    /// @param from_inside distance from the inside edge of the kerb.
    /// @return the height of the kerb above the ground at the given point.
    double elevation (double along, double from_inside);

    /// @param along distance along the segment.
    /// @param from_inside distance from the inside edge of the kerb.
    /// @return the angle of the normal vector relative to the ground.
    double angle (double along, double from_inside);

    /// @return a profile point.
    const Vamos_Geometry::Two_Vector& point (size_t substrip) const;

    /// @return the number of flat surfaces on the kerb.
    size_t substrips () const { return m_points.size () - 1; }

  private:
    TPoints 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;
  };

  //===========================================================================
  /// @class Banking
  /// Track banking parameters
  class Banking
  {
  public:
    Banking ();
    ~Banking ();

    void set (double end_angle, double pivot_from_center);
    double angle (double along) const;
    double height (double along, double from_center) const;
    void set_start (double start_angle, double length);
    double start_angle () const { return m_start_angle; }
    double end_angle () const { return m_end_angle; }

  private:
    Vamos_Geometry::Spline m_bank_angle;
    double m_start_angle;
    double m_end_angle;
    double m_pivot_from_center;
  };

  //=============================================================================
  /// @class Pit_Lane_Transition
  /// Parameters for places where the pit lane merges with the track.
  class Pit_Lane_Transition
  {
  public:
    Pit_Lane_Transition ();

    void set_merge (Vamos_Geometry::Direction direction, 
                    Vamos_Geometry::Direction side,
                    double split_or_join, 
                    double merge,
                    double angle);
    void set_width (double pit_width, 
                    double left_shoulder, 
                    double right_shoulder);
    Vamos_Geometry::Direction direction () const { return m_direction; }
    Vamos_Geometry::Direction side () const { return m_side; }
    double merge () const { return active () ? m_merge : 0.0; }
    double split_or_join () const { return active () ? m_split_or_join : 0.0; }
    double width (Vamos_Geometry::Direction side, double distance, bool narrow) const;
    double shoulder_width () const { return m_pit_shoulder_width; }
    void scale (double factor);
    double angle () const { return m_angle; }
    double skew (double length) const { return length / cos (m_angle); }
    // Scale length to account for the skew.

    bool active () const { return m_merge_is_set && m_width_is_set; }

  private:
    Vamos_Geometry::Direction m_direction;
    Vamos_Geometry::Direction m_side;
    double m_split_or_join;
    double m_merge;
    double m_angle;
    double m_pit_width;
    double m_pit_shoulder_width;

    bool m_merge_is_set;
    bool m_width_is_set;
  };

  //===========================================================================
  /// @class Road_Segment
  /// A straight or curved section of road
  class Road_Segment
  {
    friend class Segment_Iterator;

  public:
    /// @param length the length of the segment at the centereline.
    /// @param radius the radius of the curve at the centerline of the segment.
    /// Set to 0 for a straight segment.
    /// @param left_width the distance from the centerline to the left barrier.
    /// @param right_width the distance from the centerline to the right
    /// barrier. 
    /// @param left_road_width the width of road surface to the left of the
    /// centerline.
    /// @param right_road_width the width of road surface to the right of the
    /// centerline.
    Road_Segment (double length,
                  double radius,
                  double left_width,
                  double right_width,
                  double left_road_width,
                  double right_road_width);

    virtual ~Road_Segment ();

    /// @return the length of the segment at the centerline.
    double length () const { return m_length; }

    /// Change the length of he segment.
    void set_length (double new_length);

    /// @return the radius of the segment at the centerline or zero if the
    /// segment is straight.
    double radius () const { return m_radius; }

    /// Set the radius of the curve.  Preserve the length if the segment is
    /// currently straight, otherwise preserve the arc.
    void set_radius (double new_radius);

    /// @return true if the segment is straight.
    bool is_straight () const { return m_radius == 0.0; }

    /// @return the arc in radians that the segment subtends.
    double arc () const;

    /// Set the arc of the curve.  Preserve the radius if the segment is
    /// currently curve, otherwise preserve the length.
    void set_arc (double new_arc);

    Vamos_Geometry::Three_Vector center_of_curve () const;

    double left_width (double distance, bool narrow = false) const;
    double right_width (double distance, bool narrow = false) const;
    double width (double distance) const 
    { return left_width (distance) + right_width (distance); }
    double left_road_width_no_pit (double distance) const;
    double right_road_width_no_pit (double distance) const;
    double left_road_width (double distance, bool narrow = false) const;
    double right_road_width (double distance, bool narrow = false) const;
    double left_wall_height () const { return m_left_wall_height; }
    double right_wall_height () const { return m_right_wall_height; }
    double left_racing_line_width (double distance) const;
    double right_racing_line_width (double distance) const;

    void set_left_width (double distance, double width);
    void set_right_width (double distance, double width);
    void set_left_road_width (double distance, double width);
    void set_right_road_width (double distance, double width);
    void set_racing_line_adjustment (double across, double distance);
    void set_racing_line_margin (double margin);

    void set_wall_heights (double left_height, double right_height);

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

    double elevation (double along, double from_center) const;
    void build_elevation (Vamos_Geometry::Spline* elevation,
                          double start_distance);

    // A convenience function for getting the elevation at a point in
    // world coordinates.  Use 'coordinates()' if you need the x and y
    // track coodinates as well.
    double world_elevation (const Vamos_Geometry::Three_Vector& world_position) const;

    const Banking& banking () const { return m_banking; }

    // Return the normal vector at the given location.
    Vamos_Geometry::Three_Vector normal (double along, 
                                         double from_center, 
                                         const Vamos_Geometry::Three_Vector& bump,
                                         bool include_kerb = true) const;

    // Return the normal vector ignoring bumpiness.
    Vamos_Geometry::Three_Vector normal (double along, 
                                         double from_center,
                                         bool include_kerb = true) const;

    Vamos_Geometry::Three_Vector 
    barrier_normal (double along, 
                    double from_center,
                    const Vamos_Geometry::Three_Vector& bump) const;

    Vamos_Geometry::Three_Vector barrier_normal (double along, double from_center) const;

    Vamos_Geometry::Three_Vector end_coords () const;
    Vamos_Geometry::Three_Vector start_coords () const 
    { return m_start_coords; } 
    void set_start_coords (const Vamos_Geometry::Three_Vector& x)
    { m_start_coords = x; }

    double angle (double along) const 
    { return m_start_angle + arc () * along / m_length; }

    void set_kerb (Kerb* kerb, Vamos_Geometry::Direction side);

    void scale (double factor);

    void set_pit_lane (Vamos_Geometry::Direction direction,
                       Vamos_Geometry::Direction side,
                       double split_or_join, 
                       double merge,
                       double angle);
    void set_pit_width (double width, 
                        double left_shoulder, 
                        double right_shoulder);
    double pit_width () const;
    bool on_pit_merge (double distance, double from_center) const;

    virtual void set_start (const Vamos_Geometry::Three_Vector& start_coords, 
                            double start_distance,
                            double start_angle, 
                            double start_bank,
                            const std::vector <double>& texture_offsets);

    /// @return the direction at the start of the segment in radians.
    double start_angle () const { return m_start_angle; }
    void set_start_angle (double radians) { m_start_angle = radians; }

    /// @return the direction at the end of the segment in radians.
    double end_angle () const;
    double pit_angle () const { return angle (m_pit.split_or_join ()); }

    double start_distance () const { return m_start_distance; }
    double end_distance () const { return m_start_distance + m_length; }

    void last_segment (bool last) { m_last_segment = last; }

    // Do the world-to-track coordinate transformation.
    double coordinates (const Vamos_Geometry::Three_Vector& world_pos,
                        Vamos_Geometry::Three_Vector& track_pos) const;

    // Do the track-to-world coordinate transformation.
    Vamos_Geometry::Three_Vector position (double along, 
                                           double from_center) const;

    const Pit_Lane_Transition& pit () const;

    void narrow (Vamos_Geometry::Direction side, double delta_width);
    // Subtract delta_width from this segment's width.

    void set_racing_line_curvature_factor (double factor)
    { m_racing_line_curvature_factor = factor; }

    double racing_line_curvature_factor () const 
    { return m_racing_line_curvature_factor; }

  protected:
    double kerb_width (Vamos_Geometry::Direction side, double along) const;
    void set_banking (double end_angle, double pivot)
    { m_banking.set (end_angle, pivot); }

    void set_elevation_points (const TPoints& elevation)
    { m_elevation_points = elevation; }
    void set_widths (const TPoints& right, const TPoints& right_road,
                     const TPoints& left_road, const TPoints& left);
    double pit_road_connection () const;

    bool is_last_segment () const { return m_last_segment; }

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

    // The distance from the center line to the walls.
    Vamos_Geometry::Linear_Interpolator m_left_width;
    Vamos_Geometry::Linear_Interpolator m_right_width;

    // The distance from the center line to the edges of the track.
    Vamos_Geometry::Linear_Interpolator m_left_road_width;
    Vamos_Geometry::Linear_Interpolator m_right_road_width;

    double m_racing_line_adjustment;
    double m_racing_line_adjustment_distance;

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

    double m_start_skew;
    double m_end_skew;

    TPoints m_elevation_points;
    Vamos_Geometry::Spline* mp_elevation_curve;

    Banking m_banking;

    Kerb* mp_left_kerb;
    Kerb* mp_right_kerb;

    Pit_Lane_Transition m_pit;

    // The distance along the track where this segment starts.
    double m_start_distance;
    double m_start_angle;
    Vamos_Geometry::Three_Vector m_start_coords;

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

    double m_racing_line_curvature_factor;

    Road_Segment (const Road_Segment& segment);

    void scale_widths (double factor);

    // Find the angle from the beginning of a curve.
    double get_curve_angle (const Vamos_Geometry::Three_Vector& position, 
                            double across) const;

    // How far is the position off the end (+ve) or beginning (-ve) of
    // the segment?  Return 0 if the position is on the segment.
    double off_track_distance (const Vamos_Geometry::Three_Vector& 
                               track_position) const;

    double extra_road_width (Vamos_Geometry::Direction side, 
                             double distance,
                             bool narrow) const;
  };
}

#endif // not _ROAD_SEGMENT_H_
