/////////////////////////////////////////////////////////////////////////////

// Name:        dialog_design_rules.cpp
// Author:      jean-pierre Charras
/////////////////////////////////////////////////////////////////////////////

/* functions relatives to the design rules editor
 */
#include "fctsys.h"
#include "common.h"
#include "class_drawpanel.h"

#include "confirm.h"
#include "pcbnew.h"
#include "wxPcbStruct.h"

#include "id.h"
#include "dialog_design_rules.h"
#include "wx/generic/gridctrl.h"

// Fields Positions on layer grid
#define LAYERS_GRID_ROUTABLE_POSITION 0
#define LAYERS_GRID_STATUS_POSITION   1
#define LAYERS_GRID_NAME_POSITION     2

// Fields Positions on rules grid
#define RULE_GRID_TRACKSIZE_POSITION    0
#define RULE_GRID_VIASIZE_POSITION      1
#define RULE_GRID_CLEARANCE_POSITION    2
#define RULE_GRID_MINTRACKSIZE_POSITION 3
#define RULE_GRID_MINVIASIZE_POSITION   4

/***********************************************************************************/
DIALOG_DESIGN_RULES::DIALOG_DESIGN_RULES( WinEDA_PcbFrame* parent ) :
    DIALOG_DESIGN_RULES_BASE( parent )
/***********************************************************************************/
{
    m_Parent = parent;

    Init();
    SetAutoLayout( true );
    GetSizer()->Fit( this );
    GetSizer()->SetSizeHints( this );
}


/********************************************************************/
void DIALOG_DESIGN_RULES::Init()
/********************************************************************/
{
    SetFocus();
    SetReturnCode( 0 );

    // Initialize the layers grid:
    m_ActivesLayersCount = g_DesignSettings.m_CopperLayerCount;
    m_Pcb = m_Parent->GetBoard();

    m_LayersCountSelection->SetSelection( m_ActivesLayersCount / 2 );

    // Initialize the Routable column
    SetRoutableLayerStatus();

    // Initialize the Status column (layers attribute)
    LAYER_T typelist[4] = { LT_SIGNAL, LT_POWER, LT_MIXED, LT_JUMPER };
    for( int ii = 0; ii < 4; ii++ )
    {
        m_LayersType[ii]     = typelist[ii];
        m_LayersTypeName[ii] = CONV_FROM_UTF8( LAYER::ShowType( typelist[ii] ) );
    }

    for( int ii = 0; ii <  m_gridLayersProperties->GetNumberRows(); ii++ )
    {
        m_gridLayersProperties->SetCellEditor( ii, LAYERS_GRID_STATUS_POSITION,
                                              new wxGridCellChoiceEditor( WXSIZEOF(
                                                                              m_LayersTypeName ),
                                                                          m_LayersTypeName ) );
        int select = LT_SIGNAL;
        for( int jj = 0; jj < 4; jj++ )
        {
            int layer = LAYER_CMP_N - ii;
            if( m_Pcb->GetLayerType( layer ) == m_LayersType[jj] )
            {
                select = m_LayersType[jj];
                break;
            }
        }

        m_gridLayersProperties->SetCellValue( ii, LAYERS_GRID_STATUS_POSITION,
                                              m_LayersTypeName[select] );
        m_gridLayersProperties->SetCellOverflow( ii, LAYERS_GRID_STATUS_POSITION, false );
    }

    // Initialize the Name column
    for( int ii = 0; ii <  m_gridLayersProperties->GetNumberRows(); ii++ )
    {
        wxString layer_name = m_Pcb->GetLayerName( LAYER_CMP_N - ii );
        m_gridLayersProperties->SetCellValue( ii, LAYERS_GRID_NAME_POSITION, layer_name );
    }

    // Initialize the Rules List
    InitRulesList();

    /* Initialize the list of nets buffers
     *  (note the netcode 0 is not a real net, so it is not loaded)
     */
    for( unsigned ii = 1; ; ii++ )
    {
        NETINFO_ITEM* net = m_Pcb->FindNet( ii );
        if( net == NULL )
            break;
        m_StockNets.push_back( net );

        // search the index in rules list for this net
        int rules_idx = 0;
        for( int jj = 0; jj < m_gridNetClassesProperties->GetNumberRows(); jj++ )
        {
            if( m_gridNetClassesProperties->GetRowLabelValue( jj ).CmpNoCase( net->GetClassName() )
                == 0 )
            {
                rules_idx = jj;
                break;
            }
        }

        m_NetsLinkToClasses.push_back( rules_idx ); // All nets are set to default net class
    }

    InitializeRulesSelectionBoxes();
}


/** Function FillListBoxWithNetsNames
 * populates the aListBox with net names members of the aNetclassIndex net class
 * the "Client Data pointer" is used to store the index of nets in ne nets lists
 */
void DIALOG_DESIGN_RULES::FillListBoxWithNetsNames( wxListBox* aListBox, int aNetclassIndex )
{
    aListBox->Clear();
    unsigned idx = 0;
    for( unsigned ii = 0; ii < m_StockNets.size(); ii++ )
    {
        if( aNetclassIndex == m_NetsLinkToClasses[ii] )
        {
            aListBox->Append( m_StockNets[ii]->GetNetname() );

            // Store the index of this net
            // This is a trick to get an unsigned integer index from a pointer value.
            // Some compilers cannot accept to convert an unsigned to a pointer  without complains
            char * ptr = (char*)0 + ii;
            aListBox->SetClientData( idx, ptr );
            idx++;
        }
    }
}


/* Initialize the combno boxes by the list of existing net classes
 */
void DIALOG_DESIGN_RULES::InitializeRulesSelectionBoxes()
{
    m_CBoxRightSelection->Clear();
    m_CBoxLeftSelection->Clear();
    for( int ii = 0; ii < m_gridNetClassesProperties->GetNumberRows(); ii++ )
    {
        m_CBoxRightSelection->Append( m_gridNetClassesProperties->GetRowLabelValue( ii ) );
        m_CBoxLeftSelection->Append( m_gridNetClassesProperties->GetRowLabelValue( ii ) );
    }

    m_CBoxRightSelection->Select( 0 );
    m_CBoxLeftSelection->Select( 0 );
    m_buttonRightToLeft->Enable( false );
    m_buttonLeftToRight->Enable( false );;
    FillListBoxWithNetsNames( m_listBoxLeftNetSelect, m_CBoxLeftSelection->GetCurrentSelection() );
    FillListBoxWithNetsNames( m_listBoxRightNetSelect, m_CBoxRightSelection->GetCurrentSelection() );
}


/* Initialize the Routable column, and the R/W property of some cells
 */
void DIALOG_DESIGN_RULES::SetRoutableLayerStatus()
{
    m_gridLayersProperties->SetColFormatBool( LAYERS_GRID_ROUTABLE_POSITION );
    for( int ii = 0; ii <  m_gridLayersProperties->GetNumberRows(); ii++ )
    {
        int      layer = LAYER_CMP_N - ii;
        wxString value = layer < (m_ActivesLayersCount - 1) ? wxT( "1" ) : wxT( "0" );
        if( m_ActivesLayersCount > 1 && layer == LAYER_CMP_N )
            value = wxT( "1" );
        if(  layer == COPPER_LAYER_N )
            value = wxT( "1" );
        m_gridLayersProperties->SetCellValue( ii, LAYERS_GRID_ROUTABLE_POSITION, value );
        m_gridLayersProperties->SetReadOnly( ii, LAYERS_GRID_ROUTABLE_POSITION );

        // Set to Read Only cell for non existing copper layers:
        m_gridLayersProperties->SetReadOnly( ii, LAYERS_GRID_STATUS_POSITION, value != wxT( "1" ) );
        m_gridLayersProperties->SetReadOnly( ii, LAYERS_GRID_NAME_POSITION, value != wxT( "1" ) );
    }
}


/* Initialize the rules list from board
 */
void DIALOG_DESIGN_RULES::InitRulesList()
{
    NETCLASSES& netclasses = m_Pcb->m_NetClasses;

    int ii = 0;
    for( NETCLASSES::iterator i=netclasses.begin();  i!=netclasses.end();  ++i, ++ii )
    {
        NETCLASS* netclass = i->second;

        // Creates one entry if needed
        if( ii >= m_gridNetClassesProperties->GetNumberRows() )
            m_gridNetClassesProperties->AppendRows();

        // Init name
        m_gridNetClassesProperties->SetRowLabelValue( ii, netclass->GetName() );

        // Init data
        wxString msg;
        msg = ReturnStringFromValue( g_UnitMetric,
                                     netclass->GetTrackWidth(),
                                     m_Parent->m_InternalUnits, false );
        m_gridNetClassesProperties->SetCellValue( ii, RULE_GRID_TRACKSIZE_POSITION, msg );

        msg = ReturnStringFromValue( g_UnitMetric,
                                     netclass->GetViaSize(),
                                     m_Parent->m_InternalUnits, false );
        m_gridNetClassesProperties->SetCellValue( ii, RULE_GRID_VIASIZE_POSITION, msg );

        msg = ReturnStringFromValue( g_UnitMetric,
                                     netclass->GetClearance(),
                                     m_Parent->m_InternalUnits, false );
        m_gridNetClassesProperties->SetCellValue( ii, RULE_GRID_CLEARANCE_POSITION, msg );

        msg = ReturnStringFromValue( g_UnitMetric,
                                     netclass->GetTrackMinWidth(),
                                     m_Parent->m_InternalUnits, false );
        m_gridNetClassesProperties->SetCellValue( ii, RULE_GRID_MINTRACKSIZE_POSITION, msg );

        msg = ReturnStringFromValue( g_UnitMetric,
                                     netclass->GetViaMinSize(),
                                     m_Parent->m_InternalUnits, false );
        m_gridNetClassesProperties->SetCellValue( ii, RULE_GRID_MINVIASIZE_POSITION, msg );
    }
}


/* Copy the rules list to board
 */
void DIALOG_DESIGN_RULES::CopyRulesListToBoard()
{
    NETCLASSES& netclasses = m_Pcb->m_NetClasses;

    netclasses.Clear();

    for( int ii = 0; ii < m_gridNetClassesProperties->GetNumberRows(); ii++ )
    {
        NETCLASS netclass( m_Pcb, m_gridNetClassesProperties->GetRowLabelValue( ii ) );

        // Init data
        netclass.SetTrackWidth(
            ReturnValueFromString( g_UnitMetric,
                                   m_gridNetClassesProperties->GetCellValue( ii,
                                                                             RULE_GRID_TRACKSIZE_POSITION ),
                                   m_Parent->m_InternalUnits ));

        netclass.SetViaSize(
            ReturnValueFromString( g_UnitMetric,
                                   m_gridNetClassesProperties->GetCellValue( ii,
                                                                             RULE_GRID_VIASIZE_POSITION ),
                                   m_Parent->m_InternalUnits ));

        netclass.SetClearance(
            ReturnValueFromString( g_UnitMetric,
                                   m_gridNetClassesProperties->GetCellValue( ii,
                                                                             RULE_GRID_CLEARANCE_POSITION ),
                                   m_Parent->m_InternalUnits ));

        netclass.SetTrackMinWidth(
            ReturnValueFromString( g_UnitMetric,
                                   m_gridNetClassesProperties->GetCellValue( ii,
                                                                             RULE_GRID_MINTRACKSIZE_POSITION ),
                                   m_Parent->m_InternalUnits ));

        netclass.SetViaMinSize(
            ReturnValueFromString( g_UnitMetric,
                                   m_gridNetClassesProperties->GetCellValue( ii,
                                                                             RULE_GRID_MINVIASIZE_POSITION ),
                                   m_Parent->m_InternalUnits ));

        // Copy the list of nets associated to this netclass:
        for( unsigned idx = 0; idx < m_StockNets.size(); idx++ )
        {
            if( m_NetsLinkToClasses[idx] == ii )
                netclass.Add( m_StockNets[idx]->GetNetname() );
        }

        // this calls copy constructor, so all data must be in 'netclass' before Add()ing.
        m_Pcb->m_NetClasses.Add( netclass );
    }

    m_Pcb->SynchronizeNetsAndNetClasses();
}


/*****************************************************************/
void DIALOG_DESIGN_RULES::OnCancelButtonClick( wxCommandEvent& event )
/*****************************************************************/
{
    EndModal( 0 );
}


/**************************************************************************/
void DIALOG_DESIGN_RULES::OnOkButtonClick( wxCommandEvent& event )
/**************************************************************************/
{
    if( !TestDataValidity() )
    {
        DisplayError( this, _( "Errors detected, Abort" ) );
        return;
    }

    g_DesignSettings.m_CopperLayerCount = m_ActivesLayersCount;

    // Initialize the new layer name
    for( int ii = 0; ii <  m_gridLayersProperties->GetNumberRows(); ii++ )
    {
        wxString layer_name = m_gridLayersProperties->GetCellValue( ii, LAYERS_GRID_NAME_POSITION );
        if( layer_name != m_Pcb->GetLayerName( LAYER_CMP_N - ii ) )
        {
            m_Pcb->SetLayerName( LAYER_CMP_N - ii, layer_name );
        }
    }

    // Initialize the layer type
    for( int ii = 0; ii <  m_gridLayersProperties->GetNumberRows(); ii++ )
    {
        wxString txt   = m_gridLayersProperties->GetCellValue( ii, LAYERS_GRID_STATUS_POSITION );
        int      layer = LAYER_CMP_N - ii;
        for( int jj = 0; jj < 3; jj++ )
        {
            if( m_LayersTypeName[jj] == txt )
            {
                m_Pcb->SetLayerType( layer, m_LayersType[jj] );
                break;
            }
        }
    }

    CopyRulesListToBoard();

    EndModal( wxID_OK );
}


/**************************************************************************/
void DIALOG_DESIGN_RULES::OnLayerCountClick( wxCommandEvent& event )
/**************************************************************************/
{
    m_ActivesLayersCount = m_LayersCountSelection->GetSelection() * 2;
    if( m_ActivesLayersCount <= 0 )
        m_ActivesLayersCount = 1;

    // Reinit the routable layers status
    SetRoutableLayerStatus();
}


/**************************************************************************/
void DIALOG_DESIGN_RULES::OnAddNetclassClick( wxCommandEvent& event )
/**************************************************************************/
{
    wxString class_name;

    if( Get_Message( _( "New Net Class Name:" ),
                     wxEmptyString,
                     class_name,
                     this ) )
        return;

    // The name must dot exists:
    for( int ii = 0; ii < m_gridNetClassesProperties->GetNumberRows(); ii++ )
    {
        wxString value;
        value = m_gridNetClassesProperties->GetRowLabelValue( ii );
        if( class_name.CmpNoCase( value ) == 0 )       // Already exists!
        {
            DisplayError( this, _( "This NetClass is already existing, cannot add it; Aborted" ) );
            return;
        }
    }

    m_gridNetClassesProperties->AppendRows();
    m_gridNetClassesProperties->SetRowLabelValue(
        m_gridNetClassesProperties->GetNumberRows() - 1,
        class_name );

    // Copy values of the previous class:
    int irow = m_gridNetClassesProperties->GetNumberRows() - 1;
    for( int icol = 0; icol < m_gridNetClassesProperties->GetNumberCols(); icol++ )
    {
        wxString value;
        value = m_gridNetClassesProperties->GetCellValue( irow - 1, icol );
        m_gridNetClassesProperties->SetCellValue( irow, icol, value );
    }

    InitializeRulesSelectionBoxes();
}


/**************************************************************************/
void DIALOG_DESIGN_RULES::OnRemoveNetclassClick( wxCommandEvent& event )
/**************************************************************************/
{
    wxArrayInt select = m_gridNetClassesProperties->GetSelectedRows();

    for( int ii = select.GetCount() - 1; ii >= 0; ii-- )
    {
        if( select[ii] != 0 )   // Do not remove the default class
        {
            m_gridNetClassesProperties->DeleteRows( select[ii] );

            // reset the net class to default for nets member of the removed net class
            for( unsigned jj = 0; jj< m_NetsLinkToClasses.size(); jj++ )
                if( m_NetsLinkToClasses[jj] == ii )
                    m_NetsLinkToClasses[jj] = 0;    // Reset to default net class

        }
    }

    InitializeRulesSelectionBoxes();
}


/*
 * Called on the left Choice Box selection
 */
void DIALOG_DESIGN_RULES::OnLeftCBSelection( wxCommandEvent& event )
{
    FillListBoxWithNetsNames( m_listBoxLeftNetSelect, m_CBoxLeftSelection->GetCurrentSelection() );
    if( m_CBoxLeftSelection->GetCurrentSelection() ==  m_CBoxRightSelection->GetCurrentSelection() )
    {
        m_buttonRightToLeft->Enable( false );
        m_buttonLeftToRight->Enable( false );
    }
    else
    {
        m_buttonRightToLeft->Enable( true );
        m_buttonLeftToRight->Enable( true );
    }
}


/*
 * Called on the Right Choice Box selection
 */
void DIALOG_DESIGN_RULES::OnRightCBSelection( wxCommandEvent& event )
{
    FillListBoxWithNetsNames( m_listBoxRightNetSelect, m_CBoxRightSelection->GetCurrentSelection() );
    if( m_CBoxLeftSelection->GetCurrentSelection() ==  m_CBoxRightSelection->GetCurrentSelection() )
    {
        m_buttonRightToLeft->Enable( false );
        m_buttonLeftToRight->Enable( false );;
    }
    else
    {
        m_buttonRightToLeft->Enable( true );
        m_buttonLeftToRight->Enable( true );
    }
}


/* Called on clicking the "<<<" or Copy Right to Left button:
 * Selected items are moved from the right list to the left list
 */

void DIALOG_DESIGN_RULES::OnRightToLeftCopyButton( wxCommandEvent& event )
{
    int idx_class = m_CBoxLeftSelection->GetCurrentSelection();

    if( idx_class == wxNOT_FOUND )
        return;
    for( unsigned ii = 0; ii < m_listBoxRightNetSelect->GetCount(); ii++ )
    {
        if( !m_listBoxRightNetSelect->IsSelected( ii ) )
            continue;

        // This is a trick to get an unsigned integer index from a pointer value.
        // Some compilers cannot accept to convert a pointer to an unsigned without complains
        char * ptr = (char*) m_listBoxRightNetSelect->GetClientData( ii );
        unsigned idx = ptr - (char*)0;
        m_NetsLinkToClasses[idx] = idx_class;
    }

    FillListBoxWithNetsNames( m_listBoxLeftNetSelect, m_CBoxLeftSelection->GetCurrentSelection() );
    FillListBoxWithNetsNames( m_listBoxRightNetSelect, m_CBoxRightSelection->GetCurrentSelection() );
}


/* Called on clicking the ">>>" or Copy Left to Right button:
 * Selected items are moved from the left list to the right list
 */
void DIALOG_DESIGN_RULES::OnLeftToRightCopyButton( wxCommandEvent& event )
{
    int idx_class = m_CBoxRightSelection->GetCurrentSelection();

    if( idx_class == wxNOT_FOUND )
        return;
    for( unsigned ii = 0; ii < m_listBoxLeftNetSelect->GetCount(); ii++ )
    {
        if( !m_listBoxLeftNetSelect->IsSelected( ii ) )
            continue;

        // This is a trick to get an unsigned integer index from a pointer value.
        // Some compilers cannot accept to convert a pointer to an unsigned without complains
        char * ptr = (char*) m_listBoxLeftNetSelect->GetClientData( ii );
        unsigned idx = ptr - (char*)0 ;
        m_NetsLinkToClasses[idx] = idx_class;
    }

    FillListBoxWithNetsNames( m_listBoxLeftNetSelect, m_CBoxLeftSelection->GetCurrentSelection() );
    FillListBoxWithNetsNames( m_listBoxRightNetSelect, m_CBoxRightSelection->GetCurrentSelection() );
}


/* Called on clicking the left "select all" button:
 * select alls items of the left netname list lisxt box
 */
void DIALOG_DESIGN_RULES::OnLeftSelectAllButton( wxCommandEvent& event )
{
    for( unsigned ii = 0; ii < m_listBoxLeftNetSelect->GetCount(); ii++ )
        m_listBoxLeftNetSelect->SetSelection( ii );
}


/* Called on clicking the right "select all" button:
 * select alls items of the right netname list lisxt box
 */
void DIALOG_DESIGN_RULES::OnRightSelectAllButton( wxCommandEvent& event )
{
    for( unsigned ii = 0; ii < m_listBoxRightNetSelect->GetCount(); ii++ )
        m_listBoxRightNetSelect->SetSelection( ii );
}


/* TestDataValidity
 * Performs a control of data validity
 * set the background of a bad cell in RED and display an info message
 * @return true if Ok, false if error
 */
bool DIALOG_DESIGN_RULES::TestDataValidity()
{
    bool success = true;
    m_MessagesList->SetPage(wxEmptyString); // Clear message list

    //  Test duplicate layers names
    for( int ii = 0; ii < m_gridLayersProperties->GetNumberRows() - 1; ii++ )
    {
        wxString value = m_gridLayersProperties->GetCellValue( ii, LAYERS_GRID_NAME_POSITION );
        for( int jj = ii+1; jj < m_gridLayersProperties->GetNumberRows(); jj++ )
        {
            wxString othervalue = m_gridLayersProperties->GetCellValue( ii,
                                                                        LAYERS_GRID_NAME_POSITION );
            othervalue = m_gridLayersProperties->GetCellValue( jj, LAYERS_GRID_NAME_POSITION );
            if( value.CmpNoCase( othervalue ) == 0 )   // Already exists!
            {
                wxString text;
                text.Printf( _(
                                "<small>This layer name <b>%s</b> is already existing<br>" ),
                            value.GetData() );
                m_MessagesList->AppendToPage( text );
                success = false;
            }
        }
    }

    int value;
    int minvalue;

    for( int ii = 0; ii < m_gridNetClassesProperties->GetNumberRows(); ii++ )
    {
        value = ReturnValueFromString( g_UnitMetric,
                                       m_gridNetClassesProperties->GetCellValue(
                                           ii, RULE_GRID_TRACKSIZE_POSITION ),
                                       m_Parent->m_InternalUnits );
        minvalue = ReturnValueFromString( g_UnitMetric,
                                          m_gridNetClassesProperties->GetCellValue( ii,
                                                                                    RULE_GRID_MINTRACKSIZE_POSITION ),
                                          m_Parent->m_InternalUnits );
        if( value < minvalue )
        {
            success = false;
            m_MessagesList->AppendToPage( _( "The <b>track</b> minimum size is bigger than the size<br>" ) );
        }

        value = ReturnValueFromString( g_UnitMetric,
                                       m_gridNetClassesProperties->GetCellValue(
                                           ii, RULE_GRID_VIASIZE_POSITION ),
                                       m_Parent->m_InternalUnits );
        minvalue = ReturnValueFromString( g_UnitMetric,
                                          m_gridNetClassesProperties->GetCellValue( ii,
                                                                                    RULE_GRID_MINVIASIZE_POSITION ),
                                          m_Parent->m_InternalUnits );
        if( value < minvalue )
        {
            success = false;
            m_MessagesList->AppendToPage( _( "The <b>via</b> minimum size is bigger than the size<br>" ) );
        }
    }

    return success;
}
