/*
 * Copyright The 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 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 package; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef TRANSLINE_CALCULATIONS_TRANSLINE_CALCULATION_BASE_H
#define TRANSLINE_CALCULATIONS_TRANSLINE_CALCULATION_BASE_H


#include <cmath>
#include <unordered_map>


/// All possible parameters used (as inputs or outputs) by the transmission line calculations
enum class TRANSLINE_PARAMETERS : int
{
    // The following parameters are accessible from the UI
    UNKNOWN_ID = -1,
    EPSILONR,                 // Dielectric constant
    TAND,                     // Dielectric Loss Tangent
    RHO,                      // Conductivity of conductor
    H,                        // Height of substrate
    H_T,                      // Height of top surface
    T,                        // Height of top conductor
    PHYS_WIDTH,               // Width of trace
    PHYS_DIAM_IN,             // Inner diameter of cable
    PHYS_S,                   // width of gap between line and ground
    PHYS_DIAM_OUT,            // Outer diameter of cable
    PHYS_LEN,                 // Length of cable
    ROUGH,                    // Surface roughness
    MUR,                      // Magnetic permeability of substrate
    MURC,                     // magnetic permeability of conductor
    FREQUENCY,                // Frequency of operation
    STRIPLINE_A,              // Stripline : distance from track to top plane
    TWISTEDPAIR_TWIST,        // Twists per length
    TWISTEDPAIR_EPSILONR_ENV, // Dielectric constant of environment
    Z0,                       // Characteristic impedance
    Z0_E,                     // Even-mode characteristic impedance
    Z0_O,                     // Odd-mode characteristic impedance
    ANG_L,                    // Electrical length in angle
    DUMMY_PRM,

    // The following parameters are generated by calculations
    SIGMA,                // Conductivity of the metal
    SKIN_DEPTH,           // Skin depth
    LOSS_DIELECTRIC,      // Loss in dielectric (dB)
    LOSS_CONDUCTOR,       // Loss in conductors (dB)
    CUTOFF_FREQUENCY,     // Cutoff frequency for higher order modes
    EPSILON_EFF,          // Effective dielectric constant
    EPSILON_EFF_EVEN,     // Even mode effective dielectric constant
    EPSILON_EFF_ODD,      // Odd mode effective dielectric constant
    UNIT_PROP_DELAY,      // The unit propagation delay (ps/cm)
    UNIT_PROP_DELAY_ODD,  // The odd mode unit propagation delay (ps/cm)
    UNIT_PROP_DELAY_EVEN, // The even mode unit propagation delay (ps/cm)
    ATTEN_COND,           // The attenuation of the conductor
    ATTEN_COND_EVEN,      // The even mode attenuation of the conductor
    ATTEN_COND_ODD,       // The odd mode attenuation of the conductor
    ATTEN_DILECTRIC,      // The attenuation of the dilectric
    ATTEN_DILECTRIC_EVEN, // The even mode attenuation of the dilectric
    ATTEN_DILECTRIC_ODD,  // The odd mode attenuation of the dilectric
    Z_DIFF,               // The differential impedance
    EXTRAS_COUNT
};


/// Options for specifying synthesis inputs, targets, or strategies
enum class SYNTHESIZE_OPTS
{
    DEFAULT,    // Use the default synthesis options for the calculation
    FIX_WIDTH,  // Fixes the width of a differential pair
    FIX_SPACING // Fixes the spacing of a differential pair
};


/// Parameter status values
enum class TRANSLINE_STATUS
{
    OK,
    WARNING,
    TS_ERROR        // ERROR name is colliding with a define on Windows
};


/**
* The base class for all transmission line calculations.
* This is used by the PCB calculator, and by delay profiles
*/
class TRANSLINE_CALCULATION_BASE
{
public:
    /**
    * Constructs the transmission line calculation object.
    *
    * @aParameters all parameters that are used (as inputs or outputs) by the calculation
    */
    TRANSLINE_CALCULATION_BASE( std::initializer_list<TRANSLINE_PARAMETERS> aParameters )
    {
        InitProperties( aParameters );
    }

    virtual ~TRANSLINE_CALCULATION_BASE() {}

    /// Sets the given calculation property
    void SetParameter( const TRANSLINE_PARAMETERS aParam, const double aValue ) { m_parameters.at( aParam ) = aValue; }

    /// Gets the given calculation property
    double GetParameter( const TRANSLINE_PARAMETERS aParam ) const { return m_parameters.at( aParam ); }

    /// Adds a constant to the given parameter
    double& GetParameterRef( const TRANSLINE_PARAMETERS aParam ) { return m_parameters[aParam]; }

    /// Gets the output parameters following analysis
    std::unordered_map<TRANSLINE_PARAMETERS, std::pair<double, TRANSLINE_STATUS>>& GetAnalysisResults();

    /// Gets the output parameters following synthesis
    std::unordered_map<TRANSLINE_PARAMETERS, std::pair<double, TRANSLINE_STATUS>>& GetSynthesisResults();

    /// Analyses the transmission line using the current parameter set
    virtual void Analyse() = 0;

    /// Synthesis the transmission line using the current parameter set target
    /// @returns true if the synthesis converged, otherwise false
    virtual bool Synthesize( SYNTHESIZE_OPTS aOpts ) = 0;

protected:
    /// Initialises the properties used (as inputs or outputs) by the calculation
    void InitProperties( const std::initializer_list<TRANSLINE_PARAMETERS>& aParams );

    /// Sets values in the output analysis results structure
    virtual void SetAnalysisResults() = 0;

    /// Sets values in the output synthesis results structure
    virtual void SetSynthesisResults() = 0;

    /// Sets an analysis result
    void SetAnalysisResult( TRANSLINE_PARAMETERS aParam, const double aValue,
                            const TRANSLINE_STATUS aStatus = TRANSLINE_STATUS::OK );

    /// Sets a synthesis result
    void SetSynthesisResult( TRANSLINE_PARAMETERS aParam, const double aValue,
                             const TRANSLINE_STATUS aStatus = TRANSLINE_STATUS::OK );

    /**
     * minimizeZ0Error1D
     *
     * Tries to find a parameter that minimizes the error on Z0.
     * This function only works with a single parameter.
     * Calls @ref Analyse several times until the error is acceptable.
     *
     * This function does not change Z0 / Angl_L
     *
     * @param aOptimise Parameter to optimise
     * @param aMeasure The parameter to measure / optimise against
     * @param aRecalculateLength True if the angular length should be recalculated (not for differential pair usage)
     * @returns true if error < MAX_ERROR, otherwise false
     */
    bool MinimiseZ0Error1D( TRANSLINE_PARAMETERS aOptimise, TRANSLINE_PARAMETERS aMeasure,
                            bool aRecalculateLength = false );

    /**
     * minimizeZ0Error2D
     *
     * Tries to find the parameters that minimizes the error on Z_Diff.
     * This function only works with two parameters.
     * Calls @ref Analyse several times until the error is acceptable.
     * While the error is unacceptable, changes slightly the parameter.
     *
     * This function does not change Z0 / Angl_L
     *
     * @param aParam1 First parameter to optimise
     * @param aParam2 Secpmd parameter to optimise
     * @returns true if error < MAX_ERROR, otherwise false
     */
    bool MinimiseZ0Error2D( TRANSLINE_PARAMETERS aParam1, TRANSLINE_PARAMETERS aParam2 );

    /**
    * Calculate skin depth
    *
    * \f$ \frac{1}{\sqrt{ \pi \cdot f \cdot \mu \cdot \sigma }} \f$
    */
    double SkinDepth() const;

    /**
    * Calculates the unit propagation delay (ps/cm) for the given effective permittivity
    *
    * @aEpsilonEff is the effective permittivity of the material
    */
    static double UnitPropagationDelay( double aEpsilonEff );

    /**
     * Computes the complete elliptic integral of first kind K() and the second kind E() using the
     * arithmetic-geometric mean algorithm (AGM) by Abramowitz and Stegun.
     *
     * @returns std::pair<K, E> where K = first kind integral, E = second kind integral
     */
    static std::pair<double, double> EllipticIntegral( double arg );

    /// Calculates cosh of the given argument
    static double coth( const double x ) { return ( std::exp( 2.0 * x ) + 1.0 ) / ( std::exp( 2.0 * x ) - 1.0 ); }

    /// Calculates sech of the given argument
    static double sech( const double x ) { return 2.0 / ( std::exp( x ) + std::exp( -x ) ); }

    /// All input and output properties used by the calculation
    std::unordered_map<TRANSLINE_PARAMETERS, double> m_parameters;

    /// Analysis results
    std::unordered_map<TRANSLINE_PARAMETERS, std::pair<double, TRANSLINE_STATUS>> m_analysisStatus;

    /// Synthesis results
    std::unordered_map<TRANSLINE_PARAMETERS, std::pair<double, TRANSLINE_STATUS>> m_synthesisStatus;

    /// The maximum error for Z0 optimisations
    static constexpr double m_maxError{ 0.000001 };
};

#endif //TRANSLINE_CALCULATIONS_TRANSLINE_CALCULATION_BASE_H
