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

#include <functional>
#include <memory>
#include <optional>
#include <vector>

#include <board_item.h>
#include <pcb_shape.h>

#include <geometry/chamfer.h>

/**
 * @brief An object that has the ability to modify items on a board
 *
 * For example, such an object could take pairs of lines and fillet them,
 * or produce other modification to items.
 *
 * Deliberately not called a "tool" to distinguish it from true
 * tools that are used interactively by the user.
 */
class ITEM_MODIFICATION_ROUTINE
{
public:
    /*
    * Handlers for receiving changes from the tool
    *
    * These are used to allow the tool's caller to make changes to
    * affected board items using extra information that the tool
    * does not have access to (e.g. is this an FP editor, was
    * the line created from a rectangle and needs to be added, not
    * modified, etc).
    *
    * We can't store them up until the end, because modifications
    * need the old state to be known, so this allows the caller to
    * inject the dependencies for how to handle the changes.
    */
    class CHANGE_HANDLER
    {
    public:
        virtual ~CHANGE_HANDLER() = default;

        /**
         * @brief Report that the tools wants to add a new item to the board
         *
         * @param aItem the new item
         */
        virtual void AddNewItem( std::unique_ptr<PCB_SHAPE> aItem ) = 0;

        /**
         * @brief Report that the tool has modified an item on the board
         *
         * @param aItem the modified item
         */
        virtual void MarkItemModified( PCB_SHAPE& aItem ) = 0;

        /**
         * @brief Report that the tool has deleted an item on the board
         *
         * @param aItem the deleted item
         */
        virtual void DeleteItem( PCB_SHAPE& aItem ) = 0;
    };

    /**
     * @brief A handler that is based on a set of callbacks provided
     * by the user of the ITEM_MODIFICATION_ROUTINE
     */
    class CALLABLE_BASED_HANDLER : public CHANGE_HANDLER
    {
    public:
        /**
         * Handler for creating a new item on the board
         *
         * @param PCB_SHAPE& the shape to add
         */
        using CREATION_HANDLER = std::function<void( std::unique_ptr<PCB_SHAPE> )>;

        /**
         * Handler for modifying or deleting an existing item on the board
         *
         * @param PCB_SHAPE& the shape to modify
         */
        using MODIFICATION_HANDLER = std::function<void( PCB_SHAPE& )>;

        /**
         * Handler for modifying or deleting an existing item on the board
         *
         * @param PCB_SHAPE& the shape to delete
         */
        using DELETION_HANDLER = std::function<void( PCB_SHAPE& )>;

        CALLABLE_BASED_HANDLER( CREATION_HANDLER     aCreationHandler,
                                MODIFICATION_HANDLER aModificationHandler,
                                DELETION_HANDLER     aDeletionHandler ) :
                m_creationHandler( std::move( aCreationHandler ) ),
                m_modificationHandler( std::move( aModificationHandler ) ),
                m_deletionHandler( std::move( aDeletionHandler ) )
        {
        }

        /**
         * @brief Report that the tools wants to add a new item to the board
         *
         * @param aItem the new item
         */
        void AddNewItem( std::unique_ptr<PCB_SHAPE> aItem ) override
        {
            m_creationHandler( std::move( aItem ) );
        }

        /**
         * @brief Report that the tool has modified an item on the board
         *
         * @param aItem the modified item
         */
        void MarkItemModified( PCB_SHAPE& aItem ) override { m_modificationHandler( aItem ); }

        /**
         * @brief Report that the tool has deleted an item on the board
         *
         * @param aItem the deleted item
         */
        void DeleteItem( PCB_SHAPE& aItem ) override { m_deletionHandler( aItem ); }

        CREATION_HANDLER     m_creationHandler;
        MODIFICATION_HANDLER m_modificationHandler;
        DELETION_HANDLER     m_deletionHandler;
    };

    ITEM_MODIFICATION_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
            m_board( aBoard ),
            m_handler( aHandler ),
            m_numSuccesses( 0 ),
            m_numFailures( 0 )
    {
    }

    virtual ~ITEM_MODIFICATION_ROUTINE() = default;

    unsigned GetSuccesses() const { return m_numSuccesses; }

    unsigned GetFailures() const { return m_numFailures; }

    virtual wxString GetCommitDescription() const = 0;

    /**
     * @brief Get a status message to show when the routine is complete
     *
     * Usually this will be an error or nothing.
     */
    virtual std::optional<wxString> GetStatusMessage() const = 0;

protected:
    /**
     * The BOARD used when creating new shapes
     */
    BOARD_ITEM* GetBoard() const { return m_board; }

    /**
     * Mark that one of the actions succeeded.
     */
    void AddSuccess() { ++m_numSuccesses; }

    /**
     * Mark that one of the actions failed.
     */
    void AddFailure() { ++m_numFailures; }

    /**
     *  @brief Helper function useful for multiple tools: modify a line or delete
     * it if it has zero length
     *
     * @param aItem the line to modify
     * @param aSeg the new line geometry
     */
    bool ModifyLineOrDeleteIfZeroLength( PCB_SHAPE& aItem, const SEG& aSeg );

    /**
     * @brief Access the handler for making changes to the board
     */
    CHANGE_HANDLER& GetHandler() { return m_handler; }

private:
    BOARD_ITEM*          m_board;
    CHANGE_HANDLER&      m_handler;

    unsigned m_numSuccesses;
    unsigned m_numFailures;
};

/**
 * A tool that acts on a pair of lines. For example, fillets, chamfers, extensions, etc
 */
class PAIRWISE_LINE_ROUTINE : public ITEM_MODIFICATION_ROUTINE
{
public:
    PAIRWISE_LINE_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
            ITEM_MODIFICATION_ROUTINE( aBoard, aHandler )
    {
    }

    /**
     * @brief Perform the action on the pair of lines given
     *
     * The routine will be called repeatedly with all possible pairs of lines
     * in the selection. The tools should handle the ones it's interested in.
     * This means that the same line can appear multiple times with different
     * partners.
     *
     * The routine can skip lines that it's not interested in by returning without
     * adding to the success or failure count.
     *
     * @param aLineA the first line
     * @param aLineB the second line
     * @return did the action succeed
     */
    virtual void ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLineB ) = 0;
};

/**
 * Pairwise line tool that adds a fillet to the lines.
 */
class LINE_FILLET_ROUTINE : public PAIRWISE_LINE_ROUTINE
{
public:
    LINE_FILLET_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler, int filletRadiusIU ) :
            PAIRWISE_LINE_ROUTINE( aBoard, aHandler ), m_filletRadiusIU( filletRadiusIU )
    {
    }

    wxString GetCommitDescription() const override;
    std::optional<wxString> GetStatusMessage() const override;

    void ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLineB ) override;

private:
    int m_filletRadiusIU;
};

/**
 * Pairwise line tool that adds a chamfer between the lines.
 */
class LINE_CHAMFER_ROUTINE : public PAIRWISE_LINE_ROUTINE
{
public:
    LINE_CHAMFER_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler,
                          CHAMFER_PARAMS aChamferParams ) :
            PAIRWISE_LINE_ROUTINE( aBoard, aHandler ),
            m_chamferParams( std::move( aChamferParams ) )
    {
    }

    wxString GetCommitDescription() const override;
    std::optional<wxString> GetStatusMessage() const override;

    void ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLineB ) override;

private:
    const CHAMFER_PARAMS m_chamferParams;
};

/**
 * Pairwise extend to corner or meeting tool
 */
class LINE_EXTENSION_ROUTINE : public PAIRWISE_LINE_ROUTINE
{
public:
    LINE_EXTENSION_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
            PAIRWISE_LINE_ROUTINE( aBoard, aHandler )
    {
    }

    wxString GetCommitDescription() const override;
    std::optional<wxString> GetStatusMessage() const override;

    void ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLineB ) override;
};


/**
 * A routine that modifies polygons using boolean operations
 */
class POLYGON_BOOLEAN_ROUTINE : public ITEM_MODIFICATION_ROUTINE
{
public:
    POLYGON_BOOLEAN_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
            ITEM_MODIFICATION_ROUTINE( aBoard, aHandler ), m_workingPolygon( nullptr )
    {
    }

    void ProcessShape( PCB_SHAPE& aPcbShape );

protected:
    PCB_SHAPE* GetWorkingPolygon() const { return m_workingPolygon; }

    virtual bool ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon ) = 0;

private:
    PCB_SHAPE* m_workingPolygon;
};

class POLYGON_MERGE_ROUTINE : public POLYGON_BOOLEAN_ROUTINE
{
public:
    POLYGON_MERGE_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
            POLYGON_BOOLEAN_ROUTINE( aBoard, aHandler )
    {
    }

    wxString                GetCommitDescription() const override;
    std::optional<wxString> GetStatusMessage() const override;

private:
    bool ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon ) override;
};


class POLYGON_SUBTRACT_ROUTINE : public POLYGON_BOOLEAN_ROUTINE
{
public:
    POLYGON_SUBTRACT_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
            POLYGON_BOOLEAN_ROUTINE( aBoard, aHandler )
    {
    }

    wxString                GetCommitDescription() const override;
    std::optional<wxString> GetStatusMessage() const override;

private:
    bool ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon ) override;
};


class POLYGON_INTERSECT_ROUTINE : public POLYGON_BOOLEAN_ROUTINE
{
public:
    POLYGON_INTERSECT_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler ) :
            POLYGON_BOOLEAN_ROUTINE( aBoard, aHandler )
    {
    }

    wxString                GetCommitDescription() const override;
    std::optional<wxString> GetStatusMessage() const override;

private:
    bool ProcessSubsequentPolygon( const SHAPE_POLY_SET& aPolygon ) override;
};

#endif /* ITEM_MODIFICATION_ROUTINE_H_ */
