/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com
 * Copyright (C) 2004-2020 KiCad Developers, see change_log.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 SCH_ITEM_H
#define SCH_ITEM_H

#include <unordered_map>
#include <unordered_set>
#include <vector>

#include <base_struct.h>
#include <general.h>
#include <sch_sheet_path.h>

class SCH_CONNECTION;
class SCH_SHEET_PATH;
class LINE_READER;
class SCH_EDIT_FRAME;
class wxFindReplaceData;
class PLOTTER;
class NETLIST_OBJECT;
class NETLIST_OBJECT_LIST;


enum FIELDS_AUTOPLACED
{
    FIELDS_AUTOPLACED_NO = 0,
    FIELDS_AUTOPLACED_AUTO,
    FIELDS_AUTOPLACED_MANUAL
};


enum DANGLING_END_T
{
    UNKNOWN = 0,
    WIRE_START_END,
    WIRE_END_END,
    BUS_START_END,
    BUS_END_END,
    JUNCTION_END,
    PIN_END,
    LABEL_END,
    BUS_ENTRY_END,
    WIRE_ENTRY_END,
    SHEET_LABEL_END,
    NO_CONNECT_END,
};


/**
 * DANGLING_END_ITEM
 * is a helper class used to store the state of schematic  items that can be connected to
 * other schematic items.
 */
class DANGLING_END_ITEM
{
private:
    /// A pointer to the connectable object.
    EDA_ITEM*       m_item;

    /// The position of the connection point.
    wxPoint         m_pos;

    /// The type of connection of #m_item.
    DANGLING_END_T  m_type;

    /// A pointer to the parent object (in the case of pins)
    const EDA_ITEM* m_parent;

public:
    DANGLING_END_ITEM( DANGLING_END_T aType, EDA_ITEM* aItem, const wxPoint& aPosition )
    {
        m_item = aItem;
        m_type = aType;
        m_pos = aPosition;
        m_parent = aItem;
    }

    DANGLING_END_ITEM( DANGLING_END_T aType, EDA_ITEM* aItem,
            const wxPoint& aPosition, const EDA_ITEM* aParent )
    {
        m_item = aItem;
        m_type = aType;
        m_pos = aPosition;
        m_parent = aParent;
    }

    bool operator==( const DANGLING_END_ITEM& aB )
    {
        return GetItem() == aB.GetItem()
            && GetPosition() == aB.GetPosition()
            && GetType() == aB.GetType()
            && GetParent() == aB.GetParent();
    }

    bool operator!=( const DANGLING_END_ITEM& aB )
    {
        return GetItem() != aB.GetItem()
                || GetPosition() != aB.GetPosition()
                || GetType() != aB.GetType()
                || GetParent() != aB.GetParent();;
    }

    bool operator<( const DANGLING_END_ITEM& rhs ) const
    {
        return( m_pos.x < rhs.m_pos.x || ( m_pos.x == rhs.m_pos.x && m_pos.y < rhs.m_pos.y )
                || ( m_pos == rhs.m_pos && m_item < rhs.m_item ) );
    }

    wxPoint GetPosition() const { return m_pos; }
    EDA_ITEM* GetItem() const { return m_item; }
    const EDA_ITEM* GetParent() const { return m_parent; }
    DANGLING_END_T GetType() const { return m_type; }
};


typedef std::unordered_set<SCH_ITEM*> ITEM_SET;

/**
 * SCH_ITEM
 * is a base class for any item which can be embedded within the SCHEMATIC
 * container class, and therefore instances of derived classes should only be
 * found in EESCHEMA or other programs that use class SCHEMATIC and its contents.
 * The corresponding class in Pcbnew is BOARD_ITEM.
 */
class SCH_ITEM : public EDA_ITEM
{
    friend class CONNECTION_GRAPH;

protected:
    SCH_LAYER_ID      m_Layer;
    EDA_ITEMS         m_connections;      // List of items connected to this item.
    FIELDS_AUTOPLACED m_fieldsAutoplaced; // indicates status of field autoplacement
    wxPoint           m_storedPos;        // a temporary variable used in some move commands
                                          // to store a initial pos of the item or mouse cursor

    /// Stores pointers to other items that are connected to this one, per sheet
    std::unordered_map<SCH_SHEET_PATH, ITEM_SET> m_connected_items;

    /// Stores connectivity information, per sheet
    std::unordered_map<SCH_SHEET_PATH, SCH_CONNECTION*> m_connection_map;

    /// True if connectivity info might be out of date
    bool m_connectivity_dirty;

public:
    SCH_ITEM( EDA_ITEM* aParent, KICAD_T aType );

    SCH_ITEM( const SCH_ITEM& aItem );

    ~SCH_ITEM();

    virtual wxString GetClass() const override
    {
        return wxT( "SCH_ITEM" );
    }

    /**
     * Function SwapData
     * swap the internal data structures \a aItem with the schematic item.
     * Obviously, aItem must have the same type than me
     * @param aItem The item to swap the data structures with.
     */
    virtual void SwapData( SCH_ITEM* aItem );

    /**
     * Routine to create a new copy of given item.
     * The new object is not put in draw list (not linked).
     *
     * @param doClone (default = false) indicates unique values (such as timestamp and
     *     sheet name) should be duplicated.  Use only for undo/redo operations.
     */
    SCH_ITEM* Duplicate( bool doClone = false ) const;

    /**
     * Virtual function IsMovableFromAnchorPoint
     * @return true for items which are moved with the anchor point at mouse cursor
     *  and false for items moved with no reference to anchor
     * Usually return true for small items (labels, junctions) and false for
     * items which can be large (hierarchical sheets, compoments)
     */
    virtual bool IsMovableFromAnchorPoint() { return true; }

    wxPoint& GetStoredPos() { return m_storedPos; }
    void     SetStoredPos( wxPoint aPos ) { m_storedPos = aPos; }

    /**
     * Function IsLocked
     * @return bool - true if the object is locked, else false
     */
    virtual bool IsLocked() const { return false; }

    /**
     * Function SetLocked
     * modifies 'lock' status for of the item.
     */
    virtual void SetLocked( bool aLocked ) {}

    /**
     * Function GetLayer
     * returns the layer this item is on.
     */
    SCH_LAYER_ID GetLayer() const { return m_Layer; }

    /**
     * Function SetLayer
     * sets the layer this item is on.
     * @param aLayer The layer number.
     */
    void SetLayer( SCH_LAYER_ID aLayer )  { m_Layer = aLayer; }

    /**
     * Function ViewGetLayers
     * returns the layers the item is drawn on (which may be more than its "home" layer)
     */
    void ViewGetLayers( int aLayers[], int& aCount ) const override;

    /**
     * Function GetPenSize virtual pure
     * @return the size of the "pen" that be used to draw or plot this item
     */
    virtual int GetPenSize() const { return 0; }

    /**
     * Function Print
     * Print a schematic item. Each schematic item should have its own method
     * @param aDC Device Context (can be null)
     * @param aOffset drawing Offset (usually wxPoint(0,0),
     *  but can be different when moving an object)
     */
    virtual void Print( wxDC* aDC, const wxPoint&  aOffset ) = 0;

    /**
     * Function Move
     * moves the item by \a aMoveVector to a new position.
     * @param aMoveVector = the displacement vector
     */
    virtual void Move( const wxPoint& aMoveVector ) = 0;

    /**
     * Function MirrorY
     * mirrors item relative to the Y axis about \a aYaxis_position.
     * @param aYaxis_position The Y axis position to mirror around.
     */
    virtual void MirrorY( int aYaxis_position ) = 0;

    /**
     * Function MirrorX
     * mirrors item relative to the X axis about \a aXaxis_position.
     * @param aXaxis_position The X axis position to mirror around.
     */
    virtual void MirrorX( int aXaxis_position ) = 0;

    /**
     * Function Rotate
     * rotates the item around \a aPosition 90 degrees in the clockwise direction.
     * @param aPosition A reference to a wxPoint object containing the coordinates to
     *                  rotate around.
     */
    virtual void Rotate( wxPoint aPosition ) = 0;

    /**
     * Function Load
     * reads a schematic item from \a aLine in a .sch file.
     *
     * @param aLine - Essentially this is file to read the object from.
     * @param aErrorMsg - Description of the error if an error occurs while loading the object.
     * @return True if the object loaded successfully.
     */
    virtual bool Load( LINE_READER& aLine, wxString& aErrorMsg ) { return false; }

    /**
     * Function GetEndPoints
     * adds the schematic item end points to \a aItemList if the item has end points.
     *
     * The default version doesn't do anything since many of the schematic object cannot
     * be tested for dangling ends.  If you add a new schematic item that can have a
     * dangling end ( no connect ), override this method to provide the correct end
     * points.
     *
     * @param aItemList - List of DANGLING_END_ITEMS to add to.
     */
    virtual void GetEndPoints( std::vector< DANGLING_END_ITEM >& aItemList ) {}

    /**
     * Function IsDanglingStateChanged
     * tests the schematic item to \a aItemList to check if it's dangling state has changed.
     *
     * Note that the return value only true when the state of the test has changed.  Use
     * the IsDangling() method to get the current dangling state of the item.  Some of
     * the schematic objects cannot be tested for a dangling state, the default method
     * always returns false.  Only override the method if the item can be tested for a
     * dangling state.
     *
     * If aSheet is passed a non-null pointer to a SCH_SHEET_PATH, the overrided method can
     * optionally use it to update sheet-local connectivity information
     *
     * @param aItemList - List of items to test item against.
     * @param aSheet - Sheet path to update connections for
     * @return True if the dangling state has changed from it's current setting.
     */
    virtual bool UpdateDanglingState( std::vector<DANGLING_END_ITEM>& aItemList,
                                      const SCH_SHEET_PATH* aPath = nullptr )
    {
        return false;
    }

    virtual bool IsDangling() const { return false; }

    virtual bool CanConnect( const SCH_ITEM* aItem ) const { return m_Layer == aItem->GetLayer(); }

    /**
     * Function IsConnectable
     * returns true if the schematic item can connect to another schematic item.
     */
    virtual bool IsConnectable() const { return false; }

    /**
     * Function GetConnectionPoints
     * add all the connection points for this item to \a aPoints.
     *
     * Not all schematic items have connection points so the default method does nothing.
     *
     * @param aPoints List of connection points to add to.
     */
    virtual void GetConnectionPoints( std::vector< wxPoint >& aPoints ) const { }

    /**
     * Function ClearConnections
     * clears all of the connection items from the list.
     *
     * The vector release method is used to prevent the item pointers from being deleted.
     * Do not use the vector erase method on the connection list.
     */
    void ClearConnections() { m_connections.clear(); }

    /**
     * Function IsConnected
     * tests the item to see if it is connected to \a aPoint.
     *
     * @param aPoint A reference to a wxPoint object containing the coordinates to test.
     * @return True if connection to \a aPoint exists.
     */
    bool IsConnected( const wxPoint& aPoint ) const;

    /**
     * Retrieves the connection associated with this object in the given sheet
     * Note: the returned value can be nullptr.
     */
    SCH_CONNECTION* Connection( const SCH_SHEET_PATH& aPath ) const;

    /**
     * Retrieves the set of items connected to this item on the given sheet
     */
    ITEM_SET& ConnectedItems( const SCH_SHEET_PATH& aPath );

    /**
     * Adds a connection link between this item and another
     */
    void AddConnectionTo( const SCH_SHEET_PATH& aPath, SCH_ITEM* aItem );

    /**
     * Creates a new connection object associated with this object
     *
     * @param aPath is the sheet path to initialize
     */
    SCH_CONNECTION* InitializeConnection( const SCH_SHEET_PATH& aPath );

    /**
     * Returns true if this item should propagate connection info to aItem
     */
    virtual bool ConnectionPropagatesTo( const EDA_ITEM* aItem ) const { return true; }

    bool IsConnectivityDirty() { return m_connectivity_dirty; }

    void SetConnectivityDirty( bool aDirty = true ) { m_connectivity_dirty = aDirty; }

    virtual bool CanIncrementLabel() const { return false; }

    /**
     * Return whether the fields have been automatically placed.
     */
    FIELDS_AUTOPLACED GetFieldsAutoplaced() const { return m_fieldsAutoplaced; }

    /**
     * Set fields automatically placed flag false.
     */
    void ClearFieldsAutoplaced() { m_fieldsAutoplaced = FIELDS_AUTOPLACED_NO; }

    /**
     * Autoplace fields only if correct to do so automatically.
     *
     * Fields that have been moved by hand are not automatically placed.
     *
     * @param aScreen is the SCH_SCREEN associated with the current instance of the
     *                component.
     */
    void AutoAutoplaceFields( SCH_SCREEN* aScreen )
    {
        if( GetFieldsAutoplaced() )
            AutoplaceFields( aScreen, GetFieldsAutoplaced() == FIELDS_AUTOPLACED_MANUAL );
    }

    virtual void AutoplaceFields( SCH_SCREEN* aScreen, bool aManual ) { }

    /**
     * Function Plot
     * plots the schematic item to \a aPlotter.
     *
     * @param aPlotter A pointer to a #PLOTTER object.
     */
    virtual void Plot( PLOTTER* aPlotter );

    /**
     * Function GetNetListItem
     * creates a new #NETLIST_OBJECT for the schematic object and adds it to
     * \a aNetListItems.
     * <p>
     * Not all schematic objects have net list items associated with them.  This
     * method only needs to be overridden for those schematic objects that have
     * net list objects associated with them.
     * </p>
     */
    virtual void GetNetListItem( NETLIST_OBJECT_LIST& aNetListItems,
                                 SCH_SHEET_PATH*      aSheetPath ) { }

    /**
     * Function GetPosition
     * @return A wxPoint object containing the schematic item position.
     */
    virtual wxPoint GetPosition() const = 0;

    /**
     * Function SetPosition
     * set the schematic item position to \a aPosition.
     *
     * @param aPosition A reference to a wxPoint object containing the new position.
     */
    virtual void SetPosition( const wxPoint& aPosition ) = 0;

    virtual bool operator <( const SCH_ITEM& aItem ) const;

private:
    /**
     * Function doIsConnected
     * provides the object specific test to see if it is connected to \a aPosition.
     *
     * @note Override this function if the derived object can be connect to another
     *       object such as a wire, bus, or junction.  Do not override this function
     *       for objects that cannot have connections.  The default will always return
     *       false.  This functions is call through the public function IsConnected()
     *       which performs tests common to all schematic items before calling the
     *       item specific connection testing.
     *
     * @param aPosition A reference to a wxPoint object containing the test position.
     * @return True if connection to \a aPosition exists.
     */
    virtual bool doIsConnected( const wxPoint& aPosition ) const { return false; }
};

#endif /* SCH_ITEM_H */
