/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2009 Isaac Marino Bavaresco, isaacbavaresco@yahoo.com.br
 * Copyright (C) 2009 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 2009-2024 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
 */


#include <confirm.h>
#include <core/arraydim.h>
#include <core/kicad_algo.h>
#include <pcb_edit_frame.h>
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <board.h>
#include <collectors.h>
#include <footprint.h>
#include <pad.h>
#include <pcb_track.h>
#include <panel_setup_layers.h>
#include <board_stackup_manager/panel_board_stackup.h>

#include <wx/choicdlg.h>
#include <eda_list_dialog.h>


// some define to choose how copper layers widgets are shown

// if defined, display only active copper layers
// if not displays always 1=the full set (32 copper layers)
#define HIDE_INACTIVE_LAYERS


static LSEQ dlg_layers()
{
    // Layers that are put out into the dialog UI, coordinate with wxformbuilder and
    // getCTLs( int aLayerNumber )
    static const PCB_LAYER_ID layers[] = {
        F_CrtYd,
        F_Fab,
        F_Adhes,
        F_Paste,
        F_SilkS,
        F_Mask,
        F_Cu,

        In1_Cu,
        In2_Cu,
        In3_Cu,
        In4_Cu,
        In5_Cu,
        In6_Cu,
        In7_Cu,
        In8_Cu,
        In9_Cu,
        In10_Cu,
        In11_Cu,
        In12_Cu,
        In13_Cu,
        In14_Cu,
        In15_Cu,

        In16_Cu,
        In17_Cu,
        In18_Cu,
        In19_Cu,
        In20_Cu,
        In21_Cu,
        In22_Cu,
        In23_Cu,
        In24_Cu,
        In25_Cu,
        In26_Cu,
        In27_Cu,
        In28_Cu,
        In29_Cu,
        In30_Cu,

        B_Cu,
        B_Mask,
        B_SilkS,
        B_Paste,
        B_Adhes,
        B_Fab,
        B_CrtYd,

        Edge_Cuts,
        Margin,
        Eco2_User,
        Eco1_User,
        Cmts_User,
        Dwgs_User,

        User_1,
        User_2,
        User_3,
        User_4,
        User_5,
        User_6,
        User_7,
        User_8,
        User_9,
    };

    return LSEQ( layers, layers + arrayDim( layers ) );
}


PANEL_SETUP_LAYERS::PANEL_SETUP_LAYERS( wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame ) :
        PANEL_SETUP_LAYERS_BASE( aParentWindow ),
        m_frame( aFrame ),
        m_physicalStackup( nullptr ),
        m_initialized( false )
{
    m_pcb = aFrame->GetBoard();
}


PANEL_SETUP_LAYERS_CTLs PANEL_SETUP_LAYERS::getCTLs( int aLayerNumber )
{
#define RETURN_COPPER( x )    return PANEL_SETUP_LAYERS_CTLs( x##Name, x##CheckBox, x##Choice )
#define RETURN_AUX( x )       return PANEL_SETUP_LAYERS_CTLs( x##Name, x##CheckBox, x##StaticText )
#define RETURN_MANDATORY( x ) return PANEL_SETUP_LAYERS_CTLs( x##Name, nullptr, x##StaticText )

    switch( aLayerNumber )
    {
    case F_CrtYd:      RETURN_MANDATORY( m_CrtYdFront );
    case F_Fab:        RETURN_AUX( m_FabFront );
    case F_Adhes:      RETURN_AUX( m_AdhesFront );
    case F_Paste:      RETURN_AUX( m_SoldPFront );
    case F_SilkS:      RETURN_AUX( m_SilkSFront );
    case F_Mask:       RETURN_AUX( m_MaskFront );
    case F_Cu:         RETURN_COPPER( m_Front );

    case In1_Cu:       RETURN_COPPER( m_In1 );
    case In2_Cu:       RETURN_COPPER( m_In2 );
    case In3_Cu:       RETURN_COPPER( m_In3 );
    case In4_Cu:       RETURN_COPPER( m_In4 );
    case In5_Cu:       RETURN_COPPER( m_In5 );
    case In6_Cu:       RETURN_COPPER( m_In6 );
    case In7_Cu:       RETURN_COPPER( m_In7 );
    case In8_Cu:       RETURN_COPPER( m_In8 );
    case In9_Cu:       RETURN_COPPER( m_In9 );
    case In10_Cu:      RETURN_COPPER( m_In10 );
    case In11_Cu:      RETURN_COPPER( m_In11 );
    case In12_Cu:      RETURN_COPPER( m_In12 );
    case In13_Cu:      RETURN_COPPER( m_In13 );
    case In14_Cu:      RETURN_COPPER( m_In14 );
    case In15_Cu:      RETURN_COPPER( m_In15 );

    case In16_Cu:      RETURN_COPPER( m_In16 );
    case In17_Cu:      RETURN_COPPER( m_In17 );
    case In18_Cu:      RETURN_COPPER( m_In18 );
    case In19_Cu:      RETURN_COPPER( m_In19 );
    case In20_Cu:      RETURN_COPPER( m_In20 );
    case In21_Cu:      RETURN_COPPER( m_In21 );
    case In22_Cu:      RETURN_COPPER( m_In22 );
    case In23_Cu:      RETURN_COPPER( m_In23 );
    case In24_Cu:      RETURN_COPPER( m_In24 );
    case In25_Cu:      RETURN_COPPER( m_In25 );
    case In26_Cu:      RETURN_COPPER( m_In26 );
    case In27_Cu:      RETURN_COPPER( m_In27 );
    case In28_Cu:      RETURN_COPPER( m_In28 );
    case In29_Cu:      RETURN_COPPER( m_In29 );
    case In30_Cu:      RETURN_COPPER( m_In30 );

    case B_Cu:         RETURN_COPPER( m_Back );
    case B_Mask:       RETURN_AUX( m_MaskBack );
    case B_SilkS:      RETURN_AUX( m_SilkSBack );
    case B_Paste:      RETURN_AUX( m_SoldPBack );
    case B_Adhes:      RETURN_AUX( m_AdhesBack );
    case B_Fab:        RETURN_AUX( m_FabBack );
    case B_CrtYd:      RETURN_MANDATORY( m_CrtYdBack );

    case Edge_Cuts:    RETURN_MANDATORY( m_PCBEdges );
    case Margin:       RETURN_MANDATORY( m_Margin );
    case Eco2_User:    RETURN_AUX( m_Eco2 );
    case Eco1_User:    RETURN_AUX( m_Eco1 );
    case Cmts_User:    RETURN_AUX( m_Comments );
    case Dwgs_User:    RETURN_AUX( m_Drawings );

    case User_1:       RETURN_AUX( m_User1 );
    case User_2:       RETURN_AUX( m_User2 );
    case User_3:       RETURN_AUX( m_User3 );
    case User_4:       RETURN_AUX( m_User4 );
    case User_5:       RETURN_AUX( m_User5 );
    case User_6:       RETURN_AUX( m_User6 );
    case User_7:       RETURN_AUX( m_User7 );
    case User_8:       RETURN_AUX( m_User8 );
    case User_9:       RETURN_AUX( m_User9 );

    default:
        wxASSERT_MSG( 0, wxT( "bad layer id" ) );
        return PANEL_SETUP_LAYERS_CTLs( nullptr,  nullptr, nullptr );
    }
}


wxControl* PANEL_SETUP_LAYERS::getName( int aLayer )
{
    return getCTLs( aLayer ).name;
}


wxCheckBox* PANEL_SETUP_LAYERS::getCheckBox( int aLayer )
{
    return getCTLs( aLayer ).checkbox;
}


wxChoice* PANEL_SETUP_LAYERS::getChoice( int aLayer )
{
    return (wxChoice*) getCTLs( aLayer ).choice;
}


bool PANEL_SETUP_LAYERS::TransferDataToWindow()
{
    m_enabledLayers = m_pcb->GetEnabledLayers();

    // Rescue may be enabled, but should not be shown in this dialog
    m_enabledLayers.reset( Rescue );

    setCopperLayerCheckBoxes( m_pcb->GetCopperLayerCount() );

    showBoardLayerNames();
    showSelectedLayerCheckBoxes( m_enabledLayers );

    showLayerTypes();
    setMandatoryLayerCheckBoxes();
    setUserDefinedLayerCheckBoxes();

    m_initialized = true;

    return true;
}


void PANEL_SETUP_LAYERS::SyncCopperLayers( int aNumCopperLayers )
{
    setCopperLayerCheckBoxes( aNumCopperLayers );
}


void PANEL_SETUP_LAYERS::setMandatoryLayerCheckBoxes()
{
    for( int layer : { F_CrtYd, B_CrtYd, Edge_Cuts, Margin } )
        setLayerCheckBox( layer, true );
}


void PANEL_SETUP_LAYERS::setUserDefinedLayerCheckBoxes()
{
    for( LSEQ seq = LSET::UserDefinedLayers().Seq();  seq;  ++seq )
    {
        PCB_LAYER_ID layer = *seq;
        bool         state = m_pcb->IsLayerEnabled( layer );

#ifdef HIDE_INACTIVE_LAYERS
        // This code hides inactive copper layers, or redisplays hidden layers which are now needed.
        PANEL_SETUP_LAYERS_CTLs ctl = getCTLs( layer );

        // All user-defined layers should have a checkbox
        wxASSERT( ctl.checkbox );

        ctl.name->Show( state );
        ctl.checkbox->Show( state );
        ctl.choice->Show( state );
#endif

        setLayerCheckBox( layer, state );
    }

#ifdef HIDE_INACTIVE_LAYERS
    // Send an size event to force sizers to be updated,
    // because the number of copper layers can have changed.
    wxSizeEvent evt_size( m_LayersListPanel->GetSize() );
    m_LayersListPanel->GetEventHandler()->ProcessEvent( evt_size );
#endif
}


void PANEL_SETUP_LAYERS::showBoardLayerNames()
{
    // Set all the board's layer names into the dialog by calling BOARD::GetLayerName(),
    // which will call BOARD::GetStandardLayerName() for non-coppers.

    for( LSEQ seq = dlg_layers();  seq;  ++seq )
    {
        PCB_LAYER_ID layer = *seq;
        wxControl*   ctl = getName( layer );

        if( ctl )
        {
            wxString lname = m_pcb->GetLayerName( layer );

            if( auto textCtl = dynamic_cast<wxTextCtrl*>( ctl ) )
                textCtl->ChangeValue( lname );     // wxTextCtrl
            else
                ctl->SetLabel( lname );         // wxStaticText
        }
    }
}


void PANEL_SETUP_LAYERS::showSelectedLayerCheckBoxes( LSET enabledLayers )
{
    // The check boxes
    for( LSEQ seq = dlg_layers();  seq;  ++seq )
    {
        PCB_LAYER_ID layer = *seq;
        setLayerCheckBox( layer, enabledLayers[layer] );
    }
}


void PANEL_SETUP_LAYERS::showLayerTypes()
{
    for( LSEQ seq = LSET::AllCuMask().Seq();  seq;  ++seq )
    {
        PCB_LAYER_ID cu_layer = *seq;

        wxChoice* ctl = getChoice( cu_layer );
        ctl->SetSelection( m_pcb->GetLayerType( cu_layer ) );
    }
}


LSET PANEL_SETUP_LAYERS::GetUILayerMask()
{
    LSET layerMaskResult;

    for( LSEQ seq = dlg_layers();  seq;  ++seq )
    {
        PCB_LAYER_ID layer = *seq;
        wxCheckBox*  ctl = getCheckBox( layer );

        if( !ctl || ctl->GetValue() )
            layerMaskResult.set( layer );
    }

    return layerMaskResult;
}


void PANEL_SETUP_LAYERS::setLayerCheckBox( int aLayer, bool isChecked )
{
    PANEL_SETUP_LAYERS_CTLs ctl = getCTLs( aLayer );

    if( !ctl.checkbox )
        return;

    ctl.checkbox->SetValue( isChecked );
}


void PANEL_SETUP_LAYERS::setCopperLayerCheckBoxes( int copperCount )
{
    if( copperCount > 0 )
    {
        setLayerCheckBox( F_Cu, true );
        --copperCount;
    }

    if( copperCount > 0 )
    {
        setLayerCheckBox( B_Cu, true );
        --copperCount;
    }

    for( LSEQ seq = LSET::InternalCuMask().Seq();  seq;  ++seq, --copperCount )
    {
        PCB_LAYER_ID layer = *seq;
        bool     state = copperCount > 0;

#ifdef HIDE_INACTIVE_LAYERS
        // This code hides inactive copper layers, or redisplays hidden layers which are now needed.
        PANEL_SETUP_LAYERS_CTLs ctl = getCTLs( layer );

        // Inner layers should have a checkbox
        wxASSERT( ctl.checkbox );

        ctl.name->Show( state );
        ctl.checkbox->Show( state );
        ctl.choice->Show( state );
#endif

        setLayerCheckBox( layer, state );
    }

#ifdef HIDE_INACTIVE_LAYERS
    // Send an size event to force sizers to be updated,
    // because the number of copper layers can have changed.
    wxSizeEvent evt_size( m_LayersListPanel->GetSize() );
    m_LayersListPanel->GetEventHandler()->ProcessEvent( evt_size );
#endif
}


void PANEL_SETUP_LAYERS::OnCheckBox( wxCommandEvent& event )
{
    m_enabledLayers = GetUILayerMask();
}


void PANEL_SETUP_LAYERS::DenyChangeCheckBox( wxCommandEvent& event )
{
    wxObject* source = event.GetEventObject();

    for( LSEQ seq = LSET::AllCuMask().Seq(); seq; ++seq )
    {
        wxCheckBox* copper = getCheckBox( *seq );

        if( source == copper )
        {
            DisplayError( wxGetTopLevelParent( this ),
                          _( "Use the Physical Stackup page to change the number of "
                             "copper layers." ) );

            copper->SetValue( true );
            return;
        }
    }
}


bool PANEL_SETUP_LAYERS::TransferDataFromWindow()
{
    if( !testLayerNames() )
        return false;

    // Make sure we have the latest copper layer count
    if( m_physicalStackup )
        SyncCopperLayers( m_physicalStackup->GetCopperLayerCount() );

    wxString msg;
    bool     modified = false;

    // Check for removed layers with items which will get deleted from the board.
    LSEQ removedLayers = getRemovedLayersWithItems();

    // Check for non-copper layers in use in footprints, and therefore not removable.
    LSEQ notremovableLayers = getNonRemovableLayers();

    if( !notremovableLayers.empty() )
    {
        for( unsigned int ii = 0; ii < notremovableLayers.size(); ii++ )
            msg << m_pcb->GetLayerName( notremovableLayers[ii] ) << wxT( "\n" );

        if( !IsOK( wxGetTopLevelParent( this ),
                   wxString::Format( _( "Footprints have some items on removed layers:\n"
                                        "%s\n"
                                        "These items will be no longer accessible\n"
                                        "Do you wish to continue?" ), msg ) ) )
        {
            return false;
        }
    }

    if( !removedLayers.empty()
      && !IsOK( wxGetTopLevelParent( this ),
                _( "Items have been found on removed layers. This operation will "
                   "delete all items from removed layers and cannot be undone.\n"
                   "Do you wish to continue?" ) ) )
    {
        return false;
    }

    // Delete all objects on layers that have been removed.  Leaving them in copper layers
    // can (will?) result in DRC errors and it pollutes the board file with cruft.
    bool hasRemovedBoardItems = false;

    if( !removedLayers.empty() )
    {
        m_frame->GetToolManager()->RunAction( PCB_ACTIONS::selectionClear );

        PCB_LAYER_COLLECTOR collector;

        for( PCB_LAYER_ID layer_id : removedLayers )
        {
            collector.SetLayerId( layer_id );
            collector.Collect( m_pcb, GENERAL_COLLECTOR::BoardLevelItems );

            // Bye-bye items on removed layer.
            for( int i = 0; i < collector.GetCount(); i++ )
            {
                BOARD_ITEM* item = collector[i];

                // Do not remove/change an item owned by a footprint
                if( item->GetParentFootprint() )
                    continue;

                // Do not remove footprints
                if( item->Type() == PCB_FOOTPRINT_T )
                    continue;

                // Vias are also specific
                // Note: vias are specific. They are only on copper layers,  and
                // do not use a layer set, only store the copper top and the copper bottom.
                // So reinit the layer set does not work with vias
                if( item->Type() == PCB_VIA_T )
                {
                    PCB_VIA* via = static_cast<PCB_VIA*>( item );

                    if( via->GetViaType() == VIATYPE::THROUGH )
                        continue;
                    else
                    {
                        PCB_LAYER_ID top_layer;
                        PCB_LAYER_ID bottom_layer;
                        via->LayerPair( &top_layer, &bottom_layer );

                        if( top_layer != layer_id && bottom_layer != layer_id )
                            continue;
                    }
                    // blind/buried vias with a top or bottom layer on a removed layer
                    // are removed. Perhaps one could just modify the top/bottom layer,
                    // but I am not sure this is better.
                    m_pcb->Remove( item );
                    delete item;
                }
                else
                {
                    LSET        layers = item->GetLayerSet();

                    layers.reset( layer_id );
                    hasRemovedBoardItems = true;
                    modified = true;

                    if( layers.any() )
                    {
                        item->SetLayerSet( layers );
                    }
                    else
                    {
                        m_pcb->Remove( item );
                        delete item;
                    }
                }
            }
        }

        // Undo state may have copies of pointers deleted above
        m_frame->ClearUndoRedoList();
    }

    m_enabledLayers = GetUILayerMask();

    LSET previousEnabled = m_pcb->GetEnabledLayers();

    if( m_enabledLayers != previousEnabled )
    {
        m_pcb->SetEnabledLayers( m_enabledLayers );

        LSET changedLayers = m_enabledLayers ^ previousEnabled;

        /*
         * Ensure enabled layers are also visible.  This is mainly to avoid mistakes if some
         * enabled layers are not visible when exiting this dialog.
         */
        m_pcb->SetVisibleLayers( m_pcb->GetVisibleLayers() | changedLayers );

        /*
         * Ensure items with through holes have all inner copper layers.  (For historical reasons
         * this is NOT trimmed to the currently-enabled inner layers.)
         */
        for( FOOTPRINT* fp : m_pcb->Footprints() )
        {
            for( PAD* pad : fp->Pads() )
            {
                if( pad->HasHole() && pad->IsOnCopperLayer() )
                    pad->SetLayerSet( pad->GetLayerSet() | LSET::InternalCuMask() );
            }
        }

        // Tracks do not change their layer
        // Vias layers are defined by the starting layer and the ending layer, so
        // they are not modified by adding a layer.
        // So do nothing for tracks/vias

        modified = true;
    }

    for( LSEQ seq = LSET::AllLayersMask().Seq();  seq;  ++seq )
    {
        PCB_LAYER_ID  layer = *seq;

        if( m_enabledLayers[layer] )
        {
            const wxString& newLayerName = GetLayerName( layer );

            if( m_pcb->GetLayerName( layer ) != newLayerName )
            {
                m_pcb->SetLayerName( layer, newLayerName );
                modified = true;
            }

            // Only copper layers have a definable type.
            if( LSET::AllCuMask().Contains( layer ) )
            {
                LAYER_T t = (LAYER_T) getLayerTypeIndex( layer );

                if( m_pcb->GetLayerType( layer ) != t )
                {
                    m_pcb->SetLayerType( layer, t );
                    modified = true;
                }
            }
        }
    }

    for( LSEQ seq = LSET::UserDefinedLayers().Seq();  seq;  ++seq )
    {
        PCB_LAYER_ID    layer = *seq;
        const wxString& newLayerName = GetLayerName( layer );

        if( m_enabledLayers[ layer ] && m_pcb->GetLayerName( layer ) != newLayerName )
        {
            m_pcb->SetLayerName( layer, newLayerName );
            modified = true;
        }
    }

    // If some board items are deleted: Rebuild the connectivity, because it is likely some
    // tracks and vias were removed
    if( hasRemovedBoardItems )
        m_pcb->BuildConnectivity();

    if( modified )
        m_frame->OnModify();

    return true;
}


int PANEL_SETUP_LAYERS::getLayerTypeIndex( int aLayer )
{
    wxChoice* ctl = getChoice( aLayer );
    int ret = ctl->GetCurrentSelection(); // Indices must have same sequence as LAYER_T
    return ret;
}


wxString PANEL_SETUP_LAYERS::GetLayerName( int aLayer )
{
    wxControl* control = getName( aLayer );

    if( auto textCtl = dynamic_cast<wxTextCtrl*>( control ) )
        return textCtl->GetValue().Trim();
    else
        return control->GetLabel();
}


static bool hasOneOf( const wxString& str, const wxString& chars )
{
    for( unsigned i=0; i<chars.Len();  ++i )
    {
        if( str.Find( chars[i] ) != wxNOT_FOUND )
            return true;
    }

    return false;
}


bool PANEL_SETUP_LAYERS::testLayerNames()
{
    std::vector<wxString>    names;
    wxTextCtrl*  ctl;

    for( LSEQ seq = LSET::AllLayersMask().Seq();  seq;  ++seq )
    {
        PCB_LAYER_ID layer = *seq;

        // we _can_ rely on m_enabledLayers being current here:

        if( !m_enabledLayers[layer] )
            continue;

        wxString name = GetLayerName( layer );

        ctl = (wxTextCtrl*) getName( layer );

        // Check name for legality:
        // 1) Cannot be blank.
        // 2) Cannot have blanks.
        // 3) Cannot have " chars
        // 4) Cannot be 'signal'
        // 5) Must be unique.
        // 6) Cannot have illegal chars in filenames ( some filenames are built from layer names )
        //    like : % $ \ " / :
        wxString badchars = wxFileName::GetForbiddenChars( wxPATH_DOS );
        badchars.Append( '%' );

        if( !name )
        {
            PAGED_DIALOG::GetDialog( this )->SetError( _( "Layer must have a name." ), this, ctl );
            return false;
        }

        if( hasOneOf( name, badchars ) )
        {
            wxString msg = wxString::Format(_( "%s are forbidden in layer names." ), badchars );
            PAGED_DIALOG::GetDialog( this )->SetError( msg, this, ctl );
            return false;
        }

        if( name == wxT( "signal" ) )
        {
            PAGED_DIALOG::GetDialog( this )->SetError( _( "Layer name \"signal\" is reserved." ),
                                                       this, ctl );
            return false;
        }

        for( const wxString& existingName : names )
        {
            if( name == existingName )
            {
                wxString msg = wxString::Format(_( "Layer name '%s' already in use." ), name );
                PAGED_DIALOG::GetDialog( this )->SetError( msg, this, ctl );
                return false;
            }
        }

        names.push_back( name );
    }

    return true;
}


LSEQ PANEL_SETUP_LAYERS::getRemovedLayersWithItems()
{
    LSEQ removedLayers;
    LSET newLayers = GetUILayerMask();
    LSET curLayers = m_pcb->GetEnabledLayers();

    if( newLayers == curLayers ) // Return an empty list if no change
        return removedLayers;

    PCB_LAYER_COLLECTOR collector;
    LSEQ newLayerSeq = newLayers.Seq();

    for( PCB_LAYER_ID layer_id : curLayers.Seq() )
    {
        if( !alg::contains( newLayerSeq, layer_id ) )
        {
            collector.SetLayerId( layer_id );
            collector.Collect( m_pcb, GENERAL_COLLECTOR::BoardLevelItems );

            if( collector.GetCount() != 0 )
            {
                // Skip items owned by footprints and footprints when building
                // the actual list of removed layers: these items are not removed
                for( int i = 0; i < collector.GetCount(); i++ )
                {
                    BOARD_ITEM* item = collector[i];

                    if( item->Type() == PCB_FOOTPRINT_T || item->GetParentFootprint() )
                        continue;

                    // Vias are on multiple adjacent layers, but only the top and
                    // the bottom layers are stored. So there are issues only if one
                    // is on a removed layer
                    if( item->Type() == PCB_VIA_T )
                    {
                        PCB_VIA* via = static_cast<PCB_VIA*>( item );

                        if( via->GetViaType() == VIATYPE::THROUGH )
                            continue;
                        else
                        {
                            PCB_LAYER_ID top_layer;
                            PCB_LAYER_ID bottom_layer;
                            via->LayerPair( &top_layer, &bottom_layer );

                            if( top_layer != layer_id && bottom_layer != layer_id )
                                continue;
                        }
                    }

                    removedLayers.push_back( layer_id );
                    break;
                }
            }
        }
    }

    return removedLayers;
}


LSEQ PANEL_SETUP_LAYERS::getNonRemovableLayers()
{
    // Build the list of non-copper layers in use in footprints.
    LSEQ inUseLayers;
    LSET newLayers = GetUILayerMask();
    LSET curLayers = m_pcb->GetEnabledLayers();

    if( newLayers == curLayers ) // Return an empty list if no change
        return inUseLayers;

    PCB_LAYER_COLLECTOR collector;
    LSEQ newLayerSeq = newLayers.Seq();

    for( PCB_LAYER_ID layer_id : curLayers.Seq() )
    {
        if( IsCopperLayer( layer_id ) ) // Copper layers are not taken into account here
            continue;

        if( !alg::contains( newLayerSeq, layer_id ) )
        {
            collector.SetLayerId( layer_id );
            collector.Collect( m_pcb, GENERAL_COLLECTOR::FootprintItems );

            if( collector.GetCount() != 0 )
                inUseLayers.push_back( layer_id );
        }
    }

    return inUseLayers;
}


void PANEL_SETUP_LAYERS::ImportSettingsFrom( BOARD* aBoard )
{
    BOARD* savedBoard = m_pcb;

    m_pcb = aBoard;
    TransferDataToWindow();

    m_pcb = savedBoard;
}


bool PANEL_SETUP_LAYERS::CheckCopperLayerCount( BOARD* aWorkingBoard, BOARD* aImportedBoard )
{
    /*
     * This function warns users if they are going to delete inner copper layers because
     * they're importing settings from a board with less copper layers than the board
     * already loaded. We want to return "true" as default on the assumption no layer will
     * actually be deleted.
     */
    bool okToDeleteCopperLayers = true;

    // Get the number of copper layers in the loaded board and the "import settings" board
    int currNumLayers = aWorkingBoard->GetCopperLayerCount();
    int newNumLayers  = aImportedBoard->GetCopperLayerCount();

    if( newNumLayers < currNumLayers )
    {
        wxString msg = wxString::Format( _( "Imported settings have fewer copper layers than "
                                            "the current board (%i instead of %i).\n\n"
                                            "Continue and delete the extra inner copper layers "
                                            "from the current board?" ),
                                         newNumLayers,
                                         currNumLayers );

        wxWindow* topLevelParent = wxGetTopLevelParent( this );

        wxMessageDialog dlg( topLevelParent, msg, _( "Inner Layers To Be Deleted" ),
                             wxICON_WARNING | wxSTAY_ON_TOP | wxYES | wxNO | wxNO_DEFAULT );

        if( wxID_NO == dlg.ShowModal() )
            okToDeleteCopperLayers = false;
    }

    return okToDeleteCopperLayers;
}


void PANEL_SETUP_LAYERS::addUserDefinedLayer( wxCommandEvent& aEvent )
{
    wxArrayString headers;
    headers.Add( _( "Layers" ) );

    // Build the available user-defined layers list:
    std::vector<wxArrayString> list;

    for( LSEQ seq = LSET::UserDefinedLayers().Seq();  seq;  ++seq )
    {
        wxCheckBox* checkBox = getCheckBox( *seq );

        if( checkBox && checkBox->IsShown() )
            continue;

        wxArrayString available_user_layer;
        available_user_layer.Add( LayerName( *seq ) );

        list.emplace_back( available_user_layer );
    }

    if( list.empty() )
    {
        DisplayErrorMessage( PAGED_DIALOG::GetDialog( this ),
                             _( "All user-defined layers have already been added." ) );
        return;
    }

    EDA_LIST_DIALOG dlg( PAGED_DIALOG::GetDialog( this ), _( "Add User-defined Layer" ),
                         headers, list );
    dlg.SetListLabel( _( "Select layer to add:" ) );
    dlg.HideFilter();

    if( dlg.ShowModal() == wxID_CANCEL || dlg.GetTextSelection().IsEmpty() )
        return;

    LSEQ seq;

    for( seq = LSET::UserDefinedLayers().Seq();  seq;  ++seq )
    {
        if( LayerName( *seq ) == dlg.GetTextSelection() )
            break;
    }

    wxCHECK( *seq >= User_1 && *seq <= User_9, /* void */ );

    LSET newLayer( *seq );

    m_enabledLayers |= newLayer;

    PANEL_SETUP_LAYERS_CTLs ctl = getCTLs( *seq );

    // All user-defined layers should have a checkbox
    wxASSERT( ctl.checkbox );

    wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ctl.name );

    wxCHECK( textCtrl, /* void */ );
    textCtrl->ChangeValue( LSET::Name( *seq ) );
    ctl.name->Show( true );
    ctl.checkbox->Show( true );
    ctl.choice->Show( true );

    wxSizeEvent evt_size( m_LayersListPanel->GetSize() );
    m_LayersListPanel->GetEventHandler()->ProcessEvent( evt_size );

    setLayerCheckBox( *seq, true );
}


