/* -*- c++ -*- */
/*
 * Copyright 2016 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "edit_box_msg_impl.h"

#include <gnuradio/io_signature.h>
#include <gnuradio/prefs.h>
#include <gnuradio/qtgui/utils.h>
#include <sstream>

namespace gr {
namespace qtgui {

edit_box_msg::sptr edit_box_msg::make(data_type_t type,
                                      const std::string& label,
                                      const std::string& value,
                                      bool is_pair,
                                      bool is_static,
                                      const std::string& key,
                                      QWidget* parent)
{
    return gnuradio::make_block_sptr<edit_box_msg_impl>(
        type, value, label, is_pair, is_static, key, parent);
}

edit_box_msg_impl::edit_box_msg_impl(data_type_t type,
                                     const std::string& label,
                                     const std::string& value,
                                     bool is_pair,
                                     bool is_static,
                                     const std::string& key,
                                     QWidget* parent)
    : block("edit_box_msg", io_signature::make(0, 0, 0), io_signature::make(0, 0, 0)),
      QObject(parent),
      d_port(pmt::mp("msg"))
{
    if (qApp != NULL) {
        d_qApplication = qApp;
    } else {
        d_qApplication = new QApplication(d_argc, &d_argv);
    }

    // If a style sheet is set in the prefs file, enable it here.
    check_set_qss(d_qApplication);

    d_is_pair = is_pair;
    d_is_static = is_static;

    d_val = new QLineEdit();
    d_val->setObjectName("qtgui_editboxmsg_val"); // used to set background color
    d_val->setText(QString(value.c_str()));

    set_type(type);

    d_group = new QGroupBox();
    d_vlayout = new QVBoxLayout(parent);
    d_hlayout = new QHBoxLayout(parent);

    if (d_is_pair) {
        d_key = new QLineEdit();

        QString key_text = QString(key.c_str());
        d_key->setText(key_text);

        // If static, we create the d_key object, which we use later
        // to be consistent about getting the key string. But we do
        // not add it to the layout.
        if (d_is_static) {
            d_key->setEnabled(false);

            QFontMetrics fm = d_key->fontMetrics();
            int width = 15 + fm.horizontalAdvance(key_text);

            d_key->setFixedWidth(width);

            // Verify that a default key has been set or emit an error
            if (key.empty()) {
                throw std::runtime_error(
                    "When using static + pair mode, please set a default key.");
            }
        } else {
            // Adding it to the layout if in non-static mode so users
            // can see and update the key.
            d_hlayout->addWidget(d_key);
        }
    }

    d_label = NULL;
    if (!label.empty()) {
        d_label = new QLabel(QString(label.c_str()));
        d_vlayout->addWidget(d_label);
    }

    d_hlayout->addWidget(d_val);

    if (!d_is_static) {
        // If not static, we can change the key and the data type of
        // the value box.
        d_type_box = new QComboBox();
        d_type_box->setEditable(false);

        // Items listed in order of enum data_type_t
        d_type_box->addItem("Int");
        d_type_box->addItem("Float");
        d_type_box->addItem("Double");
        d_type_box->addItem("Complex");
        d_type_box->addItem("String");
        d_type_box->addItem("Int (vec)");
        d_type_box->addItem("Float (vec)");
        d_type_box->addItem("Double (vec)");
        d_type_box->addItem("Complex (vec)");
        d_type_box->setCurrentIndex(d_type);
        d_hlayout->addWidget(d_type_box);

        QObject::connect(
            d_type_box, SIGNAL(currentIndexChanged(int)), this, SLOT(set_type(int)));
    }

    d_vlayout->addItem(d_hlayout);
    d_group->setLayout(d_vlayout);

    // use returnPressed signal specifically to not trigger when focus is lost
    QObject::connect(d_val, SIGNAL(returnPressed()), this, SLOT(edit_finished()));

    d_msg = pmt::PMT_NIL;

    message_port_register_out(d_port);
    message_port_register_in(pmt::mp("val"));

    set_msg_handler(pmt::mp("val"), [this](pmt::pmt_t msg) { this->set_value(msg); });
}

edit_box_msg_impl::~edit_box_msg_impl()
{
    if (d_is_pair)
        delete d_key;
}

bool edit_box_msg_impl::start()
{
    QString text = d_val->text();
    if (!text.isEmpty()) {
        edit_finished();
    }

    return block::start();
}

void edit_box_msg_impl::exec() { d_qApplication->exec(); }

QWidget* edit_box_msg_impl::qwidget() { return (QWidget*)d_group; }

void edit_box_msg_impl::set_type(int type) { set_type(static_cast<data_type_t>(type)); }

void edit_box_msg_impl::set_type(gr::qtgui::data_type_t type)
{
    d_type = type;

    switch (d_type) {
    case INT:
    case INT_VEC:
        d_val->setStyleSheet(
            "QLineEdit#qtgui_editboxmsg_val {background-color: #4CAF50;}");
        break;
    case FLOAT:
    case FLOAT_VEC:
        d_val->setStyleSheet(
            "QLineEdit#qtgui_editboxmsg_val {background-color: #F57C00;}");
        break;
    case DOUBLE:
    case DOUBLE_VEC:
        d_val->setStyleSheet(
            "QLineEdit#qtgui_editboxmsg_val {background-color: #00BCD4;}");
        break;
    case COMPLEX:
    case COMPLEX_VEC:
        d_val->setStyleSheet(
            "QLineEdit#qtgui_editboxmsg_val {background-color: #2196F3;}");
        break;
    case STRING:
        d_val->setStyleSheet("QLineEdit#qtgui_editboxmsg_val {background-color: #FFFFFF; "
                             "color: #000000;}");
        break;
    }
}

void edit_box_msg_impl::set_value(pmt::pmt_t val)
{
    // If the contents of the new value are the same as we already
    // had, don't update anything, just exit and move on.
    if (pmt::eqv(val, d_msg)) {
        return;
    }

    int xi;
    float xf;
    double xd;
    std::string xs;
    gr_complex xc;

    d_msg = val;

    // Only update key if we're expecting a pair
    if (d_is_pair) {
        // If we are, make sure that the PMT is actually a pair
        if (pmt::is_pair(val)) {
            pmt::pmt_t key = pmt::car(val);
            std::string skey = pmt::symbol_to_string(key);

            // If static, check to make sure that the key of the
            // incoming message matches our key. If it doesn't, emit a
            // warning and exit without changing anything.
            if (d_is_static) {
                std::string cur_key = d_key->text().toStdString();
                if (skey != cur_key) {
                    d_logger->warn("Got key '{:s}' but expected '{:s}'", skey, cur_key);
                    return;
                }
            }
            val = pmt::cdr(val);
            d_key->setText(QString(skey.c_str()));
        } else {
            d_logger->warn("Did not find PMT pair");
            return;
        }
    }

    switch (d_type) {
    case INT:
        if (pmt::is_integer(val)) {
            xi = pmt::to_long(val);
            d_val->setText(QString::number(xi));
        } else {
            d_logger->warn("Conversion from integer failed");
            return;
        }
        break;
    case INT_VEC:
        if (pmt::is_s32vector(val)) {
            QStringList text_list;
            const std::vector<int32_t> xv = pmt::s32vector_elements(val);
            for (size_t i = 0; i < xv.size(); i++) {
                text_list.append(QString::number(xv[i]));
            }
            d_val->setText(text_list.join(", "));
        } else {
            d_logger->warn("Conversion from integer vector failed");
            return;
        }
        break;
    case FLOAT:
        if (pmt::is_real(val)) {
            xf = pmt::to_float(val);
            d_val->setText(QString::number(xf));
        } else {
            d_logger->warn("Conversion from float failed");
            return;
        }
        break;
    case FLOAT_VEC:
        if (pmt::is_f32vector(val)) {
            QStringList text_list;
            const std::vector<float> xv = pmt::f32vector_elements(val);
            for (size_t i = 0; i < xv.size(); i++) {
                text_list.append(QString::number(xv[i]));
            }
            d_val->setText(text_list.join(", "));
        } else {
            d_logger->warn("Conversion from float vector failed");
            return;
        }
        break;
    case DOUBLE:
        if (pmt::is_real(val)) {
            xd = pmt::to_double(val);
            d_val->setText(QString::number(xd));
        } else {
            d_logger->warn("Conversion from double failed");
            return;
        }
        break;
    case DOUBLE_VEC:
        if (pmt::is_f64vector(val)) {
            QStringList text_list;
            const std::vector<double> xv = pmt::f64vector_elements(val);
            for (size_t i = 0; i < xv.size(); i++) {
                text_list.append(QString::number(xv[i]));
            }
            d_val->setText(text_list.join(", "));
        } else {
            d_logger->warn("Conversion from double vector failed");
            return;
        }
        break;
    case COMPLEX:
        if (pmt::is_complex(val)) {
            xc = pmt::to_complex(val);
            d_val->setText(QString("(%1,%2)").arg(xc.real()).arg(xc.imag()));
        } else {
            d_logger->warn("Conversion from complex failed");
            return;
        }
        break;
    case COMPLEX_VEC:
        if (pmt::is_c32vector(val)) {
            QStringList text_list;
            const std::vector<gr_complex> xv = pmt::c32vector_elements(val);
            for (size_t i = 0; i < xv.size(); i++) {
                text_list.append(QString("(%1,%2)").arg(xv[i].real()).arg(xv[i].imag()));
            }
            d_val->setText(text_list.join(", "));
        } else {
            d_logger->warn("Conversion from complex vector failed");
            return;
        }
        break;
    case STRING:
        if (pmt::is_symbol(val)) {
            xs = pmt::symbol_to_string(val);
            d_val->setText(QString(xs.c_str()));
        } else {
            d_logger->warn("Conversion from string failed");
            return;
        }
        break;
    }

    // Emit the new message to pass updates downstream.
    // Loops are prevented by the early exit if d_msg == val.
    message_port_pub(d_port, d_msg);
}

void edit_box_msg_impl::edit_finished()
{
    const QString text = d_val->text();
    bool conv_ok = true;
    int xi;
    float xf;
    double xd;
    std::string xs;
    gr_complex xc;

    switch (d_type) {
    case INT:
        xi = text.toInt(&conv_ok);
        if (conv_ok) {
            d_msg = pmt::from_long(xi);
        } else {
            d_logger->warn("Conversion to integer failed");
            return;
        }
        break;
    case INT_VEC: {
        std::vector<int32_t> xv;
        QStringList text_list = text.split(",");
        for (int i = 0; i < text_list.size(); ++i) {
            QString s = text_list.at(i);
            s = s.remove(QChar(' '));
            int t = s.toInt(&conv_ok);
            if (conv_ok) {
                xv.push_back(t);
            } else {
                d_logger->warn("Conversion to integer vector failed");
                return;
            }
        }
        d_msg = pmt::init_s32vector(xv.size(), xv);
    } break;
    case FLOAT:
        xf = text.toFloat(&conv_ok);
        if (conv_ok) {
            d_msg = pmt::from_float(xf);
        } else {
            d_logger->warn("Conversion to float failed");
            return;
        }
        break;
    case FLOAT_VEC: {
        std::vector<float> xv;
        QStringList text_list = text.split(",");
        for (int i = 0; i < text_list.size(); ++i) {
            QString s = text_list.at(i);
            s = s.remove(QChar(' '));
            float t = s.toFloat(&conv_ok);
            if (conv_ok) {
                xv.push_back(t);
            } else {
                d_logger->warn("Conversion to float vector failed");
                return;
            }
        }
        d_msg = pmt::init_f32vector(xv.size(), xv);
    } break;
    case DOUBLE:
        xd = text.toDouble(&conv_ok);
        if (conv_ok) {
            d_msg = pmt::from_double(xd);
        } else {
            d_logger->warn("Conversion to double failed");
            return;
        }
        break;
    case DOUBLE_VEC: {
        std::vector<double> xv;
        QStringList text_list = text.split(",");
        for (int i = 0; i < text_list.size(); ++i) {
            QString s = text_list.at(i);
            s = s.remove(QChar(' '));
            double t = s.toDouble(&conv_ok);
            if (conv_ok) {
                xv.push_back(t);
            } else {
                d_logger->warn("Conversion to double vector failed");
                return;
            }
        }
        d_msg = pmt::init_f64vector(xv.size(), xv);
    } break;
    case COMPLEX: {
        std::stringstream ss(text.toStdString());
        ss >> xc;
        if (static_cast<size_t>(ss.tellg()) != ss.str().size()) {
            d_logger->warn("Conversion of {:s} to complex failed", text.toStdString());
            return;
        }
        d_msg = pmt::from_complex(xc.real(), xc.imag());
    } break;
    case COMPLEX_VEC: {
        std::vector<gr_complex> xv;
        QStringList text_list = text.split(",");
        bool even = false;
        float re, im;
        for (int i = 0; i < text_list.size(); ++i) {
            QString s = text_list.at(i);
            s = s.remove(QChar(' '));
            s = s.remove(QChar(')'));
            s = s.remove(QChar('('));
            float t = s.toFloat(&conv_ok);
            if (conv_ok) {
                if (even) {
                    im = t;
                    xv.emplace_back(re, im);
                    even = false;
                } else {
                    re = t;
                    even = true;
                }
            } else {
                d_logger->warn("Conversion to complex vector failed");
                return;
            }
        }
        d_msg = pmt::init_c32vector(xv.size(), xv);
    } break;
    case STRING:
        xs = text.toStdString();
        d_msg = pmt::intern(xs);
        break;
    }

    if (d_is_pair) {
        std::string key = d_key->text().toStdString();
        d_msg = pmt::cons(pmt::intern(key), d_msg);
    }

    message_port_pub(d_port, d_msg);
}

} /* namespace qtgui */
} /* namespace gr */
