/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2010-2019 Jean-Pierre Charras  jp.charras at wanadoo.fr
 * Copyright (C) 1992-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 GERBER_FILE_IMAGE_H
#define GERBER_FILE_IMAGE_H

#include <vector>
#include <set>

#include <dcode.h>
#include <gerber_draw_item.h>
#include <am_primitive.h>
#include <aperture_macro.h>
#include <gbr_netlist_metadata.h>

typedef std::vector<GERBER_DRAW_ITEM*> GERBER_DRAW_ITEMS;

class GERBVIEW_FRAME;
class D_CODE;

/* Gerber files have different parameters to define units and how items must be plotted.
 * some are for the entire file, and other can change along a file.
 * In Gerber world:
 * an image is the entire gerber file and its "global" parameters
 * a layer (that is very different from a board layer) is just a sub set of a file that
 * have specific parameters
 * if a Image parameter is set more than once, only the last value is used
 * Some parameters can change along a file and are not layer specific: they are stored
 * in GERBER_ITEM items, when instanced.
 *
 * In GerbView, to handle these parameters, there are 2 classes:
 * GERBER_FILE_IMAGE : the main class containing most of parameters and data to plot a
 * graphic layer
 * Some of them can change along the file
 * There is one GERBER_FILE_IMAGE per file and one graphic layer per file or GERBER_FILE_IMAGE
 * GerbView does not read and merge 2 gerber file in one graphic layer:
 * I believe this is not possible due to the constraints in Image parameters.
 * GERBER_LAYER : containing the subset of parameters that is layer specific
 * A GERBER_FILE_IMAGE must include one GERBER_LAYER to define all parameters to plot a file.
 * But a GERBER_FILE_IMAGE can use more than one GERBER_LAYER.
 */

class GERBER_FILE_IMAGE;
class X2_ATTRIBUTE;
class X2_ATTRIBUTE_FILEFUNCTION;

// For arcs, coordinates need 3 info: start point, end point and center or radius
// In Excellon files it can be a A## value (radius) or I#J# center coordinates (like in gerber)
// We need to know the last read type when reading a list of routing coordinates
enum LAST_EXTRA_ARC_DATA_TYPE
{
    ARC_INFO_TYPE_NONE,
    ARC_INFO_TYPE_CENTER,   // last info is a IJ command: arc center is given
    ARC_INFO_TYPE_RADIUS,   // last info is a A command: arc radius is given
};

class GERBER_LAYER
{
public:
    GERBER_LAYER();
    ~GERBER_LAYER();

private:
    void ResetDefaultValues();
    friend class GERBER_FILE_IMAGE;

public:
   // These parameters are layer specific:
    wxString    m_LayerName;            // Layer name, from LN <name>* command
    bool        m_LayerNegative;        // true = Negative Layer: command LP
    wxRealPoint m_StepForRepeat;        // X and Y offsets for Step and Repeat command
    int         m_XRepeatCount;         // The repeat count on X axis
    int         m_YRepeatCount;         // The repeat count on Y axis
    bool        m_StepForRepeatMetric;  // false = Inches, true = metric
                                        // needed here because repeated
                                        // gerber items can have coordinates
                                        // in different units than step parameters
                                        // and the actual coordinates calculation must handle this
};

// size of a single line of text from a gerber file.
// warning: some files can have *very long* lines, so the buffer must be large.
#define GERBER_BUFZ 1000000

/**
 * Hold the image data and parameters for one gerber file and layer parameters.
 *
 * @todo: Move them in #GERBER_LAYER class.
 */
class GERBER_FILE_IMAGE : public EDA_ITEM
{
public:
    GERBER_FILE_IMAGE( int layer );
    virtual ~GERBER_FILE_IMAGE();

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

    /**
     * @brief Performs a heuristics-based check of whether the file is an RS274 gerber file.
     *
     * Does not invoke the full parser.
     *
     * @param aFullFileName aFullFileName is the full filename of the gerber file.
     * @return True if RS274 file, false otherwise
     */
    static bool TestFileIsRS274( const wxString& aFullFileName );

    /**
     * Read and load a gerber file.
     *
     * If the file cannot be loaded, warning and information messages are stored in m_messagesList.
     *
     * @param aFullFileName The full filename of the Gerber file.
     * @return true if file loaded successfully, false if the gerber file was not loaded.
     */
    bool LoadGerberFile( const wxString& aFullFileName );

    const wxArrayString& GetMessages() const { return m_messagesList; }

    /**
     * @return the count of Dcode tools in use in the image
     */
    int GetDcodesCount();

    /**
     * Set all parameters to a default value, before reading a file
     */
    virtual void ResetDefaultValues();

    COLOR4D GetPositiveDrawColor() const { return m_PositiveDrawColor; }

    /**
     * @return a reference to the GERBER_DRAW_ITEMS deque list
     */
    GERBER_DRAW_ITEMS& GetItems() { return m_drawings; }

    /**
     * @return the count of GERBER_DRAW_ITEMS in the image
     */
    int GetItemsCount() { return m_drawings.size(); }

    /**
     * Add a new GERBER_DRAW_ITEM item to the drawings list
     *
     * @param aItem is the GERBER_DRAW_ITEM to add to list
     */
    void AddItemToList( GERBER_DRAW_ITEM* aItem )
    {
        m_drawings.push_back( aItem );
    }

    /**
     * @return the last GERBER_DRAW_ITEM* item of the items list
     */
    GERBER_DRAW_ITEM* GetLastItemInList() const
    {
        return m_drawings.back();
    }

    /**
     * @return the current layers params.
     */
    GERBER_LAYER& GetLayerParams()
    {
        return m_GBRLayerParams;
    }

    /**
     * @return true if at least one item must be drawn in background color used to optimize
     *         screen refresh (when no items are in background color refresh can be faster).
     */
    bool HasNegativeItems();

    /**
     * Clear the message list.
     *
     * Call it before reading a Gerber file
     */
    void ClearMessageList()
    {
        m_messagesList.Clear();
    }

    /**
     * Add a message to the message list
     */
    void AddMessageToList( const wxString& aMessage )
    {
        m_messagesList.Add( aMessage );
    }

    /**
     * Return the current coordinate type pointed to by XnnYnn Text (XnnnnYmmmm).
     *
     * @param aText is a pointer to the text to parse.
     * @param aExcellonMode = true to parse a Excellon drill file.
     * it forces truncation of a digit string to a maximum length because the exact coordinate
     * format is not always known.
     */
    VECTOR2I ReadXYCoord( char*& aText, bool aExcellonMode = false );

    /**
     * Return the current coordinate type pointed to by InnJnn Text (InnnnJmmmm)
     *
     * These coordinates are relative, so if coordinate is absent, its value
     * defaults to 0
     */
    VECTOR2I ReadIJCoord( char*& Text );

    /**
     * Reads the next number and returns the value
     * @param aText Pointer to the input string vector
     * @return
     */
    int CodeNumber( char*& aText );

    /**
     * Return a pointer to the D_CODE within this GERBER for the given \a aDCODE.
     *
     * @param aDCODE The numeric value of the D_CODE to look up.
     * @param aCreateIfNoExist If true, then create the D_CODE if it does not
     *                         exist in list.
     * @return The one implied by the given \a aDCODE or NULL if the requested \a aDCODE is
     *         out of range.
     */
    D_CODE* GetDCODEOrCreate( int aDCODE, bool aCreateIfNoExist = true );

    /**
     * Return a pointer to the D_CODE within this GERBER for the given \a aDCODE.
     *
     * @param aDCODE The numeric value of the D_CODE to look up.
     * @return The D code implied by the given \a aDCODE or NULL if the requested \a aDCODE is
     *         out of range.
     */
    D_CODE* GetDCODE( int aDCODE ) const;

    /**
     * Look up a previously read in aperture macro.
     *
     * @param aLookup A dummy APERTURE_MACRO with [only] the name field set.
     * @return the aperture macro with a matching name or NULL if not found.
     */
    APERTURE_MACRO* FindApertureMacro( const APERTURE_MACRO& aLookup );

    /**
     * Gerber format has a command Step an Repeat.
     *
     * This function must be called when reading a gerber file and
     * after creating a new gerber item that must be repeated
     * (i.e when m_XRepeatCount or m_YRepeatCount are > 1)
     *
     * @param aItem = the item to repeat
     */
    void StepAndRepeatItem( const GERBER_DRAW_ITEM& aItem );

    /**
     * Display information about image parameters in the status bar.
     *
     * @param aMainFrame The #GERBVIEW_FRAME to display messages.
     */
    void DisplayImageInfo( GERBVIEW_FRAME* aMainFrame );

    /**
     * Set the offset and rotation to draw a file image
     * Does not change any coordinate od draw items
     * @param aOffsetMM is the draw offset in millimeters
     * @param aRotation is the draw roation
     * draw transform order is rotation and after offset
     */
    void SetDrawOffetAndRotation( VECTOR2D aOffsetMM, EDA_ANGLE aRotation );

    /**
     * Called when a %TD command is found the Gerber file
     *
     * Remove the attribute specified by the %TD command.
     * is no attribute, all current attributes specified by the %TO and the %TA
     * commands are cleared.
     * if a attribute name is specified (for instance %TD.CN*%) is specified,
     * only this attribute is cleared
     *
     * @param aAttribute is the X2_ATTRIBUTE which stores the full command
     */
    void RemoveAttribute( X2_ATTRIBUTE& aAttribute );

    ///< @copydoc EDA_ITEM::Visit()
    INSPECT_RESULT Visit( INSPECTOR inspector, void* testData,
                          const std::vector<KICAD_T>& aScanTypes ) override;

#if defined(DEBUG)

    void    Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }

#endif

private:
    /**
     * Test for an end of line.
     *
     * If a end of line is found, read a new line.
     *
     * @param aBuff = buffer (size = GERBER_BUFZ) to fill with a new line
     * @param aText = pointer to the last useful char in aBuff
     *          on return: points the beginning of the next line.
     * @param aBuffSize = the size in bytes of aBuff
     * @param aFile = the opened GERBER file to read
     * @return a pointer to the beginning of the next line or NULL if end of file
    */
    char* GetNextLine( char *aBuff, unsigned int aBuffSize, char* aText, FILE* aFile );

    bool GetEndOfBlock( char* aBuff, unsigned int aBuffSize, char*& aText, FILE* aGerberFile );

    /**
     * Read a single RS274X command terminated with a %
     */
    bool ReadRS274XCommand( char *aBuff, unsigned int aBuffSize, char*& aText );

    /**
     * Execute a RS274X command
     */
    bool ExecuteRS274XCommand( int aCommand, char* aBuff, unsigned int aBuffSize, char*& aText );

    /**
     * Read two bytes of data and assembles them into an int with the first
     * byte in the sequence put into the most significant part of a 16 bit value
     * to build a RS274X command identifier.
     *
     * @param text A reference to a pointer to read bytes from and to advance as
     *             they are read.
     * @return a RS274X command identifier.
     */
    int ReadXCommandID( char*& text );

    /**
     * Read in an aperture macro and saves it in m_aperture_macros.
     *
     * @param aBuff a character buffer at least GERBER_BUFZ long that can be
     *              used to read successive lines from the gerber file.
     * @param text A reference to a character pointer which gives the initial
     *             text to read from.
     * @param aBuffSize is the size of aBuff
     * @param gerber_file Which file to read from for continuation.
     * @return true if a macro was read in successfully, else false.
     */
    bool ReadApertureMacro( char *aBuff, unsigned int aBuffSize, char*& text, FILE* gerber_file );

    // functions to execute G commands or D basic commands:
    bool Execute_G_Command( char*& text, int G_command );
    bool Execute_DCODE_Command( char*& text, int D_command );

public:
    bool               m_InUse;                ///< true if this image is currently in use (a file
                                               ///<   is loaded in it)
                                               ///< false if it must be not drawn
    COLOR4D            m_PositiveDrawColor;    ///< The color used to draw positive items
    wxString           m_FileName;             ///< Full File Name for this layer
    wxString           m_ImageName;            ///< Image name, from IN <name>* command

    bool               m_IsX2_file;            ///< True if a X2 gerber attribute was found in file
    X2_ATTRIBUTE_FILEFUNCTION* m_FileFunction; ///< file function parameters, found in a %TF
                                               ///<   command or a G04
    wxString           m_MD5_value;            ///< MD5 value found in a %TF.MD5 command
    wxString           m_PartString;           ///< string found in a %TF.Part command
    int                m_GraphicLayer;         ///< Graphic layer Number
    bool               m_ImageNegative;        ///< true = Negative image

    bool               m_ImageJustifyXCenter;  ///< Image Justify Center on X axis (default = false)
    bool               m_ImageJustifyYCenter;  ///< Image Justify Center on Y axis (default = false)
    VECTOR2I           m_ImageJustifyOffset;   ///< Image Justify Offset on XY axis (default = 0,0)

    bool               m_GerbMetric;           ///< false = Inches, true = metric
    bool               m_Relative;             ///< false = absolute Coord, true = relative Coord.
    bool               m_NoTrailingZeros;      ///< true: remove tailing zeros.
    VECTOR2I           m_ImageOffset;          ///< Coord Offset, from IO command
    wxSize             m_FmtScale;             ///< Fmt 2.3: m_FmtScale = 3, fmt 3.4: m_FmtScale = 4
    wxSize             m_FmtLen;               ///< Nb chars per coord. ex fmt 2.3, m_FmtLen = 5

    int                m_ImageRotation;        ///< Image rotation (0, 90, 180, 270 only) in degrees
    double             m_LocalRotation;        ///< Local rotation added to m_ImageRotation
                                               ///< @note This value is stored in 0.1 degrees

    VECTOR2I           m_Offset;               ///< Coord Offset, from OF command
    VECTOR2I           m_Scale;                ///< scale (X and Y) of layer.
    bool               m_SwapAxis;             ///< false if A = X and B = Y (default); true if
                                               ///<   A = Y, B = X
    bool               m_MirrorA;              ///< true: mirror / axis A (X)
    bool               m_MirrorB;              ///< true: mirror / axis B (Y)
    int                m_Iterpolation;         ///< Linear, 90 arc, Circ.
    int                m_Current_Tool;         ///< Current Tool (Dcode) number selected
    int                m_Last_Pen_Command;     ///< Current or last pen state (0..9, set by Dn
                                               ///<   option with n < 10
    int                m_CommandState;         ///< state of gerber analysis command
    int                m_LineNum;              ///< Line number of the gerber file while reading.
    VECTOR2I           m_CurrentPos;           ///< current specified coord for plot
    VECTOR2I           m_PreviousPos;          ///< old current specified coord for plot
    VECTOR2I           m_IJPos;                ///< IJ coord (for arcs & circles )

    ///< True if a IJ coord was read (for arcs & circles ).
    bool               m_LastCoordIsIJPos;

    ///< A value ( = radius in circular routing in Excellon files ).
    int                m_ArcRadius;

    ///< Identifier for arc data type (IJ (center) or A## (radius)).
    LAST_EXTRA_ARC_DATA_TYPE m_LastArcDataType;
    FILE*              m_Current_File;                   // Current file to read

    int                m_Selected_Tool;                  // For highlight: current selected Dcode

    ///< True if has DCodes in file or false if no DCodes found.  Perhaps deprecated RS274D file.
    bool               m_Has_DCode;

    // true = some DCodes in file are not defined (broken file or deprecated RS274D file).
    bool               m_Has_MissingDCode;
    bool               m_360Arc_enbl;                    // Enable 360 deg circular interpolation

    // Set to true when a circular interpolation command type is found. Mandatory before
    // drawing an arc.
    bool               m_AsArcG74G75Cmd;

    // Enable polygon mode (read coord as a polygon descr)
    bool               m_PolygonFillMode;

    // In polygon mode: 0 = first segm, 1 = next segm
    int                m_PolygonFillModeState;

    ///< a collection of APERTURE_MACROS, sorted by name
    APERTURE_MACRO_SET m_aperture_macros;

    // the net attributes set by a %TO.CN, %TO.C and/or %TO.N add object attribute command.
    GBR_NETLIST_METADATA m_NetAttributeDict;

    // the aperture function set by a %TA.AperFunction, xxx (stores the xxx value).
    wxString            m_AperFunction;

    std::map<wxString, int> m_ComponentsList;            // list of components
    std::map<wxString, int> m_NetnamesList;              // list of net names

    ///< Dcode (Aperture) List for this layer (max TOOLS_MAX_COUNT: see dcode.h)
    D_CODE*             m_Aperture_List[TOOLS_MAX_COUNT];

    ///< Whether an aperture macro tool is flashed on or off.
    bool               m_Exposure;

    GERBER_LAYER       m_GBRLayerParams;                 // hold params for the current gerber layer
    GERBER_DRAW_ITEMS  m_drawings;                       // linked list of Gerber Items to draw

    ///< Parameters used only to draw (display) items on this layer.
    ///< Do not change actual coordinates/orientation
    VECTOR2I           m_DisplayOffset;
    EDA_ANGLE          m_DisplayRotation;

    // A large buffer to store one line
    static char m_LineBuffer[GERBER_BUFZ+1];

private:
    wxArrayString      m_messagesList;         // A list of messages created when reading a file

    /**
     * True if the image is negative or has some negative items.
     *
     * Used to optimize drawing because when there are no negative items screen refresh does
     * not need to build an intermediate bitmap specific to this image.
     *
     *  - -1 negative items are.
     *  - 0 no negative items found.
     *  - 1 have negative items found.
     */
    int                m_hasNegativeItems;
};

#endif  // ifndef GERBER_FILE_IMAGE_H
