/*
 * This program source code file is part of KICAD, a free EDA CAD application.
 *
 * Copyright (C) 1992-2015 jean-pierre.charras
 * Copyright (C) 1992-2021 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 3
 * 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, see <http://www.gnu.org/licenses/>.
 */
#include <wx/msgdlg.h>
#include <bitmaps.h>
#include <geometry/shape_poly_set.h>
#include <kiface_base.h>
#include "attenuators/attenuator_classes.h"
#include "class_regulator_data.h"
#include "pcb_calculator_frame.h"
#include "pcb_calculator_settings.h"


// extension of pcb_calculator data filename:
const wxString DataFileNameExt( wxT( "pcbcalc" ) );


PCB_CALCULATOR_FRAME::PCB_CALCULATOR_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
    PCB_CALCULATOR_FRAME_BASE( aParent ),
    m_lastNotebookPage( -1 ),
    m_macHack( true )
{
    m_bpButtonCalcAtt->SetBitmap( KiBitmap( BITMAPS::small_down ) );
    m_bpButtonAnalyze->SetBitmap( KiBitmap( BITMAPS::small_down ) );
    m_bpButtonSynthetize->SetBitmap( KiBitmap( BITMAPS::small_up ) );

    SetKiway( this, aKiway );
    m_currTransLine     = nullptr;
    m_currTransLineType = DEFAULT_TYPE;
    m_currAttenuator    = nullptr;
    m_RegulatorListChanged = false;
    m_TWMode = TW_MASTER_CURRENT;
    m_TWNested = false;

    SHAPE_POLY_SET dummy;   // A ugly trick to force the linker to include
                            // some methods in code and avoid link errors

    // Populate transline list ordered like in dialog menu list
    const static TRANSLINE_TYPE_ID tltype_list[8] =
    {
        MICROSTRIP_TYPE,
        CPW_TYPE,
        GROUNDED_CPW_TYPE,
        RECTWAVEGUIDE_TYPE,
        COAX_TYPE,
        C_MICROSTRIP_TYPE,
        STRIPLINE_TYPE,
        TWISTEDPAIR_TYPE
    };

    for( int ii = 0; ii < 8; ii++ )
        m_transline_list.push_back( new TRANSLINE_IDENT( tltype_list[ii] ) );

    // Populate attenuator list ordered like in dialog menu list
    m_attenuator_list.push_back( new ATTENUATOR_PI() );
    m_attenuator_list.push_back( new ATTENUATOR_TEE() );
    m_attenuator_list.push_back( new ATTENUATOR_BRIDGE() );
    m_attenuator_list.push_back( new ATTENUATOR_SPLITTER() );
    m_currAttenuator = m_attenuator_list[0];

    m_staticTextAttMsg->SetFont( KIUI::GetInfoFont( this ).Italic() );

    m_IadjUnitLabel->SetLabel( wxT( "µA" ) );

    m_attZinUnit->SetLabel( wxT( "Ω" ) );
    m_attZoutUnit->SetLabel( wxT( "Ω" ) );
    m_attR1Unit->SetLabel( wxT( "Ω" ) );
    m_attR2Unit->SetLabel( wxT( "Ω" ) );
    m_attR3Unit->SetLabel( wxT( "Ω" ) );

    m_r1Units->SetLabel( wxT( "kΩ" ) );
    m_r2Units->SetLabel( wxT( "kΩ" ) );

    m_reqResUnits->SetLabel( wxT( "kΩ" ) );
    m_exclude1Units->SetLabel( wxT( "kΩ" ) );
    m_exclude2Units->SetLabel( wxT( "kΩ" ) );

    m_EpsilonR_label->SetLabel( wxT( "εr" ) );

    m_trackTempUnits->SetLabel( wxT( "°C" ) );
    m_resistivityUnits->SetLabel( wxT( "Ω•m" ) );

    m_viaResistivityUnits->SetLabel( wxT( "Ω•m" ) );

    m_viaTempUnits->SetLabel( wxT( "°C" ) );
    m_viaResUnits->SetLabel( wxT( "Ω" ) );
    m_viaThermalResUnits->SetLabel( wxT( "°C/W" ) );
    m_viaReactanceUnits->SetLabel( wxT( "Ω" ) );

    m_extTrackResUnits->SetLabel( wxT( "Ω" ) );
    m_intTrackResUnits->SetLabel( wxT( "Ω" ) );

    LoadSettings( config() );

    ReadDataFile();

    TranslineTypeSelection( m_currTransLineType );
    m_TranslineSelection->SetSelection( m_currTransLineType );

    initTrackWidthPanel();
    initColorCodePanel();
    initViaSizePanel();
    initESeriesPanel();

    SetAttenuator( m_AttenuatorsSelection->GetSelection() );

    ToleranceSelection( m_rbToleranceSelection->GetSelection() );

    BoardClassesUpdateData( m_BoardClassesUnitsSelector->GetUnitScale() );

    ElectricalSpacingUpdateData( m_ElectricalSpacingUnitsSelector->GetUnitScale() );

    m_choiceRegulatorSelector->Append( m_RegulatorList.GetRegList() );
    SelectLastSelectedRegulator();

    // Give an icon
    wxIcon icon;
    wxIconBundle icon_bundle;

    icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_pcbcalculator ) );
    icon_bundle.AddIcon( icon );
    icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_pcbcalculator_32 ) );
    icon_bundle.AddIcon( icon );
    icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_pcbcalculator_16 ) );
    icon_bundle.AddIcon( icon );

    SetIcons( icon_bundle );

    // Autosize the row label column to be sure label are not truncated
    m_gridClassesValuesDisplay->SetRowLabelSize( wxGRID_AUTOSIZE );
    m_gridElectricalSpacingValues->SetRowLabelSize( wxGRID_AUTOSIZE );

    GetSizer()->SetSizeHints( this );

    // Set previous size and position
    SetSize( m_framePos.x, m_framePos.y, m_frameSize.x, m_frameSize.y );

    if( m_framePos == wxDefaultPosition )
        Centre();
}


PCB_CALCULATOR_FRAME::~PCB_CALCULATOR_FRAME()
{
    for( unsigned ii = 0; ii < m_transline_list.size(); ii++ )
        delete m_transline_list[ii];

    for( unsigned ii = 0; ii < m_attenuator_list.size(); ii++ )
        delete m_attenuator_list[ii];

    delete m_ccValueNamesBitmap;
    delete m_ccValuesBitmap;
    delete m_ccMultipliersBitmap;
    delete m_ccTolerancesBitmap;

    // This needed for OSX: avoids further OnDraw processing after this destructor and before
    // the native window is destroyed
    this->Freeze();
}


void PCB_CALCULATOR_FRAME::OnUpdateUI( wxUpdateUIEvent& event )
{
    if( m_Notebook->GetSelection() != m_lastNotebookPage )
    {
        // Kick all the things that wxWidgets can't seem to redraw on its own.
        // This is getting seriously ridiculous....

        wxCommandEvent event2( wxEVT_RADIOBUTTON );
        event2.SetEventObject( m_TranslineSelection );
        event2.SetInt( m_currTransLineType );
        m_TranslineSelection->Command( event2 );

        for( int i = 0; i < m_attenuator_list.size(); ++i )
        {
            if( m_attenuator_list[i] == m_currAttenuator )
            {
                event2.SetEventObject( m_AttenuatorsSelection );
                event2.SetInt( i );
                m_AttenuatorsSelection->Command( event2 );
                break;
            }
        }

        ToleranceSelection( m_rbToleranceSelection->GetSelection() );

       	m_viaBitmap->SetBitmap( KiBitmap( BITMAPS::viacalc ) );
       	m_panelViaSize->Layout();

        m_attenuatorBitmap->SetBitmap( *m_currAttenuator->m_SchBitMap );
       	m_bitmapRegul3pins->SetBitmap( KiBitmap( BITMAPS::regul_3pins ) );
       	m_bitmapRegul4pins->SetBitmap( KiBitmap( BITMAPS::regul ) );
       	m_panelRegulators->Layout();

       	m_attenuatorBitmap->GetParent()->Layout();
       	m_attenuatorBitmap->GetParent()->Refresh();

        m_panelESeriesHelp->Refresh();
        m_htmlWinFormulas->Refresh();

        // Until it's shown on screen the above won't work; but doing it anyway at least keeps
        // putting new OnUpdateUI events into the queue until it *is* shown on screen.
        if( m_Notebook->IsShownOnScreen() )
        {
            // Work around an OSX bug where the wxGrid children don't get placed correctly until
            // the first resize event.
#ifdef __WXMAC__
            if( m_macHack )
            {
                wxSize pageSize = m_panelElectricalSpacing->GetSize();

                pageSize.x -= 100;
                m_panelElectricalSpacing->SetSize( pageSize );
                m_panelElectricalSpacing->Layout();

                pageSize.x += 100;
                m_panelElectricalSpacing->SetSize( pageSize );
                m_panelElectricalSpacing->Layout();

                m_macHack = false;
            }
#endif

            m_lastNotebookPage = m_Notebook->GetSelection();
        }
    }
}


void PCB_CALCULATOR_FRAME::OnClosePcbCalc( wxCloseEvent& event )
{
    if( m_RegulatorListChanged )
    {
        wxString msg;
        wxString title = _( "Write Data Failed" );

        if( GetDataFilename().IsEmpty() )
        {
            msg = _( "No data filename to save modifications.\n"
                     "Do you want to exit and abandon your changes?" );

            if( wxMessageBox( msg, title, wxYES_NO | wxICON_QUESTION ) == wxNO )
                return;
        }
        else
        {
            if( !WriteDataFile() )
            {
                msg.Printf( _( "Unable to write file '%s'\n"
                               "Do you want to exit and abandon your changes?"),
                            GetDataFilename() );

                if( wxMessageBox( msg, title, wxYES_NO | wxICON_ERROR ) == wxNO )
                    return;
            }
        }
    }

    event.Skip();
}


void PCB_CALCULATOR_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
{
    if( aCfg == nullptr )
        return;

    EDA_BASE_FRAME::LoadSettings( aCfg );

    PCB_CALCULATOR_SETTINGS* cfg = static_cast<PCB_CALCULATOR_SETTINGS*>( aCfg );

    m_currTransLineType = static_cast<TRANSLINE_TYPE_ID>( cfg->m_TransLine.type );
    m_Notebook->ChangeSelection( cfg->m_LastPage );
    m_rbToleranceSelection->SetSelection( cfg->m_ColorCodeTolerance );
    m_AttenuatorsSelection->SetSelection( cfg->m_Attenuators.type );
    m_BoardClassesUnitsSelector->SetSelection( cfg->m_BoardClassUnits );

    // Regul panel config:
    m_RegulR1Value->SetValue( cfg->m_Regulators.r1 );
    m_RegulR2Value->SetValue( cfg->m_Regulators.r2 );
    m_RegulVrefValue->SetValue( cfg->m_Regulators.vref );
    m_RegulVoutValue->SetValue( cfg->m_Regulators.vout );
    SetDataFilename( cfg->m_Regulators.data_file );
    m_lastSelectedRegulatorName = cfg->m_Regulators.selected_regulator;
    m_choiceRegType->SetSelection( cfg->m_Regulators.type );

    wxRadioButton* regprms[3] = { m_rbRegulR1, m_rbRegulR2, m_rbRegulVout };

    if( cfg->m_Regulators.last_param >= 3 )
        cfg->m_Regulators.last_param = 0;

    for( int ii = 0; ii < 3; ii++ )
        regprms[ii]->SetValue( cfg->m_Regulators.last_param == ii );

    // Electrical panel config
    m_ElectricalSpacingUnitsSelector->SetSelection( cfg->m_Electrical.spacing_units );
    m_ElectricalSpacingVoltage->SetValue( cfg->m_Electrical.spacing_voltage );

    for( TRANSLINE_IDENT* transline : m_transline_list )
        transline->ReadConfig();

    for( ATTENUATOR* attenuator : m_attenuator_list )
        attenuator->ReadConfig();
}


void PCB_CALCULATOR_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
{
    if( aCfg == nullptr )
        return;

    EDA_BASE_FRAME::SaveSettings( aCfg );

    // Save current parameters values in config.
    auto cfg = dynamic_cast<PCB_CALCULATOR_SETTINGS*>( Kiface().KifaceSettings() );

    if( cfg )
    {
        cfg->m_LastPage = m_Notebook->GetSelection();
        cfg->m_TransLine.type = m_currTransLineType;
        cfg->m_Attenuators.type = m_AttenuatorsSelection->GetSelection();
        cfg->m_ColorCodeTolerance = m_rbToleranceSelection->GetSelection();
        cfg->m_BoardClassUnits = m_BoardClassesUnitsSelector->GetSelection();

        cfg->m_Electrical.spacing_units = m_ElectricalSpacingUnitsSelector->GetSelection();
        cfg->m_Electrical.spacing_voltage = m_ElectricalSpacingVoltage->GetValue();

        Regulators_WriteConfig( cfg );
    }

    writeTrackWidthConfig();

    writeViaSizeConfig();

    for( unsigned ii = 0; ii < m_transline_list.size(); ii++ )
        m_transline_list[ii]->WriteConfig();

    for( unsigned ii = 0; ii < m_attenuator_list.size(); ii++ )
        m_attenuator_list[ii]->WriteConfig();
}


void PCB_CALCULATOR_FRAME::OnTranslineAnalyse( wxCommandEvent& event )
{
    if( m_currTransLine )
    {
        TransfDlgDataToTranslineParams();
        m_currTransLine->analyze();
    }
}


void PCB_CALCULATOR_FRAME::OnTranslineSynthetize( wxCommandEvent& event )
{
    if( m_currTransLine )
    {
        TransfDlgDataToTranslineParams();
        m_currTransLine->synthesize();
    }
}


const wxString PCB_CALCULATOR_FRAME::GetDataFilename()
{
    if( m_regulators_fileNameCtrl->GetValue().IsEmpty() )
        return wxEmptyString;

    wxFileName fn( m_regulators_fileNameCtrl->GetValue() );
    fn.SetExt( DataFileNameExt );
    return fn.GetFullPath();
}


void PCB_CALCULATOR_FRAME::SetDataFilename( const wxString& aFilename )
{
    if( aFilename.IsEmpty() )
    {
        m_regulators_fileNameCtrl->SetValue( wxEmptyString );
    }
    else
    {
        wxFileName fn( aFilename );
        fn.SetExt( DataFileNameExt );
        m_regulators_fileNameCtrl->SetValue( fn.GetFullPath() );
    }
}
