// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
// SPDX-License-Identifier: GPL-3.0-or-later

#include "App/Element/GraphicElements/Display16.h"

#include <QPainter>
#include <QSvgRenderer>

#include "App/Element/ElementFactory.h"
#include "App/Element/ElementInfo.h"
#include "App/Element/GraphicElements/Display7.h"
#include "App/IO/Serialization.h"
#include "App/IO/SerializationContext.h"
#include "App/IO/VersionInfo.h"
#include "App/Wiring/Port.h"

template<>
struct ElementInfo<Display16> {
    static constexpr ElementConstraints constraints{
        .type = ElementType::Display16,
        .group = ElementGroup::Output,
        .minInputSize = 17,
        .maxInputSize = 17,
        .canChangeAppearance = true,
        .hasColors = true,
        .hasLabel = true,
        .rotatesGraphic = false,
    };
    static_assert(validate(constraints));

    static ElementMetadata metadata()
    {
        auto meta = metadataFromConstraints(constraints);
        meta.pixmapPath = []{ return QStringLiteral(":/Components/Output/Counter/counter_16_on.svg"); };
        meta.titleText = QT_TRANSLATE_NOOP("Display16", "16-SEGMENT DISPLAY");
        meta.translatedName = QT_TRANSLATE_NOOP("Display16", "16-Segment Display");
        meta.trContext = "Display16";
        meta.defaultAppearances = QStringList({
            ":/Components/Output/Counter/counter_16_off.svg",
            ":/Components/Output/Counter/counter_a1.svg",
            ":/Components/Output/Counter/counter_a2.svg",
            ":/Components/Output/Counter/counter_b.svg",
            ":/Components/Output/Counter/counter_c.svg",
            ":/Components/Output/Counter/counter_d1.svg",
            ":/Components/Output/Counter/counter_d2.svg",
            ":/Components/Output/Counter/counter_e.svg",
            ":/Components/Output/Counter/counter_f.svg",
            ":/Components/Output/Counter/counter_g1.svg",
            ":/Components/Output/Counter/counter_g2.svg",
            ":/Components/Output/Counter/counter_h.svg",
            ":/Components/Output/Counter/counter_j.svg",
            ":/Components/Output/Counter/counter_k.svg",
            ":/Components/Output/Counter/counter_l.svg",
            ":/Components/Output/Counter/counter_m.svg",
            ":/Components/Output/Counter/counter_n.svg",
            ":/Components/Output/Counter/counter_dp.svg",
        });
        return meta;
    }

    static inline const bool registered = []() {
        ElementMetadataRegistry::registerMetadata(metadata());
        ElementFactory::registerCreator(constraints.type, [] { return new Display16(); });
        return true;
    }();
};

Display16::Display16(QGraphicsItem *parent)
    : GraphicElement(ElementType::Display16, parent)
{
    a1 = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(1));
    a2 = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(2));
    b  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(3));
    c  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(4));
    d1 = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(5));
    d2 = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(6));
    e  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(7));
    f  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(8));
    g1 = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(9));
    g2 = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(10));
    h  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(11));
    j  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(12));
    k  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(13));
    l  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(14));
    m  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(15));
    n  = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(16));
    dp = Display7::cachedSegmentRenderers(m_appearance.defaultAppearances().at(17));

    Display16::updatePortsProperties();
}

void Display16::refresh()
{
    update();
}

void Display16::updatePortsProperties()
{
    // Ports on the left (x=0) drive left-side and center segments;
    // ports on the right (x=64) drive right-side segments and DP.
    // The port order follows the standard 16-segment naming convention
    // (A1/A2 = top, B/C = right verticals, D1/D2 = bottom, E/F = left verticals,
    //  G1/G2 = middle, H/J/K/L/M/N = diagonals and center verticals).
    inputPort( 0)->setPos( 0,  -8);    inputPort( 0)->setName("G1 (" + tr("middle left horizontal")  + ")");
    inputPort( 1)->setPos( 0,   8);    inputPort( 1)->setName("F ("  + tr("upper left vertical")     + ")");
    inputPort( 2)->setPos( 0,  24);    inputPort( 2)->setName("E ("  + tr("lower left vertical")     + ")");
    inputPort( 3)->setPos( 0,  40);    inputPort( 3)->setName("D1 (" + tr("bottom left horizontal")  + ")");
    inputPort( 4)->setPos( 0,  56);    inputPort( 4)->setName("D2 (" + tr("bottom right horizontal") + ")");
    inputPort( 5)->setPos(64,  -8);    inputPort( 5)->setName("A1 (" + tr("top left horizontal")     + ")");
    inputPort( 6)->setPos(64,   8);    inputPort( 6)->setName("A2 (" + tr("top right horizontal")    + ")");
    inputPort( 7)->setPos(64,  24);    inputPort( 7)->setName("B ("  + tr("upper right vertical")    + ")");
    inputPort( 8)->setPos(64,  40);    inputPort( 8)->setName("DP (" + tr("dot")                     + ")");
    inputPort( 9)->setPos(64,  56);    inputPort( 9)->setName("C ("  + tr("lower right vertical")    + ")");
    inputPort(10)->setPos( 0,  72);    inputPort(10)->setName("G2 (" + tr("middle right horizontal") + ")");
    inputPort(11)->setPos( 0,  88);    inputPort(11)->setName("H ("  + tr("upper left diagonal")     + ")");
    inputPort(12)->setPos( 0, 104);    inputPort(12)->setName("J ("  + tr("upper center vertical")   + ")");
    inputPort(13)->setPos(64,  72);    inputPort(13)->setName("K ("  + tr("upper right diagonal")    + ")");
    inputPort(14)->setPos(64,  88);    inputPort(14)->setName("L ("  + tr("lower right diagonal")    + ")");
    inputPort(15)->setPos(64, 104);    inputPort(15)->setName("M ("  + tr("lower center vertical")   + ")");
    inputPort(16)->setPos(64, 120);    inputPort(16)->setName("N ("  + tr("lower left diagonal")    + ")");

    for (auto *port : inputs()) {
        port->setRequired(false);
        port->setDefaultStatus(Status::Inactive);
    }

    // This override doesn't chain to the base updatePortsProperties(), so re-apply the current
    // rotation/flip orientation to the freshly-positioned ports (keeps flip working on displays).
    rotatePorts();
}

void Display16::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    // Base class draws the off-state background; active segments are overlaid as vector
    // renders on top, crisp at any zoom.
    GraphicElement::paint(painter, option, widget);

    const QRectF body(0, 0, 64, 64);
    if (inputPort( 0)->status() == Status::Active) { g1.at(m_colorNumber)->render(painter, body); }
    if (inputPort( 1)->status() == Status::Active) { f.at(m_colorNumber)->render(painter, body);  }
    if (inputPort( 2)->status() == Status::Active) { e.at(m_colorNumber)->render(painter, body);  }
    if (inputPort( 3)->status() == Status::Active) { d1.at(m_colorNumber)->render(painter, body); }
    if (inputPort( 4)->status() == Status::Active) { d2.at(m_colorNumber)->render(painter, body); }
    if (inputPort( 5)->status() == Status::Active) { a1.at(m_colorNumber)->render(painter, body); }
    if (inputPort( 6)->status() == Status::Active) { a2.at(m_colorNumber)->render(painter, body); }
    if (inputPort( 7)->status() == Status::Active) { b.at(m_colorNumber)->render(painter, body);  }
    if (inputPort( 8)->status() == Status::Active) { dp.at(m_colorNumber)->render(painter, body); }
    if (inputPort( 9)->status() == Status::Active) { c.at(m_colorNumber)->render(painter, body);  }
    if (inputPort(10)->status() == Status::Active) { g2.at(m_colorNumber)->render(painter, body); }
    if (inputPort(11)->status() == Status::Active) { h.at(m_colorNumber)->render(painter, body);  }
    if (inputPort(12)->status() == Status::Active) { j.at(m_colorNumber)->render(painter, body);  }
    if (inputPort(13)->status() == Status::Active) { k.at(m_colorNumber)->render(painter, body);  }
    if (inputPort(14)->status() == Status::Active) { l.at(m_colorNumber)->render(painter, body);  }
    if (inputPort(15)->status() == Status::Active) { m.at(m_colorNumber)->render(painter, body);  }
    if (inputPort(16)->status() == Status::Active) { n.at(m_colorNumber)->render(painter, body);  }
}

void Display16::setColor(const QString &color)
{
    m_color = color;
    m_colorNumber = colorNameToIndex(color);
}

QString Display16::color() const
{
    return m_color;
}

void Display16::save(QDataStream &stream, SerializationOptions options) const
{
    GraphicElement::save(stream, options);

    QMap<QString, QVariant> map;
    // PortableFile streams omit the default; fresh-loaded elements start there
    // anyway. Snapshots write unconditionally (reloaded into live elements).
    if (options.purpose != SerializationPurpose::PortableFile || color() != kDefaultColor) {
        map.insert("color", color());
    }

    stream << map;
}

void Display16::load(QDataStream &stream, SerializationContext &context)
{
    GraphicElement::load(stream, context);

    if ((VersionInfo::hasLockState(context.version)) && (!VersionInfo::hasQMapFormat(context.version))) {
        // v3.1–4.0 stored color as a bare QString
        const QString color_ = Serialization::readBoundedString(stream);
        setColor(color_);
    }

    if (VersionInfo::hasQMapFormat(context.version)) {
        // v4.1+ uses a key-value map
        QMap<QString, QVariant> map = Serialization::readBoundedMetadata(stream);

        if (map.contains("color")) {
            setColor(map.value("color").toString());
        }
    }
}
