/*
 * Copyright (c) 2012-2018, Red Hat. All Rights Reserved.
 * Copyright (c) 2012, Nathan Scott.  All Rights Reserved.
 * Copyright (c) 2007, Aconex.  All Rights Reserved.
 * 
 * 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.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */
#include <limits>
#include "sampling.h"
#include "main.h"
#include <qnumeric.h>
#include <qwt_picker_machine.h>

SamplingItem::SamplingItem(Chart *parent,
	QmcMetric *mp, pmMetricSpec *msp, pmDesc *dp,
	const QString &legend, Chart::Style style, int samples, int index)
	: ChartItem(mp, msp, dp, legend)
{
    pmDesc desc = mp->desc().desc();

    my.chart = parent;
    my.info = QString();

    // initialize the pcp data and item data arrays
    my.dataCount = 0;
    resetValues(samples, 0.0, 0.0);

    // set base scale, then tweak if value to plot is time / time
    my.scale = 1;
    if (style != Chart::UtilisationStyle &&
	desc.sem == PM_SEM_COUNTER && desc.units.dimTime == 0) {
	if (desc.units.scaleTime == PM_TIME_USEC)
	    my.scale = 0.000001;
	else if (desc.units.scaleTime == PM_TIME_MSEC)
	    my.scale = 0.001;
    }

    // create and attach the plot right here
    my.curve = new SamplingCurve(label());
    my.curve->attach(parent);

    // the 1000 is arbitrary ... just want numbers to be monotonic
    // decreasing as plots are added
    my.curve->setZ(1000 - index);
}

SamplingItem::~SamplingItem(void)
{
}

QwtPlotItem *
SamplingItem::item(void)
{
    return my.curve;
}

QwtPlotCurve *
SamplingItem::curve(void)
{
    return my.curve;
}

void
SamplingItem::resetValues(int values, double, double)
{
    // Reset sizes of pcp data array and the plot data array
    my.data.resize(values);
    my.itemData.resize(values);
    if (my.dataCount > values)
	my.dataCount = values;
}

void
SamplingItem::preserveSample(int index, int oldindex)
{
    if (my.dataCount > oldindex)
	my.itemData[index] = my.data[index] = my.data[oldindex];
    else
	my.itemData[index] = my.data[index] = qQNaN();
}

void
SamplingItem::punchoutSample(int index)
{
    my.data[index] = my.itemData[index] = qQNaN();
}

void
SamplingItem::updateValues(bool forward,
		bool rateConvert, pmUnits *units, int sampleHistory, int,
		double, double, double)
{
    pmAtomValue	scaled, raw;
    QmcMetric	*metric = ChartItem::my.metric;
    double	value;
    int		sz;

    if (metric->numValues() < 1 || metric->error(0)) {
	value = qQNaN();
    } else {
	// convert raw value to current chart scale
	int	sts;
	raw.d = rateConvert ? metric->value(0) : metric->currentValue(0);
	sts = pmConvScale(PM_TYPE_DOUBLE, &raw, &ChartItem::my.units, &scaled, units);
	if (sts < 0) {
	    /* should never happen */
	    if (pmDebugOptions.value) {
		fprintf(stderr, "SamplingItem::updateValues: Botch: %s (%s) scale conversion from %s", 
		    pmIDStr(metric->metricID()), metric->name().toStdString().c_str(), pmUnitsStr(&ChartItem::my.units));
		fprintf(stderr, " to %s failed: %s\n", pmUnitsStr(units), pmErrStr(sts));
	    }
	    value = 0;
	}
	else
	    value = scaled.d * my.scale;
    }

    // sz will be the number of previous samples we want to keep.
    if (my.dataCount < sampleHistory)
	sz = qMax(0, (int)(my.dataCount));
    else
	sz = qMax(0, (int)((my.dataCount - 1)));

    if (forward) {
	// Keep sz samples and add the new sample to the beginning.
	my.data.resize(sz);
	my.itemData.resize(sz);
	my.data.push_front(value);
	my.itemData.push_front(value);
    } else {
	// Keep sz samples and add the new sample to the end.
	if (my.dataCount) {
	    my.data.pop_front();
	    my.itemData.pop_front();
	}
	my.data.resize(sz);
	my.itemData.resize(sz);
	my.data.push_back(value);
	my.itemData.push_back(value);
    }

    if (my.dataCount < sampleHistory)
	my.dataCount++;
}

void
SamplingItem::rescaleValues(const pmUnits *old_units, const pmUnits *new_units)
{
    pmAtomValue	old_av, new_av;

    console->post("Chart::rescaleValues change units from %s to %s",
			pmUnitsStr(old_units), pmUnitsStr(new_units));

    for (int i = my.dataCount - 1; i >= 0; i--) {
	int	sts = 0;
	new_av.d = 0;
	if (my.data[i] != qQNaN()) {
	    old_av.d = my.data[i];
	    sts = pmConvScale(PM_TYPE_DOUBLE, &old_av, old_units, &new_av, new_units);
	    my.data[i] = new_av.d;
	}
	if (my.itemData[i] != qQNaN()) {
	    old_av.d = my.itemData[i];
	    sts = pmConvScale(PM_TYPE_DOUBLE, &old_av, old_units, &new_av, new_units);
	    my.itemData[i] = new_av.d;
	}
	if (sts < 0) {
	    /* should never happen */
	    if (pmDebugOptions.value) {
		fprintf(stderr, "SamplingItem::rescaleValues: Botch: %s (%s) scale conversion from %s", 
		    pmIDStr(ChartItem::my.metric->metricID()), ChartItem::my.metric->name().toStdString().c_str(), pmUnitsStr(old_units));
		fprintf(stderr, " to %s failed: %s\n", pmUnitsStr(new_units), pmErrStr(sts));
	    }
	}
    }
}

void
SamplingItem::replot(int history, const QVector<double> &timeData)
{
    // Restrict the number of samples to the minimum of history and my.dataCount
    int count = qMin(history, my.dataCount);

    // Use QwtPlotCurve::setSamples( const QVector<QPointF> & ) which is a
    // non-deprecated instance of setSamples.
    my.samples.clear();
    for (int i = 0; i < count; ++i) {
	QPointF sample(timeData[i], my.itemData[i]);
	my.samples.push_back(sample);
    }
    my.curve->setSamples(my.samples);
    console->post("SamplingItem::replot");
}

void
SamplingItem::revive(void)
{
    if (removed()) {
	setRemoved(false);
	my.curve->attach(my.chart);
    }
}

void
SamplingItem::remove(void)
{
    setRemoved(true);
    my.curve->detach();

    // We can't really do this properly (free memory, etc) - working around
    // metrics class limit (its using an ordinal index for metrics, remove any
    // and we'll get problems.  Which means the plots array must also remain
    // unchanged, as we drive things via the metriclist at times.  D'oh.
    // This blows - it means we have to continue to fetch metrics for those
    // metrics that have been removed from the chart, which may be remote
    // hosts, hosts which are down (introducing retry issues...).  Bother.

    //delete my.curve;
    //free(my.legend);
}

void
SamplingItem::setStroke(Chart::Style style, QColor color, bool antiAlias)
{
    int  sem = metricPtr()->desc().desc().sem;
    bool step = (sem == PM_SEM_INSTANT || sem == PM_SEM_DISCRETE);

    my.curve->setLegendColor(color);
    my.curve->setRenderHint(QwtPlotItem::RenderAntialiased, antiAlias);

    switch (style) {
	case Chart::BarStyle:
	    my.curve->setPen(color);
	    my.curve->setBrush(QBrush(color, Qt::SolidPattern));
	    my.curve->setStyle(QwtPlotCurve::Sticks);
	    break;

	case Chart::AreaStyle:
	    my.curve->setPen(color);
	    my.curve->setBrush(QBrush(color, Qt::SolidPattern));
	    my.curve->setStyle(step? QwtPlotCurve::Steps : QwtPlotCurve::Lines);
	    break;

	case Chart::UtilisationStyle:
	    my.curve->setPen(QColor(Qt::black));
	    my.curve->setStyle(QwtPlotCurve::Steps);
	    my.curve->setBrush(QBrush(color, Qt::SolidPattern));
	    break;

	case Chart::LineStyle:
	    my.curve->setPen(color);
	    my.curve->setBrush(QBrush(Qt::NoBrush));
	    my.curve->setStyle(step? QwtPlotCurve::Steps : QwtPlotCurve::Lines);
	    break;

	case Chart::StackStyle:
	    my.curve->setPen(QColor(Qt::black));
	    my.curve->setBrush(QBrush(color, Qt::SolidPattern));
	    my.curve->setStyle(QwtPlotCurve::Steps);
	    break;

	default:
	    break;
    }
}

void
SamplingItem::clearCursor()
{
    my.info.clear();
}

bool
SamplingItem::containsPoint(const QRectF &, int)
{
    return false;
}

void
SamplingItem::updateCursor(const QPointF &, int index)
{
    // Use the point on our curve represented by the given data index.
    GroupControl		*group = my.chart->tab()->group();
    const QVector<double>	&timeData = group->timeAxisData();
    Q_ASSERT(index < my.dataCount);
    QPointF curvePoint( timeData[index], my.itemData[index]);

    // Now get the point info.
    my.info = my.chart->pointValueText(curvePoint);
}

const QString &
SamplingItem::cursorInfo()
{
    return my.info;
}

void
SamplingItem::copyRawDataPoint(int index)
{
    if (index < 0)
	index = my.dataCount - 1;
    my.itemData[index] = my.data[index];
}

int
SamplingItem::maximumDataCount(int maximum)
{
    return qMax(maximum, my.dataCount);
}

void
SamplingItem::truncateData(int offset)
{
    for (int index = my.dataCount + 1; index < offset; index++) {
	my.data[index] = 0;
	// don't re-set dataCount ... so we don't plot these values,
	// we just want them to count 0 towards any Stack aggregation
    }
}

double
SamplingItem::sumData(int index, double sum)
{
    if (index < 0)
	index = my.dataCount - 1;
    if (index < my.dataCount && !qIsNaN(my.data[index]))
	sum += my.data[index];
    return sum;
}

void
SamplingItem::copyRawDataArray(void)
{
    for (int index = 0; index < my.dataCount; index++)
	my.itemData[index] = my.data[index];
}

void
SamplingItem::copyDataPoint(int index)
{
    if (hidden() || index >= my.dataCount)
	my.itemData[index] = qQNaN();
    else
	my.itemData[index] = my.data[index];
}

void
SamplingItem::setPlotUtil(int index, double sum)
{
    if (index < 0)
	index = my.dataCount - 1;
    if (hidden() || sum == 0.0 ||
	index >= my.dataCount || qIsNaN(my.data[index]))
	my.itemData[index] = 0.0;
    else
	my.itemData[index] = 100.0 * my.data[index] / sum;
}

double
SamplingItem::setPlotStack(int index, double sum)
{
    if (index < 0)
	index = my.dataCount - 1;
    if (!hidden() && !qIsNaN(my.itemData[index])) {
	sum += my.itemData[index];
	my.itemData[index] = sum;
    } else
	my.itemData[index] = 0.0;
    return sum;
}

double
SamplingItem::setDataStack(int index, double sum)
{
    if (index < 0)
	index = my.dataCount - 1;
    if (hidden() || qIsNaN(my.data[index])) {
	my.itemData[index] = qQNaN();
    } else {
	sum += my.data[index];
	my.itemData[index] = sum;
    }
    return sum;
}


//
// SamplingCurve deals with overriding some QwtPlotCurve defaults;
// particularly around dealing with empty sections of chart (NaN),
// and the way the legend is rendered.
//

void
SamplingCurve::drawSeries(QPainter *p,
		const QwtScaleMap &xMap, const QwtScaleMap &yMap,
		const QRectF &canvasRect, int from, int to) const
{
    int okFrom, okTo = from;
    int size = (to > 0) ? to : dataSize();

    while (okTo < size) {
	okFrom = okTo;
	while (okFrom < size && qIsNaN(sample(okFrom).y()))
	    ++okFrom;
	okTo = okFrom;
	while (okTo < size && !qIsNaN(sample(okTo).y()))
	    ++okTo;
	if (okFrom < size)
	    QwtPlotCurve::drawSeries(p, xMap, yMap, canvasRect, okFrom, okTo-1);
    }
}


//
// SamplingScaleEngine deals with rendering the vertical Y-Axis
//

SamplingScaleEngine::SamplingScaleEngine() : QwtLinearScaleEngine()
{
    my.autoScale = true;
    my.minimum = 0.0;
    my.maximum = 1.0;
}

void
SamplingScaleEngine::setScale(bool autoScale,
			double minValue, double maxValue)
{
    my.autoScale = autoScale;
    my.minimum = minValue;
    my.maximum = maxValue;
}

void
SamplingScaleEngine::autoScale(int maxSteps, double &minValue,
			double &maxValue, double &stepSize) const
{
    if (my.autoScale) {
       if (minValue > 0)
           minValue = 0.0;
    } else {
       minValue = my.minimum;
       maxValue = my.maximum;
    }
    QwtLinearScaleEngine::autoScale(maxSteps, minValue, maxValue, stepSize);
}


//
// The SamplingEngine implements all sampling-specific Chart behaviour
//
SamplingEngine::SamplingEngine(Chart *chart, pmDesc &desc)
{
    QwtPlotPicker *picker = chart->my.picker;
    ChartEngine *engine = chart->my.engine;

    my.chart = chart;
    my.rateConvert = engine->rateConvert();
    my.antiAliasing = engine->antiAliasing();

    normaliseUnits(desc);
    my.units = desc.units;

    my.scaleEngine = new SamplingScaleEngine();
    chart->setAxisScaleEngine(QwtPlot::yLeft, my.scaleEngine);
    chart->setAxisScaleDraw(QwtPlot::yLeft, new QwtScaleDraw());

    // use an individual point picker for sampled data
    picker->setStateMachine(new QwtPickerDragPointMachine());
    picker->setRubberBand(QwtPicker::CrossRubberBand);
    picker->setRubberBandPen(QColor(Qt::green));
}

SamplingItem *
SamplingEngine::samplingItem(int index)
{
    return (SamplingItem *)my.chart->my.items[index];
}

ChartItem *
SamplingEngine::addItem(QmcMetric *mp, pmMetricSpec *msp, pmDesc *desc, const QString &legend)
{
    int sampleHistory = my.chart->my.tab->group()->sampleHistory();
    int existingItemCount = my.chart->metricCount();
    SamplingItem *item = new SamplingItem(my.chart, mp, msp, desc, legend,
				my.chart->my.style,
				sampleHistory, existingItemCount);

    // Find current max count for all plot items
    int i, size = 0;
    for (i = 0; i < existingItemCount; i++)
	size = samplingItem(i)->maximumDataCount(size);
    // Zero any plot from there to end, so Stack<->Line transitions work
    for (i = 0; i < existingItemCount; i++)
	samplingItem(i)->truncateData(size);

    return item;
}

void
SamplingEngine::normaliseUnits(pmDesc &desc)
{
    if (my.rateConvert && desc.sem == PM_SEM_COUNTER) {
	if (desc.units.dimTime == 0) {
	    desc.units.dimTime = -1;
	    desc.units.scaleTime = PM_TIME_SEC;
	}
	else if (desc.units.dimTime == 1) {
	    desc.units.dimTime = 0;
	    // don't play with scaleTime, need native per item scaleTime
	    // so we can apply correct scaling via item->scale, e.g. in
	    // the msec -> msec/sec after rate conversion ... see the
	    // calculation for item->scale below
	}
    }
}

bool
SamplingEngine::isCompatible(pmDesc &desc)
{
    console->post("SamplingEngine::isCompatible"
		  " type=%d, units=%s", desc.type, pmUnitsStr(&desc.units));

    if (desc.type == PM_TYPE_EVENT || desc.type == PM_TYPE_HIGHRES_EVENT)
	return false;
    normaliseUnits(desc);
    if (my.units.dimSpace != desc.units.dimSpace ||
	my.units.dimTime != desc.units.dimTime ||
	my.units.dimCount != desc.units.dimCount)
	return false;
    return true;
}

void
SamplingEngine::updateValues(bool forward,
	int size, int points, double left, double right, double delta)
{
    int	i, index = forward ? 0 : -1;	/* first or last data point */
    int itemCount = my.chart->metricCount();
    Chart::Style style = my.chart->my.style;

    // Drive new values into each chart item
    for (int i = 0; i < itemCount; i++) {
	samplingItem(i)->updateValues(forward, my.rateConvert, &my.units,
					size, points, left, right, delta);
    }

    if (style == Chart::BarStyle || style == Chart::AreaStyle || style == Chart::LineStyle) {
	for (i = 0; i < itemCount; i++)
	    samplingItem(i)->copyRawDataPoint(index);
    }
    // Utilisation: like Stack, but normalize value to a percentage (0,100)
    else if (style == Chart::UtilisationStyle) {
	double	sum = 0.0;
	// compute sum
	for (i = 0; i < itemCount; i++)
	    sum = samplingItem(i)->sumData(index, sum);
	// scale all components
	for (i = 0; i < itemCount; i++)
	    samplingItem(i)->setPlotUtil(index, sum);
	// stack components
	sum = 0.0;
	for (i = 0; i < itemCount; i++)
	    sum = samplingItem(i)->setPlotStack(index, sum);
    }
    else if (style == Chart::StackStyle) {
	double	sum = 0.0;
	for (i = 0; i < itemCount; i++)
	    sum = samplingItem(i)->setDataStack(index, sum);
    }

#if DESPERATE
    for (i = 0; i < my.chart->metricCount(); i++) {
	console->post(PmChart::DebugForce, "metric[%d] value %f", i,
			samplingItem(i)->metricPtr()->currentValue(0));
    }
#endif
}

void
SamplingEngine::redoScale(void)
{
    bool	rescale = false;

    // We need the old units in order to drive the rescaling step.
    pmUnits old_units = my.units;

    // The 1,000 and 0.1 thresholds are just a heuristic guess.
    //
    // We're assuming lBound() plays no part in this, which is OK as
    // the upper bound of the y-axis range (hBound()) drives the choice
    // of appropriate units scaling.
    //
    if (my.scaleEngine->autoScale() &&
	my.chart->axisScaleDiv(QwtPlot::yLeft).upperBound() > 1000) {
	double scaled_max = my.chart->axisScaleDiv(QwtPlot::yLeft).upperBound();
	if (my.units.dimSpace == 1) {
	    switch (my.units.scaleSpace) {
		case PM_SPACE_BYTE:
		    my.units.scaleSpace = PM_SPACE_KBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_KBYTE:
		    my.units.scaleSpace = PM_SPACE_MBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_MBYTE:
		    my.units.scaleSpace = PM_SPACE_GBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_GBYTE:
		    my.units.scaleSpace = PM_SPACE_TBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_TBYTE:
		    my.units.scaleSpace = PM_SPACE_PBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_PBYTE:
		    my.units.scaleSpace = PM_SPACE_EBYTE;
		    rescale = true;
		    break;
	    }
	    if (rescale) {
		// logic here depends on PM_SPACE_* values being consecutive
		// integer values as the scale increases
		scaled_max /= 1024;
		while (scaled_max > 1000) {
		    my.units.scaleSpace++;
		    scaled_max /= 1024;
		    if (my.units.scaleSpace == PM_SPACE_EBYTE) break;
		}
	    }
	}
	else if (my.units.dimTime == 1) {
	    switch (my.units.scaleTime) {
		case PM_TIME_NSEC:
		    my.units.scaleTime = PM_TIME_USEC;
		    rescale = true;
		    scaled_max /= 1000;
		    break;
		case PM_TIME_USEC:
		    my.units.scaleTime = PM_TIME_MSEC;
		    rescale = true;
		    scaled_max /= 1000;
		    break;
		case PM_TIME_MSEC:
		    my.units.scaleTime = PM_TIME_SEC;
		    rescale = true;
		    scaled_max /= 1000;
		    break;
		case PM_TIME_SEC:
		    my.units.scaleTime = PM_TIME_MIN;
		    rescale = true;
		    scaled_max /= 60;
		    break;
		case PM_TIME_MIN:
		    my.units.scaleTime = PM_TIME_HOUR;
		    rescale = true;
		    scaled_max /= 60;
		    break;
	    }
	    if (rescale) {
		// logic here depends on PM_TIME* values being consecutive
		// integer values as the scale increases
		while (scaled_max > 1000) {
		    my.units.scaleTime++;
		    if (my.units.scaleTime <= PM_TIME_SEC)
			scaled_max /= 1000;
		    else
			scaled_max /= 60;
		    if (my.units.scaleTime == PM_TIME_HOUR) break;
		}
	    }
	}
    }

    if (rescale == false &&
	my.scaleEngine->autoScale() &&
	my.chart->axisScaleDiv(QwtPlot::yLeft).upperBound() < 0.1) {
	double scaled_max = my.chart->axisScaleDiv(QwtPlot::yLeft).upperBound();
	if (my.units.dimSpace == 1) {
	    switch (my.units.scaleSpace) {
		case PM_SPACE_KBYTE:
		    my.units.scaleSpace = PM_SPACE_BYTE;
		    rescale = true;
		    break;
		case PM_SPACE_MBYTE:
		    my.units.scaleSpace = PM_SPACE_KBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_GBYTE:
		    my.units.scaleSpace = PM_SPACE_MBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_TBYTE:
		    my.units.scaleSpace = PM_SPACE_GBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_PBYTE:
		    my.units.scaleSpace = PM_SPACE_TBYTE;
		    rescale = true;
		    break;
		case PM_SPACE_EBYTE:
		    my.units.scaleSpace = PM_SPACE_PBYTE;
		    rescale = true;
		    break;
	    }
	    if (rescale) {
		// logic here depends on PM_SPACE_* values being consecutive
		// integer values (in reverse) as the scale decreases
		scaled_max *= 1024;
		while (scaled_max < 0.1) {
		    my.units.scaleSpace--;
		    scaled_max *= 1024;
		    if (my.units.scaleSpace == PM_SPACE_BYTE) break;
		}
	    }
	}
	else if (my.units.dimTime == 1) {
	    switch (my.units.scaleTime) {
		case PM_TIME_USEC:
		    my.units.scaleTime = PM_TIME_NSEC;
		    rescale = true;
		    scaled_max *= 1000;
		    break;
		case PM_TIME_MSEC:
		    my.units.scaleTime = PM_TIME_USEC;
		    rescale = true;
		    scaled_max *= 1000;
		    break;
		case PM_TIME_SEC:
		    my.units.scaleTime = PM_TIME_MSEC;
		    rescale = true;
		    scaled_max *= 1000;
		    break;
		case PM_TIME_MIN:
		    my.units.scaleTime = PM_TIME_SEC;
		    rescale = true;
		    scaled_max *= 60;
		    break;
		case PM_TIME_HOUR:
		    my.units.scaleTime = PM_TIME_MIN;
		    rescale = true;
		    scaled_max *= 60;
		    break;
	    }
	    if (rescale) {
		// logic here depends on PM_TIME* values being consecutive
		// integer values (in reverse) as the scale decreases
		while (scaled_max < 0.1) {
		    my.units.scaleTime--;
		    if (my.units.scaleTime < PM_TIME_SEC)
			scaled_max *= 1000;
		    else
			scaled_max *= 60;
		    if (my.units.scaleTime == PM_TIME_NSEC) break;
		}
	    }
	}
    }

    if (rescale) {
	//
	// need to rescale ... we transform all of the historical (raw)
	// data, new data will be taken care of by changing my.units.
	//
	for (int i = 0; i < my.chart->metricCount(); i++)
	    samplingItem(i)->rescaleValues(&old_units, &my.units);

	if (my.chart->my.style == Chart::UtilisationStyle)
	    my.chart->setYAxisTitle("% utilization");
	else
	    my.chart->setYAxisTitle(pmUnitsStr(&my.units));
	my.chart->replot();
    }
}

void
SamplingEngine::replot(void)
{
    GroupControl		*group = my.chart->my.tab->group();
    int				vh = group->visibleHistory();
    const QVector<double>	&vp = group->timeAxisData();
    int				itemCount = my.chart->metricCount();
    int				maxCount = 0;
    int				i, m;
    double			sum;

#if DESPERATE
    console->post(PmChart::DebugForce, "SamplingEngine::replot (%d items)", itemCount);
#endif

    for (i = 0; i < itemCount; i++)
	samplingItem(i)->replot(vh, vp);

    switch (my.chart->style()) {
	case Chart::BarStyle:
	case Chart::AreaStyle:
	case Chart::LineStyle:
	    for (i = 0; i < itemCount; i++)
		samplingItem(i)->copyRawDataArray();
	    break;

	case Chart::UtilisationStyle:
	    for (i = 0; i < itemCount; i++)
		maxCount = samplingItem(i)->maximumDataCount(maxCount);
	    for (m = 0; m < maxCount; m++) {
		sum = 0.0;
		for (i = 0; i < itemCount; i++)
		    sum = samplingItem(i)->sumData(m, sum);
		for (i = 0; i < itemCount; i++)
		    samplingItem(i)->setPlotUtil(m, sum);
		sum = 0.0;
		for (i = 0; i < itemCount; i++)
		    sum = samplingItem(i)->setPlotStack(m, sum);
	    }
	    break;

	case Chart::StackStyle:
	    for (i = 0; i < itemCount; i++)
		maxCount = samplingItem(i)->maximumDataCount(maxCount);
	    for (m = 0; m < maxCount; m++) {
		for (i = 0; i < itemCount; i++)
		    samplingItem(i)->copyDataPoint(m);
		sum = 0.0;
		for (i = 0; i < itemCount; i++)
		    sum = samplingItem(i)->setPlotStack(m, sum);
	    }
	    break;

	default:
	    break;
    }
}

void
SamplingEngine::scale(bool *autoScale, double *yMin, double *yMax)
{
    *autoScale = my.scaleEngine->autoScale();
    *yMin = my.scaleEngine->minimum();
    *yMax = my.scaleEngine->maximum();
}

void
SamplingEngine::setScale(bool autoScale, double yMin, double yMax)
{
    my.scaleEngine->setScale(autoScale, yMin, yMax);

    if (autoScale)
	my.chart->setAxisAutoScale(QwtPlot::yLeft);
    else
	my.chart->setAxisScale(QwtPlot::yLeft, yMin, yMax);
}

void
SamplingEngine::selected(const QPolygon &)
{
    // Nothing to do here.
}

void
SamplingEngine::moved(const QPointF &p)
{
    my.chart->showPoint(p);
}

void
SamplingEngine::setStyle(Chart::Style style)
{
    // Y-Axis title choice is difficult.  A Utilisation plot by definition
    // is dimensionless and scaled to a percentage, so a label of just
    // "% utilization" makes sense ... there has been some argument in
    // support of "% time utilization" as a special case when the metrics
    // involve some aspect of time, but the base metrics in the common case
    // are counters in units of time (e.g. the CPU view), which after rate
    // conversion is indistinguishable from instantaneous or discrete
    // metrics of dimension time^0 which are units compatible ... so we're
    // opting for the simplest possible interpretation of utilization or
    // everything else.
    //
    switch (style) {
	case Chart::BarStyle:
	case Chart::AreaStyle:
	case Chart::LineStyle:
	case Chart::StackStyle:
	    if (my.chart->style() == Chart::UtilisationStyle)
		my.scaleEngine->setAutoScale(true);
	    my.chart->setYAxisTitle(pmUnitsStr(&my.units));
	    break;
	case Chart::UtilisationStyle:
	    my.scaleEngine->setScale(false, 0.0, 100.0);
	    my.chart->setYAxisTitle("% utilization");
	    break;
	default:
	    break;
    }
}
