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

// Name:        dialog_copper_zones.cpp
// Author:      jean-pierre Charras
// Created:     09/oct/2008
/// Licence:     GNU License
/////////////////////////////////////////////////////////////////////////////

#include <wx/wx.h>
#include <wx/imaglist.h>
#include "fctsys.h"
#include "appl_wxstruct.h"
#include "common.h"
#include "confirm.h"
#include "PolyLine.h"
#include "pcbnew.h"
#include "wxPcbStruct.h"
#include "trigo.h"
#include "zones.h"

#include "dialog_copper_zones.h"
#include "class_zone_setting.h"

#define LAYER_BITMAP_SIZE_X 20
#define LAYER_BITMAP_SIZE_Y 10

// Initialize static member variables
wxString DIALOG_COPPER_ZONE::m_netNameShowFilter( wxT( "*" ) );
wxPoint DIALOG_COPPER_ZONE::prevPosition( -1, -1 );
wxSize DIALOG_COPPER_ZONE::prevSize;


DIALOG_COPPER_ZONE::DIALOG_COPPER_ZONE( PCB_EDIT_FRAME* parent, ZONE_SETTING* zone_setting ) :
    DIALOG_COPPER_ZONE_BASE( parent )
{
    m_Parent = parent;
    m_Config = wxGetApp().m_EDA_Config;
    m_Zone_Setting = zone_setting;
    m_NetSortingByPadCount = true;     // false = alphabetic sort, true = pad count sort
    m_OnExitCode = ZONE_ABORT;

    SetReturnCode( ZONE_ABORT ); // Will be changed on buttons click

    m_LayerSelectionCtrl = new wxListView( this, wxID_ANY,
                                           wxDefaultPosition, wxDefaultSize,
                                           wxLC_NO_HEADER | wxLC_REPORT
                                           | wxLC_SINGLE_SEL | wxRAISED_BORDER );
    wxListItem col0;
    col0.SetId( 0 );
    m_LayerSelectionCtrl->InsertColumn( 0, col0 );
    m_layerSizer->Add( m_LayerSelectionCtrl, 1,
                       wxGROW | wxLEFT | wxRIGHT | wxBOTTOM, 5 );

    // Fix static text widget minimum width to a suitable value so that
    // resizing the dialog is not necessary when changing the corner smoothing type.
    // Depends on the default text in the widget.
    m_cornerSmoothingTitle->SetMinSize( m_cornerSmoothingTitle->GetSize() );

    initDialog();

    GetSizer()->SetSizeHints( this );

    if( prevPosition.x != -1 )
        SetSize( prevPosition.x, prevPosition.y,
                 prevSize.x, prevSize.y );
    else
        Center();
}


void DIALOG_COPPER_ZONE::initDialog()
{
    BOARD* board = m_Parent->GetBoard();

    SetFocus();     // Required under wxGTK if we want to demiss the dialog with the ESC key

    wxString msg;

    if( g_Zone_45_Only )
        m_OrientEdgesOpt->SetSelection( 1 );

    m_FillModeCtrl->SetSelection( m_Zone_Setting->m_FillMode ? 1 : 0 );

    AddUnitSymbol( *m_ClearanceValueTitle, g_UserUnit );
    msg = ReturnStringFromValue( g_UserUnit,
                                 m_Zone_Setting->m_ZoneClearance,
                                 m_Parent->m_InternalUnits );
    m_ZoneClearanceCtrl->SetValue( msg );

    AddUnitSymbol( *m_MinThicknessValueTitle, g_UserUnit );
    msg = ReturnStringFromValue( g_UserUnit,
                                 m_Zone_Setting->m_ZoneMinThickness,
                                 m_Parent->m_InternalUnits );
    m_ZoneMinThicknessCtrl->SetValue( msg );

    switch( m_Zone_Setting->m_Zone_Pad_Options )
    {
    case PAD_NOT_IN_ZONE:           // Pads are not covered
        m_PadInZoneOpt->SetSelection( 2 );
        break;

    case THERMAL_PAD:               // Use thermal relief for pads
        m_PadInZoneOpt->SetSelection( 1 );
        break;

    case PAD_IN_ZONE:               // pads are covered by copper
        m_PadInZoneOpt->SetSelection( 0 );
        break;
    }

    if( m_Zone_Setting->m_Zone_Pad_Options != THERMAL_PAD )
    {
        m_AntipadSizeValue->Enable( false );
        m_CopperWidthValue->Enable( false );
    }
    else
    {
        m_AntipadSizeValue->Enable( true );
        m_CopperWidthValue->Enable( true );
    }

    AddUnitSymbol( *m_AntipadSizeText, g_UserUnit );
    AddUnitSymbol( *m_CopperBridgeWidthText, g_UserUnit );
    PutValueInLocalUnits( *m_AntipadSizeValue,
                          m_Zone_Setting->m_ThermalReliefGapValue,
                          PCB_INTERNAL_UNIT );
    PutValueInLocalUnits( *m_CopperWidthValue,
                          m_Zone_Setting->m_ThermalReliefCopperBridgeValue,
                          PCB_INTERNAL_UNIT );

    m_cornerSmoothingChoice->SetSelection( m_Zone_Setting->GetCornerSmoothingType() );

    PutValueInLocalUnits( *m_cornerSmoothingCtrl,
                          m_Zone_Setting->GetCornerRadius(),
                          PCB_INTERNAL_UNIT );

    switch( m_Zone_Setting->m_Zone_HatchingStyle )
    {
    case CPolyLine::NO_HATCH:
        m_OutlineAppearanceCtrl->SetSelection( 0 );
        break;

    case CPolyLine::DIAGONAL_EDGE:
        m_OutlineAppearanceCtrl->SetSelection( 1 );
        break;

    case CPolyLine::DIAGONAL_FULL:
        m_OutlineAppearanceCtrl->SetSelection( 2 );
        break;
    }

    m_ArcApproximationOpt->SetSelection(
        m_Zone_Setting->m_ArcToSegmentsCount == ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF ? 1 : 0 );

    // Build copper layer list and append to layer widget
    int layerCount = board->GetCopperLayerCount();
    int layerNumber, itemIndex, layerColor;
    wxImageList* imageList = new wxImageList( LAYER_BITMAP_SIZE_X, LAYER_BITMAP_SIZE_Y );
    m_LayerSelectionCtrl->AssignImageList( imageList, wxIMAGE_LIST_SMALL );
    for( int ii = 0; ii < layerCount; ii++ )
    {
        layerNumber = LAYER_N_BACK;

        if( layerCount <= 1 || ii < layerCount - 1 )
            layerNumber = ii;
        else if( ii == layerCount - 1 )
            layerNumber = LAYER_N_FRONT;

        m_LayerId.insert( m_LayerId.begin(), layerNumber );

        msg = board->GetLayerName( layerNumber ).Trim();
        layerColor = board->GetLayerColor( layerNumber );
        imageList->Add( makeLayerBitmap( layerColor ) );
        itemIndex = m_LayerSelectionCtrl->InsertItem( 0, msg, ii );

        if( m_Zone_Setting->m_CurrentZone_Layer == layerNumber )
            m_LayerSelectionCtrl->Select( itemIndex );
    }

    wxString netNameDoNotShowFilter = wxT( "N-*" );
    if( m_Config )
    {
        int opt = m_Config->Read( ZONE_NET_SORT_OPTION_KEY, 1l );
        m_NetDisplayOption->SetSelection( opt );
        m_Config->Read( ZONE_NET_FILTER_STRING_KEY, netNameDoNotShowFilter );
    }
    else
        m_NetDisplayOption->SetSelection( 1 );

    m_ShowNetNameFilter->SetValue( m_netNameShowFilter );
    initListNetsParams();

    // Build list of nets:
    m_DoNotShowNetNameFilter->SetValue( netNameDoNotShowFilter );
    buildAvailableListOfNets();

    wxCommandEvent event;
    OnCornerSmoothingModeChoice( event );
}


void DIALOG_COPPER_ZONE::OnButtonCancelClick( wxCommandEvent& event )
{
    Close( true );
}


void DIALOG_COPPER_ZONE::OnClose( wxCloseEvent& event )
{
    prevPosition = GetPosition();
    prevSize = GetSize();
    EndModal( m_OnExitCode );
}


void DIALOG_COPPER_ZONE::OnSize( wxSizeEvent& event )
{
    Layout();

    // Set layer list column width to widget width minus a few pixels
    m_LayerSelectionCtrl->SetColumnWidth( 0, m_LayerSelectionCtrl->GetSize().x - 5 );
    event.Skip();
}


void DIALOG_COPPER_ZONE::OnCornerSmoothingModeChoice( wxCommandEvent& event )
{
    int selection = m_cornerSmoothingChoice->GetSelection();

    switch( selection )
    {
    case ZONE_SETTING::SMOOTHING_NONE:
        m_cornerSmoothingTitle->Enable( false );
        m_cornerSmoothingCtrl->Enable( false );
        break;
    case ZONE_SETTING::SMOOTHING_CHAMFER:
        m_cornerSmoothingTitle->Enable( true );
        m_cornerSmoothingCtrl->Enable( true );
        m_cornerSmoothingTitle->SetLabel( _( "Chamfer distance" ) );
        AddUnitSymbol( *m_cornerSmoothingTitle, g_UserUnit );
        break;
    case ZONE_SETTING::SMOOTHING_FILLET:
        m_cornerSmoothingTitle->Enable( true );
        m_cornerSmoothingCtrl->Enable( true );
        m_cornerSmoothingTitle->SetLabel( _( "Fillet radius" ) );
        AddUnitSymbol( *m_cornerSmoothingTitle, g_UserUnit );
        break;
    }
}


bool DIALOG_COPPER_ZONE::AcceptOptions( bool aPromptForErrors, bool aUseExportableSetupOnly )
{
    switch( m_PadInZoneOpt->GetSelection() )
    {
    case 2:
        m_Zone_Setting->m_Zone_Pad_Options = PAD_NOT_IN_ZONE;       // Pads are not covered
        break;

    case 1:
        m_Zone_Setting->m_Zone_Pad_Options = THERMAL_PAD;           // Use thermal relief for pads
        break;

    case 0:
        m_Zone_Setting->m_Zone_Pad_Options = PAD_IN_ZONE;           // pads are covered by copper
        break;
    }

    switch( m_OutlineAppearanceCtrl->GetSelection() )
    {
    case 0:
        m_Zone_Setting->m_Zone_HatchingStyle = CPolyLine::NO_HATCH;
        break;

    case 1:
        m_Zone_Setting->m_Zone_HatchingStyle = CPolyLine::DIAGONAL_EDGE;
        break;

    case 2:
        m_Zone_Setting->m_Zone_HatchingStyle = CPolyLine::DIAGONAL_FULL;
        break;
    }

    m_Zone_Setting->m_ArcToSegmentsCount = m_ArcApproximationOpt->GetSelection() == 1 ?
                                           ARC_APPROX_SEGMENTS_COUNT_HIGHT_DEF :
                                           ARC_APPROX_SEGMENTS_COUNT_LOW_DEF;

    if( m_Config )
    {
        m_Config->Write( ZONE_NET_OUTLINES_HATCH_OPTION_KEY,
                         (long) m_Zone_Setting->m_Zone_HatchingStyle );
        wxString Filter = m_DoNotShowNetNameFilter->GetValue();
        m_Config->Write( ZONE_NET_FILTER_STRING_KEY, Filter );
    }

    m_netNameShowFilter = m_ShowNetNameFilter->GetValue();
    m_Zone_Setting->m_FillMode = (m_FillModeCtrl->GetSelection() == 0) ? 0 : 1;

    wxString txtvalue = m_ZoneClearanceCtrl->GetValue();
    m_Zone_Setting->m_ZoneClearance =
        ReturnValueFromString( g_UserUnit, txtvalue, m_Parent->m_InternalUnits );

    // Test if this is a reasonnable value for this parameter
    // A too large value can hang pcbnew
    #define CLEARANCE_MAX_VALUE 5000    // in 1/10000 inch
    if( m_Zone_Setting->m_ZoneClearance > CLEARANCE_MAX_VALUE )
    {
        DisplayError( this, _( "Clearance must be smaller than 0.5\" / 12.7 mm." ) );
        return false;
    }

    txtvalue = m_ZoneMinThicknessCtrl->GetValue();
    m_Zone_Setting->m_ZoneMinThickness =
        ReturnValueFromString( g_UserUnit, txtvalue, m_Parent->m_InternalUnits );
    if( m_Zone_Setting->m_ZoneMinThickness < 10 )
    {
        DisplayError( this,
                     _( "Minimum width must be larger than 0.001\" / 0.0254 mm." ) );
        return false;
    }

    m_Zone_Setting->SetCornerSmoothingType( m_cornerSmoothingChoice->GetSelection() );
    txtvalue = m_cornerSmoothingCtrl->GetValue();
    m_Zone_Setting->SetCornerRadius( ReturnValueFromString( g_UserUnit, txtvalue, m_Parent->m_InternalUnits ) );

    if( m_OrientEdgesOpt->GetSelection() == 0 )
        g_Zone_45_Only = FALSE;
    else
        g_Zone_45_Only = TRUE;

    m_Zone_Setting->m_ThermalReliefGapValue = ReturnValueFromTextCtrl( *m_AntipadSizeValue,
                                                                       PCB_INTERNAL_UNIT );
    m_Zone_Setting->m_ThermalReliefCopperBridgeValue = ReturnValueFromTextCtrl(
        *m_CopperWidthValue,
        PCB_INTERNAL_UNIT );

    m_Config->Write( ZONE_THERMAL_RELIEF_GAP_STRING_KEY,
                     (long) m_Zone_Setting->m_ThermalReliefGapValue );
    m_Config->Write(
        ZONE_THERMAL_RELIEF_COPPER_WIDTH_STRING_KEY,
        (long) m_Zone_Setting->m_ThermalReliefCopperBridgeValue );

    if( m_Zone_Setting->m_ThermalReliefCopperBridgeValue <= m_Zone_Setting->m_ZoneMinThickness )
    {
        DisplayError( this,
                     _( "Thermal relief spoke width is larger than the minimum width." ) );
        return false;
    }

    // If we use only exportable to others zones parameters, exit here:
    if( aUseExportableSetupOnly )
        return true;

    // Get the layer selection for this zone
    int ii = m_LayerSelectionCtrl->GetFirstSelected();

    if( ii < 0 && aPromptForErrors )
    {
        DisplayError( this, _( "No layer selected." ) );
        return false;
    }

    m_Zone_Setting->m_CurrentZone_Layer = m_LayerId[ii];

    // Get the net name selection for this zone
    ii = m_ListNetNameSelection->GetSelection();

    if( ii < 0 && aPromptForErrors )
    {
        DisplayError( this, _( "No net selected." ) );
        return false;
    }

    if( ii == 0 )   // the not connected option was selected: this is not a good practice: warn:
    {
        if( !IsOK( this, _(
                      "You have chosen the \"not connected\" option. This will create insulated copper islands. Are you sure ?" ) )
            )
            return false;
    }

    wxString net_name = m_ListNetNameSelection->GetString( ii );

    g_Zone_Default_Setting.m_NetcodeSelection = 0;

    // Search net_code for this net, if a net was selected
    if( m_ListNetNameSelection->GetSelection() > 0 )
    {
        NETINFO_ITEM* net = m_Parent->GetBoard()->FindNet( net_name );
        if( net )
            g_Zone_Default_Setting.m_NetcodeSelection = net->GetNet();
    }

    return true;
}


void DIALOG_COPPER_ZONE::OnNetSortingOptionSelected( wxCommandEvent& event )
{
    initListNetsParams();
    buildAvailableListOfNets();

    m_netNameShowFilter = m_ShowNetNameFilter->GetValue();
    if( m_Config )
    {
        m_Config->Write( ZONE_NET_SORT_OPTION_KEY, (long) m_NetDisplayOption->GetSelection() );
        wxString Filter = m_DoNotShowNetNameFilter->GetValue();
        m_Config->Write( ZONE_NET_FILTER_STRING_KEY, Filter );
    }
}


void DIALOG_COPPER_ZONE::OnButtonOkClick( wxCommandEvent& event )
{
    m_netNameShowFilter = m_ShowNetNameFilter->GetValue();
    prevPosition = GetPosition();
    prevSize = GetSize();

    if( AcceptOptions( true ) )
        EndModal( ZONE_OK );
}


void DIALOG_COPPER_ZONE::ExportSetupToOtherCopperZones( wxCommandEvent& event )
{
    prevPosition = GetPosition();
    prevSize = GetSize();

    if( !AcceptOptions( true, true ) )
        return;

    // Export settings ( but layer and netcode ) to others zones:
    BOARD* pcb = m_Parent->GetBoard();
    for( int ii = 0; ii < pcb->GetAreaCount(); ii++ )
    {
        ZONE_CONTAINER* zone = pcb->GetArea( ii );
        m_Zone_Setting->ExportSetting( *zone, false );  // false = partiel export
        m_Parent->OnModify();
    }

    m_OnExitCode = ZONE_EXPORT_VALUES;     // values are exported to others zones
}


void DIALOG_COPPER_ZONE::OnPadsInZoneClick( wxCommandEvent& event )
{
    switch( m_PadInZoneOpt->GetSelection() )
    {
    default:
        m_AntipadSizeValue->Enable( false );
        m_CopperWidthValue->Enable( false );
        break;

    case 1:
        m_AntipadSizeValue->Enable( true );
        m_CopperWidthValue->Enable( true );
        break;
    }
}


void DIALOG_COPPER_ZONE::initListNetsParams()
{
    switch( m_NetDisplayOption->GetSelection() )
    {
    case 0:
        m_NetSortingByPadCount = false;
        m_NetFiltering = false;
        break;

    case 1:
        m_NetSortingByPadCount = true;
        m_NetFiltering = false;
        break;

    case 2:
        m_NetSortingByPadCount = false;
        m_NetFiltering = true;
        break;

    case 3:
        m_NetSortingByPadCount = true;
        m_NetFiltering = true;
        break;
    }
}


void DIALOG_COPPER_ZONE::OnRunFiltersButtonClick( wxCommandEvent& event )
{
    m_netNameShowFilter = m_ShowNetNameFilter->GetValue();

    // Ensure filtered option for nets
    if( m_NetDisplayOption->GetSelection() == 0 )
        m_NetDisplayOption->SetSelection( 2 );
    else if( m_NetDisplayOption->GetSelection() == 1 )
        m_NetDisplayOption->SetSelection( 3 );
    initListNetsParams();
    buildAvailableListOfNets();
}


void DIALOG_COPPER_ZONE::buildAvailableListOfNets()
{
    wxArrayString listNetName;

    m_Parent->GetBoard()->ReturnSortedNetnamesList( listNetName, m_NetSortingByPadCount );

    if( m_NetFiltering )
    {
        wxString doNotShowFilter = m_DoNotShowNetNameFilter->GetValue();
        wxString ShowFilter = m_ShowNetNameFilter->GetValue();
        for( unsigned ii = 0; ii < listNetName.GetCount(); ii++ )
        {
            if( listNetName[ii].Matches( doNotShowFilter ) )
            {
                listNetName.RemoveAt( ii );
                ii--;
            }
            else if( !listNetName[ii].Matches( ShowFilter ) )
            {
                listNetName.RemoveAt( ii );
                ii--;
            }
        }
    }
    m_ListNetNameSelection->Clear();
    listNetName.Insert( wxT( "<no net>" ), 0 );
    m_ListNetNameSelection->InsertItems( listNetName, 0 );
    m_ListNetNameSelection->SetSelection( 0 );

    // Ensure current select net for the zone is visible:
    int net_select = m_Zone_Setting->m_NetcodeSelection;
    if( net_select > 0 )
    {
        NETINFO_ITEM* equipot = m_Parent->GetBoard()->FindNet( net_select );
        if( equipot )  // Search net in list and select it
        {
            for( unsigned ii = 0; ii < listNetName.GetCount(); ii++ )
            {
                if( listNetName[ii] == equipot->GetNetname() )
                {
                    m_ListNetNameSelection->SetSelection( ii );
                    m_ListNetNameSelection->EnsureVisible( ii );
                    break;
                }
            }
        }
    }
}


wxBitmap DIALOG_COPPER_ZONE::makeLayerBitmap( int aColor )
{
    wxBitmap    bitmap( LAYER_BITMAP_SIZE_X, LAYER_BITMAP_SIZE_Y );
    wxBrush     brush;
    wxMemoryDC  iconDC;

    iconDC.SelectObject( bitmap );
    brush.SetColour( MakeColour( aColor ) );
    brush.SetStyle( wxSOLID );
    iconDC.SetBrush( brush );
    iconDC.DrawRectangle( 0, 0, LAYER_BITMAP_SIZE_X, LAYER_BITMAP_SIZE_Y );

    return bitmap;
}
