/***************************************************************************
        Copyright (C) 2002-2026 Kentaro Kitagawa
		                   kitag@issp.u-tokyo.ac.jp
		
		This program is free software; you can redistribute it and/or
		modify it under the terms of the GNU Library General Public
		License as published by the Free Software Foundation; either
		version 2 of the License, or (at your option) any later version.
		
		You should have received a copy of the GNU Library General 
		Public License and a list of authors along with this program; 
		see the files COPYING and AUTHORS.
***************************************************************************/
//---------------------------------------------------------------------------
#include "ui_graphform.h"
#include "graphwidget.h"
#include "analyzer.h"
#include "graph.h"
#include "driver.h"
#include "measure.h"
#include "thermometer.h"

#include <QStatusBar>

//---------------------------------------------------------------------------
XScalarEntry::XScalarEntry(const char *name, bool runtime, const shared_ptr<XDriver> &driver,
						   const char *format)
	: XNode(name, runtime),
	  m_driver(driver),
	  m_delta(create<XDoubleNode>("Delta", false)),
	  m_store(create<XBoolNode>("Store", false)),
	  m_value(create<XDoubleNode>("Value", true)),
	  m_storedValue(create<XDoubleNode>("StoredValue", true)) {

	m_delta->setFormat(format);
	m_storedValue->setFormat(format);
	m_value->setFormat(format);
	trans( *store()) = false;
}
void
XScalarEntry::storeValue(Transaction &tr) {
    tr[ *storedValue()] = (double)tr[ *value()];
    tr[ *this].m_bTriggered = false;
}

XString
XScalarEntry::getLabel() const {
    auto drv = driver();
    if( !drv) return XNode::getLabel();
	return drv->getLabel() + "-" + XNode::getLabel();
}

void
XScalarEntry::value(Transaction &tr, double val) {
	Snapshot &shot(tr);
	if((shot[ *delta()] != 0) && (fabs(val - shot[ *storedValue()]) > shot[ *delta()])) {
		tr[ *this].m_bTriggered = true;
	}
	tr[ *value()] = val;
}

XValChart::XValChart(const char *name, bool runtime,
	const shared_ptr<XScalarEntry> &entry)
	: XNode(name, runtime),
	  m_entry(entry),
	  m_graph(create<XGraph>(name, false)),
      m_graphForm(new FrmGraph(g_pFrmMain, Qt::Window)) {

    m_graphForm->m_graphwidget->setGraph(m_graph);
    m_graphForm->m_axisSelector->hide();

    m_graph->iterate_commit([=](Transaction &tr){
        m_chart = m_graph->plots()->create<XXYPlot>(tr, entry->getLabel().c_str(), true, tr, m_graph);
        if( !m_chart) return; //transaction has failed.
		tr[ *m_chart->label()] = entry->getLabel();
		const XNode::NodeList &axes_list( *tr.list(m_graph->axes()));
		shared_ptr<XAxis> axisx = static_pointer_cast<XAxis>(axes_list.at(0));
		shared_ptr<XAxis> axisy = static_pointer_cast<XAxis>(axes_list.at(1));

		tr[ *m_chart->axisX()] = axisx;
		tr[ *m_chart->axisY()] = axisy;
		tr[ *m_chart->maxCount()] = 600;
		tr[ *axisx->length()] = 0.95 - tr[ *axisx->x()];
		tr[ *axisy->length()] = 0.90 - tr[ *axisy->y()];
		tr[ *axisx->label()] = "Time";
        tr[ *axisx->ticLabelFormat()] = "TIME:%H:%M:%S";
		tr[ *axisy->label()] = entry->getLabel();
		tr[ *axisy->ticLabelFormat()] = entry->value()->format();
		tr[ *axisx->minValue()].setUIEnabled(false);
		tr[ *axisx->maxValue()].setUIEnabled(false);
		tr[ *axisx->autoScale()].setUIEnabled(false);
		tr[ *axisx->logScale()].setUIEnabled(false);
		tr[ *m_graph->label()] = entry->getLabel();

        m_graph->applyTheme(tr, true);
    });

    if(auto drv = entry->driver()) {
        drv->iterate_commit([=](Transaction &tr){
            m_lsnOnRecord = tr[ *drv].onVisualization().connectWeakly(
                shared_from_this(), &XValChart::onVisualization);
        });
    }
}
void
XValChart::onVisualization(const Snapshot &shot, bool afterRecorded, XDriver *driver) {
    XTime time = shot[ *driver].time();
    if(afterRecorded && time.isSet()) {
        try {
            double val = shot.at( *m_entry->value());
            iterate_commit([=](Transaction &tr){
                m_chart->addPoint(tr, time.sec() + time.usec() * 1e-6, val);
    //            tr[ *m_graph->onScreenStrings()] = time.getTimeStr();
            });
        }
        catch (XNode::NodeNotFoundError &e) {
            //Entry has been already released from the list.
            static XMutex s_mutex;
            XScopedLock<XMutex> lock(s_mutex);
            m_lsnOnRecord.reset();
        }
    }
}
void
XValChart::showChart(void) {
	m_graphForm->setWindowTitle(i18n("Chart - ") + getLabel() );
    m_graphForm->showNormal();
    m_graphForm->raise();
}

XChartList::XChartList(const char *name, bool runtime, const shared_ptr<XScalarEntryList> &entries)
	: XAliasListNode<XValChart>(name, runtime),
	  m_entries(entries) {
    entries->iterate_commit([=](Transaction &tr){
        m_lsnOnCatchEntry = tr[ *entries].onCatch().connectWeakly(shared_from_this(), &XChartList::onCatchEntry, Listener::FLAG_MAIN_THREAD_CALL);
        m_lsnOnReleaseEntry = tr[ *entries].onRelease().connectWeakly(shared_from_this(), &XChartList::onReleaseEntry);
    });
}

void
XChartList::onCatchEntry(const Snapshot &shot, const XListNodeBase::Payload::CatchEvent &e) {
    shared_ptr<XScalarEntry> entry = static_pointer_cast<XScalarEntry>(e.caught);
    if( !entry->driver()) return;
    create<XValChart>(entry->getLabel().c_str(), true, entry);
}
void
XChartList::onReleaseEntry(const Snapshot &shot, const XListNodeBase::Payload::ReleaseEvent &e) {
	shared_ptr<XScalarEntry> entry = dynamic_pointer_cast<XScalarEntry>(e.released);
    iterate_commit_while([=](Transaction &tr)->bool{
		shared_ptr<XValChart> valchart;
		if(tr.size()) {
			const XNode::NodeList &list( *tr.list());
			for(auto it = list.begin(); it != list.end(); it++) {
				auto chart = dynamic_pointer_cast<XValChart>( *it);
				if(chart->entry() == entry) valchart = chart;
			}
		}
		if( !valchart)
            return false;
		if( !release(tr, valchart))
            return true;//will fail.
        return true;
    });
}

FrmGraph *
XValGraph::graphForm() {
    if( !m_graphForm)
        m_graphForm.reset(new FrmGraph(g_pFrmMain, Qt::Window));
    return m_graphForm.get();
}
XValGraph::XValGraph(const char *name, bool runtime,
					 Transaction &tr_entries, const shared_ptr<XScalarEntryList> &entries)
	: XNode(name, runtime),
	  m_graphForm(),
	  m_axisX(create<tAxis>("AxisX", false, ref(tr_entries), entries)),
	  m_axisY1(create<tAxis>("AxisY1", false, ref(tr_entries), entries)),
	  m_axisZ(create<tAxis>("AxisZ", false, ref(tr_entries), entries)),
	  m_entries(entries) {
	iterate_commit([=](Transaction &tr){
	    m_lsnAxisChanged = tr[ *axisX()].onValueChanged().connectWeakly(
	        shared_from_this(), &XValGraph::onAxisChanged,
            Listener::FLAG_MAIN_THREAD_CALL | Listener::FLAG_AVOID_DUP);
	    tr[ *axisY1()].onValueChanged().connect(m_lsnAxisChanged);
	    tr[ *axisZ()].onValueChanged().connect(m_lsnAxisChanged);
    });
}
void
XValGraph::onAxisChanged(const Snapshot &shot, XValueNodeBase *) {
    shared_ptr<XScalarEntry> entryx;
    shared_ptr<XScalarEntry> entryy1;
    shared_ptr<XScalarEntry> entryz;
    shared_ptr<XGraph> graph;
    iterate_commit([=, &entryx, &entryy1, &entryz, &graph](Transaction &tr){
		const Snapshot &shot_this(tr);
	    entryx = shot_this[ *axisX()];
	    entryy1 = shot_this[ *axisY1()];
	    entryz = shot_this[ *axisZ()];

		if(tr[ *this].m_graph) release(tr, tr[ *this].m_graph);
		tr[ *this].m_graph = create<XGraph>(tr, getName().c_str(), false);
        if( !tr[ *this].m_graph) return; //transaction has failed.
        graph = tr[ *this].m_graph;

		if( !entryx || !entryy1) {
			tr[ *this].m_livePlot.reset();
			tr[ *this].m_storePlot.reset();
			return;
		}

		tr[ *this].m_livePlot =
			graph->plots()->create<XXYPlot>(tr,
				(graph->getName() + "-Live").c_str(), false, tr, graph);
        if( !tr[ *this].m_livePlot) return;
		tr[ *shot_this[ *this].m_livePlot->label()] = graph->getLabel() + " Live";
		tr[ *this].m_storePlot =
			graph->plots()->create<XXYPlot>(tr,
				(graph->getName() + "-Stored").c_str(), false, tr, graph);
        if( !tr[ *this].m_storePlot) return;
		tr[ *shot_this[ *this].m_storePlot->label()] = graph->getLabel() + " Stored";

		const XNode::NodeList &axes_list( *tr.list(graph->axes()));
		auto axisx = static_pointer_cast<XAxis>(axes_list.at(0));
		auto axisy = static_pointer_cast<XAxis>(axes_list.at(1));

		tr[ *axisx->ticLabelFormat()] = entryx->value()->format();
		tr[ *axisy->ticLabelFormat()] = entryy1->value()->format();
		tr[ *shot_this[ *this].m_livePlot->axisX()] = axisx;
		tr[ *shot_this[ *this].m_livePlot->axisY()] = axisy;
		tr[ *shot_this[ *this].m_storePlot->axisX()] = axisx;
		tr[ *shot_this[ *this].m_storePlot->axisY()] = axisy;

		tr[ *axisx->length()] = 0.95 - shot_this[ *axisx->x()];
		tr[ *axisy->length()] = 0.90 - shot_this[ *axisy->y()];
		if(entryz) {
			shared_ptr<XAxis> axisz = graph->axes()->create<XAxis>(
                tr, "Z Axis", false, XAxis::AxisDirection::Z, false, tr, graph);
			tr[ *axisz->ticLabelFormat()] = entryz->value()->format();
			tr[ *shot_this[ *this].m_livePlot->axisZ()] = axisz;
			tr[ *shot_this[ *this].m_storePlot->axisZ()] = axisz;
	//	axisz->label()] = "Z Axis";
			tr[ *axisz->label()] = entryz->getLabel();
		}

		tr[ *shot_this[ *this].m_storePlot->displayMajorGrid()] = false;
        tr[ *shot_this[ *this].m_livePlot->maxCount()] = 10000;
        tr[ *shot_this[ *this].m_storePlot->maxCount()] = 10000;
		tr[ *axisx->label()] = entryx->getLabel();
		tr[ *axisy->label()] = entryy1->getLabel();
		tr[ *graph->label()] = getLabel();

        graph->applyTheme(tr, true);
    });
    graphForm()->m_graphwidget->setGraph(graph);
    m_lsnOnVisualization.reset();
    if(auto drv = entryx ? entryx->driver() : shared_ptr<XDriver>()) {
        drv->iterate_commit([=](Transaction &tr){
            m_lsnOnVisualization = tr[ *drv].onVisualization().connectWeakly(
                shared_from_this(), &XValGraph::onVisualization);
        });
    }
    auto entries = m_entries.lock();
    if( !entries) return;
    entries->iterate_commit([=](Transaction &tr){
		if( !tr.isUpperOf( *entryx)) return;
		if( !tr.isUpperOf( *entryy1)) return;
		if(entryz && !tr.isUpperOf( *entryz)) return;
		m_lsnStoreChanged = tr[ *entryx->storedValue()].onValueChanged().connectWeakly(
			shared_from_this(), &XValGraph::onStoreChanged);
		tr[ *entryy1->storedValue()].onValueChanged().connect(m_lsnStoreChanged);
		if(entryz) tr[ *entryz->storedValue()].onValueChanged().connect(m_lsnStoreChanged);
    });

	showGraph();
}

void
XValGraph::clearAllPoints() {
	iterate_commit([=](Transaction &tr){
		if( !tr[ *this].m_graph) return;
		tr[ *this].m_storePlot->clearAllPoints(tr);
		tr[ *this].m_livePlot->clearAllPoints(tr);
    });
}
void
XValGraph::onVisualization(const Snapshot &shot, bool afterRecorded, XDriver *driver) {
    if( !afterRecorded || !shot[ *driver].time().isSet()) return;
	Snapshot shot_this( *this);
	shared_ptr<XScalarEntry> entryx = shot_this[ *axisX()];
	shared_ptr<XScalarEntry> entryy1 = shot_this[ *axisY1()];
	shared_ptr<XScalarEntry> entryz = shot_this[ *axisZ()];
	if( !entryx || !entryy1) return;
	auto entries_locked = m_entries.lock();
	if( !entries_locked) return;
	Snapshot shot_entries( *entries_locked);
	if( !shot_entries.isUpperOf( *entryx)) return;
	if( !shot_entries.isUpperOf( *entryy1)) return;
	if(entryz && !shot_entries.isUpperOf( *entryz)) return;

	double x, y, z = 0.0;
	x = shot_entries[ *entryx->value()];
	y = shot_entries[ *entryy1->value()];
	if(entryz) z = shot_entries[ *entryz->value()];

	iterate_commit([=](Transaction &tr){
		if( !tr[ *this].m_livePlot) return;
		tr[ *this].m_livePlot->addPoint(tr, x, y, z);
    });
}
void
XValGraph::onStoreChanged(const Snapshot &shot, XValueNodeBase *) {
	Snapshot shot_this( *this);
	shared_ptr<XScalarEntry> entryx = shot_this[ *axisX()];
	shared_ptr<XScalarEntry> entryy1 = shot_this[ *axisY1()];
	shared_ptr<XScalarEntry> entryz = shot_this[ *axisZ()];
	if( !entryx || !entryy1) return;
	auto entries_locked2 = m_entries.lock();
	if( !entries_locked2) return;
	Snapshot shot_entries( *entries_locked2);
	if( !shot_entries.isUpperOf( *entryx)) return;
	if( !shot_entries.isUpperOf( *entryy1)) return;
	if(entryz && !shot_entries.isUpperOf( *entryz)) return;

	double x, y, z = 0.0;
	x = shot_entries[ *entryx->storedValue()];
	y = shot_entries[ *entryy1->storedValue()];
	if(entryz) z = shot_entries[ *entryz->storedValue()];

	iterate_commit([=](Transaction &tr){
		if( !tr[ *this].m_storePlot) return;
		tr[ *this].m_storePlot->addPoint(tr, x, y, z);
    });
}
void
XValGraph::showGraph() {
    if(m_graphForm && Snapshot( *this)[ *this].m_graph) {
		m_graphForm->setWindowTitle(i18n("Graph - ") + getLabel() );
        m_graphForm->showNormal();
        m_graphForm->raise();
    }
}

XGraphList::XGraphList(const char *name, bool runtime, const shared_ptr<XScalarEntryList> &entries)
	: XCustomTypeListNode<XValGraph>(name, runtime),
	  m_entries(entries) {
}

shared_ptr<XNode>
XGraphList::createByTypename(const XString &, const XString& name)  {
    shared_ptr<XValGraph> x;
    m_entries->iterate_commit([=, &x](Transaction &tr){
        if(x) release(x);
        x = create<XValGraph>(name.c_str(), false, tr, m_entries);
    });
    return x;
}

XCalibratedEntry::XCalibratedEntry(const char *name, bool runtime,
    Transaction &tr_meas, const shared_ptr<XMeasure> &meas,
    const shared_ptr<XScalarEntryList> &entries,
    const shared_ptr<XCalibrationCurveList> &curves)
    : XDriver(name, runtime, tr_meas, meas),
      m_source(create<tSource>("Source", false, tr_meas, entries)),
      m_curve(create<tCurve>("Curve", false, tr_meas, curves)),
      m_entries(entries) {
    iterate_commit([=](Transaction &tr){
        m_lsnSelectionChanged = tr[ *m_source].onValueChanged().connectWeakly(
            shared_from_this(), &XCalibratedEntry::onSelectionChanged,
            Listener::FLAG_MAIN_THREAD_CALL | Listener::FLAG_AVOID_DUP);
        tr[ *m_curve].onValueChanged().connect(m_lsnSelectionChanged);
    });
}
XCalibratedEntry::~XCalibratedEntry() {
    if(auto entry = m_entry)
        if(auto entries = m_entries.lock()) {
            entries->iterate_commit([=](Transaction &tr){
                if(tr.isUpperOf( *entry))
                    entries->release(tr, entry);
            });
        }
}
void
XCalibratedEntry::onSelectionChanged(const Snapshot &, XValueNodeBase *) {
    Snapshot shot( *this);
    shared_ptr<XScalarEntry> src = shot[ *m_source];
    shared_ptr<XCalibrationCurve> curve = shot[ *m_curve];
    // Reconnect onRecord listener to the new source driver.
    m_lsnOnRecord.reset();
    auto entry = m_entry;
    auto entries_list = m_entries.lock();
    if(entry) {
        if(entries_list) entries_list->release(entry);
        m_entry.reset();
    }
    if(src && curve) {
        if(auto drv = src->driver()) {
            drv->iterate_commit([=](Transaction &tr){
                m_lsnOnRecord = tr[ *drv].onRecord().connectWeakly(
                    shared_from_this(), &XCalibratedEntry::onSourceDriverRecord);
            });
        }
        // Update output unit format on the entry nodes.
        XString unit = curve->outputUnit();
        XString fmt = unit.empty() ? "%.5g" : ("%.5g " + unit);
        entry = create<XScalarEntry>(src->getLabel().c_str(), true,
            static_pointer_cast<XDriver>(shared_from_this()));
        entry->value()->setFormat(fmt.c_str());
        entry->storedValue()->setFormat(fmt.c_str());
        entry->delta()->setFormat(fmt.c_str());
        if(entries_list) entries_list->insert(entry);
        m_entry = entry;
    }
}
void
XCalibratedEntry::onSourceDriverRecord(const Snapshot &shot_driver, XDriver *driver) {
    Snapshot shot_this( *this);
    shared_ptr<XScalarEntry> src = shot_this[ *m_source];
    shared_ptr<XCalibrationCurve> curve = shot_this[ *m_curve];
    if( !src || !curve) return;
    if(src->driver().get() != driver) return; // stale connection

    double raw;
    if(shot_driver.isUpperOf( *src->value())) {
        raw = shot_driver[ *src->value()];
    } else {
        Snapshot shot_src( *src);
        raw = shot_src[ *src->value()];
    }
    double out;
    try {
        out = curve->getOutput(raw);
    } catch(...) {
        return;
    }

    XTime time_recorded = shot_driver[ *driver].time();
    XTime time_awared = shot_driver[ *driver].timeAwared();
    auto entry = m_entry;
    if( !entry) return;
    for(;;) {
        Transaction tr( *this);
        entry->value(tr, out);
        record(tr, time_awared, time_recorded);
        if(tr.commit()) {
            Snapshot &shot(tr);
            visualize(shot);
            trans( *this).onVisualization().talk(shot, true, this);
            break;
        }
    }
}

XCalibratedEntryList::XCalibratedEntryList(const char *name, bool runtime,
    const shared_ptr<XScalarEntryList> &entries,
    const shared_ptr<XCalibrationCurveList> &curves,
    const shared_ptr<XMeasure> &meas)
    : XCustomTypeListNode<XCalibratedEntry>(name, runtime),
      m_entries(entries), m_curves(curves), m_measure(meas) {
}
shared_ptr<XNode>
XCalibratedEntryList::createByTypename(const XString &, const XString &name) {
    auto meas = m_measure.lock();
    if( !meas) return {};
    shared_ptr<XCalibratedEntry> calibEntry;
    meas->iterate_commit_if([=, &calibEntry](Transaction &tr) -> bool {
        if( !calibEntry)
            calibEntry = XNode::createOrphan<XCalibratedEntry>(
                name.c_str(), false, tr, meas, entries(), curves());
        if( !insert(tr, calibEntry)) return false;
        return true;
    });
    return calibEntry;
}
