/***************************************************************************
 *   Copyright (C) 2005 by David Saxton                                    *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "oscilloscope.h"
#include "oscilloscopedata.h"
#include "oscilloscopeview.h"
#include "probe.h"
#include "probepositioner.h"
#include "simulator.h"
#include "ktechlab.h"

#include <cmath>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KIconLoader>
#include <KLocalizedString>


// #include <q3button.h>

#include <QDebug>
#include <QLabel>
#include <QScrollBar>
#include <QSlider>
#include <QTimer>
#include <QToolButton>

#include <cassert>


//BEGIN Oscilloscope Class
QColor probeColors[9] = {
	QColor( 0x52, 0x22, 0x00),
	QColor( 0xB5, 0x00, 0x2F),
	QColor( 0xF9, 0xBA, 0x07),
	QColor( 0x53, 0x93, 0x16),
	QColor( 0x00, 0x66, 0x2F),
	QColor( 0x00, 0x41, 0x88),
	QColor( 0x1B, 0x2D, 0x83),
	QColor( 0x55, 0x12, 0x7B),
	QColor( 0x7B, 0x0C, 0x82) };

Oscilloscope * Oscilloscope::m_pSelf = nullptr;

Oscilloscope * Oscilloscope::self( KateMDI::ToolView * parent)
{
	if( !m_pSelf) {
		assert(parent);
		m_pSelf = new Oscilloscope(parent);
	}
	return m_pSelf;
}


Oscilloscope::Oscilloscope( KateMDI::ToolView * parent)
	: QWidget(parent)
{
    setupUi(this);

    if (parent->layout()) {
        parent->layout()->addWidget(this);
        qDebug() << Q_FUNC_INFO << " added item selector to parent's layout " << parent;
    } else {
        qWarning() << Q_FUNC_INFO << " unexpected null layout on parent " << parent ;
    }

	m_nextColor = 0;
	m_nextId = 1;
	m_oldestId = -1;
	m_oldestProbe = nullptr;
// 	b_isPaused = false;
	m_zoomLevel = 0.5;
	m_pSimulator = Simulator::self();

	horizontalScroll->setSingleStep(32);
	horizontalScroll->setPageStep( oscilloscopeView->width());

	connect( resetBtn, SIGNAL(clicked()), this, SLOT(reset()));
	connect( zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int)));
	connect( horizontalScroll, SIGNAL(valueChanged(int)), this, SLOT(slotSliderValueChanged(int)));

// 	connect( pauseBtn, SIGNAL(clicked()), this, SLOT(slotTogglePause()));

	QTimer * updateScrollTmr = new QTimer(this);
	connect( updateScrollTmr, SIGNAL(timeout()), this, SLOT(updateScrollbars()));
	updateScrollTmr->start(20);

	//KGlobal::config()->setGroup("Oscilloscope");
    KConfigGroup grOscill(KSharedConfig::openConfig(), "Oscilloscope");
	setZoomLevel( grOscill.readEntry( "ZoomLevel", 0.5));

	connect( this, SIGNAL(probeRegistered(int, ProbeData *)), probePositioner, SLOT(slotProbeDataRegistered(int, ProbeData *)));
	connect( this, SIGNAL(probeUnregistered(int)), probePositioner, SLOT(slotProbeDataUnregistered(int)));
}


Oscilloscope::~Oscilloscope()
{
    m_pSelf = nullptr;
}

bool Oscilloscope::isInstantiated() {
    return m_pSelf != nullptr;
}

void Oscilloscope::slotTogglePause()
{
// 	b_isPaused = !b_isPaused;
// 	pauseBtn->setText( b_isPaused ? i18n("Resume") : i18n("Pause"));
// 	const ProbeDataMap::iterator end = m_probeDataMap.end();
// 	for( ProbeDataMap::iterator it = m_probeDataMap.begin(); it != end; ++it)
// 		(*it)->setPaused(b_isPaused);
}


int Oscilloscope::sliderTicksPerSecond() const
{
	return int(1e4);
}


void Oscilloscope::setZoomLevel( double zoomLevel)
{
	if( zoomLevel < 0.0)
		zoomLevel = 0.0;

	else if( zoomLevel > 1.0)
		zoomLevel = 1.0;

	//KGlobal::config()->setGroup("Oscilloscope");
    KConfigGroup grOscill = KSharedConfig::openConfig()->group("Oscilloscope");
	//KGlobal::config()->writeEntry( "ZoomLevel", zoomLevel);
    grOscill.writeEntry( "ZoomLevel", zoomLevel);

	// We want to maintain the position of the *center* of the view, not the
	// left edge, so have to record time at center of view... We also have to
	// handle the case where the scroll is at the end separately.
	bool wasAtUpperEnd = horizontalScroll->maximum() == horizontalScroll->value();
	int pageLength = int(oscilloscopeView->width()*sliderTicksPerSecond()/pixelsPerSecond());
	int at_ticks = horizontalScroll->value() + (pageLength/2);

	m_zoomLevel = zoomLevel;
	zoomSlider->setValue( int((double(zoomSlider->maximum())*zoomLevel)+0.5));
	updateScrollbars();

	// And restore the center position of the slider
	if(!wasAtUpperEnd)
	{
		int pageLength = int(oscilloscopeView->width()*sliderTicksPerSecond()/pixelsPerSecond());
		horizontalScroll->setValue( at_ticks - (pageLength/2));
		oscilloscopeView->updateView();
	}
}


void Oscilloscope::slotZoomSliderChanged( int value)
{
	setZoomLevel( double(value)/double(zoomSlider->maximum()));
}


ProbeData * Oscilloscope::registerProbe( Probe * probe)
{
	if(!probe) return nullptr;

	const uint id = m_nextId++;

	ProbeData * probeData = nullptr;

	if( dynamic_cast<LogicProbe*>(probe)) {
		probeData = new LogicProbeData(id);
		m_logicProbeDataMap[id] = static_cast<LogicProbeData*>(probeData);
	} else {
		probeData = new FloatingProbeData(id);
		m_floatingProbeDataMap[id] = static_cast<FloatingProbeData*>(probeData);
	}

	m_probeDataMap[id] = probeData;

	if(!m_oldestProbe) {
		m_oldestProbe = probeData;
		m_oldestId = id;
	}

	probeData->setColor( probeColors[m_nextColor]);
	m_nextColor = (m_nextColor+1)%9;
//	probeData->setPaused(b_isPaused);

	emit probeRegistered( id, probeData);
	return probeData;
}


void Oscilloscope::unregisterProbe( int id)
{
	ProbeDataMap::iterator it = m_probeDataMap.find(id);

	if( it == m_probeDataMap.end())
		return;

	m_logicProbeDataMap.remove(id);
	m_floatingProbeDataMap.remove(id);

	bool oldestDestroyed = it.value() == m_oldestProbe;

	if( it != m_probeDataMap.end())
		m_probeDataMap.erase(it);

	if(oldestDestroyed)
		getOldestProbe();

	emit probeUnregistered(id);
}


ProbeData * Oscilloscope::probeData( int id) const
{
	const ProbeDataMap::const_iterator bit = m_probeDataMap.find(id);
	if( bit != m_probeDataMap.end())
		return bit.value();

	return nullptr;
}


int Oscilloscope::probeNumber( int id) const
{
	const ProbeDataMap::const_iterator end = m_probeDataMap.end();
	int i=0;
	for( ProbeDataMap::const_iterator it = m_probeDataMap.begin(); it != end; ++it)
	{
		if( it.key() == id)
			return i;
		i++;
	}
	return -1;
}


int Oscilloscope::numberOfProbes() const
{
	return m_probeDataMap.size();
}


void Oscilloscope::getOldestProbe()
{
	if( m_probeDataMap.isEmpty())
	{
		m_oldestProbe = nullptr;
		m_oldestId = -1;
		return;
	}

	m_oldestProbe = m_probeDataMap.begin().value();
	m_oldestId = m_probeDataMap.begin().key();
}


void Oscilloscope::reset()
{
	const ProbeDataMap::iterator end = m_probeDataMap.end();
	for( ProbeDataMap::iterator it = m_probeDataMap.begin(); it != end; ++it)
		(*it)->eraseData();

	oscilloscopeView->updateView();
}


void Oscilloscope::slotSliderValueChanged( int value)
{
	Q_UNUSED(value);
	oscilloscopeView->updateView();
}


void Oscilloscope::updateScrollbars()
{
	bool wasAtUpperEnd = horizontalScroll->maximum() == horizontalScroll->value();

	const float pps = pixelsPerSecond();

	int pageLength = int(oscilloscopeView->width()*sliderTicksPerSecond()/pps);
	int64_t timeAsTicks = time()*sliderTicksPerSecond()/LOGIC_UPDATE_RATE;
	int64_t upper = (timeAsTicks > pageLength) ? (timeAsTicks - pageLength) : 0;
	horizontalScroll->setRange( 0, upper);

	horizontalScroll->setPageStep( uint64_t(oscilloscopeView->width()*sliderTicksPerSecond()/pps));

	if(wasAtUpperEnd)
	{
		horizontalScroll->setValue( horizontalScroll->maximum());
		oscilloscopeView->updateView();
	}
}


uint64_t Oscilloscope::time() const
{
	if(!m_oldestProbe)
		return 0;

	return uint64_t( m_pSimulator->time() - m_oldestProbe->resetTime());
}


int64_t Oscilloscope::scrollTime() const
{
// 	if( b_isPaused || numberOfProbes() == 0)
// 		return 0;

	if( numberOfProbes() == 0) return 0;

	if( horizontalScroll->maximum() == 0)
	{
		int64_t lengthAsTime = int64_t( oscilloscopeView->width() * LOGIC_UPDATE_RATE / pixelsPerSecond());
		int64_t ret =  m_pSimulator->time() - lengthAsTime;
		if(ret < 0) return 0;
		return ret;
	} else return int64_t( m_oldestProbe->resetTime() + (int64_t(horizontalScroll->value()) * LOGIC_UPDATE_RATE / sliderTicksPerSecond()));
}


double Oscilloscope::pixelsPerSecond() const
{
	return 2 * MIN_BITS_PER_S * std::pow( 2.0, m_zoomLevel * MIN_MAX_LOG_2_DIFF);
}
//END Oscilloscope Class



void addOscilloscopeAsToolView( KTechlab *ktechlab)
{
	KateMDI::ToolView * tv;
	tv = ktechlab->createToolView( Oscilloscope::toolViewIdentifier(),
				KMultiTabBar::Bottom,
				KIconLoader::global()->loadIcon( "oscilloscope", KIconLoader::Small),
				i18n("Oscilloscope"));

	Oscilloscope::self(tv);
}


ProbeData * registerProbe( Probe * probe)
{
    if (!Oscilloscope::isInstantiated()) {
        qDebug() << Q_FUNC_INFO << "no oscilloscope to register to, doing nothing";
        return nullptr;
    }
	return Oscilloscope::self()->registerProbe(probe);
}


void unregisterProbe( int id)
{
    if (!Oscilloscope::isInstantiated()) {
        qDebug() << Q_FUNC_INFO << "no oscilloscope to unregister from, doing nothing";
        return;
    }
	Oscilloscope::self()->unregisterProbe(id);
}
