#include "calstandard.h"
#include "ui_CalStandardOpenEditDialog.h"
#include "ui_CalStandardShortEditDialog.h"
#include "ui_CalStandardLoadEditDialog.h"
#include "ui_CalStandardReflectEditDialog.h"
#include "ui_CalStandardThroughEditDialog.h"
#include "ui_CalStandardLineEditDialog.h"
#include "unit.h"
#include "Util/util.h"

using namespace std;
using namespace CalStandard;

Virtual::Virtual(QString name) :
    name(name),
    minFreq(std::numeric_limits<double>::lowest()),
    maxFreq(std::numeric_limits<double>::max())
{
    id = Util::random(numeric_limits<unsigned long long>::max());
}

Virtual *Virtual::create(Virtual::Type type)
{
    switch(type) {
    case Type::Open: return new Open;
    case Type::Short: return new Short;
    case Type::Load: return new Load;
    case Type::Reflect: return new Reflect;
    case Type::Through: return new Through;
    case Type::Line: return new Line;
    case Type::Last:
        break;
    }
    return nullptr;
}

std::vector<Virtual::Type> Virtual::availableTypes()
{
    std::vector<Type> ret;
    for(int i=0;i<(int) Type::Last;i++) {
        ret.push_back((Type) i);
    }
    return ret;
}

QString Virtual::TypeToString(Virtual::Type type)
{
    switch(type) {
    case Type::Open: return "Open";
    case Type::Short: return "Short";
    case Type::Load: return "Load";
    case Type::Reflect: return "Reflect";
    case Type::Through: return "Through";
    case Type::Line: return "Line";
    case Type::Last: return "Invalid";
    }
    return "Invalid";
}

Virtual::Type Virtual::TypeFromString(QString s)
{
    for(int i=0;i<(int) Type::Last;i++) {
        if(TypeToString((Type) i) == s) {
            return (Type) i;
        }
    }
    return Type::Last;
}

QString Virtual::getDescription()
{
    return TypeToString(getType())+", "+name;
}

nlohmann::json Virtual::toJSON()
{
    nlohmann::json j;
    j["name"] = name.toStdString();
    j["id"] = id;
    return j;
}

void Virtual::fromJSON(nlohmann::json j)
{
    name = QString::fromStdString(j.value("name", ""));
    id = j.value("id", id);
}

unsigned long long Virtual::getID()
{
    return id;
}

QString Virtual::getName() const
{
    return name;
}

void Virtual::setName(const QString &value)
{
    name = value;
}

void OnePort::setMeasurement(const Touchstone &ts, int port)
{
    if(!touchstone) {
        touchstone = new Touchstone(ts);
    } else {
        *touchstone = ts;
    }
    if(touchstone->ports() > 1) {
        touchstone->reduceTo1Port(port);
    }
    minFreq = touchstone->minFreq();
    maxFreq = touchstone->maxFreq();
}

void OnePort::clearMeasurement()
{
    delete touchstone;
    touchstone = nullptr;
    minFreq = std::numeric_limits<double>::lowest();
    minFreq = std::numeric_limits<double>::max();
}

nlohmann::json OnePort::toJSON()
{
    auto j = Virtual::toJSON();
    if(touchstone) {
       j["touchstone"] = touchstone->toJSON();
    }
    return j;
}

void OnePort::fromJSON(nlohmann::json j)
{
    Virtual::fromJSON(j);
    if(j.contains("touchstone")) {
        Touchstone ts(1);
        ts.fromJSON(j["touchstone"]);
        setMeasurement(ts);
    }
}

Open::Open()
{
    Z0 = 50.0;
    delay = loss = C0 = C1 = C2 = C3 = 0.0;
}

std::complex<double> Open::toS11(double freq)
{
    if(touchstone) {
        return touchstone->interpolate(freq).S[0];
    } else {
        // calculate fringing capacitance for open
        double Cfringing = C0 * 1e-15 + C1 * 1e-27 * freq + C2 * 1e-36 * pow(freq, 2) + C3 * 1e-45 * pow(freq, 3);
        // convert to impedance
        complex<double> open;
        if (Cfringing == 0) {
            // special case to avoid issues with infinity
            open = complex<double>(1.0, 0);
        } else {
            auto imp_open = complex<double>(0, -1.0 / (freq * 2 * M_PI * Cfringing));
            open = (imp_open - complex<double>(50.0)) / (imp_open + complex<double>(50.0));
        }
        return Util::addTransmissionLine(open, Z0, delay*1e-12, loss*1e9, freq);
    }
}

void Open::edit(std::function<void(void)> finishedCallback)
{
    auto d = new QDialog;
    auto ui = new Ui::CalStandardOpenEditDialog;
    ui->setupUi(d);

    ui->name->setText(name);
    ui->Z0->setUnit("Ω");
    ui->Z0->setPrecision(4);
    ui->Z0->setValue(Z0);
    ui->delay->setValue(delay);
    ui->loss->setValue(loss);
    ui->C0->setValue(C0);
    ui->C1->setValue(C1);
    ui->C2->setValue(C2);
    ui->C3->setValue(C3);

    auto updateMeasurementLabel = [=](){
        QString label;
        if(touchstone) {
            label = QString::number(touchstone->points())+" points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG")+" to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG");
        } else {
            label = "No measurements stored yet";
        }
        ui->measurementLabel->setText(label);
    };

    QObject::connect(ui->coefficients, &QRadioButton::toggled, [=](bool checked) {
        if(checked) {
            clearMeasurement();
        }
        ui->stackedWidget->setCurrentIndex(checked ? 0 : 1);
    });
    QObject::connect(ui->measurement, &QRadioButton::toggled, [=](bool checked) {
        updateMeasurementLabel();
        ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);
    });

    QObject::connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, ui->updateFile, &QPushButton::setEnabled);

    ui->touchstoneImport->setPorts(1);
    if(touchstone) {
        ui->measurement->setChecked(true);
        ui->touchstoneImport->setFile(touchstone->getFilename());
    } else {
        ui->coefficients->setChecked(true);
    }

    QObject::connect(ui->updateFile, &QPushButton::clicked, [=](){
        setMeasurement(ui->touchstoneImport->getTouchstone(), ui->touchstoneImport->getPorts()[0]);
        updateMeasurementLabel();
    });

    QObject::connect(d, &QDialog::accepted, [=](){
        name = ui->name->text();
        Z0 = ui->Z0->value();
        delay = ui->delay->value();
        loss = ui->loss->value();
        C0 = ui->C0->value();
        C1 = ui->C1->value();
        C2 = ui->C2->value();
        C3 = ui->C3->value();
        if(finishedCallback) {
            finishedCallback();
        }
    });

    d->show();
}

nlohmann::json Open::toJSON()
{
    auto j = OnePort::toJSON();
    j["Z0"] = Z0;
    j["delay"] = delay;
    j["loss"] = loss;
    j["C0"] = C0;
    j["C1"] = C1;
    j["C2"] = C2;
    j["C3"] = C3;
    return j;
}

void Open::fromJSON(nlohmann::json j)
{
    OnePort::fromJSON(j);
    Z0 = j.value("Z0", 50.0);
    delay = j.value("delay", 0.0);
    loss = j.value("loss", 0.0);
    C0 = j.value("C0", 0.0);
    C1 = j.value("C1", 0.0);
    C2 = j.value("C2", 0.0);
    C3 = j.value("C3", 0.0);
}

Short::Short()
{
    Z0 = 50.0;
    delay = loss = L0 = L1 = L2 = L3 = 0.0;
}

std::complex<double> Short::toS11(double freq)
{
    if(touchstone) {
        return touchstone->interpolate(freq).S[0];
    } else {
        // calculate inductance for short
        double Lseries = L0 * 1e-12 + L1 * 1e-24 * freq + L2 * 1e-33 * pow(freq, 2) + L3 * 1e-42 * pow(freq, 3);
        // convert to impedance
        auto imp_short = complex<double>(0, freq * 2 * M_PI * Lseries);
        complex<double> _short = (imp_short - complex<double>(50.0)) / (imp_short + complex<double>(50.0));
        return Util::addTransmissionLine(_short, Z0, delay*1e-12, loss*1e9, freq);
    }
}

void Short::edit(std::function<void(void)> finishedCallback)
{
    auto d = new QDialog;
    auto ui = new Ui::CalStandardShortEditDialog;
    ui->setupUi(d);

    ui->name->setText(name);
    ui->Z0->setUnit("Ω");
    ui->Z0->setPrecision(4);
    ui->Z0->setValue(Z0);
    ui->delay->setValue(delay);
    ui->loss->setValue(loss);
    ui->L0->setValue(L0);
    ui->L1->setValue(L1);
    ui->L2->setValue(L2);
    ui->L3->setValue(L3);

    auto updateMeasurementLabel = [=](){
        QString label;
        if(touchstone) {
            label = QString::number(touchstone->points())+" points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG")+" to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG");
        } else {
            label = "No measurements stored yet";
        }
        ui->measurementLabel->setText(label);
    };

    QObject::connect(ui->coefficients, &QRadioButton::toggled, [=](bool checked) {
        if(checked) {
            clearMeasurement();
        }
        ui->stackedWidget->setCurrentIndex(checked ? 0 : 1);
    });
    QObject::connect(ui->measurement, &QRadioButton::toggled, [=](bool checked) {
        updateMeasurementLabel();
        ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);
    });

    QObject::connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, ui->updateFile, &QPushButton::setEnabled);

    ui->touchstoneImport->setPorts(1);
    if(touchstone) {
        ui->measurement->setChecked(true);
        ui->touchstoneImport->setFile(touchstone->getFilename());
    } else {
        ui->coefficients->setChecked(true);
    }

    QObject::connect(ui->updateFile, &QPushButton::clicked, [=](){
        setMeasurement(ui->touchstoneImport->getTouchstone(), ui->touchstoneImport->getPorts()[0]);
        updateMeasurementLabel();
    });

    QObject::connect(d, &QDialog::accepted, [=](){
        name = ui->name->text();
        Z0 = ui->Z0->value();
        delay = ui->delay->value();
        loss = ui->loss->value();
        L0 = ui->L0->value();
        L1 = ui->L1->value();
        L2 = ui->L2->value();
        L3 = ui->L3->value();
        if(finishedCallback) {
            finishedCallback();
        }
    });

    d->show();
}

nlohmann::json Short::toJSON()
{
    auto j = OnePort::toJSON();
    j["Z0"] = Z0;
    j["delay"] = delay;
    j["loss"] = loss;
    j["L0"] = L0;
    j["L1"] = L1;
    j["L2"] = L2;
    j["L3"] = L3;
    return j;
}

void Short::fromJSON(nlohmann::json j)
{
    OnePort::fromJSON(j);
    Z0 = j.value("Z0", 50.0);
    delay = j.value("delay", 0.0);
    loss = j.value("loss", 0.0);
    L0 = j.value("L0", 0.0);
    L1 = j.value("L1", 0.0);
    L2 = j.value("L2", 0.0);
    L3 = j.value("L3", 0.0);
}

Load::Load()
{
    Z0 = 50.0;
    resistance = 50.0;
    delay = Cparallel = Lseries = 0;
    Cfirst = true;
}

std::complex<double> Load::toS11(double freq)
{
    if(touchstone) {
        return touchstone->interpolate(freq).S[0];
    } else {
        auto imp_load = complex<double>(resistance, 0);
        if (Cfirst) {
            // C is the first parameter starting from the VNA port. But the load is modeled here starting from
            // the other end, so we need to start with the inductor
            imp_load += complex<double>(0, freq * 2 * M_PI * Lseries);
        }
        // Add parallel capacitor to impedance
        if(Cparallel > 0) {
            auto imp_C = complex<double>(0, -1.0 / (freq * 2 * M_PI * Cparallel));
            imp_load = (imp_load * imp_C) / (imp_load + imp_C);
        }
        if (!Cfirst) {
            // inductor not added yet, do so now
            imp_load += complex<double>(0, freq * 2 * M_PI * Lseries);
        }
        complex<double> load = (imp_load - complex<double>(50.0)) / (imp_load + complex<double>(50.0));
        return Util::addTransmissionLine(load, Z0, delay*1e-12, loss*1e9, freq);
    }
}

void Load::edit(std::function<void(void)> finishedCallback)
{
    auto d = new QDialog;
    auto ui = new Ui::CalStandardLoadEditDialog;
    ui->setupUi(d);

    ui->name->setText(name);
    ui->resistance->setUnit("Ω");
    ui->resistance->setPrecision(4);
    ui->resistance->setValue(resistance);
    ui->Z0->setUnit("Ω");
    ui->Z0->setPrecision(4);
    ui->Z0->setValue(Z0);
    ui->delay->setValue(delay);
    ui->loss->setValue(loss);
    ui->parC->setUnit("F");
    ui->parC->setPrefixes("pnum ");
    ui->parC->setPrecision(4);
    ui->parC->setValue(Cparallel);
    ui->serL->setUnit("H");
    ui->serL->setPrefixes("num ");
    ui->serL->setPrecision(4);
    ui->serL->setValue(Lseries);

    if(Cfirst) {
        ui->C_first->setChecked(true);
    } else {
        ui->L_first->setChecked(true);
    }

    auto updateMeasurementLabel = [=](){
        QString label;
        if(touchstone) {
            label = QString::number(touchstone->points())+" points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG")+" to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG");
        } else {
            label = "No measurements stored yet";
        }
        ui->measurementLabel->setText(label);
    };

    QObject::connect(ui->coefficients, &QRadioButton::toggled, [=](bool checked) {
        if(checked) {
            clearMeasurement();
        }
        ui->stackedWidget->setCurrentIndex(checked ? 0 : 1);
    });
    QObject::connect(ui->measurement, &QRadioButton::toggled, [=](bool checked) {
        updateMeasurementLabel();
        ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);
    });

    QObject::connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, ui->updateFile, &QPushButton::setEnabled);

    ui->touchstoneImport->setPorts(1);
    if(touchstone) {
        ui->measurement->setChecked(true);
        ui->touchstoneImport->setFile(touchstone->getFilename());
    } else {
        ui->coefficients->setChecked(true);
    }

    QObject::connect(ui->updateFile, &QPushButton::clicked, [=](){
        setMeasurement(ui->touchstoneImport->getTouchstone(), ui->touchstoneImport->getPorts()[0]);
        updateMeasurementLabel();
    });

    QObject::connect(d, &QDialog::accepted, [=](){
        name = ui->name->text();
        resistance = ui->resistance->value();
        Z0 = ui->Z0->value();
        delay = ui->delay->value();
        loss = ui->loss->value();
        Cparallel = ui->parC->value();
        Lseries = ui->serL->value();
        Cfirst = ui->C_first->isChecked();
        if(finishedCallback) {
            finishedCallback();
        }
    });

    d->show();
}

nlohmann::json Load::toJSON()
{
    auto j = OnePort::toJSON();
    j["Z0"] = Z0;
    j["delay"] = delay;
    j["loss"] = loss;
    j["resistance"] = resistance;
    j["Cparallel"] = Cparallel;
    j["Lseries"] = Lseries;
    j["Cfirst"] = Cfirst;
    return j;
}

void Load::fromJSON(nlohmann::json j)
{
    OnePort::fromJSON(j);
    Z0 = j.value("Z0", 50.0);
    delay = j.value("delay", 0.0);
    loss = j.value("loss", 0.0);
    resistance = j.value("resistance", 0.0);
    Cparallel = j.value("Cparallel", 0.0);
    Lseries = j.value("Lseries", 0.0);
    Cfirst = j.value("Cfirst", true);
}

void TwoPort::setMeasurement(const Touchstone &ts, int port1, int port2)
{
    if(!touchstone) {
        touchstone = new Touchstone(ts);
    } else {
        *touchstone = ts;
    }
    if(touchstone->ports() > 2) {
        touchstone->reduceTo2Port(port1, port2);
    }
    minFreq = touchstone->minFreq();
    maxFreq = touchstone->maxFreq();
}

void TwoPort::clearMeasurement()
{
    delete touchstone;
    touchstone = nullptr;
    minFreq = std::numeric_limits<double>::lowest();
    minFreq = std::numeric_limits<double>::max();
}

nlohmann::json TwoPort::toJSON()
{
    auto j = Virtual::toJSON();
    if(touchstone) {
       j["touchstone"] = touchstone->toJSON();
    }
    return j;
}

void TwoPort::fromJSON(nlohmann::json j)
{
    Virtual::fromJSON(j);
    if(j.contains("touchstone")) {
        Touchstone ts(1);
        ts.fromJSON(j["touchstone"]);
        setMeasurement(ts);
    }
}

Through::Through()
{
    Z0 = 50.0;
    delay = 0.0;
    loss = 0.0;
}

Sparam Through::toSparam(double freq)
{
    if(touchstone) {
        auto interp = touchstone->interpolate(freq).S;
        return Sparam(interp[0], interp[1], interp[2], interp[3]);
    } else {
        // calculate effect of through
        double through_phaseshift = -2 * M_PI * freq * delay * 1e-12;
        double through_att_db = loss * 1e9 * 4.3429 * delay * 1e-12 / Z0 * sqrt(freq / 1e9);;
        double through_att = pow(10.0, -through_att_db / 10.0);
        auto through = polar<double>(through_att, through_phaseshift);
        // Assume symmetric and perfectly matched through for other parameters
        return Sparam(0.0, through, through, 0.0);
    }
}

void Through::edit(std::function<void(void)> finishedCallback)
{
    auto d = new QDialog;
    auto ui = new Ui::CalStandardThroughEditDialog;
    ui->setupUi(d);

    ui->name->setText(name);
    ui->Z0->setUnit("Ω");
    ui->Z0->setPrecision(4);
    ui->Z0->setValue(Z0);
    ui->delay->setValue(delay);
    ui->loss->setValue(loss);

    auto updateMeasurementLabel = [=](){
        QString label;
        if(touchstone) {
            label = QString::number(touchstone->points())+" points from "+Unit::ToString(touchstone->minFreq(), "Hz", " kMG")+" to "+Unit::ToString(touchstone->maxFreq(), "Hz", " kMG");
        } else {
            label = "No measurements stored yet";
        }
        ui->measurementLabel->setText(label);
    };

    QObject::connect(ui->coefficients, &QRadioButton::toggled, [=](bool checked) {
        if(checked) {
            clearMeasurement();
        }
        ui->stackedWidget->setCurrentIndex(checked ? 0 : 1);
    });
    QObject::connect(ui->measurement, &QRadioButton::toggled, [=](bool checked) {
        updateMeasurementLabel();
        ui->stackedWidget->setCurrentIndex(checked ? 1 : 0);
    });

    QObject::connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, ui->updateFile, &QPushButton::setEnabled);

    ui->touchstoneImport->setPorts(1);
    if(touchstone) {
        ui->measurement->setChecked(true);
        ui->touchstoneImport->setFile(touchstone->getFilename());
    } else {
        ui->coefficients->setChecked(true);
    }

    QObject::connect(ui->updateFile, &QPushButton::clicked, [=](){
        setMeasurement(ui->touchstoneImport->getTouchstone(), ui->touchstoneImport->getPorts()[0]);
        updateMeasurementLabel();
    });

    QObject::connect(d, &QDialog::accepted, [=](){
        name = ui->name->text();
        Z0 = ui->Z0->value();
        delay = ui->delay->value();
        loss = ui->loss->value();
        if(finishedCallback) {
            finishedCallback();
        }
    });

    d->show();
}

nlohmann::json Through::toJSON()
{
    auto j = TwoPort::toJSON();
    j["Z0"] = Z0;
    j["delay"] = delay;
    j["loss"] = loss;
    return j;
}

void Through::fromJSON(nlohmann::json j)
{
    TwoPort::fromJSON(j);
    Z0 = j.value("Z0", 50.0);
    delay = j.value("delay", 0.0);
    loss = j.value("loss", 0.0);
}

Reflect::Reflect()
{
    isShort = true;
}

std::complex<double> Reflect::toS11(double freq)
{
    Q_UNUSED(freq)
    return std::numeric_limits<complex<double>>::quiet_NaN();
}

void Reflect::edit(std::function<void ()> finishedCallback)
{
    auto d = new QDialog;
    auto ui = new Ui::CalStandardReflectEditDialog;
    ui->setupUi(d);

    ui->name->setText(name);
    ui->type->setCurrentIndex(isShort ? 1 : 0);

    QObject::connect(d, &QDialog::accepted, [=](){
        name = ui->name->text();
        isShort = ui->type->currentIndex() == 1;
        if(finishedCallback) {
            finishedCallback();
        }
    });

    d->show();
}

nlohmann::json Reflect::toJSON()
{
    auto j = OnePort::toJSON();
    j["isShort"] = isShort;
    return j;
}

void Reflect::fromJSON(nlohmann::json j)
{
    OnePort::fromJSON(j);
    isShort = j.value("isShort", true);
}

bool Reflect::getIsShort() const
{
    return isShort;
}

Line::Line()
{
    Z0 = 50.0;
    setDelay(0.0);
}

Sparam Line::toSparam(double freq)
{
    Q_UNUSED(freq)
    Sparam ret;
    ret.m11 = numeric_limits<complex<double>>::quiet_NaN();
    ret.m12 = numeric_limits<complex<double>>::quiet_NaN();
    ret.m21 = numeric_limits<complex<double>>::quiet_NaN();
    ret.m22 = numeric_limits<complex<double>>::quiet_NaN();
    return ret;
}

void Line::edit(std::function<void ()> finishedCallback)
{
    auto d = new QDialog;
    auto ui = new Ui::CalStandardLineEditDialog;
    ui->setupUi(d);

    ui->name->setText(name);
    ui->Z0->setUnit("Ω");
    ui->Z0->setPrecision(4);
    ui->Z0->setValue(Z0);
    ui->delay->setValue(delay);
    ui->delay->setUnit("s");
    ui->delay->setPrefixes("pnum ");
    ui->delay->setPrecision(4);
    ui->minFreq->setUnit("Hz");
    ui->minFreq->setPrecision(4);
    ui->minFreq->setPrefixes(" kMG");
    ui->minFreq->setValue(minFreq);
    ui->maxFreq->setUnit("Hz");
    ui->maxFreq->setPrecision(4);
    ui->maxFreq->setPrefixes(" kMG");
    ui->maxFreq->setValue(maxFreq);

    QObject::connect(ui->delay, &SIUnitEdit::valueChanged, [=](double val){
        ui->minFreq->setValue(1.0 / val * 20 / 360);
        ui->maxFreq->setValue(1.0 / val * 160 / 360);
    });

    QObject::connect(d, &QDialog::accepted, [=](){
        name = ui->name->text();
        Z0 = ui->Z0->value();
        setDelay(ui->delay->value());
        if(finishedCallback) {
            finishedCallback();
        }
    });

    d->show();
}

nlohmann::json Line::toJSON()
{
    auto j = TwoPort::toJSON();
    j["delay"] = delay;
    return j;
}

void Line::fromJSON(nlohmann::json j)
{
    TwoPort::fromJSON(j);
    setDelay(j.value("delay", 0.0));
}

void Line::setDelay(double delay)
{
    this->delay = delay;
    minFreq = 1.0 / delay * 20 / 360;
    maxFreq = 1.0 / delay * 160 / 360;
}
