/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2020 Brian Piccioni brian@documenteddesigns.com
 * Copyright (C) 2020-2023 KiCad Developers, see AUTHORS.txt for contributors.
 * @author Brian Piccioni <brian@documenteddesigns.com>
 *
 * 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 <algorithm>
#include <base_units.h>
#include <bitmaps.h>
#include <board_commit.h>
#include <confirm.h>
#include <ctype.h>
#include <dialog_board_reannotate.h>
#include <string_utils.h>  // StrNumCmp
#include <kiface_base.h>
#include <pcbnew_settings.h>
#include <refdes_utils.h>
#include <tool/grid_menu.h>
#include <widgets/wx_html_report_panel.h>
#include <wx/valtext.h>


bool SortYFirst;
bool DescendingFirst;
bool DescendingSecond;

//
// This converts the index into a sort code. Note that Back sort code will have left and
// right swapped.
//
int FrontDirectionsArray[] = {
    SORTYFIRST + ASCENDINGFIRST + ASCENDINGSECOND,   // "Top to bottom, left to right",  //  100
    SORTYFIRST + ASCENDINGFIRST + DESCENDINGSECOND,  // "Top to bottom, right to left",  //  101
    SORTYFIRST + DESCENDINGFIRST + ASCENDINGSECOND,  // "Back to Front, left to right",  //  110
    SORTYFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Back to Front, right to left",  //  111
    SORTXFIRST + ASCENDINGFIRST + ASCENDINGSECOND,   // "Left to right, Front to Back",  //  000
    SORTXFIRST + ASCENDINGFIRST + DESCENDINGSECOND,  // "Left to right, Back to Front",  //  001
    SORTXFIRST + DESCENDINGFIRST + ASCENDINGSECOND,  // "Right to left, Front to Back",  //  010
    SORTXFIRST + DESCENDINGFIRST + DESCENDINGSECOND  // "Right to left, Back to Front",  //  011
};


//
// Back Left/Right is opposite because it is a mirror image (coordinates are from the top)
//
int BackDirectionsArray[] = {
    SORTYFIRST + ASCENDINGFIRST + DESCENDINGSECOND,  // "Top to bottom, left to right",  //  101
    SORTYFIRST + ASCENDINGFIRST + ASCENDINGSECOND,   // "Top to bottom, right to left",  //  100
    SORTYFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Bottom to top, left to right",  //  111
    SORTYFIRST + DESCENDINGFIRST + ASCENDINGSECOND,  // "Bottom to top, right to left",  //  110
    SORTXFIRST + DESCENDINGFIRST + ASCENDINGSECOND,  // "Left to right, top to bottom",  //  010
    SORTXFIRST + DESCENDINGFIRST + DESCENDINGSECOND, // "Left to right, bottom to top",  //  011
    SORTXFIRST + ASCENDINGFIRST + ASCENDINGSECOND,   // "Right to left, top to bottom",  //  000
    SORTXFIRST + ASCENDINGFIRST + DESCENDINGSECOND   // "Right to left, bottom to top",  //  001
};

#define SetSortCodes( DirArray, Code )                                     \
    {                                                                      \
        SortYFirst       = ( ( DirArray[Code] & SORTYFIRST ) != 0 );       \
        DescendingFirst  = ( ( DirArray[Code] & DESCENDINGFIRST ) != 0 );  \
        DescendingSecond = ( ( DirArray[Code] & DESCENDINGSECOND ) != 0 ); \
    }


wxString AnnotateString[] = {
    _( "All" ),          // AnnotateAll
    _( "Only front" ),   // AnnotateFront
    _( "Only back" ),    // AnnotateBack
    _( "Only selected" ) // AnnotateSelected
};


wxString ActionMessage[] = {
    "",             // UpdateRefDes
    _( "Empty" ),   // EmptyRefDes
    _( "Invalid" ), // InvalidRefDes
    _( "Excluded" ) // Exclude
};


DIALOG_BOARD_REANNOTATE::DIALOG_BOARD_REANNOTATE( PCB_EDIT_FRAME* aParentFrame )
        : DIALOG_BOARD_REANNOTATE_BASE( aParentFrame ),
          m_footprints( aParentFrame->GetBoard()->Footprints() )
{
    m_frame  = aParentFrame;
    m_Config = Kiface().KifaceSettings();
    InitValues();

    m_FrontRefDesStart->SetValidator( wxTextValidator( wxFILTER_DIGITS ) );
    m_BackRefDesStart->SetValidator( wxTextValidator( wxFILTER_DIGITS ) );

    m_sdbSizerOK->SetLabel( _( "Reannotate PCB" ) );
    m_sdbSizerCancel->SetLabel( _( "Close" ) );
    m_sdbSizer->Layout();

    m_settings = aParentFrame->config();
    wxArrayString gridslist;
    GRID_MENU::BuildChoiceList( &gridslist, m_settings, aParentFrame );

    if( -1 == m_gridIndex ) // If no default loaded
        m_gridIndex = m_settings->m_Window.grid.last_size_idx;        // Get the current grid size

    m_sortGridx = m_frame->GetCanvas()->GetGAL()->GetGridSize().x;
    m_sortGridy = m_frame->GetCanvas()->GetGAL()->GetGridSize().y;

    m_GridChoice->Set( gridslist );
    m_GridChoice->SetSelection( m_gridIndex );

    for( wxRadioButton* button : m_sortButtons )
        button->SetValue( false );

    if( m_sortCode < 0 || m_sortCode >= (int) m_sortButtons.size() )
        m_sortCode = 0;

    m_sortButtons[m_sortCode]->SetValue( true );

    m_selection = m_frame->GetToolManager()->GetTool<PCB_SELECTION_TOOL>()->GetSelection();

    if( !m_selection.Empty() )
        m_annotationScope = AnnotateSelected;

    for( wxRadioButton* button : m_scopeRadioButtons )
        button->SetValue( false );

    if( m_annotationScope < 0 || m_annotationScope >= (int) m_scopeRadioButtons.size() )
        m_annotationScope = 0;

    m_scopeRadioButtons[m_annotationScope]->SetValue( true );

    reannotate_down_right_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_right_down ) );
    reannotate_right_down_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_left_down ) );
    reannotate_down_left_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_right_up ) );
    reannotate_left_down_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_left_up ) );
    reannotate_up_right_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_down_left ) );
    reannotate_right_up_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_up_left ) );
    reannotate_up_left_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_down_right ) );
    reannotate_left_up_bitmap->SetBitmap( KiBitmap( BITMAPS::reannotate_up_right ) );

    m_ExcludeList->SetToolTip( m_ExcludeListText->GetToolTipText() );
    m_GridChoice->SetToolTip( m_SortGridText->GetToolTipText() );

    m_MessageWindow->SetFileName( Prj().GetProjectPath() + wxT( "report.txt" ) );

    finishDialogSettings();
}


DIALOG_BOARD_REANNOTATE::~DIALOG_BOARD_REANNOTATE()
{
    GetParameters(); // Get the current menu settings
    PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings();
    cfg->m_Reannotate.sort_on_fp_location = m_locationChoice->GetSelection() == 0;
    cfg->m_Reannotate.remove_front_prefix     = m_RemoveFrontPrefix->GetValue();
    cfg->m_Reannotate.remove_back_prefix      = m_RemoveBackPrefix->GetValue();
    cfg->m_Reannotate.exclude_locked          = m_ExcludeLocked->GetValue();

    cfg->m_Reannotate.grid_index              = m_gridIndex;
    cfg->m_Reannotate.sort_code               = m_sortCode;
    cfg->m_Reannotate.annotation_choice       = m_annotationScope;
    cfg->m_Reannotate.report_severity         = m_severity;

    cfg->m_Reannotate.front_refdes_start      = m_FrontRefDesStart->GetValue();
    cfg->m_Reannotate.back_refdes_start       = m_BackRefDesStart->GetValue();
    cfg->m_Reannotate.front_prefix            = m_FrontPrefix->GetValue();
    cfg->m_Reannotate.back_prefix             = m_BackPrefix->GetValue();
    cfg->m_Reannotate.exclude_list            = m_ExcludeList->GetValue();
    cfg->m_Reannotate.report_file_name        = m_MessageWindow->GetFileName();
}


void DIALOG_BOARD_REANNOTATE::InitValues( void )
{
    PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings();
    m_locationChoice->SetSelection( cfg->m_Reannotate.sort_on_fp_location ? 0 : 1 );
    m_RemoveFrontPrefix->SetValue( cfg->m_Reannotate.remove_front_prefix );
    m_RemoveBackPrefix->SetValue( cfg->m_Reannotate.remove_back_prefix );
    m_ExcludeLocked->SetValue( cfg->m_Reannotate.exclude_locked );

    m_gridIndex         = cfg->m_Reannotate.grid_index ;
    m_sortCode          = cfg->m_Reannotate.sort_code ;
    m_annotationScope   = cfg->m_Reannotate.annotation_choice ;
    m_severity          = cfg->m_Reannotate.report_severity;

    m_FrontRefDesStart->SetValue( cfg->m_Reannotate.front_refdes_start );
    m_BackRefDesStart->SetValue( cfg->m_Reannotate.back_refdes_start );
    m_FrontPrefix->SetValue( cfg->m_Reannotate.front_prefix );
    m_BackPrefix->SetValue( cfg->m_Reannotate.back_prefix );
    m_ExcludeList->SetValue( cfg->m_Reannotate.exclude_list );
    m_MessageWindow->SetFileName( cfg->m_Reannotate.report_file_name );
}


void DIALOG_BOARD_REANNOTATE::OnCloseClick( wxCommandEvent& event )
{
    EndDialog( wxID_OK );
}


void DIALOG_BOARD_REANNOTATE::FilterPrefix( wxTextCtrl* aPrefix )
{
    std::string tmps = VALIDPREFIX;

    if( aPrefix->GetValue().empty() )
        return; //Should never happen

    char lastc = aPrefix->GetValue().Last();

    if( isalnum( (int) lastc ) )
        return;

    if( std::string::npos != tmps.find( lastc ) )
        return;

    tmps = aPrefix->GetValue();
    aPrefix->Clear();
    tmps.pop_back();
    aPrefix->AppendText( tmps );
}


RefDesTypeStr* DIALOG_BOARD_REANNOTATE::GetOrBuildRefDesInfo( const wxString& aRefDesPrefix,
                                                              unsigned int    aStartRefDes )
{
    unsigned int requiredLastRef = ( aStartRefDes == 0 ? 1 : aStartRefDes ) - 1;

    for( size_t i = 0; i < m_refDesTypes.size(); i++ ) // See if it is in the types array
    {
        if( m_refDesTypes[i].RefDesType == aRefDesPrefix ) // Found it!
        {
            m_refDesTypes[i].LastUsedRefDes = std::max( m_refDesTypes[i].LastUsedRefDes,
                                                        requiredLastRef );

            return &m_refDesTypes[i];
        }
    }

    // Wasn't in the types array so add it
    RefDesTypeStr newtype;
    newtype.RefDesType = aRefDesPrefix;
    newtype.LastUsedRefDes = requiredLastRef;
    m_refDesTypes.push_back( newtype );

    return &m_refDesTypes.back();
}


void DIALOG_BOARD_REANNOTATE::FilterFrontPrefix( wxCommandEvent& event )
{
    FilterPrefix( m_FrontPrefix );
}


void DIALOG_BOARD_REANNOTATE::FilterBackPrefix( wxCommandEvent& event )
{
    FilterPrefix( m_BackPrefix );
}


void DIALOG_BOARD_REANNOTATE::OnApplyClick( wxCommandEvent& event )
{
    wxString warning;

    if( m_frame->GetBoard()->IsEmpty() )
    {
        ShowReport( _( "No PCB to reannotate!" ), RPT_SEVERITY_ERROR );
        return;
    }

    GetParameters(); // Figure out how this is to be done
    MakeSampleText( warning );

    if( !IsOK( m_frame, warning ) )
        return;

    if( ReannotateBoard() )
    {
        ShowReport( _( "PCB successfully reannotated" ), RPT_SEVERITY_ACTION );
        ShowReport( _( "PCB annotation changes should be synchronized with schematic using "
                       "the \"Update Schematic from PCB\" tool." ), RPT_SEVERITY_WARNING );
    }

    m_MessageWindow->SetLazyUpdate( false );
    m_MessageWindow->Flush( false );
    m_frame->GetCanvas()->Refresh(); // Redraw
    m_frame->OnModify();             // Need to save file on exit.
}


void DIALOG_BOARD_REANNOTATE::MakeSampleText( wxString& aMessage )
{
    aMessage.Printf( _( "\n%s footprints will be reannotated." ),
                     _( AnnotateString[m_annotationScope] ) );

    if( !m_ExcludeList->GetValue().empty() )
    {
        aMessage += wxString::Format( _( "\nAny reference types %s will not be annotated." ),
                                      m_ExcludeList->GetValue() );
    }

    if( m_ExcludeLocked->GetValue() )
        aMessage += wxString::Format( _( "\nLocked footprints will not be annotated" ) );

    if( !m_AnnotateBack->GetValue() )
    {
        aMessage += wxString::Format( _( "\nFront footprints will start at %s" ),
                                      m_FrontRefDesStart->GetValue() );
    }

    if( !m_AnnotateFront->GetValue() )
    {
        bool frontPlusOne = ( 0 == wxAtoi( m_BackRefDesStart->GetValue() ) )
                            && !m_AnnotateBack->GetValue();

        aMessage += wxString::Format( _( "\nBack footprints will start at %s." ),
                                      frontPlusOne ? _( "the last front footprint + 1" ) :
                                      m_BackRefDesStart->GetValue() );
    }

    if( !m_FrontPrefix->GetValue().empty() )
    {
        if( m_RemoveFrontPrefix->GetValue() )
        {
            aMessage += wxString::Format( _( "\nFront footprints starting with '%s' will have "
                                             "the prefix removed." ),
                                          m_FrontPrefix->GetValue() );
        }
        else
        {
            aMessage += wxString::Format( _( "\nFront footprints will have '%s' inserted as a "
                                             "prefix." ),
                                          m_FrontPrefix->GetValue() );
        }
    }

    if( !m_BackPrefix->GetValue().empty() )
    {
        if( m_RemoveBackPrefix->GetValue() )
        {
            aMessage += wxString::Format( _( "\nBack footprints starting with '%s' will have the "
                                             "prefix removed." ),
                                          m_BackPrefix->GetValue() );
        }
        else
        {
            aMessage += wxString::Format( _( "\nBack footprints will have '%s' inserted as a "
                                             "prefix." ),
                                          m_BackPrefix->GetValue() );
        }
    }

    bool fpLocation = m_locationChoice->GetSelection() == 0;

    aMessage += wxString::Format( _( "\nPrior to sorting by %s, the coordinates of which will be "
                                     "rounded to a %s, %s grid." ),
                                  fpLocation ? _( "footprint location" )
                                             : _( "reference designator location" ),
                                  m_frame->MessageTextFromValue( m_sortGridx ),
                                  m_frame->MessageTextFromValue( m_sortGridy ) );

    ShowReport( aMessage, RPT_SEVERITY_INFO );
}


void DIALOG_BOARD_REANNOTATE::GetParameters()
{
    m_sortCode = 0; // Convert radio button to sort direction code

    for( wxRadioButton* sortbuttons : m_sortButtons )
    {
        if( sortbuttons->GetValue() )
            break;

        m_sortCode++;
    }

    if( m_sortCode >= (int) m_sortButtons.size() )
        m_sortCode = 0;

    m_frontPrefixString = m_FrontPrefix->GetValue();
    m_backPrefixString  = m_BackPrefix->GetValue();

    // Get the chosen sort grid for rounding
    m_gridIndex = m_GridChoice->GetSelection();

    if( m_gridIndex >= ( int ) m_settings->m_Window.grid.sizes.size() )
    {
        m_sortGridx = EDA_UNIT_UTILS::UI::DoubleValueFromString(
                pcbIUScale, EDA_UNITS::MILS, m_settings->m_Window.grid.user_grid_x );
        m_sortGridy = EDA_UNIT_UTILS::UI::DoubleValueFromString(
                pcbIUScale, EDA_UNITS::MILS, m_settings->m_Window.grid.user_grid_y );
    }
    else
    {
        m_sortGridx = EDA_UNIT_UTILS::UI::DoubleValueFromString(
                pcbIUScale, EDA_UNITS::MILS, m_settings->m_Window.grid.sizes[m_gridIndex] );
        m_sortGridy = m_sortGridx;
    }

    m_annotationScope = AnnotateAll;

    for( wxRadioButton* button : m_scopeRadioButtons )
    {
        if( button->GetValue() )
            break;
        else
            m_annotationScope++;
    }

    if( m_annotationScope >= (int) m_scopeRadioButtons.size() )
        m_annotationScope = 0;

    m_MessageWindow->SetLazyUpdate( true );
}


int DIALOG_BOARD_REANNOTATE::RoundToGrid( int aCoord, int aGrid )
{
    if( 0 == aGrid )
        aGrid = MINGRID;

    int rounder;
    rounder = aCoord % aGrid;
    aCoord -= rounder;

    if( abs( rounder ) > ( aGrid / 2 ) )
        aCoord += ( aCoord < 0 ? -aGrid : aGrid );

    return ( aCoord );
}


/// Compare function used to compare ChangeArray element for sort
/// @return true is A < B
static bool ChangeArrayCompare( const RefDesChange& aA, const RefDesChange& aB )
{
    return ( StrNumCmp( aA.OldRefDesString, aB.OldRefDesString ) < 0 );
}


/// Compare function to sort footprints.
/// @return true if the first coordinate should be before the second coordinate
static bool ModuleCompare( const RefDesInfo& aA, const RefDesInfo& aB )
{
    int X0 = aA.roundedx, X1 = aB.roundedx, Y0 = aA.roundedy, Y1 = aB.roundedy;

    if( SortYFirst ) //If sorting by Y then X, swap X and Y
    {
        std::swap( X0, Y0 );
        std::swap( X1, Y1 );
    }

    // If descending, same compare just swap directions
    if( DescendingFirst )
        std::swap( X0, X1 );

    if( DescendingSecond )
        std::swap( Y0, Y1 );

    if( X0 < X1 )
        return ( true );  // yes, its smaller

    if( X0 > X1 )
        return ( false ); // No its not

    if( Y0 < Y1 )
        return ( true );  // same but equal

    return ( false );
}


wxString DIALOG_BOARD_REANNOTATE::CoordTowxString( int aX, int aY )
{
    return wxString::Format( wxT( "%s, %s" ),
                             m_frame->MessageTextFromValue( aX ),
                             m_frame->MessageTextFromValue( aY ) );
}


void DIALOG_BOARD_REANNOTATE::ShowReport( const wxString& aMessage, SEVERITY aSeverity )
{
    size_t pos = 0, prev = 0;

    do
    {
        pos = aMessage.ToStdString().find( '\n', prev );
        m_MessageWindow->Report( aMessage.ToStdString().substr( prev, pos - prev ), aSeverity );
        prev = pos + 1;
    } while( std::string::npos != pos );

}


void DIALOG_BOARD_REANNOTATE::LogChangePlan()
{
    int      i = 1;
    wxString message;

    message.Printf( _( "\n\nThere are %i types of reference designations\n"
                       "**********************************************************\n" ),
                    (int) m_refDesTypes.size() );

    for( RefDesTypeStr Type : m_refDesTypes ) // Show all the types of refdes
        message += Type.RefDesType + ( 0 == ( i++ % 16 ) ? wxT( "\n" ) : wxS( " " ) );

    if( !m_excludeArray.empty() )
    {
        wxString excludes;

        for( wxString& exclude : m_excludeArray ) // Show the refdes we are excluding
            excludes += exclude + wxS( " " );

        message += wxString::Format( _( "\nExcluding: %s from reannotation\n\n" ), excludes );
    }

    message += _( "\n    Change Array\n***********************\n" );

    for( const RefDesChange& change : m_changeArray )
    {
        message += wxString::Format( wxT( "%s -> %s  %s %s\n" ),
                                     change.OldRefDesString,
                                     change.NewRefDes,
                                     ActionMessage[change.Action],
                                     UpdateRefDes != change.Action ? _( " will be ignored" )
                                                                   : wxT( "" ) );
    }

    ShowReport( message, RPT_SEVERITY_INFO );
}


void DIALOG_BOARD_REANNOTATE::LogFootprints( const wxString& aMessage,
                                             const std::vector<RefDesInfo>& aFootprints )
{
    wxString message = aMessage;

    if( aFootprints.empty() )
        message += _( "\nNo footprints" );
    else
    {
        int i = 1;
        bool fpLocations = m_locationChoice->GetSelection() == 0;

        message += wxString::Format( _( "\n*********** Sort on %s ***********" ),
                                     fpLocations ? _( "Footprint Coordinates" )
                                                 : _( "Reference Designator Coordinates" ) );

        message += wxString::Format( _( "\nSort Code %d" ), m_sortCode );

        for( const RefDesInfo& mod : aFootprints )
        {
            message += wxString::Format( _( "\n%d %s UUID: [%s], X, Y: %s, Rounded X, Y, %s" ),
                                         i++,
                                         mod.RefDesString,
                                         mod.Uuid.AsString(),
                                         CoordTowxString( mod.x, mod.y ),
                                         CoordTowxString( mod.roundedx, mod.roundedy ) );
        }
    }

    ShowReport( message, RPT_SEVERITY_INFO );
}


bool DIALOG_BOARD_REANNOTATE::ReannotateBoard()
{
    std::vector<RefDesInfo> BadRefDes;
    wxString                message, badrefdes;
    STRING_FORMATTER        stringformatter;
    RefDesChange*           newref;
    NETLIST                 netlist;

    if( !BuildFootprintList( BadRefDes ) )
    {
        ShowReport( _( "Selected options resulted in errors! Change them and try again." ),
                    RPT_SEVERITY_ERROR );
        return false;
    }

    if( !BadRefDes.empty() )
    {
        message.Printf( _( "\nPCB has %d empty or invalid reference designations."
                           "\nRecommend running DRC with 'Test for parity between PCB and "
                           "schematic' checked.\n" ),
                        (int) BadRefDes.size() );

        for( const RefDesInfo& mod : BadRefDes )
        {
            badrefdes += wxString::Format( _( "\nRefDes: %s Footprint: %s:%s at %s on PCB." ),
                                           mod.RefDesString,
                                           mod.FPID.GetLibNickname().wx_str(),
                                           mod.FPID.GetLibItemName().wx_str(),
                                           CoordTowxString( mod.x, mod.y ) );
        }

        ShowReport( message + badrefdes + wxT( "\n" ), RPT_SEVERITY_WARNING );
        message += _( "Reannotate anyway?" );

        if( !IsOK( m_frame, message ) )
            return false;
    }

    BOARD_COMMIT commit( m_frame );

    for( FOOTPRINT* footprint : m_footprints )
    {
        newref = GetNewRefDes( footprint );

        if( nullptr == newref )
            return false;

        commit.Modify( footprint );                           // Make a copy for undo
        footprint->SetReference( newref->NewRefDes );         // Update the PCB reference
        m_frame->GetCanvas()->GetView()->Update( footprint ); // Touch the footprint
    }

    commit.Push( wxT( "Geographic reannotation" ) );
    return true;
}


bool DIALOG_BOARD_REANNOTATE::BuildFootprintList( std::vector<RefDesInfo>& aBadRefDes )
{
    bool annotateSelected = m_AnnotateSelection->GetValue();
    bool annotateFront = m_AnnotateFront->GetValue(); // Unless only doing back
    bool annotateBack  = m_AnnotateBack->GetValue();  // Unless only doing front
    bool skipLocked    = m_ExcludeLocked->GetValue();

    int    errorcount = 0;
    size_t firstnum   = 0;

    m_frontFootprints.clear();
    m_backFootprints.clear();
    m_excludeArray.clear();
    m_footprints = m_frame->GetBoard()->Footprints();

    std::vector<KIID> selected;

    if( annotateSelected )
    {
        for( EDA_ITEM* item : m_selection )
        {
            // Get the timestamps of selected footprints
            if( item->Type() == PCB_FOOTPRINT_T )
                selected.push_back( item->m_Uuid );
        }
    }

    wxString exclude;

    // Break exclude list into words.
    for( auto thischar : m_ExcludeList->GetValue() )
    {
    	if( ( ' ' == thischar ) || ( ',' == thischar ) )
        {
            m_excludeArray.push_back( exclude );
            exclude.clear();
        }
        else
            exclude += thischar;

        if( !exclude.empty() )
            m_excludeArray.push_back( exclude );
    }

    RefDesInfo fpData;
    bool       useModuleLocation = m_locationChoice->GetSelection() == 0;

    for( FOOTPRINT* footprint : m_footprints )
    {
        fpData.Uuid         = footprint->m_Uuid;
        fpData.RefDesString = footprint->GetReference();
        fpData.FPID         = footprint->GetFPID();
        fpData.x            = useModuleLocation ? footprint->GetPosition().x
                                                : footprint->Reference().GetPosition().x;
        fpData.y            = useModuleLocation ? footprint->GetPosition().y
                                                : footprint->Reference().GetPosition().y;
        fpData.roundedx     = RoundToGrid( fpData.x, m_sortGridx ); // Round to sort
        fpData.roundedy     = RoundToGrid( fpData.y, m_sortGridy );
        fpData.Front        = footprint->GetLayer() == F_Cu;
        fpData.Action       = UpdateRefDes; // Usually good

        if( fpData.RefDesString.IsEmpty() )
        {
            fpData.Action = EmptyRefDes;
        }
        else
        {
            firstnum = fpData.RefDesString.find_first_of( wxT( "0123456789" ) );

            if( std::string::npos == firstnum )
                fpData.Action = InvalidRefDes; // do not change ref des such as 12 or +1, or L
        }

        // Get the type (R, C, etc)
        fpData.RefDesType = fpData.RefDesString.substr( 0, firstnum );

        for( wxString excluded : m_excludeArray )
        {
            if( excluded == fpData.RefDesType ) // Am I supposed to exclude this type?
            {
                fpData.Action = Exclude; // Yes
                break;
            }
        }

        if(( fpData.Front && annotateBack ) ||            // If a front fp and doing backs only
                ( !fpData.Front && annotateFront ) ||     // If a back fp and doing front only
                ( footprint->IsLocked() && skipLocked ) ) // If excluding locked and it is locked
        {
            fpData.Action = Exclude;
        }

        if( annotateSelected )
        {                                // If only annotating selected c
            fpData.Action = Exclude;     // Assume it isn't selected

            for( KIID sel : selected )
            {
                if( fpData.Uuid == sel )
                {                                  // Found in selected footprints
                    fpData.Action = UpdateRefDes;  // Update it
                    break;
                }
            }
        }

        if( fpData.Front )
            m_frontFootprints.push_back( fpData );
        else
            m_backFootprints.push_back( fpData );
    }

    // Determine the sort order for the front.
    SetSortCodes( FrontDirectionsArray, m_sortCode );

    // Sort the front footprints.
    sort( m_frontFootprints.begin(), m_frontFootprints.end(), ModuleCompare );

    // Determine the sort order for the back.
    SetSortCodes( BackDirectionsArray, m_sortCode );

    // Sort the back footprints.
    sort( m_backFootprints.begin(), m_backFootprints.end(), ModuleCompare );

    m_refDesTypes.clear();
    m_changeArray.clear();

    BuildUnavailableRefsList();

    if( !m_frontFootprints.empty() )
    {
        BuildChangeArray( m_frontFootprints, wxAtoi( m_FrontRefDesStart->GetValue() ),
                          m_FrontPrefix->GetValue(), m_RemoveFrontPrefix->GetValue(), aBadRefDes );
    }

    if( !m_backFootprints.empty() )
    {
        BuildChangeArray( m_backFootprints, wxAtoi( m_BackRefDesStart->GetValue() ),
                          m_BackPrefix->GetValue(), m_RemoveBackPrefix->GetValue(), aBadRefDes );
    }

    if( !m_changeArray.empty() )
        sort( m_changeArray.begin(), m_changeArray.end(), ChangeArrayCompare );

    LogChangePlan();

    size_t changearraysize = m_changeArray.size();

    for( size_t i = 0; i < changearraysize; i++ ) // Scan through for duplicates if update or skip
    {
        if( ( m_changeArray[i].Action != EmptyRefDes )
                && ( m_changeArray[i].Action != InvalidRefDes ) )
        {
            for( size_t j = i + 1; j < changearraysize; j++ )
            {
                if( m_changeArray[i].NewRefDes == m_changeArray[j].NewRefDes )
                {
                    ShowReport( wxString::Format( _( "Duplicate instances of %s" ),
                                                  m_changeArray[j].NewRefDes ),
                                RPT_SEVERITY_ERROR );

                    if( errorcount++ > MAXERROR )
                    {
                        ShowReport( _( "Aborted: too many errors" ), RPT_SEVERITY_ERROR );
                        break;
                    }
                }
            }
        }

        if( errorcount > MAXERROR )
            break;
    }

    return ( 0 == errorcount );
}

void DIALOG_BOARD_REANNOTATE::BuildUnavailableRefsList()
{
    std::vector<RefDesInfo> excludedFootprints;

    for( RefDesInfo fpData : m_frontFootprints )
    {
        if( fpData.Action == Exclude )
            excludedFootprints.push_back( fpData );
    }

    for( RefDesInfo fpData : m_backFootprints )
    {
        if( fpData.Action == Exclude )
            excludedFootprints.push_back( fpData );
    }

    for( RefDesInfo fpData : excludedFootprints )
    {
        if( fpData.Action == Exclude )
        {
            RefDesTypeStr* refDesInfo = GetOrBuildRefDesInfo( fpData.RefDesType );
            refDesInfo->UnavailableRefs.insert( UTIL::GetRefDesNumber( fpData.RefDesString ) );
        }
    }
}


void DIALOG_BOARD_REANNOTATE::BuildChangeArray( std::vector<RefDesInfo>& aFootprints,
                                                unsigned int aStartRefDes, const wxString& aPrefix,
                                                bool aRemovePrefix,
                                                std::vector<RefDesInfo>& aBadRefDes )
{
    size_t   prefixsize = aPrefix.size();

    bool haveprefix = ( 0 != prefixsize );         // Do I have a prefix?
    bool addprefix  = haveprefix & !aRemovePrefix; // Yes- and I'm not removing it
    aRemovePrefix &= haveprefix;                   // Only remove if I have a prefix

    bool prefixpresent; // Prefix found

    wxString logstring = ( aFootprints.front().Front ) ? _( "\n\nFront Footprints" )
                                                       : _( "\n\nBack Footprints" );
    LogFootprints( logstring, aFootprints );

    if( 0 != aStartRefDes ) // Initialize the change array if present
    {
    	for( size_t i = 0; i < m_refDesTypes.size(); i++ )
            m_refDesTypes[i].LastUsedRefDes = aStartRefDes;
    }


    for( RefDesInfo fpData : aFootprints )
    {
        RefDesChange change;

        change.Uuid            = fpData.Uuid;
        change.Action          = fpData.Action;
        change.OldRefDesString = fpData.RefDesString;
        change.NewRefDes       = fpData.RefDesString;
        change.Front           = fpData.Front;

        if( fpData.RefDesString.IsEmpty() )
            fpData.Action = EmptyRefDes;

        if( ( change.Action == EmptyRefDes ) || ( change.Action == InvalidRefDes ) )
        {
            m_changeArray.push_back( change );
            aBadRefDes.push_back( fpData );
            continue;
        }

        if( change.Action == UpdateRefDes )
        {
            prefixpresent = ( 0 == fpData.RefDesType.find( aPrefix ) );

            if( addprefix && !prefixpresent )
                fpData.RefDesType.insert( 0, aPrefix ); // Add prefix once only

            if( aRemovePrefix && prefixpresent ) // If there is a prefix remove it
                fpData.RefDesType.erase( 0, prefixsize );

            RefDesTypeStr* refDesInfo = GetOrBuildRefDesInfo( fpData.RefDesType, aStartRefDes );
            unsigned int  newRefDesNumber = refDesInfo->LastUsedRefDes + 1;

            while( refDesInfo->UnavailableRefs.count( newRefDesNumber ) )
                newRefDesNumber++;

            change.NewRefDes = refDesInfo->RefDesType + std::to_string( newRefDesNumber );
            refDesInfo->LastUsedRefDes = newRefDesNumber;
        }

        m_changeArray.push_back( change );
    }
}


RefDesChange* DIALOG_BOARD_REANNOTATE::GetNewRefDes( FOOTPRINT* aFootprint )
{
    size_t i;

    for( i = 0; i < m_changeArray.size(); i++ )
    {
        if( aFootprint->m_Uuid == m_changeArray[i].Uuid )
            return ( &m_changeArray[i] );
    }

    ShowReport( _( "Footprint not found in changelist" ) + wxS( " " ) + aFootprint->GetReference(),
                RPT_SEVERITY_ERROR );

    return nullptr; // Should never happen
}
