/*
 * This program source code file is part of KICAD, a free EDA CAD application.
 *
 * Copyright (C) 2014-2017 CERN
 * @author Maciej Suminski <maciej.suminski@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 EDIT_POINTS_H_
#define EDIT_POINTS_H_

#include <base_struct.h>
#include <layers_id_colors_and_visibility.h>

#include <list>
#include <deque>

#include "edit_constraints.h"

#include <memory>

#include <view/view.h>

/**
 * EDIT_POINT
 *
 * Represents a single point that can be used for modifying items. It is directly related to one
 * of points in a graphical item (e.g. vertex of a zone or center of a circle).
 */
class EDIT_POINT
{
public:
    /**
     * Constructor
     *
     * @param aPoint stores coordinates for EDIT_POINT.
     */
    EDIT_POINT( const VECTOR2I& aPoint, EDA_ITEM* aConnection = nullptr ) :
        m_position( aPoint ),
        m_connection( aConnection )
    {
    }

    virtual ~EDIT_POINT() {}

    /**
     * Function GetPosition()
     *
     * Returns coordinates of an EDIT_POINT. Note that it may be different than coordinates of
     * a graphical item that is bound to the EDIT_POINT.
     */
    virtual VECTOR2I GetPosition() const
    {
        return m_position;
    }

    virtual EDA_ITEM* GetConnection() const
    {
        return m_connection;
    }

    /**
     * Function GetX()
     *
     * Returns X coordinate of an EDIT_POINT.
     */
    inline int GetX() const
    {
        return GetPosition().x;
    }

    /**
     * Function GetY()
     *
     * Returns Y coordinate of an EDIT_POINT.
     */
    inline int GetY() const
    {
        return GetPosition().y;
    }

    /**
     * Function SetPosition()
     *
     * Sets new coordinates for an EDIT_POINT. It does not change the coordinates of a graphical
     * item.
     * @param aPosition are new coordinates.
     */
    virtual void SetPosition( const VECTOR2I& aPosition )
    {
        m_position = aPosition;
    }

    virtual void SetPosition( int x, int y )
    {
        m_position.x = x;
        m_position.y = y;
    }

    /**
     * Function WithinPoint()
     *
     * Checks if given point is within a square centered in the EDIT_POINT position.
     * @param aPoint is point to be checked.
     * @param aSize is length of the square side.
     */
    bool WithinPoint( const VECTOR2I& aPoint, unsigned int aSize ) const;

    /**
     * Function SetConstraint()
     *
     * Sets a constraint for and EDIT_POINT.
     * @param aConstraint is the constraint to be set.
     */
    void SetConstraint( EDIT_CONSTRAINT<EDIT_POINT>* aConstraint )
    {
        m_constraint.reset( aConstraint );
    }

    /**
     * Function GetConstraint()
     *
     * Returns the constraint imposed on an EDIT_POINT. If there are no constraints, NULL is
     * returned.
     */
    EDIT_CONSTRAINT<EDIT_POINT>* GetConstraint() const
    {
        return m_constraint.get();
    }

    /**
     * Function ClearConstraint()
     *
     * Removes previously set constraint.
     */
    inline void ClearConstraint()
    {
        m_constraint.reset();
    }

    /**
     * Function IsConstrained()
     *
     * Checks if point is constrained.
     * @return True is point is constrained, false otherwise.
     */
    inline bool IsConstrained() const
    {
        return m_constraint != NULL;
    }

    /**
     * Function ApplyConstraint()
     *
     * Corrects coordinates of an EDIT_POINT by applying previously set constraint.
     */
    virtual void ApplyConstraint()
    {
        if( m_constraint )
            m_constraint->Apply();
    }

    bool operator==( const EDIT_POINT& aOther ) const
    {
        return m_position == aOther.m_position;
    }

    ///> Single point size in pixels
    static const int POINT_SIZE = 10;

private:
    ///> Position of EDIT_POINT
    VECTOR2I  m_position;

    ///> An optional item to a connected item.  Used to mimic polyLine behaviour
    ///> with individual line segments.
    EDA_ITEM* m_connection;

    ///> Constraint for the point, NULL if none
    std::shared_ptr<EDIT_CONSTRAINT<EDIT_POINT> > m_constraint;
};


/**
 * EDIT_LINE
 *
 * Represents a line connecting two EDIT_POINTs. That allows one to move them
 * both by dragging the EDIT_POINT in the middle. As it uses references to
 * EDIT_POINTs, all coordinates are automatically synchronized.
 */
class EDIT_LINE : public EDIT_POINT
{
public:
    /**
     * Constructor
     *
     * @param aOrigin is the origin of EDIT_LINE.
     * @param aEnd is the end of EDIT_LINE.
     */
    EDIT_LINE( EDIT_POINT& aOrigin, EDIT_POINT& aEnd ) :
        EDIT_POINT( aOrigin.GetPosition() + ( aEnd.GetPosition() - aOrigin.GetPosition() ) / 2 ),
        m_origin( aOrigin ), m_end( aEnd )
    {
    }

    ///> @copydoc EDIT_POINT::GetPosition()
    virtual VECTOR2I GetPosition() const override
    {
        return ( m_origin.GetPosition() + m_end.GetPosition() ) / 2;
    }

    ///> @copydoc EDIT_POINT::GetPosition()
    virtual void SetPosition( const VECTOR2I& aPosition ) override
    {
        VECTOR2I difference = aPosition - GetPosition();

        m_origin.SetPosition( m_origin.GetPosition() + difference );
        m_end.SetPosition( m_end.GetPosition() + difference );
    }

    ///> @copydoc EDIT_POINT::ApplyConstraint()
    virtual void ApplyConstraint() override
    {
        if( m_constraint )
            m_constraint->Apply();

        m_origin.ApplyConstraint();
        m_end.ApplyConstraint();
    }

    /**
     * Function SetConstraint()
     *
     * Sets a constraint for and EDIT_POINT.
     * @param aConstraint is the constraint to be set.
     */
    void SetConstraint( EDIT_CONSTRAINT<EDIT_LINE>* aConstraint )
    {
        m_constraint.reset( aConstraint );
    }

    /**
     * Function GetConstraint()
     *
     * Returns the constraint imposed on an EDIT_POINT. If there are no constraints, NULL is
     * returned.
     */
    EDIT_CONSTRAINT<EDIT_LINE>* GetConstraint() const
    {
        return m_constraint.get();
    }

    /**
     * Function GetOrigin()
     *
     * Returns the origin EDIT_POINT.
     */
    EDIT_POINT& GetOrigin()
    {
        return m_origin;
    }

    const EDIT_POINT& GetOrigin() const
    {
        return m_origin;
    }

    /**
     * Function GetEnd()
     *
     * Returns the end EDIT_POINT.
     */
    EDIT_POINT& GetEnd()
    {
        return m_end;
    }

    const EDIT_POINT& GetEnd() const
    {
        return m_end;
    }

    bool operator==( const EDIT_POINT& aOther ) const
    {
        return GetPosition() == aOther.GetPosition();
    }

    bool operator==( const EDIT_LINE& aOther ) const
    {
        return m_origin == aOther.m_origin && m_end == aOther.m_end;
    }

private:
    EDIT_POINT& m_origin;           ///< Origin point for a line
    EDIT_POINT& m_end;              ///< End point for a line

    ///> Constraint for the point, NULL if none
    std::shared_ptr<EDIT_CONSTRAINT<EDIT_LINE> > m_constraint;
};


/**
 * EDIT_POINTS
 *
 * EDIT_POINTS is a VIEW_ITEM that manages EDIT_POINTs and EDIT_LINEs and draws them.
 */
class EDIT_POINTS : public EDA_ITEM
{
public:
    /**
     * Constructor.
     *
     * @param aParent is the item to which EDIT_POINTs are related.
     */
    EDIT_POINTS( EDA_ITEM* aParent );

    /**
     * Function FindPoint()
     *
     * Returns a point that is at given coordinates or NULL if there is no such point.
     * @param aLocation is the location for searched point.
     */
    EDIT_POINT* FindPoint( const VECTOR2I& aLocation, KIGFX::VIEW *aView );

    /**
     * Function GetParent()
     *
     * Returns parent of the EDIT_POINTS.
     */
    EDA_ITEM* GetParent() const
    {
        return m_parent;
    }

    /**
     * Function AddPoint()
     *
     * Adds an EDIT_POINT.
     * @param aPoint is the new point.
     */
    void AddPoint( const EDIT_POINT& aPoint )
    {
        m_points.push_back( aPoint );
    }

    /**
     * Function AddPoint()
     *
     * Adds an EDIT_POINT.
     * @param aPoint are coordinates of the new point.
     */
    void AddPoint( const VECTOR2I& aPoint, EDA_ITEM* aConnection = nullptr )
    {
        AddPoint( EDIT_POINT( aPoint, aConnection ) );
    }

    /**
     * Function AddLine()
     *
     * Adds an EDIT_LINE.
     * @param aLine is the new line.
     */
    void AddLine( const EDIT_LINE& aLine )
    {
        m_lines.push_back( aLine );
    }

    /**
     * Function AddLine()
     *
     * Adds an EDIT_LINE.
     * @param aOrigin is the origin for a new line.
     * @param aEnd is the end for a new line.
     */
    void AddLine( EDIT_POINT& aOrigin, EDIT_POINT& aEnd )
    {
        m_lines.emplace_back( aOrigin, aEnd );
    }

    /**
     * Function AddBreak()
     *
     * Adds a break, indicating the end of a contour.
     */
    void AddBreak()
    {
        assert( m_points.size() > 0 );
        m_contours.push_back( m_points.size() - 1 );
    }

    /**
     * Function GetContourStartIdx()
     *
     * Returns index of the contour origin for a point with given index.
     * @param aPointIdx is the index of point for which the contour origin is searched.
     * @return Index of the contour origin point.
     */
    int GetContourStartIdx( int aPointIdx ) const;

    /**
     * Function GetContourEndIdx()
     *
     * Returns index of the contour finish for a point with given index.
     * @param aPointIdx is the index of point for which the contour finish is searched.
     * @return Index of the contour finish point.
     */
    int GetContourEndIdx( int aPointIdx ) const;

    /**
     * Function IsContourStart()
     *
     * Checks is a point with given index is a contour origin.
     * @param aPointIdx is the index of the point to be checked.
     * @return True if the point is an origin of a contour.
     */
    bool IsContourStart( int aPointIdx ) const;

    /**
     * Function IsContourEnd()
     *
     * Checks is a point with given index is a contour finish.
     * @param aPointIdx is the index of the point to be checked.
     * @return True if the point is a finish of a contour.
     */
    bool IsContourEnd( int aPointIdx ) const;

    /**
     * Function Previous()
     *
     * Returns the point that is after the given point in the list.
     * @param aPoint is the point that is supposed to be preceding the searched point.
     * @param aTraverseContours decides if in case of breaks should we return to the origin
     * of contour or continue with the next contour.
     * @return The point following aPoint in the list. If aPoint is the first in
     * the list, the last from the list will be returned. If there are no points at all, NULL
     * is returned.
     */
    EDIT_POINT* Previous( const EDIT_POINT& aPoint, bool aTraverseContours = true );

    EDIT_LINE* Previous( const EDIT_LINE& aLine );

    /**
     * Function Next()
     *
     * Returns the point that is before the given point in the list.
     * @param aPoint is the point that is supposed to be following the searched point.
     * @param aTraverseContours decides if in case of breaks should we return to the origin
     * of contour or continue with the next contour.
     * @return The point preceding aPoint in the list. If aPoint is the last in
     * the list, the first point from the list will be returned. If there are no points at all,
     * NULL is returned.
     */
    EDIT_POINT* Next( const EDIT_POINT& aPoint, bool aTraverseContours = true );

    EDIT_LINE* Next( const EDIT_LINE& aLine );

    EDIT_POINT& Point( unsigned int aIndex )
    {
        return m_points[aIndex];
    }

    const EDIT_POINT& Point( unsigned int aIndex ) const
    {
        return m_points[aIndex];
    }

    EDIT_LINE& Line( unsigned int aIndex )
    {
        return m_lines[aIndex];
    }

    const EDIT_LINE& Line( unsigned int aIndex ) const
    {
        return m_lines[aIndex];
    }

    /**
     * Function PointsSize()
     *
     * Returns number of stored EDIT_POINTs.
     */
    unsigned int PointsSize() const
    {
        return m_points.size();
    }

    /**
     * Function LinesSize()
     *
     * Returns number of stored EDIT_LINEs.
     */
    unsigned int LinesSize() const
    {
        return m_lines.size();
    }

    ///> @copydoc VIEW_ITEM::ViewBBox()
    virtual const BOX2I ViewBBox() const override;

    ///> @copydoc VIEW_ITEM::ViewDraw()
    virtual void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override;

    ///> @copydoc VIEW_ITEM::ViewGetLayers()
    virtual void ViewGetLayers( int aLayers[], int& aCount ) const override
    {
        aCount = 1;
        aLayers[0] = LAYER_GP_OVERLAY ;
    }

#if defined(DEBUG)
    void Show( int x, std::ostream& st ) const override
    {
    }
#endif

    /** Get class name
     * @return  string "EDIT_POINTS"
     */
    virtual wxString GetClass() const override
    {
        return wxT( "EDIT_POINTS" );
    }

private:
    EDA_ITEM* m_parent;                 ///< Parent of the EDIT_POINTs
    std::deque<EDIT_POINT> m_points;    ///< EDIT_POINTs for modifying m_parent
    std::deque<EDIT_LINE> m_lines;      ///< EDIT_LINEs for modifying m_parent
    std::list<int> m_contours;          ///< Indices of end contour points
    bool m_allowPoints;                 ///< If false, only allow editing of EDIT_LINES
};

#endif /* EDIT_POINTS_H_ */