/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2016 CERN
 * Copyright (C) 2022-2023 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * @author Maciej Suminski <maciej.suminski@cern.ch>
 *
 * 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, you may find one here:
 * https://www.gnu.org/licenses/gpl-3.0.html
 * or you may search the http://www.gnu.org website for the version 3 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */


#include <sim/simulator_frame_ui.h>
#include <sim/simulator_frame.h>
#include <sch_symbol.h>
#include <template_fieldnames.h>
#include <widgets/bitmap_button.h>
#include <widgets/std_bitmap_button.h>

#include <cmath>   // log log1p expm1
#include <complex> // norm

// Must be after other includes to avoid conflict with a window header on msys2
#include "tuner_slider.h"
#include "core/kicad_algo.h"

TUNER_SLIDER::TUNER_SLIDER( SIMULATOR_FRAME_UI* aFrame, wxWindow* aParent,
                            const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol ) :
        TUNER_SLIDER_BASE( aParent ),
        m_symbol( aSymbol->m_Uuid ),
        m_sheetPath( aSheetPath ),
        m_ref( aSymbol->GetRef( &aSheetPath ) ),
        m_min( 0.0 ),
        m_max( 0.0 ),
        m_value( 0.0 ),
        m_frame( aFrame )
{
    const SPICE_ITEM* item = m_frame->GetExporter()->FindItem( std::string( m_ref.ToUTF8() ) );

    if( !item )
        throw KI_PARAM_ERROR( wxString::Format( _( "%s not found" ), m_ref ) );

    m_name->SetLabel( wxString::Format( _( "Tune %s" ), m_ref ) );
    m_closeBtn->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );

    m_e24->SetBitmap( KiBitmapBundle( BITMAPS::e_24 ) );
    m_e24->SetIsCheckButton();
    m_separator->SetIsSeparator();
    m_e48->SetBitmap( KiBitmapBundle( BITMAPS::e_48 ) );
    m_e48->SetIsCheckButton();
    m_e96->SetBitmap( KiBitmapBundle( BITMAPS::e_96 ) );
    m_e96->SetIsCheckButton();
    m_e192->SetBitmap( KiBitmapBundle( BITMAPS::e_192 ) );
    m_e192->SetIsCheckButton();

    const SIM_MODEL::PARAM* tunerParam = item->model->GetTunerParam();

    if( !tunerParam )
    {
        throw KI_PARAM_ERROR( wxString::Format( _( "%s has simulation model of type '%s %s'; "
                                                   "only RLC passives be tuned" ),
                                                m_ref,
                                                item->model->GetDeviceInfo().fieldValue,
                                                item->model->GetTypeInfo().fieldValue ) );
    }

    // Special case for potentiometers because we don't have value ranges implemented yet.
    if( item->model->GetType() == SIM_MODEL::TYPE::R_POT )
    {
        std::string valueStr = SIM_VALUE::ToSpice( item->model->GetTunerParam()->value );

        if( valueStr != "" )
            m_value = SPICE_VALUE( valueStr );
        else
            m_value = SPICE_VALUE( "0.5" );

        m_min = SPICE_VALUE( 0 );
        m_max = SPICE_VALUE( 1 );
    }
    else
    {
        m_value = SPICE_VALUE( SIM_VALUE::ToSpice( item->model->GetTunerParam()->value ) );
        m_min = SPICE_VALUE( 0.5 ) * m_value;
        m_max = SPICE_VALUE( 2.0 ) * m_value;
    }

    m_minText->SetValue( m_min.ToOrigString() );
    m_maxText->SetValue( m_max.ToOrigString() );

    updateValueText();
    updateSlider();

    Layout();
}


void TUNER_SLIDER::ShowChangedLanguage()
{
    m_name->SetLabel( wxString::Format( _( "Tune %s" ), m_ref ) );
}


void TUNER_SLIDER::onESeries( wxCommandEvent& event )
{
    for( BITMAP_BUTTON* btn : { m_e24, m_e48, m_e96, m_e192 } )
    {
        if( btn != event.GetEventObject() )
            btn->Check( false );
    }

    wxString oldValue = m_valueText->GetValue();

    updateValueText();

    if( m_valueText->GetValue() != oldValue )
        updateComponentValue();

    event.Skip();
}


bool TUNER_SLIDER::SetValue( const SPICE_VALUE& aVal )
{
    // Get the value into the current range boundaries
    if( aVal > m_max )
        m_value = m_max;
    else if( aVal < m_min )
        m_value = m_min;
    else
        m_value = aVal;

    updateValueText();
    updateSlider();
    updateComponentValue();

    return true;
}


bool TUNER_SLIDER::SetMin( const SPICE_VALUE& aVal )
{
    if( aVal >= m_max )
        return false;

    m_min = aVal;

    if( m_value < aVal )      // Limit the current value
        SetValue( aVal );

    m_minText->SetValue( aVal.ToOrigString() );
    updateSlider();

    return true;
}


bool TUNER_SLIDER::SetMax( const SPICE_VALUE& aVal )
{
    if( aVal <= m_min )
        return false;

    m_max = aVal;

    if( m_value > aVal )      // Limit the current value
        SetValue( aVal );

    m_maxText->SetValue( aVal.ToOrigString() );
    updateSlider();

    return true;
}


void TUNER_SLIDER::updateComponentValue()
{
    wxQueueEvent( m_frame, new wxCommandEvent( EVT_SIM_UPDATE ) );
}


void TUNER_SLIDER::updateSlider()
{
    wxASSERT( m_max >= m_value && m_value >= m_min );
    double value = ( ( m_value - m_min ) / ( m_max - m_min ) ).ToDouble();
    m_slider->SetValue( KiROUND( value * 100.0 ) );
}


void TUNER_SLIDER::updateValueText()
{
    static std::vector<double> e24 = { 1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0,
                                       3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1 };

    static std::vector<double> e192 = { 1.00, 1.01, 1.02, 1.04, 1.05, 1.06, 1.07, 1.09, 1.10, 1.11,
                                        1.13, 1.14, 1.15, 1.17, 1.18, 1.20, 1.21, 1.23, 1.24, 1.26,
                                        1.27, 1.29, 1.30, 1.32, 1.33, 1.35, 1.37, 1.38, 1.40, 1.42,
                                        1.43, 1.45, 1.47, 1.49, 1.50, 1.52, 1.54, 1.56, 1.58, 1.60,
                                        1.62, 1.64, 1.65, 1.67, 1.69, 1.72, 1.74, 1.76, 1.78, 1.80,
                                        1.82, 1.84, 1.87, 1.89, 1.91, 1.93, 1.96, 1.98, 2.00, 2.03,
                                        2.05, 2.08, 2.10, 2.13, 2.15, 2.18, 2.21, 2.23, 2.26, 2.29,
                                        2.32, 2.34, 2.37, 2.40, 2.43, 2.46, 2.49, 2.52, 2.55, 2.58,
                                        2.61, 2.64, 2.67, 2.71, 2.74, 2.77, 2.80, 2.84, 2.87, 2.91,
                                        2.94, 2.98, 3.01, 3.05, 3.09, 3.12, 3.16, 3.20, 3.24, 3.28,
                                        3.32, 3.36, 3.40, 3.44, 3.48, 3.52, 3.57, 3.61, 3.65, 3.70,
                                        3.74, 3.79, 3.83, 3.88, 3.92, 3.97, 4.02, 4.07, 4.12, 4.17,
                                        4.22, 4.27, 4.32, 4.37, 4.42, 4.48, 4.53, 4.59, 4.64, 4.70,
                                        4.75, 4.81, 4.87, 4.93, 4.99, 5.05, 5.11, 5.17, 5.23, 5.30,
                                        5.36, 5.42, 5.49, 5.56, 5.62, 5.69, 5.76, 5.83, 5.90, 5.97,
                                        6.04, 6.12, 6.19, 6.26, 6.34, 6.42, 6.49, 6.57, 6.65, 6.73,
                                        6.81, 6.90, 6.98, 7.06, 7.15, 7.23, 7.32, 7.41, 7.50, 7.59,
                                        7.68, 7.77, 7.87, 7.96, 8.06, 8.16, 8.25, 8.35, 8.45, 8.56,
                                        8.66, 8.76, 8.87, 8.98, 9.09, 9.20, 9.31, 9.42, 9.53, 9.65,
                                        9.76, 9.88 };

    int      precision = 3;
    wxString prefix;
    double   value = m_value.ToNormalizedDouble( &prefix );

    bool e_24 = m_e24->IsChecked();
    bool e_extended = m_e48->IsChecked() || m_e96->IsChecked() || m_e192->IsChecked();

    if( e_24 || e_extended )
    {
        std::vector<double> table;
        table.reserve( 192 + 24 + 1 /* worst case */ );

        if( e_extended )
        {
            int step = m_e48->IsChecked() ? 4 : m_e96->IsChecked() ? 2 : 1;

            for( size_t ii = 0; ii < e192.size(); ii += step )
                table.push_back( e192[ii] );
        }

        if( e_24 )
            table.insert( table.end(), e24.begin(), e24.end() );

        table.push_back( 10.0 );

        std::sort( table.begin(), table.end() );
        alg::remove_duplicates( table );

        for( double decade : { 1.0, 10.0, 100.0 } )
        {
            for( size_t ii = 0; ii < table.size() - 1; ++ii )
            {
                if( value < ( table[ii] + table[ii+1] ) * decade / 2 )
                {
                    precision = 0;

                    if( decade == 1.0 )
                        precision++;

                    if( e_extended && decade != 100.0 )
                        precision++;

                    m_valueText->SetValue( wxString::Format( wxT( "%.*f%s" ),
                                                             precision,
                                                             table[ii] * decade,
                                                             prefix ) );
                    return;
                }
            }
        }
    }

    wxString valueStr = wxString::Format( wxT( "%.3f" ), value );
    SPICE_VALUE::StripZeros( valueStr );
    m_valueText->SetValue( valueStr + prefix );
}


void TUNER_SLIDER::updateMax()
{
    try
    {
        SPICE_VALUE newMax( m_maxText->GetValue() );
        SetMax( newMax );
    }
    catch( const KI_PARAM_ERROR& )
    {
        // Restore the previous value
        m_maxText->SetValue( m_max.ToOrigString() );
    }
}


void TUNER_SLIDER::updateValue()
{
    try
    {
        SPICE_VALUE newCur( m_valueText->GetValue() );
        SetValue( newCur );
    }
    catch( const KI_PARAM_ERROR& )
    {
        // Restore the previous value
        m_valueText->SetValue( m_value.ToOrigString() );
    }
}


void TUNER_SLIDER::updateMin()
{
    try
    {
        SPICE_VALUE newMin( m_minText->GetValue() );
        SetMin( newMin );
    }
    catch( const KI_PARAM_ERROR& )
    {
        // Restore the previous value
        m_minText->SetValue( m_min.ToOrigString() );
    }
}


void TUNER_SLIDER::onClose( wxCommandEvent& event )
{
    m_frame->RemoveTuner( this );
}


void TUNER_SLIDER::onSave( wxCommandEvent& event )
{
    m_frame->UpdateTunerValue( m_sheetPath, m_symbol, GetSymbolRef(), m_valueText->GetValue() );
}


void TUNER_SLIDER::onSliderScroll( wxScrollEvent& event )
{
    m_value = m_min + ( m_max - m_min ) * SPICE_VALUE( m_slider->GetValue() / 100.0 );
    updateValueText();
}


void TUNER_SLIDER::onSliderChanged( wxScrollEvent& event )
{
    m_value = m_min + ( m_max - m_min ) * SPICE_VALUE( m_slider->GetValue() / 100.0 );
    updateValueText();
    updateComponentValue();
}


void TUNER_SLIDER::onMaxKillFocus( wxFocusEvent& event )
{
    updateMax();
    event.Skip();       // Mandatory in wxFocusEvent
}


void TUNER_SLIDER::onValueKillFocus( wxFocusEvent& event )
{
    updateValue();
    event.Skip();       // Mandatory in wxFocusEvent
}


void TUNER_SLIDER::onMinKillFocus( wxFocusEvent& event )
{
    updateMin();
    event.Skip();       // Mandatory in wxFocusEvent
}


void TUNER_SLIDER::onMaxTextEnter( wxCommandEvent& event )
{
    updateMax();
    event.Skip();       // Mandatory in wxFocusEvent
}


void TUNER_SLIDER::onValueTextEnter( wxCommandEvent& event )
{
    updateValue();
}


void TUNER_SLIDER::onMinTextEnter( wxCommandEvent& event )
{
    updateMin();
}
