/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 2012-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 PCB_IO_EAGLE_H_
#define PCB_IO_EAGLE_H_

#include <io/eagle/eagle_parser.h>
#include <pcb_io/pcb_io.h>
#include <pcb_io/pcb_io_mgr.h>
#include <pcb_io/common/plugin_common_layer_mapping.h>

#include <eda_units.h>
#include <layer_ids.h>
#include <netclass.h>

#include <map>
#include <tuple>
#include <wx/xml/xml.h>

class PAD;
class PCB_TEXT;
class ZONE;

typedef std::map<wxString, FOOTPRINT*> FOOTPRINT_MAP;
typedef std::vector<ZONE*>             ZONES;
typedef std::map<wxString, ENET>        NET_MAP;
typedef NET_MAP::const_iterator         NET_MAP_CITER;


/// subset of eagle.drawing.board.designrules in the XML document
struct ERULES
{
    ERULES() :
        psElongationLong    ( 100 ),
        psElongationOffset  ( 0 ),

        mvStopFrame         ( 1.0 ),
        mvCreamFrame        ( 0.0 ),
        mlMinStopFrame      ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  4.0 ) ),
        mlMaxStopFrame      ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  4.0 ) ),
        mlMinCreamFrame     ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  0.0 ) ),
        mlMaxCreamFrame     ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  0.0 ) ),

        psTop               ( EPAD::UNDEF ),
        psBottom            ( EPAD::UNDEF ),
        psFirst             ( EPAD::UNDEF ),

        srRoundness         ( 0.0 ),
        srMinRoundness      ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  0.0 ) ),
        srMaxRoundness      ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  0.0 ) ),

        rvPadTop            ( 0.25 ),
        // rvPadBottom      ( 0.25 ),
        rlMinPadTop         ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  10 ) ),
        rlMaxPadTop         ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  20 ) ),

        rvViaOuter          ( 0.25 ),
        rlMinViaOuter       ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  10 ) ),
        rlMaxViaOuter       ( EDA_UNIT_UTILS::Mils2IU( pcbIUScale,  20 ) ),
        mdWireWire          ( 0 )
    {}

    void parse( wxXmlNode* aRules, std::function<void()> aCheckpoint );

    ///< percent over 100%.  0-> not elongated, 100->twice as wide as is tall
    ///< Goes into making a scaling factor for "long" pads.
    int    psElongationLong;

    int    psElongationOffset;  ///< the offset of the hole within the "long" pad.

    ///< solder mask, expressed as percentage of the smaller pad/via dimension
    double mvStopFrame;

    ///< solderpaste mask, expressed as percentage of the smaller pad/via dimension
    double mvCreamFrame;
    int    mlMinStopFrame;      ///< solder mask, minimum size (Eagle mils, here nanometers)
    int    mlMaxStopFrame;      ///< solder mask, maximum size (Eagle mils, here nanometers)
    int    mlMinCreamFrame;     ///< solder paste mask, minimum size (Eagle mils, here nanometers)
    int    mlMaxCreamFrame;     ///< solder paste mask, maximum size (Eagle mils, here nanometers)

    int    psTop;               ///< Shape of the top pads
    int    psBottom;            ///< Shape of the bottom pads
    int    psFirst;             ///< Shape of the first pads

    double srRoundness;         ///< corner rounding ratio for SMD pads (percentage)

    ///< corner rounding radius, minimum size (Eagle mils, here nanometers)
    int    srMinRoundness;

    ///< corner rounding radius, maximum size (Eagle mils, here nanometers)
    int    srMaxRoundness;

    double rvPadTop;            ///< top pad size as percent of drill size
    // double   rvPadBottom;    ///< bottom pad size as percent of drill size

    double rlMinPadTop;         ///< minimum copper annulus on through hole pads
    double rlMaxPadTop;         ///< maximum copper annulus on through hole pads

    double rvViaOuter;          ///< copper annulus is this percent of via hole
    double rlMinViaOuter;       ///< minimum copper annulus on via
    double rlMaxViaOuter;       ///< maximum copper annulus on via
    double mdWireWire;          ///< wire to wire spacing I presume.
};


/**
 * Works with Eagle 6.x XML board files and footprints to implement the Pcbnew #PLUGIN API
 * or a portion of it.
 */
class PCB_IO_EAGLE : public PCB_IO, public LAYER_REMAPPABLE_PLUGIN
{
public:
    const IO_BASE::IO_FILE_DESC GetBoardFileDesc() const override
    {
        return IO_BASE::IO_FILE_DESC( _HKI( "Eagle ver. 6.x XML PCB files" ), { "brd" } );
    }

    const IO_BASE::IO_FILE_DESC GetLibraryDesc() const override
    {
        return IO_BASE::IO_FILE_DESC( _HKI( "Eagle ver. 6.x XML library files" ), { "lbr" } );
    }

    bool CanReadBoard( const wxString& aFileName ) const override;
    bool CanReadLibrary( const wxString& aFileName ) const override;
    bool CanReadFootprint( const wxString& aFileName ) const override;

    BOARD* LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
                      const STRING_UTF8_MAP* aProperties = nullptr, PROJECT* aProject = nullptr ) override;

    std::vector<FOOTPRINT*> GetImportedCachedLibraryFootprints() override;

    void FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath,
                             bool aBestEfforts, const STRING_UTF8_MAP* aProperties = nullptr) override;

    FOOTPRINT* FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName,
                              bool  aKeepUUID = false,
                              const STRING_UTF8_MAP* aProperties = nullptr ) override;

    long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override
    {
        return getModificationTime( aLibraryPath ).GetValue().GetValue();
    }

    bool IsLibraryWritable( const wxString& aLibraryPath ) override
    {
        return false;   // until someone writes others like FootprintSave(), etc.
    }

    typedef int BIU;

    PCB_IO_EAGLE();
    ~PCB_IO_EAGLE();

    /**
     * Return the automapped layers.
     *
     * The callback needs to have the context of the current board so it can
     * correctly determine copper layer mapping. Thus, it is not static and is
     * expected to be bind to an instance of PCB_IO_EAGLE.
     *
     * @param aInputLayerDescriptionVector
     * @return Auto-mapped layers
     */
    std::map<wxString, PCB_LAYER_ID> DefaultLayerMappingCallback(
            const std::vector<INPUT_LAYER_DESC>& aInputLayerDescriptionVector );

private:
    /// initialize PLUGIN like a constructor would, and futz with fresh BOARD if needed.
    void init( const STRING_UTF8_MAP* aProperties );

    bool checkHeader( const wxString& aFileName ) const;

    void checkpoint();

    void clear_cu_map();

    /// Convert an Eagle distance to a KiCad distance.
    int kicad_y( const ECOORD& y ) const { return -y.ToPcbUnits(); }
    int kicad_x( const ECOORD& x ) const { return x.ToPcbUnits(); }

    /// create a font size (fontz) from an eagle font size scalar and KiCad font thickness
    VECTOR2I kicad_fontsize( const ECOORD& d, int aTextThickness ) const;

    /**
     * Generate mapping between Eagle and KiCad layers.
     *
     * @warning It is imperative that this gets called correctly because footprint libraries
     *          do not get remapped by the user on load.  Otherwise, Pcbnew will crash when
     *          attempting to load footprint libraries that contain layers that do not exist
     *          in the #EAGLE_LAYER definitions.
     *
     * @param aIsLibraryCache is the flag to indicate when mapping the footprint library cache
     *                        layers rather than the board layers.
     */
    void mapEagleLayersToKicad( bool aIsLibraryCache = false );

    /// Convert an Eagle layer to a KiCad layer.
    PCB_LAYER_ID kicad_layer( int aLayer ) const;

    /**
     * Get the default KiCad layer corresponding to an Eagle layer of the board,
     * a set of sensible layer mapping options and required flag
     *
     * @note The Eagle MILLING, TTEST, BTEST, and HOLES layers are set to #UNDEFINED_LAYER
     *       for historical purposes.  All other Eagle layers that do not directly map to
     *       KiCad layers will be set to #UNDEFINED_LAYER when loading Eagle footprint
     *       libraries.  This should be addressed in the future because in some cases this
     *       will cause data loss.
     *
     * @see #EAGLE_LAYER and defaultKiCadLayer().
     *
     * @param aEagleLayer is the Eagle layer to map.
     * @param aIsLibraryCache is a flag to indicate if the mapping is for board or footprint
     *                        library cache objects.
     * @return a tuple containing the mapped layer.
     */
    std::tuple<PCB_LAYER_ID, LSET, bool> defaultKicadLayer( int aEagleLayer,
                                                            bool aIsLibraryCache = false ) const;

    /// Get Eagle layer name by its number
    const wxString& eagle_layer_name( int aLayer ) const;

    /// Get Eagle layer number by its name
    int eagle_layer_id( const wxString& aLayerName ) const;

    void setKeepoutSettingsToZone( ZONE* aZone, int aLayer ) const;

    /// This PLUGIN only caches one footprint library, this determines which one.
    void cacheLib( const wxString& aLibraryPath );

    /// get a file's  or dir's modification time.
    static wxDateTime getModificationTime( const wxString& aPath );

    /// Determines the minimum copper layer stackup count that includes all mapped layers.
    int getMinimumCopperLayerCount() const;

    // all these loadXXX() throw IO_ERROR or ptree_error exceptions:

    void loadAllSections( wxXmlNode* aDocument );
    void loadDesignRules( wxXmlNode* aDesignRules );
    void loadLayerDefs( wxXmlNode* aLayers );
    void loadPlain( wxXmlNode* aPlain );
    void loadClasses( wxXmlNode* aClasses );
    void loadSignals( wxXmlNode* aSignals );

    /**
     * Load the Eagle "library" XML element, which can occur either under a "libraries"
     * element (if a *.brd file) or under a "drawing" element if a *.lbr file.
     *
     * @param aLib is the portion of the loaded XML document tree that is the "library"
     *             element.
     * @param aLibName is a pointer to the library name or NULL.  If NULL this means
     *                 we are loading a *.lbr not a *.brd file and the key used in m_templates
     *                 is to exclude the library name.
     */
    void loadLibrary( wxXmlNode* aLib, const wxString* aLibName );

    void loadLibraries( wxXmlNode* aLibs );
    void loadElements( wxXmlNode* aElements );

    /**
     * Load a copper or keepout polygon and adds it to the board.
     *
     * @return The loaded zone or nullptr if was not processed.
     */
    ZONE* loadPolygon( wxXmlNode* aPolyNode );

    void orientFootprintAndText( FOOTPRINT* aFootprint, const EELEMENT& e, const EATTR* aNameAttr,
                                 const EATTR* aValueAttr );

    void orientFPText( FOOTPRINT* aFootprint, const EELEMENT& e, PCB_TEXT* aFPText,
                       const EATTR* aAttr );


    /// move the BOARD into the center of the page
    void centerBoard();

    /**
     * Create a FOOTPRINT from an Eagle package.
     */
    FOOTPRINT* makeFootprint( wxXmlNode* aPackage, const wxString& aPkgName );

    void packageWire( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const;
    void packagePad( FOOTPRINT* aFootprint, wxXmlNode* aTree );
    void packageText( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const;
    void packageRectangle( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const;
    void packagePolygon( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const;
    void packageCircle( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const;

    /**
     * @param aFootprint The KiCad footprint to which to assign the hole.
     * @param aTree The Eagle XML node that is of type "hole".
     * @param aCenter If true, center the hole in the footprint and offset the footprint position.
     */
    void packageHole( FOOTPRINT* aFootprint, wxXmlNode* aTree, bool aCenter ) const;
    void packageSMD( FOOTPRINT* aFootprint, wxXmlNode* aTree ) const;

    ///< Handles common pad properties
    void transferPad( const EPAD_COMMON& aEaglePad, PAD* aPad ) const;

    ///< Deletes the footprint templates list
    void deleteTemplates();

    typedef std::vector<ELAYER>     ELAYERS;
    typedef ELAYERS::const_iterator EITER;

    int                              m_cu_map[17];     ///< map eagle to KiCad, cu layers only.
    std::map<int, ELAYER>            m_eagleLayers;    ///< Eagle layer data stored by layer number
    std::map<wxString, int>          m_eagleLayersIds; ///< Eagle layer ids stored by layer name
    std::map<wxString, PCB_LAYER_ID> m_layer_map;      ///< Map of Eagle layers to KiCad layers

    ///< Eagle class number to KiCad netclass
    std::map<wxString, std::shared_ptr<NETCLASS>>  m_classMap;

    wxString                         m_customRules;

    ERULES*       m_rules;          ///< Eagle design rules.
    XPATH*        m_xpath;          ///< keeps track of what we are working on within
                                    ///< XML document during a Load().

    int           m_hole_count;     ///< generates unique footprint names from eagle "hole"s.

    NET_MAP       m_pads_to_nets;   ///< net list

    std::map<wxString, FOOTPRINT*> m_templates;  ///< is part of a FOOTPRINT factory that operates
                                                 ///< using copy construction.
                                                 ///< lookup key is either libname.packagename or
                                                 ///< simply packagename if FootprintLoad() or
                                                 ///< FootprintEnumberate()

    PROGRESS_REPORTER*  m_progressReporter;  ///< optional; may be nullptr
    unsigned            m_doneCount;
    unsigned            m_lastProgressCount;
    unsigned            m_totalCount;         ///< for progress reporting

    int         m_min_trace;        ///< smallest trace we find on Load(), in BIU.
    int         m_min_hole;         ///< smallest diameter hole we find on Load(), in BIU.
    int         m_min_via;          ///< smallest via we find on Load(), in BIU.
    int         m_min_annulus;      ///< smallest via annulus we find on Load(), in BIU.

    wxString    m_lib_path;
    wxDateTime  m_mod_time;
};

#endif  // PCB_IO_EAGLE_H_