/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2020 Thomas Pointhuber <thomas.pointhuber@gmx.at>
 * Copyright (C) 2020 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_barcode.h
 * @brief BARCODE class definition.
 */

#ifndef BARCODE_H_
#define BARCODE_H_


#include <board_item.h>
#include <geometry/shape_poly_set.h>
#include <pcb_text.h>

class LINE_READER;
class MSG_PANEL_ITEM;


enum class BARCODE_T : int
{
    CODE_39 = 0,
    CODE_128 = 1,
    DATA_MATRIX = 2,
    QR_CODE = 3,
    MICRO_QR_CODE = 4
};

enum class BARCODE_ECC_T : int
{
    L = 1, // Low
    M = 2, // Medium
    Q = 3, // Quartile
    H = 4  // High
};

#ifndef SWIG
DECLARE_ENUM_TO_WXANY( BARCODE_T );
DECLARE_ENUM_TO_WXANY( BARCODE_ECC_T );
#endif

class PCB_BARCODE : public BOARD_ITEM
{
public:
    /**
     * Construct a PCB_BARCODE.
     *
     * @param aParent Parent board item (usually the BOARD object).
     */
    PCB_BARCODE( BOARD_ITEM* aParent );

    /**
     * Copy constructor.
     */
    PCB_BARCODE( const PCB_BARCODE& aOther );

    /**
     * Copy assignment operator.
     *
     * Re-parents the embedded m_text object after copying to ensure the parent chain
     * remains correct for text variable resolution.
     */
    PCB_BARCODE& operator=( const PCB_BARCODE& aOther );

    /**
     * Destructor.
     */
    ~PCB_BARCODE();

    /**
     * Type-check helper.
     *
     * @return true if the given EDA_ITEM is a PCB_BARCODE.
     */
    static inline bool ClassOf( const EDA_ITEM* aItem ) { return aItem && PCB_BARCODE_T == aItem->Type(); }

    /**
     * Get the position (center) of the barcode in internal units.
     *
     * @return center position of the barcode.
     */
    VECTOR2I GetPosition() const override;
    void SetPosition( const VECTOR2I& aPos ) override;

    /**
     * Change the height of the human-readable text displayed below the barcode.
     *
     * @param aTextSize text size in internal units.  Will be used for both x and y.
     */
    void SetTextSize( int aTextSize );

    int  GetTextSize() const;

    /**
     * Set the drawing layer for the barcode and its text.
     *
     * @param aLayer target PCB layer id.
     */
    void SetLayer( PCB_LAYER_ID aLayer ) override;

    void Serialize( google::protobuf::Any& aContainer ) const override;
    bool Deserialize( const google::protobuf::Any& aContainer ) override;

    /**
     * Get the barcode width (in internal units).
     */
    int  GetWidth() const { return m_width; }
    void SetWidth( int aWidth ) { m_width = aWidth; }

    /**
     * Get the barcode height (in internal units).
     */
    int  GetHeight() const { return m_height; }
    void SetHeight( int aHeight ) { m_height = aHeight; }

    /**
     * Get the barcode margin (in internal units).
     */
    const VECTOR2I& GetMargin() const { return m_margin; }
    void SetMargin( const VECTOR2I& aMargin ) { m_margin = aMargin; }

    /**
     * Access the underlying polygonal representation generated for the barcode.
     *
     * @return reference to internal SHAPE_POLY_SET holding outlines for the barcode.
     */
    const SHAPE_POLY_SET& GetPolyShape() const { return m_poly; }

    /**
     * Access the cached polygon for the barcode symbol only (no text, no margins/knockout).
     */
    const SHAPE_POLY_SET& GetSymbolPoly() const { return m_symbolPoly; }

    /**
     * Access the cached polygon for the human-readable text only (already scaled/placed).
     */
    const SHAPE_POLY_SET& GetTextPoly() const { return m_textPoly; }

    /**
     * Convert the barcode (text + symbol shapes) to polygonal geometry suitable for filling/collision tests.
     *
     * The function appends geometry to @p aBuffer for the specified @p aLayer. When @p aClearance is non-zero
     * the internal polygon is inflated by that amount before being merged.
     *
     * @param aBuffer output buffer to receive polygon outlines.
     * @param aLayer layer to convert (only geometry on this layer is added).
     * @param aClearance clearance/inflation to apply around shapes (in internal units).
     * @param aMaxError maximum error when approximating arcs.
     * @param aErrorLoc whether approximation error should be placed outside or inside the polygon.
     * @param ignoreLineWidth unused.
     */
    void TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
                                  int aMaxError, ERROR_LOC aErrorLoc = ERROR_INSIDE,
                                  bool ignoreLineWidth = false ) const override;


    // @copydoc BOARD_ITEM::GetEffectiveShape
    virtual std::shared_ptr<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER,
                                                      FLASHING aFlash = FLASHING::DEFAULT ) const override;

    /*
     * Add two rectangular polygons separately bounding the barcode's symbol and the barcode's text.
     */
    void GetBoundingHull( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
                          int aMaxError, ERROR_LOC aErrorLoc = ERROR_INSIDE ) const;

   /**
     * Generate the internal polygon representation for the current barcode text, kind and error correction.
     *
     * This uses the Zint backend to encode the text and populate @c m_poly. After calling this the
     * barcode bounding box and polygon are available and scaled to the current width/height.
     */
    void ComputeBarcode();

    /**
     * Generate the internal polygon representation for the human-readable text.
     *
     * This uses the internal PCB_TEXT object to generate the text polygon and position it
     * below the barcode symbol. The resulting polygon is stored in @c m_textPoly.
     */
    void ComputeTextPoly();

    /**
     * Assemble the barcode polygon and text polygons into a single polygonal representation.
     * Optionally apply a knockout and margins.
     */
    void AssembleBarcode();

    /**
     * Set the barcode content text to encode.
     *
     * @param aText UTF-8 string content for the barcode.
     */
    void     SetText( const wxString& aText );
    wxString GetText() const;
    wxString GetShownText() const;

    /**
     * Access the internal `PCB_TEXT` object used for showing the human-readable text.
     */
    PCB_TEXT&       Text() { return m_text; }
    const PCB_TEXT& Text() const { return m_text; }

    /**
     * Function Move
     * @param offset : moving vector
     */
    /**
     * Translate the barcode and its text by the given offset.
     *
     * @param offset translation vector in internal units.
     */
    void Move( const VECTOR2I& offset ) override;

    /**
     * Rotate the barcode around a given centre by the given angle.
     * The underlying polygon and text are rotated; the width/height are updated from the new bounding box.
     *
     * @param aRotCentre rotation centre in internal units.
     * @param aAngle rotation angle.
     */
    void Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle ) override;

    /**
     * Flip the barcode horizontally or vertically around a centre point.
     * The layer may be adjusted when flipping.
     *
     * @param aCentre flip origin in internal units.
     * @param aFlipLeftRight flip direction enum.
     */
    void Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipLeftRight ) override;

    void StyleFromSettings( const BOARD_DESIGN_SETTINGS& settings, bool aCheckSide ) override;

    /**
     * Get the centre of the barcode (alias for GetPosition).
     */
    VECTOR2I GetCenter() const override { return GetPosition(); }

    /**
     * Set the bounding rectangle of the barcode. This will scale the internal polygon outlines
     * to fit the given rectangle and update width/height accordingly.  This refers to
     * the size of the barcode symbol excluding margins and text.
     *
     * @param aTopLeft top-left coordinate in internal units.
     * @param aBotRight bottom-right coordinate in internal units.
     */
    void SetRect( const VECTOR2I& aTopLeft, const VECTOR2I& aBotRight );

    /**
     * Populate message panel information entries (e.g. for selection/property panel).
     *
     * @param aFrame parent draw frame used for context.
     * @param aList vector to append MSG_PANEL_ITEM entries to.
     */
    void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;

    /**
     * Hit-test a point against the barcode (text and symbol area).
     *
     * @param aPosition point to test in internal units.
     * @param aAccuracy tolerance (in internal units) for the hit test.
     * @return true if the point hits the barcode or its text.
     */
    bool HitTest( const VECTOR2I& aPosition, int aAccuracy ) const override;

    /**
     * Hit-test a rectangle against the barcode bounding area.
     *
     * @param aRect rectangle to test.
     * @param aContained if true, test whether barcode is fully contained in aRect; otherwise test intersection.
     * @param aAccuracy optional tolerance to inflate the barcode bounding box before testing.
     */
    bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;

    /**
     * Return the class name for display/debugging purposes.
     */
    wxString GetClass() const override { return wxT( "BARCODE" ); }

    // Virtual function
    /**
     * Get the axis-aligned bounding box of the barcode including text.
     *
     * @return bounding BOX2I in internal units.
     */
    const BOX2I GetBoundingBox() const override;

    /**
     * Produce a short human-readable description of the item for UI lists.
     *
     * @param aUnitsProvider unit conversion helper (may be null).
     * @param aFull if true produce a more verbose description.
     * @return localized description string.
     */
    wxString GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const override;

    /**
     * Icon to show in context menus/toolbars for this item type.
     */
    BITMAPS GetMenuImage() const override;

    /**
     * Create a copy of this item. The returned pointer is owned by the caller.
     *
     * @return newly allocated EDA_ITEM pointer that is a copy of this object.
     */
    EDA_ITEM* Clone() const override;

    void swapData( BOARD_ITEM* aImage ) override;

    /**
     * Get the bbox used for drawing/view culling, may include additional view-only extents.
     */
    virtual const BOX2I ViewBBox() const override;

    /**
     * Compute a simple similarity score between this barcode and another board item.
     *
     * @param aItem other item to compare.
     * @return similarity value between 0.0 and 1.0.
     */
    double Similarity( const BOARD_ITEM& aItem ) const override;

    static int Compare( const PCB_BARCODE* aBarcode, const PCB_BARCODE* aOther );

    /**
     * Equality comparison operator for board-level deduplication.
     *
     * @param aItem other item to compare against.
     * @return true if the other item is a PCB_BARCODE and has identical key properties.
     */
    bool operator==( const BOARD_ITEM& aItem ) const override;

    bool operator==( const PCB_BARCODE& aBarcode ) const;

    /**
     * Returns the type of the barcode (QR, CODE_39, etc.).
     */
    BARCODE_T GetKind() const { return m_kind; }
    void SetKind( BARCODE_T aKind );
    void SetBarcodeKind( BARCODE_T aKind ); // Includes re-compute

    bool KeepSquare() const
    {
        return m_kind == BARCODE_T::QR_CODE
                || m_kind == BARCODE_T::MICRO_QR_CODE
                || m_kind == BARCODE_T::DATA_MATRIX;
    }

    /**
     * Set the error correction level used for QR codes.
     *
     * @param aErrorCorrection error correction enum value.
     */
    void SetErrorCorrection( BARCODE_ECC_T aErrorCorrection );
    BARCODE_ECC_T GetErrorCorrection() const { return m_errorCorrection; }
    void SetBarcodeErrorCorrection( BARCODE_ECC_T aErrorCorrection );  // Includes re-compute

    void SetBarcodeText( const wxString& aText ) { SetText( aText ); AssembleBarcode(); }
    void SetShowText( bool aShow ) { m_text.SetVisible( aShow ); AssembleBarcode(); }
    bool GetShowText() const { return m_text.IsVisible(); }

    void SetBarcodeWidth( int aWidth );
    void SetBarcodeHeight( int aHeight );

    EDA_ANGLE GetAngle() const { return m_angle; }
    double GetOrientation() const { return m_angle.AsDegrees(); }
    void   SetOrientation( double aDegrees )
    {
        EDA_ANGLE newAngle( aDegrees, DEGREES_T );
        EDA_ANGLE oldAngle = m_angle;

        if( newAngle != oldAngle )
        {
            Rotate( GetPosition(), newAngle - oldAngle );
        }
    }

    int  GetMarginX() const { return m_margin.x; }
    int  GetMarginY() const { return m_margin.y; }

    void SetMarginX( int aX )
    {
        aX = std::max( pcbIUScale.mmToIU( 1 ), aX );
        m_margin.x = aX;
        AssembleBarcode();
    }

    void SetMarginY( int aY )
    {
        aY = std::max( pcbIUScale.mmToIU( 1 ), aY );
        m_margin.y = aY;
        AssembleBarcode();
    }

    bool IsKnockout() const override { return BOARD_ITEM::IsKnockout(); }
    void SetIsKnockout( bool aEnable ) override
    {
        BOARD_ITEM::SetIsKnockout( aEnable );
        AssembleBarcode();
    }

    const wxString& GetLastError() const { return m_lastError; }

private:
    int            m_width;      ///< Barcode width
    int            m_height;     ///< Barcode height
    VECTOR2I       m_pos;        ///< Position of the barcode
    VECTOR2I       m_margin;     ///< Margin around the barcode (only valid for knockout)
    PCB_TEXT       m_text;
    BARCODE_T      m_kind;
    EDA_ANGLE      m_angle;
    BARCODE_ECC_T  m_errorCorrection; ///< Error correction level for QR codes

    SHAPE_POLY_SET m_poly;            ///< Full geometry (barcode + optional text or knockout)
    SHAPE_POLY_SET m_symbolPoly;      ///< Barcode symbol only (cached, centered at origin)
    SHAPE_POLY_SET m_textPoly;        ///< Human-readable text only (cached, centered/positioned)
    BOX2I          m_bbox;            ///< BBox of m_poly (ie: barcode + text)
    wxString       m_lastError;
};

#endif // DIMENSION_H_
