/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2021 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * 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 TEARDROP_H
#define TEARDROP_H

#include <pcb_edit_frame.h>
#include <board.h>
#include <footprint.h>
#include <pad.h>
#include <pcb_track.h>
#include <zone.h>
#include "teardrop_parameters.h"

#define MAGIC_TEARDROP_PADVIA_NAME "$teardrop_padvia$"
#define MAGIC_TEARDROP_TRACK_NAME "$teardrop_track$"

class TRACK_BUFFER;

/**
 * Stores info of a pad, via or track end to build a teardrop
 */
struct VIAPAD
{
    VIAPAD( PCB_VIA* aVia );
    VIAPAD( PAD* aPad );
    VIAPAD( PCB_TRACK* aTrack, ENDPOINT_T aEndPoint );

    bool IsOnLayer( PCB_LAYER_ID aLayer ) const
    {
        return m_Parent->IsOnLayer( aLayer );
    }

    VECTOR2I m_Pos;
    int m_Width;        // The diameter of a round shape, or the min size for others
    int m_Drill;
    int m_NetCode;
    bool m_IsRound;     // true for round shapes, false for any other shape
    bool m_IsPad;       // true for pads, false for vias and tracks
    BOARD_CONNECTED_ITEM* m_Parent;
};


/**
 * TEARDROP_MANAGER manage and build teardrop areas
 * A teardrop area is a polygonal area (a copper ZONE) having:
 * 2 points on the track connected to a pad or via
 * 2 points on the outline of this pad or via
 * 1 point near the pad/via position (calculated in order to have this pad/via position
 * inside the area
 * The 2 sides joining a point on the track to the corresponding point on the pad/via
 * outline can be a straight line or a curved shape (defined from a Bezier curve)
 * This curved shape is built by segments (3 to 10) from this Bezier curve
 * Because one cannot build a suitable shape for a custom pad, custom pads are ignored.
 * Size of area (height and length) are defined from the pad/via size or for pads having
 * a size X and a size Y, the smallest of X,Y size.
 */
class TEARDROP_MANAGER
{
    friend class TEARDROP_PARAMETERS;

public:
    enum TEARDROP_VARIANT
    {
        TD_TYPE_PADVIA,     // Specify a teardrop on a pad via
        TD_TYPE_TRACKEND    // specify a teardrop on a rond end of a wide track
    };

    TEARDROP_MANAGER( BOARD* aBoard, PCB_EDIT_FRAME* aFrame );

    /**
     * Set teardrops on a teardrop free board
     * @return the number of teardrop created
     * @param aCommitter is a BOARD_COMMIT reference (can be null)
     * @param aFollowTracks = true to use a track connected to the initial track connected
     * to a pad / via if this initial track is too short to build the teardrop
     */
    int SetTeardrops( BOARD_COMMIT* aCommitter,
                      bool aFollowTracks = true );


    /**
     * Remove all teardrops
     * @return the count of removed teardrops
     * @param aCommitter is a BOARD_COMMITto handle undo/Redo commands
     * @param aCommitAfterRemove = true to commit when removed,
     * false if some other changes must be made and the actual commit postponed
     * Can be nullptr
     */
    int  RemoveTeardrops( BOARD_COMMIT* aCommitter, bool aCommitAfterRemove );


private:
    /**
     * Collect and build the list of all vias from the given board
     */
    void collectVias( std::vector< VIAPAD >& aList ) const;

    /**
     * Build a list of pads candidate for teardrops from the given board
     * Pads with no net are not candidate (and have no track connected
     * Custom pads are not candidate because one cannot define a teardrop shape
     * @param aList is the list of VIAPAD to fill
     * @param aDrilledViaPad = true to include drilled shapes
     * @param aRoundShapesOnly = true to ignore not round shapes
     * @param aIncludeNotDrilled = true to include not drilled pads like SMD
     */
    void collectPadsCandidate( std::vector< VIAPAD >& aList,
                               bool aDrilledViaPad,
                               bool aRoundShapesOnly,
                               bool aIncludeNotDrilled ) const;

    /**
     * Build a list of all teardrops on the current board
     * @param aList is the list to populate
     */
    void collectTeardrops( std::vector< ZONE* >& aList ) const;

    /**
     * Add teardrop on tracks of different sizes connected by their end
     */
    int addTeardropsOnTracks( BOARD_COMMIT* aCommitter );

    /**
     * @return true if the given aVia + aTrack is located inside a zone of the same netname
     */
    bool isViaAndTrackInSameZone( VIAPAD& aVia, PCB_TRACK* aTrack) const;

    /**
     * Compute the curve part points for teardrops connected to a round shape
     * The Bezier curve control points are optimized for a round pad/via shape,
     * and do not give a good curve shape for other pad shapes
     * use m_m_heightRatio
     */
    void computeCurvedForRoundShape( TEARDROP_PARAMETERS* aCurrParams,
                                     std::vector<VECTOR2I>& aPoly,
                                     int aTrackHalfWidth,
                                     VECTOR2D aTrackDir, VIAPAD& aViaPad,
                                     std::vector<VECTOR2I>& aPts ) const;


    /**
     * Compute the curve part points for teardrops connected to a rectangular/polygonal shape
     * The Bezier curve control points are not optimized for a special shape,
     * so use computeCurvedForRoundShape() for round shapes for better result
     */
    void computeCurvedForRectShape( TEARDROP_PARAMETERS* aCurrParams,
                                    std::vector<VECTOR2I>& aPoly, int aTdHeight,
                                    int aTrackHalfWidth, VIAPAD& aViaPad,
                                    std::vector<VECTOR2I>& aPts ) const;

    /**
     * Compute all teardrop points of the polygon shape
     * @return true if the polygonal shape was calculated, false if not buildable
     * use m_lengthRatio and m_heightRatio
     */
    bool computeTeardropPolygonPoints( TEARDROP_PARAMETERS* aCurrParams,
                                       std::vector<VECTOR2I>& aCorners,
                                       PCB_TRACK* aTrack, VIAPAD& aVia,
                                       bool aFollowTracks,
                                       TRACK_BUFFER& aTrackLookupList) const;
    /**
     * Compute the 2 points on pad/via of the teardrop shape
     * @return false if these 2 points are not found
     * @param aLayer is the layer for the teardrop
     * @param aViaPad is the teardrop anchor
     * teardrop height = aViaPad size * aHeightRatio
     * @param aPts is the buffer that contains initial and final teardrop polygonal shape
     * in aPts:
     * A and B ( aPts[0] and  aPts[1] ) are points on the track
     * C and E ( aPts[2] and  aPts[4] ) are points on the aViaPad
     * D ( aPts[3] ) is midpoint behind the aViaPad centre
     * m_heightRatio is the factor to calculate the aViaPad teardrop size
    */
    bool ComputePointsOnPadVia( TEARDROP_PARAMETERS* aCurrParams,
                                PCB_LAYER_ID aLayer, VIAPAD& aViaPad,
                                std::vector<VECTOR2I>& aPts ) const;

    /**
     * Find a track connected to the end of another track
     * @return a reference to the touching track (or nullptr)
     * @param aMatchType returns the end point id 0, STARTPOINT, ENDPOINT
     * @param aTrackRef is the reference track
     * @param aEndpoint is the coordinate to test
     * @param aTrackLookupList is the buffer of available tracks
     */
    PCB_TRACK* findTouchingTrack( EDA_ITEM_FLAGS& aMatchType, PCB_TRACK* aTrackRef,
                                  const VECTOR2I& aEndPoint,
                                  TRACK_BUFFER& aTrackLookupList ) const;

    /**
     * Creates a teardrop (a ZONE item) from its polygonal shape, track netcode and layer
     * @param aTeardropVariant = variant of the teardrop( attached to a pad, or a track end )
     * @param aPoints is the polygonal shape
     * @param aTrack is the track connected to the starting points of the teardrop
     * (mainly for net info)
     */
    ZONE* createTeardrop( TEARDROP_VARIANT aTeardropVariant,
                          std::vector<VECTOR2I>& aPoints, PCB_TRACK* aTrack) const;

    /**
     * Set priority of created teardrops. smaller have bigger priority
     */
    void setTeardropPriorities();

    /**
     * @return true if a point on a track can be found as anchor point of a teardrop
     * @param aStartPoint is the start point of the track found (always inside the teardrop)
     * @param aEndPoint is the start point of the track found (always outside the teardrop)
     * @param aTrack is the track connected to the pad/via used to search a anchor point
     *  this reference can be modified if a connected track to the initial track is selected
     * @param aViaPad is the via/pad used to build the teardrop
     * @param aEffectiveTeardropLen is the actual teardrop length, that can be smaller than expected
     *  if the connected track length is too small
     * @param aFollowTracks = true to use a connected track to aTrack if aTrack is too small
     * @param aTrackLookupList is the list of tracks to explore if aFollowTracks = true
     * m_lengthRatio is the length of teardrop (ratio pad/via size/teardrop len)
    */
    bool findAnchorPointsOnTrack( TEARDROP_PARAMETERS* aCurrParams,
                                  VECTOR2I& aStartPoint, VECTOR2I& aEndPoint,
                                  PCB_TRACK*& aTrack, VIAPAD& aViaPad,
                                  int* aEffectiveTeardropLen,
                                  bool aFollowTracks, TRACK_BUFFER& aTrackLookupList ) const;

private:
    int     m_tolerance;                // max distance between a track end point and a pad/via center to
                                        // see them connected to ut a teardrop
    BOARD*  m_board;
    TEARDROP_PARAMETERS_LIST* m_prmsList; // the teardrop parameters list, from the board design settings
    std::vector<ZONE*> m_createdTdList; // list of new created teardrops
};


// A class to store tracks grouped by layer and netcode
class TRACK_BUFFER
{
public:
    TRACK_BUFFER() {}

    /**
     * Add a track in buffer, in space grouping tracks having the same netcode and the same layer
     */
    void AddTrack( PCB_TRACK* aTrack, int aLayer, int aNetcode );

    /**
     * @return the buffer that stores tracks having the same layer and the same netcode
     */
    std::vector<PCB_TRACK*>* GetTrackList( int aLayer, int aNetcode )
    {
        return m_map_tracks.at( idxFromLayNet( aLayer, aNetcode ) );
    }

    /**
     * @return a reference to the internal buffer
     */
    std::map< int, std::vector<PCB_TRACK*>* >& GetBuffer() { return m_map_tracks ; }

    static void GetNetcodeAndLayerFromIndex( int aIdx, int* aLayer, int* aNetcode )
    {
        *aLayer = aIdx & 0xFF;
        *aNetcode = aIdx >> 8;
    }

private:
    // Build an index from the layer id and the netcode, to store a track in buffer
    int idxFromLayNet( int aLayer, int aNetcode ) const
    {
        return ( aNetcode << 8 ) + ( aLayer & 0xFF );
    }

    // Track buffer, tracks are grouped by layer+netcode
    std::map< int, std::vector<PCB_TRACK*>* > m_map_tracks;
};

#endif  // ifndef TEARDROP_H