/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013 CERN
 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 *
 * 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, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#ifndef __SHAPE_LINE_CHAIN
#define __SHAPE_LINE_CHAIN

#include <vector>
#include <sstream>

#include <boost/optional.hpp>

#include <math/vector2d.h>
#include <geometry/shape.h>
#include <geometry/seg.h>

/**
 * Class SHAPE_LINE_CHAIN
 *
 * Represents a polyline (an zero-thickness chain of connected line segments).
 * I purposedly didn't name it "polyline" to avoid confusion with the existing CPolyLine class in pcbnew. 
 *
 * SHAPE_LINE_CHAIN class shall not be used for polygons!
 */
class SHAPE_LINE_CHAIN : public SHAPE {
	private:
		typedef std::vector<VECTOR2I>::iterator point_iter;
		typedef std::vector<VECTOR2I>::const_iterator point_citer;

	public:

		/** 
		 * Struct Intersection
		 *
		 * Represents an intersection between two line segments 
		 */
		struct Intersection
		{
			/// segment belonging from the (this) argument of Intersect()
			SEG our;
			/// segment belonging from the aOther argument of Intersect()
			SEG their;
			/// point of intersection between our and their.
			VECTOR2I p;
		};

		typedef std::vector<Intersection> Intersections;

		/**
		 * Constructor
		 * Initializes an empty line chain.
		 */
		SHAPE_LINE_CHAIN():
			SHAPE( SH_LINE_CHAIN ), m_closed( false ) {};

		/**
		 * Copy Constructor
		 */
		SHAPE_LINE_CHAIN( const SHAPE_LINE_CHAIN& aShape ) :
			SHAPE( SH_LINE_CHAIN ), m_points( aShape.m_points ), m_closed( aShape.m_closed ) {};

		/**
		 * Constructor
		 * Initializes a 2-point line chain (a single segment)
		 */
		SHAPE_LINE_CHAIN( const VECTOR2I& aA, const VECTOR2I& aB ) :
			SHAPE( SH_LINE_CHAIN ),
			m_closed( false )
			{
				m_points.resize( 2 );
				m_points[0] = aA;
				m_points[1] = aB;
			}

		SHAPE_LINE_CHAIN( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I& aC ):
			SHAPE( SH_LINE_CHAIN ),
			m_closed( false )
			{
				m_points.resize( 3 );
				m_points[0] = aA;
				m_points[1] = aB;
				m_points[2] = aC;
			}

		SHAPE_LINE_CHAIN(const VECTOR2I* aV, int aCount ) :
			SHAPE( SH_LINE_CHAIN ),
			m_closed( false )
		{
			m_points.resize( aCount );

			for( int i = 0; i < aCount; i++ )
				m_points[i] = *aV++;
		}

		~SHAPE_LINE_CHAIN() {};

		/** 
		 * Function Clear()
		 * Removes all points from the line chain.
		 */
		void Clear()
		{
			m_points.clear();
			m_closed = false;
		}

		/**
		 * Function SetClosed()
		 *
		 * Marks the line chain as closed (i.e. with a segment connecting the last point with the first point).
		 * @param aClosed: whether the line chain is to be closed or not.
 		 */
		void SetClosed( bool aClosed )
		{
			m_closed = aClosed;
		}

		/**
		 * Function IsClosed()
		 *
		 * @return aClosed: true, when our line is closed.
		 */
		bool IsClosed() const
		{
			return m_closed;
		}

		/**
		 * Function SegmentCount()
		 *
		 * Returns number of segments in this line chain.
		 * @return number of segments
		 */
		int SegmentCount() const 
		{ 
			int c = m_points.size() - 1;
			if( m_closed )
				c++;

			return std::max( 0, c );
		}

		/**
		 * Function PointCount()
		 *
		 * Returns the number of points (vertices) in this line chain
		 * @return number of points
		 */
		int PointCount() const 
		{
			return m_points.size();
		}

		/**
		 * Function Segment()
		 *
		 * Returns a segment referencing to the segment (index) in the line chain.
		 * Modifying ends of the returned segment will modify corresponding points in the line chain.
		 * @param aIndex: index of the segment in the line chain. Negative values are counted from the end (i.e. -1 means
		 * the last segment in the line chain)
		 * @return SEG referenced to given segment in the line chain
		 */
		SEG Segment( int aIndex )
		{ 
			if( aIndex < 0 )
				aIndex += SegmentCount();

			if( aIndex == ( m_points.size() - 1 ) && m_closed )
				return SEG( m_points[aIndex], m_points[0], aIndex );
			else
				return SEG( m_points[aIndex], m_points[aIndex + 1], aIndex );
		}

		/**
		 * Function CSegment()
		 *
		 * Returns a read-only segment referencing to the segment (index) in the line chain.
		 * @param aIndex: index of the segment in the line chain. Negative values are counted from the end (i.e. -1 means
		 * the last segment in the line chain)
		 * @return SEG referenced to given segment in the line chain
		 */
		const SEG CSegment( int aIndex ) const
		{ 
			if( aIndex < 0 )
				aIndex += SegmentCount();

			if( aIndex == ( m_points.size() - 1 ) && m_closed )
				return SEG( const_cast<VECTOR2I&>( m_points[aIndex] ),
							const_cast<VECTOR2I&>( m_points[0] ), aIndex );
			else
				return SEG( const_cast<VECTOR2I&>( m_points[aIndex] ),
							const_cast<VECTOR2I&>( m_points[aIndex + 1] ), aIndex );
		}

		/**
		 * Function Point()
		 *
		 * Returns a reference to a given point in the line chain.
		 * @param aIndex index of the point
		 * @return reference to the point
		 */
		VECTOR2I& Point( int aIndex )
		{ 
			if( aIndex < 0 )
				aIndex += PointCount();

			return m_points[aIndex];
		}

		/**
		 * Function CPoint()
		 *
		 * Returns a const reference to a given point in the line chain.
		 * @param aIndex index of the point
		 * @return const reference to the point
		 */
		const VECTOR2I& CPoint( int aIndex ) const
		{ 
			if( aIndex < 0 )
				aIndex += PointCount();

			return m_points[aIndex];
		}

		/// @copydoc SHAPE::BBox()
		const BOX2I BBox( int aClearance = 0 ) const
		{
			BOX2I bbox;
			bbox.Compute( m_points );

			return bbox;
		}

		/** 
		 * Function Collide()
		 *
		 * Checks if point aP lies closer to us than aClearance.
		 * @param aP the point to check for collisions with
		 * @param aClearance minimum distance that does not qualify as a collision.
		 * @return true, when a collision has been found
		 */
		bool Collide( const VECTOR2I& aP, int aClearance = 0 ) const;
		
		/** 
		 * Function Collide()
		 *
		 * Checks if box aBox lies closer to us than aClearance.
		 * @param aP the box to check for collisions with
		 * @param aClearance minimum distance that does not qualify as a collision.
		 * @return true, when a collision has been found
		 */
		bool Collide( const BOX2I& aBox, int aClearance = 0 ) const;
		
		/** 
		 * Function Collide()
		 *
		 * Checks if segment aSeg lies closer to us than aClearance.
		 * @param aSeg the segment to check for collisions with
		 * @param aClearance minimum distance that does not qualify as a collision.
		 * @return true, when a collision has been found
		 */
		bool Collide( const SEG& aSeg, int aClearance = 0 ) const;
		
		/**
		 * Function Distance()
		 * 
		 * Computes the minimum distance between the line chain and a point aP. 
		 * @param aP the point
		 * @return minimum distance.
		 */
		int Distance( const VECTOR2I& aP ) const;

		/** 
		 * Function Reverse()
		 *
		 * Reverses point order in the line chain.
		 * @return line chain with reversed point order (original A-B-C-D: returned D-C-B-A)
		 */
		const SHAPE_LINE_CHAIN Reverse() const; 

		/**
		 * Function Length()
		 *
		 * Returns length of the line chain in Euclidean metric.
		 * @return length of the line chain
		 */
		int Length() const;
		
		/**
		 * Function Append()
		 *
		 * Appends a new point at the end of the line chain.
		 * @param aX is X coordinate of the new point
		 * @param aY is Y coordinate of the new point
		 */
		void Append( int aX, int aY )
		{
			VECTOR2I v( aX, aY );
			Append( v );
		}

		/**
		 * Function Append()
		 *
		 * Appends a new point at the end of the line chain.
		 * @param aP the new point
		 */
		void Append( const VECTOR2I& aP )
		{
			if( m_points.size() == 0 )
				m_bbox = BOX2I( aP, VECTOR2I( 0, 0 ) );
			
			if( m_points.size() == 0 || CPoint( -1 ) != aP )
			{
				m_points.push_back( aP );
				m_bbox.Merge( aP );
			}
		}

		/**
		 * Function Append()
		 *
		 * Appends another line chain at the end.
		 * @param aOtherLine the line chain to be appended.
		 */
		void Append( const SHAPE_LINE_CHAIN& aOtherLine )
		{
			if( aOtherLine.PointCount() == 0 )
				return;

			else if( PointCount() == 0 || aOtherLine.CPoint( 0 ) != CPoint( -1 ) )
			{
				const VECTOR2I p = aOtherLine.CPoint( 0 );
				m_points.push_back( p );
				m_bbox.Merge( p );
			}

			for( int i = 1; i < aOtherLine.PointCount(); i++ )
			{
				const VECTOR2I p = aOtherLine.CPoint( i );
				m_points.push_back( p );
				m_bbox.Merge( p );
			}
		}

		/**
		 * Function Replace()
		 * 
		 * Replaces points with indices in range [start_index, end_index] with a single
		 * point aP.
		 * @param aStartIndex start of the point range to be replaced (inclusive)
		 * @param aEndIndex end of the point range to be replaced (inclusive)
		 * @param aP replacement point
		 */
		void Replace( int aStartIndex, int aEndIndex, const VECTOR2I& aP );

		/**
		 * Function Replace()
		 * 
		 * Replaces points with indices in range [start_index, end_index] with the points from
		 * line chain aLine.
		 * @param aStartIndex start of the point range to be replaced (inclusive)
		 * @param aEndIndex end of the point range to be replaced (inclusive)
		 * @param aLine replacement line chain.
		 */
		void Replace( int aStartIndex, int aEndIndex, const SHAPE_LINE_CHAIN& aLine );

		/**
		 * Function Remove()
		 * 
		 * Removes the range of points [start_index, end_index] from the line chain.
		 * @param aStartIndex start of the point range to be replaced (inclusive)
		 * @param aEndIndex end of the point range to be replaced (inclusive)
		 */
		void Remove( int aStartIndex, int aEndIndex );
		
		/**
		 * Function Split()
		 * 
		 * Inserts the point aP belonging to one of the our segments, splitting the adjacent
		 * segment in two.
		 * @param aP the point to be inserted
		 * @return index of the newly inserted point (or a negative value if aP does not lie on our line)
		 */
		int Split( const VECTOR2I& aP );
		
		/**
		 * Function Find()
		 * 
		 * Searches for point aP.
		 * @param aP the point to be looked for
		 * @return index of the correspoinding point in the line chain or negative when not found.
		 */
		int Find ( const VECTOR2I& aP ) const;
		
		/**
		 * Function Slice()
		 * 
		 * Returns a subset of this line chain containing the [start_index, end_index] range of points.
		 * @param aStartIndex start of the point range to be returned (inclusive)
		 * @param aEndIndex end of the point range to be returned (inclusive)
		 * @return cut line chain.
		 */
		const SHAPE_LINE_CHAIN Slice( int aStartIndex, int aEndIndex = -1) const;

		struct compareOriginDistance
		{
			compareOriginDistance( VECTOR2I& aOrigin ):
				m_origin( aOrigin ) {};

			bool operator()( const Intersection& aA, const Intersection& aB )
			{
				return ( m_origin - aA.p ).EuclideanNorm() < ( m_origin - aB.p ).EuclideanNorm();
			}

			VECTOR2I m_origin;
		};

		/**
		 * Function Intersect()
		 *
		 * Finds all intersection points between our line chain and the segment aSeg.
		 * @param aSeg the segment chain to find intersections with
		 * @param aIp reference to a vector to store found intersections. Intersection points
		 * are sorted with increasing distances from point aSeg.a.
		 * @return number of intersections found
		 */
		int Intersect ( const SEG& aSeg, Intersections& aIp ) const;
		
		/**
		 * Function Intersect()
		 *
		 * Finds all intersection points between our line chain and the line chain aChain.
		 * @param aChain the line chain to find intersections with
		 * @param aIp reference to a vector to store found intersections. Intersection points
		 * are sorted with increasing path lengths from the starting point of aChain.
		 * @return number of intersections found
		 */
		int Intersect( const SHAPE_LINE_CHAIN& aChain, Intersections& aIp ) const;
		
		/**
		 * Function PathLength()
		 *
		 * Computes the walk path length from the beginning of the line chain and 
		 * the point aP belonging to our line.
		 * @return: path length in Euclidean metric or negative if aP does not belong to the line chain.
		 */
		int PathLength( const VECTOR2I& aP ) const;
		
		/**
		 * Function PointInside()
		 *
		 * Checks if point aP lies inside a convex polygon defined by the line chain. For closed
		 * shapes only.
		 * @param aP point to check
		 * @return true if the point is inside the shape (edge is not treated as being inside). 
		 */
		 bool PointInside( const VECTOR2I& aP ) const;
	
		/**
		 * Function PointOnEdge()
		 *
		 * Checks if point aP lies on an edge or vertex of the line chain.
		 * @param aP point to check
		 * @return true if the point lies on the edge.
		 */
		bool PointOnEdge( const VECTOR2I& aP ) const;
		
		/**
		 * Function SelfIntersecting()
		 *
		 * Checks if the line chain is self-intersecting.
		 * @return (optional) first found self-intersection point.
		 */
		const boost::optional<Intersection> SelfIntersecting() const;
		
		/**
		 * Function Simplify()
		 *
		 * Simplifies the line chain by removing colinear adjacent segments and duplicate vertices.
		 * @return reference to self.
		 */
		SHAPE_LINE_CHAIN& Simplify();
		
		/**
		 * Function NearestPoint()
		 *
		 * Finds a point on the line chain that is closest to point aP.
		 * @return the nearest point.
		 */
		const VECTOR2I NearestPoint( const VECTOR2I& aP ) const;
		
		/// @copydoc SHAPE::Format()
		const std::string Format() const;

		bool operator!=( const SHAPE_LINE_CHAIN& aRhs ) const
		{
			if( PointCount() != aRhs.PointCount() )
				return true;

			for( int i = 0; i < PointCount(); i++ )
			{
				if( CPoint( i ) != aRhs.CPoint( i ) )
					return true;
			}

			return false;
		}

	private:
		/// array of vertices
		std::vector<VECTOR2I> m_points;

		/// is the line chain closed?
		bool m_closed;

		/// cached bounding box
		BOX2I m_bbox;
};

#endif // __SHAPE_LINE_CHAIN
