/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2012 CERN
 * Copyright (C) 2012-2022 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
 */

/**
 * @file pcb_parser.h
 * @brief Pcbnew s-expression file format parser definition.
 */

#ifndef _PCBNEW_PARSER_H_
#define _PCBNEW_PARSER_H_

#include <core/wx_stl_compat.h>
#include <hashtables.h>
#include <layer_ids.h>     // PCB_LAYER_ID
#include <pcb_lexer.h>
#include <kiid.h>
#include <math/box2.h>

#include <chrono>
#include <unordered_map>


class PCB_ARC;
class BOARD;
class BOARD_ITEM;
class BOARD_ITEM_CONTAINER;
class PAD;
class BOARD_DESIGN_SETTINGS;
class PCB_DIMENSION_BASE;
class PCB_SHAPE;
class PCB_BITMAP;
class EDA_TEXT;
class FP_SHAPE;
class FP_TEXT;
class PCB_TEXT;
class PCB_TRACK;
class FOOTPRINT;
class PCB_GROUP;
class PCB_TARGET;
class PCB_VIA;
class ZONE;
class FP_3DMODEL;
class SHAPE_LINE_CHAIN;
struct LAYER;
class PROGRESS_REPORTER;


/**
 * Read a Pcbnew s-expression formatted #LINE_READER object and returns the appropriate
 * #BOARD_ITEM object.
 */
class PCB_PARSER : public PCB_LEXER
{
public:
    PCB_PARSER( LINE_READER* aReader, BOARD* aAppendToMe,
                std::function<bool( wxString, int, wxString, wxString )> aQueryUserCallback,
                PROGRESS_REPORTER* aProgressReporter = nullptr, unsigned aLineCount = 0 ) :
        PCB_LEXER( aReader ),
        m_board( aAppendToMe ),
        m_appendToExisting( aAppendToMe != nullptr ),
        m_progressReporter( aProgressReporter ),
        m_lastProgressTime( std::chrono::steady_clock::now() ),
        m_lineCount( aLineCount ),
        m_queryUserCallback( aQueryUserCallback )
    {
        init();
    }

    // ~PCB_PARSER() {}

    BOARD_ITEM* Parse();

    /**
     * @param aInitialComments may be a pointer to a heap allocated initial comment block
     *                         or NULL.  If not NULL, then caller has given ownership of a
     *                         wxArrayString to this function and care must be taken to
     *                         delete it even on exception.
     */
    FOOTPRINT* parseFOOTPRINT( wxArrayString* aInitialComments = nullptr );

    /**
     * Return whether a version number, if any was parsed, was too recent
     */
    bool IsTooRecent()
    {
        return m_tooRecent;
    }

    /**
     * Return a string representing the version of KiCad required to open this
     * file. Not particularly meaningful if IsTooRecent() returns false.
     */
    wxString GetRequiredVersion();

private:
    ///< Convert net code using the mapping table if available,
    ///< otherwise returns unchanged net code if < 0 or if it's out of range
    inline int getNetCode( int aNetCode )
    {
        if( ( aNetCode >= 0 ) && ( aNetCode < (int) m_netCodes.size() ) )
            return m_netCodes[aNetCode];

        return aNetCode;
    }

    /**
     * Add aValue value in netcode mapping (m_netCodes) at \a aIndex.
     *
     * Ensure there is room in m_netCodes for that, and add room if needed.
     *
     * @param aIndex is the index ( expected >=0 )of the location to use in m_netCodes.
     * @param aValue is the netcode value to map.
     */
    void pushValueIntoMap( int aIndex, int aValue );

    /**
     * Clear and re-establish m_layerMap with the default layer names.
     *
     * m_layerMap will have some of its entries overwritten whenever a (new) board
     * is encountered.
     */
    void init();

    void checkpoint();

    /**
     * Create a mapping from the (short-lived) bug where layer names were translated.
     *
     * @todo Remove this once we support custom layer names.
     *
     * @param aMap string mapping from translated to English layer names.
     */
    void createOldLayerMapping( std::unordered_map< std::string, std::string >& aMap );

    /**
     * Skip the current token level, i.e search for the RIGHT parenthesis which closes the
     * current description.
     */
    void skipCurrent();

    void parseHeader();
    void parseGeneralSection();
    void parsePAGE_INFO();
    void parseTITLE_BLOCK();

    void parseLayers();
    void parseLayer( LAYER* aLayer );

    void parseBoardStackup();

    void parseSetup();
    void parseDefaults( BOARD_DESIGN_SETTINGS& aSettings );
    void parseDefaultTextDims( BOARD_DESIGN_SETTINGS& aSettings, int aLayer );
    void parseNETINFO_ITEM();
    void parseNETCLASS();

    PCB_SHAPE*          parsePCB_SHAPE();
    PCB_TEXT*           parsePCB_TEXT();
    PCB_BITMAP*         parsePCB_BITMAP( BOARD_ITEM* aParent );
    PCB_TEXTBOX*        parsePCB_TEXTBOX();
    PCB_DIMENSION_BASE* parseDIMENSION( BOARD_ITEM* aParent, bool aInFP );

    // Parse a footprint, but do not replace PARSE_ERROR with FUTURE_FORMAT_ERROR automatically.
    FOOTPRINT*          parseFOOTPRINT_unchecked( wxArrayString* aInitialComments = nullptr );

    FP_TEXT*            parseFP_TEXT();
    FP_TEXTBOX*         parseFP_TEXTBOX();
    FP_SHAPE*           parseFP_SHAPE();
    PAD*                parsePAD( FOOTPRINT* aParent = nullptr );

    // Parse only the (option ...) inside a pad description
    bool                parsePAD_option( PAD* aPad );

    PCB_ARC*            parseARC();
    PCB_TRACK*          parsePCB_TRACK();
    PCB_VIA*            parsePCB_VIA();
    ZONE*               parseZONE( BOARD_ITEM_CONTAINER* aParent );
    PCB_TARGET*         parsePCB_TARGET();
    BOARD*              parseBOARD();
    void                parseGROUP( BOARD_ITEM* aParent );

    // Parse a board, but do not replace PARSE_ERROR with FUTURE_FORMAT_ERROR automatically.
    BOARD*              parseBOARD_unchecked();

    /**
     * Parse the current token for the layer definition of a #BOARD_ITEM object.
     *
     * @param aMap is the LAYER_{NUM|MSK}_MAP to use for the lookup.
     * @return The result of the parsed #BOARD_ITEM layer or set designator.
     * @throw IO_ERROR if the layer is not valid.
     * @throw PARSE_ERROR if the layer syntax is incorrect.
     */
    template<class T, class M>
    T lookUpLayer( const M& aMap );

    /**
     * Parse the layer definition of a #BOARD_ITEM object.
     *
     * @return The index the parsed #BOARD_ITEM layer.
     * @throw IO_ERROR if the layer is not valid.
     * @throw PARSE_ERROR if the layer syntax is incorrect.
     */
    PCB_LAYER_ID parseBoardItemLayer();

    /**
     * Parse the layers definition of a #BOARD_ITEM object.
     *
     * @return The mask of layers the parsed #BOARD_ITEM is on.
     * @throw IO_ERROR if any of the layers is not valid.
     * @throw PARSE_ERROR if the layers syntax is incorrect.
     */
    LSET parseBoardItemLayersAsMask();

    /**
     * Parse a coordinate pair (xy X Y) in board units (mm).
     *
     * The parser checks if the previous token was T_LEFT and parses the remainder of
     * the token syntax.  This is used when parsing a list of coordinate points.  This
     * way the parser can be used in either case.
     *
     * @return A wxPoint object containing the coordinate pair.
     * @throw PARSE_ERROR if the coordinate pair syntax is incorrect.
     */
    VECTOR2I parseXY();

    void parseXY( int* aX, int* aY );

    std::pair<wxString, wxString> parseProperty();

    /**
     * Parses possible outline points and stores them into \p aPoly.  This accepts points
     * for DRAWSEGMENT polygons, EDGEMODULE polygons and ZONE_CONTAINER polygons.  Points
     * and arcs are added to the most recent outline
     *
     * @param aPoly polygon container to add points and arcs
     */
    void parseOutlinePoints( SHAPE_LINE_CHAIN& aPoly );

    /**
     * Parse the common settings for any object derived from #EDA_TEXT.
     *
     * @param aText A pointer to the #EDA_TEXT object to save the parsed settings into.
     * @throw PARSE_ERROR if the text syntax is not valid.
     */
    void parseEDA_TEXT( EDA_TEXT* aText );

    /**
     * Parse the render cache for any object derived from #EDA_TEXT.
     *
     * @param aText A pointer to the #EDA_TEXT object to save the parsed settings into.
     * @throw PARSE_ERROR if the text syntax is not valid.
     */
    void parseRenderCache( EDA_TEXT* text );

    FP_3DMODEL* parse3DModel();

    /**
     * Parse the current token as an ASCII numeric string with possible leading
     * whitespace into a double precision floating point number.
     *
     * @return The result of the parsed token.
     * @throw IO_ERROR if an error occurs attempting to convert the current token.
     */

    int parseBoardUnits();

    int parseBoardUnits( const char* aExpected );

    inline int parseBoardUnits( PCB_KEYS_T::T aToken )
    {
        return parseBoardUnits( GetTokenText( aToken ) );
    }

    inline int parseInt()
    {
        return (int)strtol( CurText(), nullptr, 10 );
    }

    inline int parseInt( const char* aExpected )
    {
        NeedNUMBER( aExpected );
        return parseInt();
    }

    inline long parseHex()
    {
        NextTok();
        return strtol( CurText(), nullptr, 16 );
    }

    bool parseBool();

    /*
     * @return if m_appendToExisting, returns new KIID(), otherwise returns CurStr() as KIID.
     */
    KIID CurStrToKIID();

    /**
     * Called after parsing a footprint definition or board to build the group membership
     * lists.
     */
    void resolveGroups( BOARD_ITEM* aParent );

    typedef std::unordered_map< std::string, PCB_LAYER_ID > LAYER_ID_MAP;
    typedef std::unordered_map< std::string, LSET >         LSET_MAP;
    typedef std::unordered_map< wxString, KIID >            KIID_MAP;

    ///< The type of progress bar timeout
    using TIMEOUT = std::chrono::milliseconds;

    ///< The clock used for the timestamp (guaranteed to be monotonic).
    using CLOCK = std::chrono::steady_clock;

    ///< The type of the time stamps.
    using TIME_PT = std::chrono::time_point<CLOCK>;

    BOARD*              m_board;
    LAYER_ID_MAP        m_layerIndices;     ///< map layer name to it's index
    LSET_MAP            m_layerMasks;       ///< map layer names to their masks
    std::set<wxString>  m_undefinedLayers;  ///< set of layers not defined in layers section
    std::vector<int>    m_netCodes;         ///< net codes mapping for boards being loaded
    bool                m_tooRecent;        ///< true if version parses as later than supported
    int                 m_requiredVersion;  ///< set to the KiCad format version this board requires
    bool                m_appendToExisting; ///< reading into an existing board; reset UUIDs

    ///< if resetting UUIDs, record new ones to update groups with.
    KIID_MAP            m_resetKIIDMap;

    bool                m_showLegacySegmentZoneWarning;
    bool                m_showLegacy5ZoneWarning;

    PROGRESS_REPORTER*  m_progressReporter;  ///< optional; may be nullptr
    TIME_PT             m_lastProgressTime;  ///< for progress reporting
    unsigned            m_lineCount;         ///< for progress reporting

    // Group membership info refers to other Uuids in the file.
    // We don't want to rely on group declarations being last in the file, so
    // we store info about the group declarations here during parsing and then resolve
    // them into BOARD_ITEM* after we've parsed the rest of the file.
    struct GROUP_INFO
    {
        BOARD_ITEM*       parent;
        wxString          name;
        bool              locked;
        KIID              uuid;
        std::vector<KIID> memberUuids;
    };

    std::vector<GROUP_INFO> m_groupInfos;

    std::function<bool( wxString aTitle, int aIcon, wxString aMsg, wxString aAction )> m_queryUserCallback;
};


#endif    // _PCBNEW_PARSER_H_
