/*
 * 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-2018 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 <fctsys.h>
#include <macros.h>

#include <confirm.h>
#include <pcbnew.h>
#include <pcb_edit_frame.h>
#include <view/view.h>

#include <invoke_pcb_dialog.h>

#include <class_board.h>
#include <collectors.h>

#include <dialog_layers_setup_base.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


/**
 * Holds the 3 UI control pointers for a single board layer.
 */
struct CTLs
{
    CTLs( wxControl* aName, wxCheckBox* aCheckBox, wxControl* aChoiceOrDesc,
          wxPanel* aPanel = NULL )
    {
        name     = aName;
        checkbox = aCheckBox;
        choice   = aChoiceOrDesc;
        panel    = aPanel;
    }

    wxControl*      name;
    wxCheckBox*     checkbox;
    wxControl*      choice;
    wxPanel *       panel;
};


static LSEQ dlg_layers()
{
    // layers that are put out into the dialog UI, coordinate with wxformbuilder and
    // getCTLs( LAYER_NUM 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,
    };

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


class DIALOG_LAYERS_SETUP : public DIALOG_LAYERS_SETUP_BASE
{
public:
    DIALOG_LAYERS_SETUP( PCB_EDIT_FRAME* aCaller, BOARD* aBoard );

private:
    int             m_copperLayerCount;
    LSET            m_enabledLayers;

    BOARD*          m_pcb;

    wxStaticText*   m_nameStaticText;
    wxStaticText*   m_enabledStaticText;
    wxStaticText*   m_typeStaticText;

    void setLayerCheckBox( LAYER_NUM layer, bool isChecked );
    void setCopperLayerCheckBoxes( int copperCount );
    // Force mandatory non copper layers enabled
    void setStateMandatoryLayerCheckBoxes();

    void showCopperChoice( int copperCount );
    void showBoardLayerNames();
    void showSelectedLayerCheckBoxes( LSET enableLayerMask );
    void showLayerTypes();
    void showPresets( LSET enabledLayerMask );

    /** Return the selected layer mask within the UI checkboxes */
    LSET getUILayerMask();
    wxString getLayerName( LAYER_NUM layer );
    int getLayerTypeIndex( LAYER_NUM layer );

    void OnInitDialog( wxInitDialogEvent& aEvent ) override;
    void OnCheckBox( wxCommandEvent& event ) override;
    void DenyChangeCheckBox( wxCommandEvent& event ) override;
    void OnPresetsChoice( wxCommandEvent& event ) override;
    void OnCopperLayersChoice( wxCommandEvent& event ) override;
    bool TransferDataToWindow() override;
    bool TransferDataFromWindow() override;

    bool testLayerNames();

    /**
     * Return a list of layers removed from the board that contain items.
     */
    LSEQ getRemovedLayersWithItems();

    /**
     * Return a list of layers in use in footprints, and therefore not removable.
     */
    LSEQ getNonRemovableLayers();

    /**
     * Map \a aLayerNumber to the wx IDs for that layer which are
     * the layer name control ID, checkbox control ID, and choice control ID
     */
    CTLs getCTLs( LAYER_NUM aLayerNumber );

    wxControl* getName( LAYER_NUM aLayer )
    {
        return getCTLs( aLayer ).name;
    }

    wxCheckBox* getCheckBox( LAYER_NUM aLayer )
    {
        return getCTLs( aLayer ).checkbox;
    }

    wxChoice* getChoice( LAYER_NUM aLayer )
    {
        return (wxChoice*) getCTLs( aLayer ).choice;
    }

    void moveTitles()
    {
        wxArrayInt widths = m_LayerListFlexGridSizer->GetColWidths();

        int offset = 0;
        wxSize txtz;

        wxSize panel_sz = m_TitlePanel->GetSize();
        int voffset = panel_sz.y/2 - VertPixelsFromDU( 4 );

        txtz = m_nameStaticText->GetSize();
        m_nameStaticText->Move( offset + (widths[0] - txtz.x)/2, voffset );
        offset += widths[0];

        txtz = m_enabledStaticText->GetSize();
        m_enabledStaticText->Move( offset + (widths[1] - txtz.x)/2, voffset );
        offset += widths[1];

        txtz = m_typeStaticText->GetSize();
        m_typeStaticText->Move( offset + (widths[2] - txtz.x)/2, voffset );
    }

    void OnSize( wxSizeEvent& event ) override;
};


// Layer bit masks for each defined "Preset Layer Grouping"
static const LSET presets[] =
{
    LSET(),     // shift the array index up by one, matches with "Custom".

    // "Two layers, parts on Front only"
    LSET( 2, F_Cu, B_Cu ) | LSET::FrontTechMask() | LSET::UserMask(),

    // "Two layers, parts on Back only",
    LSET( 2, F_Cu, B_Cu ) | LSET::BackTechMask() | LSET::UserMask(),

    // "Two layers, parts on Front and Back",
    LSET( 2, F_Cu, B_Cu ) | LSET::FrontTechMask() | LSET::BackTechMask() | LSET::UserMask(),

    // "Four layers, parts on Front only"
    LSET( 4, F_Cu, B_Cu, In1_Cu, In2_Cu ) | LSET::FrontTechMask() | LSET::UserMask(),

    // "Four layers, parts on Front and Back"
    LSET( 4, F_Cu, B_Cu, In1_Cu, In2_Cu ) | LSET::FrontTechMask() | LSET::BackTechMask() |
        LSET::UserMask(),

    //  "All layers on",
    LSET().set(),
};


CTLs DIALOG_LAYERS_SETUP::getCTLs( LAYER_NUM aLayerNumber )
{
#define RETCOP(x)    return CTLs( x##Name, x##CheckBox, x##Choice, x##Panel );
#define RETAUX(x)    return CTLs( x##Name, x##CheckBox, x##StaticText, x##Panel );

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

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

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

    case B_Cu:                  RETCOP( m_Back );
    case B_Mask:                RETAUX( m_MaskBack );
    case B_SilkS:               RETAUX( m_SilkSBack );
    case B_Paste:               RETAUX( m_SoldPBack );
    case B_Adhes:               RETAUX( m_AdhesBack );
    case B_Fab:                 RETAUX( m_FabBack );
    case B_CrtYd:               RETAUX( m_CrtYdBack );

    case Edge_Cuts:             RETAUX( m_PCBEdges );
    case Margin:                RETAUX( m_Margin );
    case Eco2_User:             RETAUX( m_Eco2 );
    case Eco1_User:             RETAUX( m_Eco1 );
    case Cmts_User:             RETAUX( m_Comments );
    case Dwgs_User:             RETAUX( m_Drawings );
    default:
        wxASSERT_MSG( 0, wxT( "bad layer id" ) );
        return CTLs( 0, 0, 0 );
    }

#undef RETCOP
#undef RETAUX
}


DIALOG_LAYERS_SETUP::DIALOG_LAYERS_SETUP( PCB_EDIT_FRAME* aParent, BOARD* aBoard ) :
    DIALOG_LAYERS_SETUP_BASE( aParent )
{
    m_pcb = aBoard;

    m_copperLayerCount = m_pcb->GetCopperLayerCount();
    m_enabledLayers = m_pcb->GetEnabledLayers();

    SetAutoLayout( true );

    // these 3 controls are handled outside wxformbuilder so that we can add
    // them without a sizer.  Then we position them manually based on the column
    // widths from m_LayerListFlexGridSizer->GetColWidths()
    m_nameStaticText = new wxStaticText( m_TitlePanel, wxID_ANY, _( "Name" ),
                                         wxDefaultPosition, wxDefaultSize, 0 );

    m_enabledStaticText = new wxStaticText( m_TitlePanel, wxID_ANY, _( "Enabled" ),
                                            wxDefaultPosition, wxDefaultSize, 0 );

    m_typeStaticText = new wxStaticText( m_TitlePanel, wxID_ANY, _( "Type" ),
                                         wxDefaultPosition, wxDefaultSize, 0 );
}


void DIALOG_LAYERS_SETUP::OnInitDialog( wxInitDialogEvent& aEvent )
{
    wxWindowBase::OnInitDialog( aEvent );

    m_TitlePanel->SetMinSize( wxSize( -1, VertPixelsFromDU( 10 ) ) );

    m_LayersListPanel->ShowScrollbars( wxSHOW_SB_ALWAYS, wxSHOW_SB_ALWAYS );

    Layout();
    SetSizeInDU( 240, 240 );
    Center();

    m_sdbSizerOK->SetFocus();
    m_sdbSizerOK->SetDefault();

    // OnSize() will fix the title spacing.
    QueueEvent( new wxSizeEvent( GetSize() ) );
}


bool DIALOG_LAYERS_SETUP::TransferDataToWindow()
{
    if( !wxDialog::TransferDataToWindow() )
        return false;

    showCopperChoice( m_copperLayerCount );
    setCopperLayerCheckBoxes( m_copperLayerCount );
    m_staticTextBrdThicknessUnit->SetLabel( GetAbbreviatedUnitsLabel( g_UserUnit ) );
    PutValueInLocalUnits( *m_textCtrlBrdThickness,
                          m_pcb->GetDesignSettings().GetBoardThickness() );

    showBoardLayerNames();
    showSelectedLayerCheckBoxes( m_enabledLayers );
    showPresets( m_enabledLayers );
    showLayerTypes();
    setStateMandatoryLayerCheckBoxes();

    // All widgets are now initialized. Fix the min sizes:
    GetSizer()->SetSizeHints( this );

    return true;
}


void DIALOG_LAYERS_SETUP::setStateMandatoryLayerCheckBoxes()
{
    int layerList[4] =
    {
        F_CrtYd, B_CrtYd, Edge_Cuts, Margin
    };

    for( int ii = 0; ii < 4; ii++ )
    {
        setLayerCheckBox( layerList[ii], true );
        getCheckBox( layerList[ii] )->Enable( false );  // do not allow changes
    }
}


void DIALOG_LAYERS_SETUP::OnSize( wxSizeEvent& event )
{
    moveTitles();
    event.Skip();
    Refresh();
}


void DIALOG_LAYERS_SETUP::showCopperChoice( int copperCount )
{
    if( copperCount > MAX_CU_LAYERS )
        copperCount = MAX_CU_LAYERS;

    if( copperCount < 2 )
        copperCount = 2;

    for( int lyrCnt = 2; lyrCnt <= MAX_CU_LAYERS; lyrCnt += 2 )
    {
        // note this will change a one layer board to 2:
        if( copperCount <= lyrCnt )
        {
            int idx = lyrCnt/2 - 1;
            m_CopperLayersChoice->SetSelection(idx);
            break;
        }
    }
}


void DIALOG_LAYERS_SETUP::showBoardLayerNames()
{
    // Establish all the board's layer names into the dialog presentation, by
    // obtaining them from BOARD::GetLayerName() which calls
    // BOARD::GetStandardLayerName() for non-coppers.

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

        wxControl*  ctl = getName( layer );

        wxASSERT( ctl );

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

            if( ctl->IsKindOf( CLASSINFO( wxTextCtrl ) ) )
                ((wxTextCtrl*)ctl)->SetValue( lname );     // wxTextCtrl
            else
                ctl->SetLabel( lname );     // wxStaticText
        }
    }
}


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


void DIALOG_LAYERS_SETUP::showPresets( LSET enabledLayers )
{
    int presetsNdx = 0;     // the "Custom" setting, matches nothing

    for( unsigned i=1; i<DIM( presets );  ++i )
    {
        if( enabledLayers == presets[i] )
        {
            presetsNdx = i;
            break;
        }
    }

    m_PresetsChoice->SetSelection( presetsNdx );
}


void DIALOG_LAYERS_SETUP::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 DIALOG_LAYERS_SETUP::getUILayerMask()
{
    LSET layerMaskResult;

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

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

    return layerMaskResult;
}


void DIALOG_LAYERS_SETUP::setLayerCheckBox( LAYER_NUM aLayer, bool isChecked )
{
    wxCheckBox*  ctl = getCheckBox( aLayer );
    ctl->SetValue(  isChecked );
}


void DIALOG_LAYERS_SETUP::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 non-active copper layers, or redisplays hidden
        // layers which are now needed.
        CTLs ctl = getCTLs( layer );

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

        if( ctl.panel )
            ctl.panel->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 DIALOG_LAYERS_SETUP::OnCheckBox( wxCommandEvent& event )
{
    m_enabledLayers = getUILayerMask();

    showPresets( m_enabledLayers );
}


void DIALOG_LAYERS_SETUP::DenyChangeCheckBox( wxCommandEvent& event )
{
    // user may not change copper layer checkboxes from anything other than
    // either presets choice or the copper layer choice controls.

    // I tried to simply disable the copper CheckBoxes but they look like crap,
    // so leave them enabled and reverse the user's attempt to toggle them.

    setCopperLayerCheckBoxes( m_copperLayerCount );
}


void DIALOG_LAYERS_SETUP::OnPresetsChoice( wxCommandEvent& event )
{
    unsigned presetNdx = m_PresetsChoice->GetCurrentSelection();

    if( presetNdx == 0 )        // the Custom setting controls nothing.
        return;

    if( presetNdx < DIM(presets) )
    {
        m_enabledLayers = presets[ presetNdx ];
        LSET copperSet = m_enabledLayers & LSET::AllCuMask();
        int copperCount = copperSet.count();

        m_copperLayerCount = copperCount;
        showCopperChoice( m_copperLayerCount );
        showSelectedLayerCheckBoxes( m_enabledLayers );
        setCopperLayerCheckBoxes( m_copperLayerCount );
    }

    // Ensure mandatory layers are activated
    setStateMandatoryLayerCheckBoxes();
}


void DIALOG_LAYERS_SETUP::OnCopperLayersChoice( wxCommandEvent& event )
{
    m_copperLayerCount = m_CopperLayersChoice->GetCurrentSelection() * 2 + 2;
    setCopperLayerCheckBoxes( m_copperLayerCount );
    m_enabledLayers = getUILayerMask();
    showPresets( m_enabledLayers );
}


bool DIALOG_LAYERS_SETUP::TransferDataFromWindow()
{
    if( !wxWindow::TransferDataFromWindow() || !testLayerNames() )
        return false;

    wxString msg;

    // Make sure the board thickness is sane.
    int thickness = ValueFromTextCtrl( *m_textCtrlBrdThickness );

    if( thickness < Millimeter2iu( 0.1 ) || thickness > Millimeter2iu( 10.0 ) )
    {
        msg.Printf( _( "Board thickness %s is out of range." ),
                    StringFromValue( g_UserUnit, thickness, true ) );
        DisplayError( this, msg );
        return 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] ) << "\n";

        if( !IsOK( 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( this, _( "Items have been found on removed layers. This operation will delete "
                        "all items from removed layers and cannot be undone. 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() )
    {
        PCB_LAYER_COLLECTOR collector;

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

            // Bye-bye items on on removed layer.
            if( collector.GetCount() != 0 )
            {
                hasRemovedBoardItems = true;

                for( int i = 0; i < collector.GetCount(); i++ )
                {
                    BOARD_ITEM* item = collector[i];
                    m_pcb->Remove( item );
                    delete item;
                }
            }
        }
    }

    wxString name;

    m_enabledLayers = getUILayerMask();
    m_pcb->SetEnabledLayers( m_enabledLayers );

    /* 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_enabledLayers );

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

        if( m_enabledLayers[layer] )
        {
            name = getLayerName( layer );
            m_pcb->SetLayerName( layer, name );
            LAYER_T t = (LAYER_T) getLayerTypeIndex( layer );
            m_pcb->SetLayerType( layer, t );
        }
    }

    m_pcb->GetDesignSettings().SetBoardThickness( thickness );

    // If some board items are deleted: rebuild the connectivity,
    // because it is likely some tracks and vias where removed
    if( hasRemovedBoardItems )
    {
        PCB_EDIT_FRAME* editFrame = static_cast<PCB_EDIT_FRAME*>( GetParent() );
        // Rebuild list of nets (full ratsnest rebuild)
        editFrame->Compile_Ratsnest( NULL, true );
        m_pcb->BuildConnectivity();
    }

    return true;
}


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


wxString DIALOG_LAYERS_SETUP::getLayerName( LAYER_NUM aLayer )
{
    wxString ret;
    wxASSERT( IsCopperLayer( aLayer ) );
    wxTextCtrl*  ctl = (wxTextCtrl*) getName( aLayer );
    ret = ctl->GetValue().Trim();

    return ret;
}


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 DIALOG_LAYERS_SETUP::testLayerNames()
{
    std::vector<wxString>    names;
    wxTextCtrl*  ctl;

    for( LSEQ seq = LSET::AllCuMask().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 )
        {
            DisplayError( this, _( "Layer name may not be empty." ) );
            ctl->SetFocus();    // on the bad name
            return false;
        }

        if( hasOneOf( name, badchars ) )
        {
            DisplayError( this, _( "Layer name has an illegal character, one of: '" ) +
                          badchars + wxT( "'" ) );
            ctl->SetFocus();    // on the bad name
            return false;
        }

        if( name == wxT( "signal" ) )
        {
            DisplayError( this, _( "Layer name 'signal' is reserved." ) );
            ctl->SetFocus();    // on the bad name
            return false;
        }

        for( std::vector<wxString>::iterator it = names.begin();  it != names.end();  ++it )
        {
            if( name == *it )
            {
                DisplayError( this, _( "Duplicate layer names are not permitted." ) );
                ctl->SetFocus();    // on the bad name
                return false;
            }
        }

        names.push_back( name );
    }

    return true;
}


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

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

    PCB_LAYER_COLLECTOR collector;
    LSEQ newLayerSeq = newLayers.Seq();
    std::vector< PCB_LAYER_ID >::iterator it;

    for( auto layer_id : curLayers.Seq() )
    {
        if( std::find( newLayerSeq.begin(), newLayerSeq.end(), layer_id ) == newLayerSeq.end() )
        {
            collector.SetLayerId( layer_id );
            collector.Collect( m_pcb, GENERAL_COLLECTOR::BoardLevelItems );

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

    return removedLayers;
}


LSEQ DIALOG_LAYERS_SETUP::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 a empty list if no change
        return inUseLayers;

    PCB_LAYER_COLLECTOR collector;
    LSEQ newLayerSeq = newLayers.Seq();
    std::vector< PCB_LAYER_ID >::iterator it;

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

        if( std::find( newLayerSeq.begin(), newLayerSeq.end(), layer_id ) == newLayerSeq.end() )
        {
            collector.SetLayerId( layer_id );
            collector.Collect( m_pcb, GENERAL_COLLECTOR::ModuleItems );

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

    return inUseLayers;
}


bool InvokeLayerSetup( PCB_EDIT_FRAME* aCaller, BOARD* aBoard )
{
    DIALOG_LAYERS_SETUP dlg( aCaller, aBoard );

    return dlg.ShowModal() == wxID_OK;
}
