/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2022 Fabien Corona f.corona<at>laposte.net
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific
 * prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */


#ifndef IBIS_PARSER_H
#define IBIS_PARSER_H

#define _( x ) x

#define NAN_NA "1"
#define NAN_INVALID "0"

#define IBIS_MAX_VERSION 7.0      // Up to v7.0, IBIS is fully backward compatible
#define IBIS_MAX_LINE_LENGTH 2048 // official limit is 1024

#include <wx/string.h>
#include <reporter.h>
//#include "common.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <math.h>
#include <cstring>


class IBIS_REPORTER
{
public:
    /** @brief Print a message
     * 
     * In the future, this function could do more than just printing a message.
     * All KIBIS messages are concentrated at a single point in the code. 
     * 
     * @param aMsg Message
     * @param aSeverity Message sevirity
     */
    void Report( std::string aMsg, SEVERITY aSeverity ) { std::cout << aMsg << std::endl; };
};

class IBIS_ANY
{
public:
    IBIS_ANY( IBIS_REPORTER* aReporter ) { m_reporter = aReporter; };
    IBIS_REPORTER* m_reporter;

    /** @brief Print a message
     * 
     * Call m_reporter->Report if m_reporter exists.
     * 
     * @param aMsg Message
     * @param aSeverity Message sevirity
     */
    void           Report( std::string aMsg, SEVERITY aSeverity = RPT_SEVERITY_INFO )
    {
        if( m_reporter )
        {
            m_reporter->Report( aMsg, aSeverity );
        }
    };
protected:
    /** @brief Convert a double to string using scientific notation
     * 
     * @param aNumber Number
     * @return Output string
     */
    std::string doubleToString( double aNumber );
};


class IBIS_INPUT : public IBIS_ANY
{
public:
    IBIS_INPUT( IBIS_REPORTER* aReporter ) : IBIS_ANY( aReporter ){};
    /** @brief Check if the data held by the object is valid. 
     * 
     * @return true in case of success
     */
    bool virtual Check() { return false; };
};


enum IBIS_CORNER
{
    TYP = 0,
    MIN,
    MAX
};


enum class IBIS_MATRIX_TYPE
{
    // All matrices are supposed to be symmetrical, only upper right triangle is given
    UNDEFINED,
    BANDED, // Give the main diagonal + [bandwidth] elements on the right
    SPARSE, // Only give non-zero values.
    FULL,   // Give the whole upper triangle.
};

class IBIS_MATRIX : public IBIS_INPUT
{
public:
    IBIS_MATRIX( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
    virtual ~IBIS_MATRIX(){};

    IBIS_MATRIX_TYPE m_type = IBIS_MATRIX_TYPE::UNDEFINED;
    int              m_dim = -5;
    std::vector<double> m_data;
};

class IBIS_MATRIX_BANDED : public IBIS_MATRIX
{
public:
    IBIS_MATRIX_BANDED( IBIS_REPORTER* aReporter ) : IBIS_MATRIX( aReporter ){};
    IBIS_MATRIX_TYPE m_type = IBIS_MATRIX_TYPE::BANDED;
    int              m_dim = -2;
    int              m_bandwidth = 0;
    std::vector<double> m_data;

    bool Check() override;
};

class IBIS_MATRIX_SPARSE : public IBIS_MATRIX
{
public:
    IBIS_MATRIX_SPARSE( IBIS_REPORTER* aReporter ) : IBIS_MATRIX( aReporter ){};
    IBIS_MATRIX_TYPE m_type = IBIS_MATRIX_TYPE::BANDED;
    int              m_dim = -3;
    std::vector<double> m_data;

    bool Check() override;
};


class IBIS_MATRIX_FULL : public IBIS_MATRIX
{
public:
    IBIS_MATRIX_FULL( IBIS_REPORTER* aReporter ) : IBIS_MATRIX( aReporter ){};
    IBIS_MATRIX_TYPE m_type = IBIS_MATRIX_TYPE::FULL;
    int              m_dim = -4;
    std::vector<double> m_data;

    bool Check() override;
};


class IBIS_SECTION : public IBIS_INPUT
{
public:
    IBIS_SECTION( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
};


class IbisHeader : IBIS_SECTION
{
public:
    IbisHeader( IBIS_REPORTER* aReporter ) : IBIS_SECTION( aReporter ){};
    double   m_ibisVersion = -1;
    double   m_fileRevision = -1;
    std::string m_fileName;
    std::string m_source;
    std::string m_date;
    std::string m_notes;
    std::string m_disclaimer;
    std::string m_copyright;

    bool Check() override;
};


class TypMinMaxValue : public IBIS_INPUT
{
public:
    TypMinMaxValue( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
    double value[3] = { -1, -1, -1 };

    bool Check() override;
};


class IbisComponentPackage : public IBIS_INPUT
{
public:
    IbisComponentPackage( IBIS_REPORTER* aReporter ) :
        IBIS_INPUT( aReporter ),
        m_Rpkg( aReporter ),
        m_Lpkg( aReporter ),
        m_Cpkg( aReporter )
        {};

    TypMinMaxValue m_Rpkg;
    TypMinMaxValue m_Lpkg;
    TypMinMaxValue m_Cpkg;

    bool Check() override;
};


class IbisComponentPin : public IBIS_INPUT
{
public:
    IbisComponentPin( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};

    std::string m_pinName;
    std::string m_signalName;
    std::string m_modelName;
    double   m_Rpin = nan( NAN_NA );
    double   m_Lpin = nan( NAN_NA );
    double   m_Cpin = nan( NAN_NA );

    int m_Rcol = 0;
    int m_Lcol = 0;
    int m_Ccol = 0;

    bool Check() override;

    bool m_dummy = false;
};


class IbisComponentPinMapping : public IBIS_INPUT
{
public:
    IbisComponentPinMapping( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
    std::string m_pinName;
    std::string m_PDref;
    std::string m_PUref;
    std::string m_GNDClampRef;
    std::string m_POWERClampRef;
    std::string m_extRef;

    bool m_virtual = false;
};


class IbisDiffPinEntry : public IBIS_INPUT
{
public:
    IbisDiffPinEntry( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ), tdelay( aReporter ){};

    std::string     pinA;
    std::string     pinB;
    double          Vdiff = 0.2; // ignored for input
    TypMinMaxValue  tdelay = 0;  // ignored for outputs
};


class IbisDiffPin : IBIS_INPUT
{
public:
    IbisDiffPin( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
    std::vector<IbisDiffPinEntry> m_entries;
};

class IbisComponent : public IBIS_INPUT
{
public:
    IbisComponent( IBIS_REPORTER* aReporter ) :
        IBIS_INPUT( aReporter ),
        m_package( aReporter ),
        m_diffPin( aReporter )
        {};

    std::string                             m_name = "";
    std::string                             m_manufacturer = "";
    IbisComponentPackage                    m_package;
    std::vector<IbisComponentPin>           m_pins;
    std::vector<IbisComponentPinMapping>    m_pinMappings;
    std::string                             m_packageModel;
    std::string                             m_busLabel;
    std::string                             m_dieSupplyPads;
    IbisDiffPin                             m_diffPin;

    bool Check() override;
};


class IbisModelSelectorEntry
{
public:
    std::string m_modelName;
    std::string m_modelDescription;
};


class IbisModelSelector : public IBIS_INPUT
{
public:
    IbisModelSelector( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
    std::string                            m_name;
    std::vector<IbisModelSelectorEntry> m_models;

    bool Check() override;
};


class IVtableEntry : public IBIS_INPUT
{
public:
    IVtableEntry( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ), I( aReporter ){};
    double         V = 0;
    TypMinMaxValue I;
};


class IVtable : public IBIS_INPUT
{
public:
    IVtable( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
    std::vector<IVtableEntry> m_entries;

    bool Check() override;

    /** @brief Interpolate the IV table
     * 
     * Linear interpolation to find the current for voltage aV
     * 
     * @param aV voltage
     * @param aCorner Power supply corner
     * @return current
     */
    double   InterpolatedI( double aV, IBIS_CORNER aCorner );

    /** @brief Interpolate the IV table
     * 
     * Generate the spice directive needed to define a model defined by its IV table.
     * The order of aPort1 and aPort2 is important. ( Inverting them will reverse the component )
     * 
     * @param aN Index of the 'a' device
     * @param aPort1 Spice node
     * @param aPort2 Spice node
     * @param aPort2 Name of the generated model
     * @param aCorner Power supply corner
     * @return Multline spice directives
     */
    std::string Spice( int aN, std::string aPort1, std::string aPort2, std::string aModelName,
                    IBIS_CORNER aCorner );

private:
};

class VTtableEntry : public IBIS_INPUT
{
public:
    VTtableEntry( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ), V( aReporter ){};
    double         t = 0;
    TypMinMaxValue V = 0;
};

class VTtable : public IBIS_INPUT
{
public:
    VTtable( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
    std::vector<VTtableEntry> m_entries;
};

/*
Model_type must be one of the following: 
Input, Output, I/O, 3-state, Open_drain, I/O_open_drain, Open_sink, I/O_open_sink, 
Open_source, I/O_open_source, Input_ECL, Output_ECL, I/O_ECL, 3-state_ECL, Terminator, 
Series, and Series_switch. 
*/

enum class IBIS_MODEL_TYPE
{
    UNDEFINED,
    INPUT,
    OUTPUT,
    IO,
    THREE_STATE,
    OPEN_DRAIN,
    IO_OPEN_DRAIN,
    OPEN_SINK,
    IO_OPEN_SINK,
    OPEN_SOURCE,
    IO_OPEN_SOURCE,
    INPUT_ECL,
    OUTPUT_ECL,
    IO_ECL,
    THREE_STATE_ECL,
    TERMINATOR,
    SERIES,
    SERIES_SWITCH
};

enum class IBIS_MODEL_ENABLE
{
    UNDEFINED,
    ACTIVE_HIGH,
    ACTIVE_LOW
};

class dvdt
{
public:
    double m_dv = 1;
    double m_dt = 1;
};

class dvdtTypMinMax : public IBIS_INPUT
{
public:
    dvdtTypMinMax( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};
    dvdt value[3];

    bool Check() override;
};


class IbisRamp : public IBIS_INPUT
{
public:
    IbisRamp( IBIS_REPORTER* aReporter ) :
        IBIS_INPUT( aReporter ),
        m_falling( aReporter ),
        m_rising( aReporter )
        {};

    dvdtTypMinMax m_falling;
    dvdtTypMinMax m_rising;
    double m_Rload = 50; // The R_load subparameter is optional if the default 50 ohm load is used

    bool Check() override;
};

enum class IBIS_WAVEFORM_TYPE
{
    RISING,
    FALLING
};

class IbisWaveform : public IBIS_INPUT
{
public:
    IbisWaveform( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ), m_table( aReporter ){};

    VTtable            m_table;
    IBIS_WAVEFORM_TYPE m_type = IBIS_WAVEFORM_TYPE::RISING;
    double             m_R_dut = 0;
    double             m_C_dut = 0;
    double             m_L_dut = 0;
    double             m_R_fixture = 0;
    double             m_C_fixture = 0;
    double             m_L_fixture = 0;
    double             m_V_fixture = 0;
    double             m_V_fixture_min = 0;
    double             m_V_fixture_max = 0;
};

enum class IBIS_MODEL_POLARITY
{
    UNDEFINED,
    INVERTING,
    NON_INVERTING
};

class IbisModel : IBIS_INPUT
{
public:
    IbisModel( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ),
    m_C_comp( aReporter ),
    m_voltageRange( aReporter ),
    m_temperatureRange( aReporter ),
    m_pullupReference( aReporter ),
    m_pulldownReference( aReporter ),
    m_GNDClampReference( aReporter ),
    m_POWERClampReference( aReporter ),
    m_Rgnd( aReporter ),
    m_Rpower( aReporter ),
    m_Rac( aReporter ),
    m_Cac( aReporter ),
    m_GNDClamp( aReporter ),
    m_POWERClamp( aReporter ),
    m_pullup( aReporter ),
    m_pulldown( aReporter ),
    m_ramp( aReporter )
    {};

    std::string        m_name;
    IBIS_MODEL_TYPE m_type = IBIS_MODEL_TYPE::UNDEFINED;
    /* The Polarity, Enable, Vinl, Vinh, Vmeas, Cref, Rref, and Vref subparameters are optional. */
    /* the default values of Vinl = 0.8 V and Vinh = 2.0 V are assumed. */
    double              m_vinl = 0.8;
    double              m_vinh = 2;
    double              m_vref = 0;
    double              m_rref = 0;
    double              m_cref = 0;
    double              m_vmeas = 0;
    IBIS_MODEL_ENABLE   m_enable = IBIS_MODEL_ENABLE::UNDEFINED;
    IBIS_MODEL_POLARITY m_polarity = IBIS_MODEL_POLARITY::UNDEFINED;
    // End of optional subparameters

    TypMinMaxValue             m_C_comp;
    TypMinMaxValue             m_voltageRange;
    TypMinMaxValue             m_temperatureRange;
    TypMinMaxValue             m_pullupReference;
    TypMinMaxValue             m_pulldownReference;
    TypMinMaxValue             m_GNDClampReference;
    TypMinMaxValue             m_POWERClampReference;
    TypMinMaxValue             m_Rgnd;
    TypMinMaxValue             m_Rpower;
    TypMinMaxValue             m_Rac;
    TypMinMaxValue             m_Cac;
    IVtable                    m_GNDClamp;
    IVtable                    m_POWERClamp;
    IVtable                    m_pullup;
    IVtable                    m_pulldown;
    std::vector<IbisWaveform*> m_risingWaveforms;
    std::vector<IbisWaveform*> m_fallingWaveforms;
    IbisRamp                   m_ramp;

    bool Check() override;
};


class IbisPackageModel : public IBIS_INPUT
{
public:
    IbisPackageModel( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ){};

    std::string              m_name;
    std::string              m_manufacturer;
    std::string              m_OEM;
    std::string              m_description;
    int                      m_numberOfPins = 0;
    std::vector<std::string> m_pins;

    std::shared_ptr<IBIS_MATRIX> m_resistanceMatrix;
    std::shared_ptr<IBIS_MATRIX> m_capacitanceMatrix;
    std::shared_ptr<IBIS_MATRIX> m_inductanceMatrix;

    bool Check() override;
};

class IbisFile : public IBIS_INPUT
{
public:
    IbisFile( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ), m_header( aReporter ){};

    IbisHeader                      m_header;
    std::vector<IbisComponent>      m_components;
    std::vector<IbisModelSelector>  m_modelSelectors;
    std::vector<IbisModel>          m_models;
    std::vector<IbisPackageModel>   m_packageModels;
};


enum class IBIS_PARSER_CONTINUE
{
    NONE,
    STRING,
    COMPONENT_PACKAGE,
    COMPONENT_PINMAPPING,
    COMPONENT_DIFFPIN,
    COMPONENT_DIESUPPLYPADS,
    COMPONENT_PIN,
    MATRIX,
    MODELSELECTOR,
    MODEL,
    IV_TABLE,
    VT_TABLE,
    RAMP,
    WAVEFORM,
    PACKAGEMODEL_PINS
};

enum class IBIS_PARSER_CONTEXT
{
    HEADER,
    COMPONENT,
    MODELSELECTOR,
    MODEL,
    PACKAGEMODEL,
    PACKAGEMODEL_MODELDATA,
    END
};

class IbisParser : public IBIS_INPUT
{
public:
    IbisParser( IBIS_REPORTER* aReporter ) : IBIS_INPUT( aReporter ), m_ibisFile( aReporter ){};

    bool m_parrot = true; // Write back all lines.

    long  m_lineCounter = 0;
    char  m_commentChar = '|';
    std::vector<char> m_buffer;
    int   m_bufferIndex = 0;
    int   m_lineOffset = 0;
    int   m_lineIndex = 0;
    int   m_lineLength = 0;

    IbisFile           m_ibisFile;
    IbisComponent*     m_currentComponent = nullptr;
    IbisModelSelector* m_currentModelSelector = nullptr;
    IbisModel*         m_currentModel = nullptr;
    IbisPackageModel*  m_currentPackageModel = nullptr;
    std::shared_ptr<IBIS_MATRIX> m_currentMatrix = nullptr;
    int                m_currentMatrixRow = 0;
    int                m_currentMatrixRowIndex = 0;
    IVtable*           m_currentIVtable = nullptr;
    VTtable*           m_currentVTtable = nullptr;
    IbisWaveform*      m_currentWaveform = nullptr;

    /** @brief Parse a file
     * 
     * This is the entry point to parse a file
     * 
     * @param aFileName input file name
     * @return True in case of success
     */
    bool ParseFile( std::string& aFileName );

private:
    std::string* m_continuingString = nullptr;

    /** @brief compare two strings without being case sensitive
     * 
     * Ibis: "The content of the files is case sensitive, except for reserved words and keywords."
     * 
     * @param a string to compare
     * @param b string to compare
     * @return true if the string are equal
     */
    bool compareIbisWord( const std::string& a, const std::string& b );

    /** @brief Parse a single keyword in the header context
     * 
     * @param aKeyword Keyword
     * @return True in case of success
     */
    bool parseHeader( std::string& aKeyword );
    /** @brief Parse a single keyword in the component context
     * 
     * @param aKeyword Keyword
     * @return True in case of success
     */
    bool parseComponent( std::string& aKeyword );
    /** @brief Parse a single keyword in the component context
     * 
     * @param aKeyword Keyword
     * @return True in case of success
     */
    bool parseModelSelector( std::string& aKeyword );
    /** @brief Parse a single keyword in the model selector context
     * 
     * @param aKeyword Keyword
     * @return True in case of success
     */
    bool parseModel( std::string& aKeyword );
    /** @brief Parse a single keyword in the model context
     * 
     * @param aKeyword Keyword
     * @return True in case of success
     */
    bool parsePackageModel( std::string& aKeyword );
    /** @brief Parse a single keyword in the package model context
     * 
     * @param aKeyword Keyword
     * @return True in case of success
     */
    bool parsePackageModelModelData( std::string& );
    /** @brief Parse a double according to the ibis standard
     * 
     * @param aDest Where the double should be stored
     * @param aStr The string to parse
     * @param aAllowModifiers Allows modifiers ( p for pico, f for femto, k for kilo, ... )
     * @return True in case of success
     */
    bool parseDouble( double& aDest, std::string& aStr, bool aAllowModifiers = false );

    /** @brief Parse the current line
     *
     * @return True in case of success
     */
    bool onNewLine(); // Gets rid of comments ( except on a comment character change command...)
    /** @brief Load the next line
     *
     * @return True in case of success
     */
    bool getNextLine();
    /** @brief Print the current line */
    void printLine();

    void      skipWhitespaces();
    bool      checkEndofLine(); // To be used when there cannot be any character left on the line
    bool      isLineEmptyFromCursor();
    std::string getKeyword();

    bool readInt( int& aDest );
    bool readDouble( double& aDest );
    bool readWord( std::string& aDest );
    bool readDvdt( std::string& aString, dvdt& aDest );
    bool readMatrix( std::shared_ptr<IBIS_MATRIX> aDest );
    bool readMatrixBanded( std::string, IBIS_MATRIX_BANDED& aDest );
    bool readMatrixFull( std::string, IBIS_MATRIX_FULL& aDest );
    bool readMatrixSparse( std::string, IBIS_MATRIX_SPARSE& aDest );
    bool readRampdvdt( dvdtTypMinMax& aDest );
    bool readRamp();
    bool readWaveform( IbisWaveform* aDest, IBIS_WAVEFORM_TYPE aType );
    bool readString( std::string& aDest );
    bool storeString( std::string& aDest, bool aMultiline );
    bool readTableLine( std::vector<std::string>& aDest );

    bool readNumericSubparam( std::string aSubparam, double& aDest );
    bool readIVtableEntry( IVtable& aTable );
    bool readVTtableEntry( VTtable& aTable );
    bool readTypMinMaxValue( TypMinMaxValue& aDest );
    bool readTypMinMaxValueSubparam( std::string aSubparam, TypMinMaxValue& aDest );
    //bool ReadDieSupplyPads();

    bool readPackage();
    bool readPin();
    bool readPinMapping();
    bool readDiffPin();
    bool readModelSelector();
    bool readModel();
    bool readPackageModelPins();

    /** @brief Ibis can change the character used for comments */
    bool changeCommentChar();
    bool changeContext( std::string& aKeyword );

    IBIS_PARSER_CONTINUE m_continue = IBIS_PARSER_CONTINUE::NONE;
    IBIS_PARSER_CONTEXT  m_context = IBIS_PARSER_CONTEXT::HEADER;
};

#endif