/**
 * @file sel_layer.cpp
 * @brief minor dialogs for one layer selection and a layer pair selection.
 */
/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 1992-2021 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 3 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, see <http://www.gnu.org/licenses/>.
 */


#include <kiplatform/ui.h>
#include <confirm.h>
#include <pcb_base_frame.h>
#include <widgets/layer_box_selector.h>
#include <board.h>
#include <dialogs/dialog_layer_selection_base.h>
#include <router/router_tool.h>
#include <settings/color_settings.h>
#include <tools/pcb_actions.h>

// Column position by function:
#define SELECT_COLNUM       0
#define COLOR_COLNUM        1
#define LAYERNAME_COLNUM    2
#define LAYER_HK_COLUMN     3

/*
 * Display a layer list using a wxGrid.
 */
class PCB_LAYER_SELECTOR: public LAYER_SELECTOR
{
public:
    PCB_LAYER_SELECTOR( PCB_BASE_FRAME* aFrame ) :
        LAYER_SELECTOR()
    {
        m_frame = aFrame;
    }

protected:
    PCB_BASE_FRAME*  m_frame;

    ///< @return true if the layer id is enabled (i.e. is it should be displayed).
    bool isLayerEnabled( int aLayer ) const override
    {
        return m_frame->GetBoard()->IsLayerEnabled( PCB_LAYER_ID( aLayer ) );
    }

    // Return the color index from the layer ID.
    COLOR4D getLayerColor( int aLayer ) const override
    {
        return m_frame->GetColorSettings()->GetColor( aLayer );
    }

    // Return the name of the layer ID.
    wxString getLayerName( int aLayer ) const override
    {
        return m_frame->GetBoard()->GetLayerName( ToLAYER_ID( aLayer ) );
    }
};


/**
 * Display a PCB layers list in a dialog to select one layer from this list.
 */
class PCB_ONE_LAYER_SELECTOR : public PCB_LAYER_SELECTOR, public DIALOG_LAYER_SELECTION_BASE
{
public:
    PCB_ONE_LAYER_SELECTOR( PCB_BASE_FRAME* aParent, BOARD * aBrd, PCB_LAYER_ID aDefaultLayer,
                            LSET aNotAllowedLayersMask, bool aHideCheckBoxes = false );
    ~PCB_ONE_LAYER_SELECTOR();

    int GetLayerSelection()   { return m_layerSelected; }

private:
    // Event handlers
    void OnLeftGridCellClick( wxGridEvent& aEvent ) override;
    void OnRightGridCellClick( wxGridEvent& aEvent ) override;
    void OnMouseMove( wxUpdateUIEvent& aEvent ) override;

    // Will close the dialog on ESC key
    void onCharHook( wxKeyEvent& event );

    wxString getLayerHotKey( PCB_LAYER_ID aLayer ) const
    {
        int code = PCB_ACTIONS::LayerIDToAction( aLayer )->GetHotKey();
        return AddHotkeyName( wxS( "" ), code, IS_COMMENT );
    }

    void buildList();

    PCB_LAYER_ID              m_layerSelected;
    LSET                      m_notAllowedLayersMask;
    BOARD*                    m_brd;
    std::vector<PCB_LAYER_ID> m_layersIdLeftColumn;
    std::vector<PCB_LAYER_ID> m_layersIdRightColumn;
};


PCB_ONE_LAYER_SELECTOR::PCB_ONE_LAYER_SELECTOR( PCB_BASE_FRAME* aParent, BOARD* aBrd,
                                                PCB_LAYER_ID aDefaultLayer,
                                                LSET aNotAllowedLayersMask,
                                                bool aHideCheckBoxes ) :
        PCB_LAYER_SELECTOR( aParent ),
        DIALOG_LAYER_SELECTION_BASE( aParent )
{
    m_useCalculatedSize = true;

    m_layerSelected = aDefaultLayer;
    m_notAllowedLayersMask = aNotAllowedLayersMask;
    m_brd = aBrd;

    m_leftGridLayers->SetCellHighlightPenWidth( 0 );
    m_rightGridLayers->SetCellHighlightPenWidth( 0 );
    m_leftGridLayers->SetColFormatBool( SELECT_COLNUM );
    m_rightGridLayers->SetColFormatBool( SELECT_COLNUM );

    m_leftGridLayers->AppendCols( 1 );

    buildList();

    if( aHideCheckBoxes )
    {
        m_leftGridLayers->HideCol( SELECT_COLNUM );
        m_rightGridLayers->HideCol( SELECT_COLNUM );
    }

    Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( PCB_ONE_LAYER_SELECTOR::onCharHook ) );

    Layout();
    GetSizer()->SetSizeHints( this );
    SetFocus();
}


PCB_ONE_LAYER_SELECTOR::~PCB_ONE_LAYER_SELECTOR()
{
    Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( PCB_ONE_LAYER_SELECTOR::onCharHook ) );
}


void PCB_ONE_LAYER_SELECTOR::OnMouseMove( wxUpdateUIEvent& aEvent )
{
    /// We have to assign this in UpdateUI events because the wxGrid is not properly receiving
    /// MouseMove events.  It seems to only get them on the edges.  So, for now we use this
    /// workaround

    wxPoint mouse_pos = KIPLATFORM::UI::GetMousePosition();
    wxPoint left_pos = m_leftGridLayers->ScreenToClient( mouse_pos );
    wxPoint right_pos = m_rightGridLayers->ScreenToClient( mouse_pos );

    if( m_leftGridLayers->HitTest( left_pos ) == wxHT_WINDOW_INSIDE )
    {
        int row = m_leftGridLayers->YToRow( left_pos.y );

        if( row != wxNOT_FOUND && row < static_cast<int>( m_layersIdLeftColumn.size() ) )
        {
            m_layerSelected = m_layersIdLeftColumn[ row ];
            m_leftGridLayers->SelectBlock( row, LAYERNAME_COLNUM, row, LAYER_HK_COLUMN );
            return;
        }
    }

    if( m_rightGridLayers->HitTest( right_pos ) == wxHT_WINDOW_INSIDE )
    {
        int row = m_rightGridLayers->YToRow( right_pos.y );

        if( row == wxNOT_FOUND || row >= static_cast<int>( m_layersIdRightColumn.size() ) )
            return;

        m_layerSelected = m_layersIdRightColumn[ row ];
        m_rightGridLayers->SelectBlock( row, LAYERNAME_COLNUM, row, LAYERNAME_COLNUM);
    }
}


void PCB_ONE_LAYER_SELECTOR::onCharHook( wxKeyEvent& event )
{
    if( event.GetKeyCode() == WXK_ESCAPE )
        Close();
}


void PCB_ONE_LAYER_SELECTOR::buildList()
{
    wxColour bg = getLayerColor( LAYER_PCB_BACKGROUND ).ToColour();
    int      left_row = 0;
    int      right_row = 0;
    wxString layername;

    for( LSEQ ui_seq = m_brd->GetEnabledLayers().UIOrder(); ui_seq; ++ui_seq )
    {
        PCB_LAYER_ID  layerid = *ui_seq;

        if( m_notAllowedLayersMask[layerid] )
            continue;

        wxColour fg = getLayerColor( layerid ).ToColour();
        wxColour color( wxColour::AlphaBlend( fg.Red(), bg.Red(), fg.Alpha() / 255.0 ),
                        wxColour::AlphaBlend( fg.Green(), bg.Green(), fg.Alpha() / 255.0 ),
                        wxColour::AlphaBlend( fg.Blue(), bg.Blue(), fg.Alpha() / 255.0 ) );

        layername = wxT( " " ) + getLayerName( layerid );

        if( IsCopperLayer( layerid ) )
        {
            if( left_row )
                m_leftGridLayers->AppendRows( 1 );

            m_leftGridLayers->SetCellBackgroundColour ( left_row, COLOR_COLNUM, color );
            m_leftGridLayers->SetCellValue( left_row, LAYERNAME_COLNUM, layername );
            m_leftGridLayers->SetCellValue( left_row, LAYER_HK_COLUMN, getLayerHotKey( layerid ) );

            if( m_layerSelected == layerid )
                m_leftGridLayers->SetCellValue( left_row, SELECT_COLNUM, wxT( "1" ) );

            m_layersIdLeftColumn.push_back( layerid );
            left_row++;
        }
        else
        {
            if( right_row )
                m_rightGridLayers->AppendRows( 1 );

            m_rightGridLayers->SetCellBackgroundColour( right_row, COLOR_COLNUM, color );
            m_rightGridLayers->SetCellValue( right_row, LAYERNAME_COLNUM, layername );

            if( m_layerSelected == layerid )
                m_rightGridLayers->SetCellValue( right_row, SELECT_COLNUM, wxT( "1" ) );

            m_layersIdRightColumn.push_back( layerid );
            right_row++;
        }
    }

    // Show only populated lists:
    if( left_row <= 0 )
        m_leftGridLayers->Show( false );

    if( right_row <= 0 )
        m_rightGridLayers->Show( false );

    // Now fix min grid column size (it also sets a minimal size)
    m_leftGridLayers->AutoSizeColumns();
    m_rightGridLayers->AutoSizeColumns();
}


void PCB_ONE_LAYER_SELECTOR::OnLeftGridCellClick( wxGridEvent& event )
{
    m_layerSelected = m_layersIdLeftColumn[ event.GetRow() ];

    if( IsQuasiModal() )
        EndQuasiModal( 1 );
    else
        EndDialog( 1 );
}


void PCB_ONE_LAYER_SELECTOR::OnRightGridCellClick( wxGridEvent& event )
{
    m_layerSelected = m_layersIdRightColumn[ event.GetRow() ];

    if( IsQuasiModal() )
        EndQuasiModal( 2 );
    else
        EndDialog( 2 );
}


PCB_LAYER_ID PCB_BASE_FRAME::SelectOneLayer( PCB_LAYER_ID aDefaultLayer, LSET aNotAllowedLayersMask,
                                             wxPoint aDlgPosition )
{
    PCB_ONE_LAYER_SELECTOR dlg( this, GetBoard(), aDefaultLayer, aNotAllowedLayersMask, true );

    if( aDlgPosition != wxDefaultPosition )
    {
        wxSize dlgSize = dlg.GetSize();
        aDlgPosition.x -= dlgSize.x/2;
        aDlgPosition.y -= dlgSize.y/2;
        dlg.SetPosition( aDlgPosition );
    }

    if( dlg.ShowModal() != wxID_CANCEL )
        return ToLAYER_ID( dlg.GetLayerSelection() );
    else
        return UNDEFINED_LAYER;
}


/**
 * Display a pair PCB copper layers list in a dialog to select a layer pair from these lists.
 */
class SELECT_COPPER_LAYERS_PAIR_DIALOG: public PCB_LAYER_SELECTOR,
                                        public DIALOG_COPPER_LAYER_PAIR_SELECTION_BASE
{
public:
    SELECT_COPPER_LAYERS_PAIR_DIALOG( PCB_BASE_FRAME* aParent, BOARD* aPcb,
                                      PCB_LAYER_ID aFrontLayer, PCB_LAYER_ID aBackLayer );

    void GetLayerPair( PCB_LAYER_ID& aFrontLayer, PCB_LAYER_ID& aBackLayer )
    {
        aFrontLayer = m_frontLayer;
        aBackLayer = m_backLayer;
    }

private:
    void OnLeftGridCellClick( wxGridEvent& event ) override;
    void OnRightGridCellClick( wxGridEvent& event ) override;

    void buildList();

    BOARD*       m_brd;
    PCB_LAYER_ID m_frontLayer;
    PCB_LAYER_ID m_backLayer;
    int          m_leftRowSelected;
    int          m_rightRowSelected;

    std::vector<PCB_LAYER_ID> m_layersId;
};


int ROUTER_TOOL::SelectCopperLayerPair( const TOOL_EVENT& aEvent )
{
    PCB_SCREEN* screen = frame()->GetScreen();

    SELECT_COPPER_LAYERS_PAIR_DIALOG dlg( frame(), frame()->GetBoard(), screen->m_Route_Layer_TOP,
                                          screen->m_Route_Layer_BOTTOM );

    if( dlg.ShowModal() == wxID_OK )
    {
        dlg.GetLayerPair( screen->m_Route_Layer_TOP, screen->m_Route_Layer_BOTTOM );

        // select the same layer for both layers is allowed (normal in some boards)
        // but could be a mistake. So display an info message
        if( screen->m_Route_Layer_TOP == screen->m_Route_Layer_BOTTOM )
            DisplayInfoMessage( frame(), _( "Warning: top and bottom layers are same." ) );
    }

    return 0;
}


SELECT_COPPER_LAYERS_PAIR_DIALOG::SELECT_COPPER_LAYERS_PAIR_DIALOG(
        PCB_BASE_FRAME* aParent, BOARD * aPcb, PCB_LAYER_ID aFrontLayer, PCB_LAYER_ID aBackLayer) :
    PCB_LAYER_SELECTOR( aParent ),
    DIALOG_COPPER_LAYER_PAIR_SELECTION_BASE( aParent )
{
    m_frontLayer = aFrontLayer;
    m_backLayer = aBackLayer;
    m_leftRowSelected = 0;
    m_rightRowSelected = 0;
    m_brd = aPcb;

    m_leftGridLayers->SetCellHighlightPenWidth( 0 );
    m_rightGridLayers->SetCellHighlightPenWidth( 0 );
    m_leftGridLayers->SetColFormatBool( SELECT_COLNUM );
    m_rightGridLayers->SetColFormatBool( SELECT_COLNUM );
    buildList();

    SetFocus();

    GetSizer()->SetSizeHints( this );
    Center();
}


void SELECT_COPPER_LAYERS_PAIR_DIALOG::buildList()
{
    wxColour bg = getLayerColor( LAYER_PCB_BACKGROUND ).ToColour();
    int      row = 0;
    wxString layername;

    for( LSEQ ui_seq = m_brd->GetEnabledLayers().UIOrder(); ui_seq; ++ui_seq )
    {
        PCB_LAYER_ID layerid = *ui_seq;

        if( !IsCopperLayer( layerid ) )
            continue;

        wxColour fg = getLayerColor( layerid ).ToColour();
        wxColour color( wxColour::AlphaBlend( fg.Red(), bg.Red(), fg.Alpha() / 255.0 ),
                        wxColour::AlphaBlend( fg.Green(), bg.Green(), fg.Alpha() / 255.0 ),
                        wxColour::AlphaBlend( fg.Blue(), bg.Blue(), fg.Alpha() / 255.0 ) );

        layername = wxT( " " ) + getLayerName( layerid );

        if( row )
            m_leftGridLayers->AppendRows( 1 );

        m_leftGridLayers->SetCellBackgroundColour( row, COLOR_COLNUM, color );
        m_leftGridLayers->SetCellValue( row, LAYERNAME_COLNUM, layername );
        m_layersId.push_back( layerid );

        if( m_frontLayer == layerid )
        {
            m_leftGridLayers->SetCellValue( row, SELECT_COLNUM, wxT( "1" ) );
            m_leftGridLayers->SetGridCursor( row, COLOR_COLNUM );
            m_leftRowSelected = row;
        }

        if( row )
            m_rightGridLayers->AppendRows( 1 );

        m_rightGridLayers->SetCellBackgroundColour( row, COLOR_COLNUM, color );
        m_rightGridLayers->SetCellValue( row, LAYERNAME_COLNUM, layername );

        if( m_backLayer == layerid )
        {
            m_rightGridLayers->SetCellValue( row, SELECT_COLNUM, wxT( "1" ) );
            m_rightRowSelected = row;
        }

        row++;
    }

    // Now fix min grid layer name column size (it also sets a minimal size)
    m_leftGridLayers->AutoSizeColumn( LAYERNAME_COLNUM );
    m_rightGridLayers->AutoSizeColumn( LAYERNAME_COLNUM );
}


void SELECT_COPPER_LAYERS_PAIR_DIALOG::OnLeftGridCellClick( wxGridEvent& event )
{
    int          row = event.GetRow();
    PCB_LAYER_ID layer = m_layersId[row];

    if( m_frontLayer == layer )
        return;

    m_leftGridLayers->SetCellValue( m_leftRowSelected, SELECT_COLNUM, wxEmptyString );
    m_frontLayer = layer;
    m_leftRowSelected = row;
    m_leftGridLayers->SetCellValue( m_leftRowSelected, SELECT_COLNUM, wxT( "1" ) );
}


void SELECT_COPPER_LAYERS_PAIR_DIALOG::OnRightGridCellClick( wxGridEvent& event )
{
    int          row = event.GetRow();
    PCB_LAYER_ID layer = m_layersId[row];

    if( m_backLayer == layer )
        return;

    m_rightGridLayers->SetCellValue( m_rightRowSelected, SELECT_COLNUM, wxEmptyString );
    m_backLayer = layer;
    m_rightRowSelected = row;
    m_rightGridLayers->SetCellValue( m_rightRowSelected, SELECT_COLNUM, wxT( "1" ) );
}
