/*
 * Copyright (c) 1997 Silicon Graphics, Inc.  All Rights Reserved.
 * Copyright (c) 2009 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 <Inventor/SoPath.h>
#include <Inventor/nodes/SoBaseColor.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Inventor/nodes/SoScale.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoSelection.h>
#include "barmod.h"
#include "modlist.h"
#include "launch.h"

//
// Use debug flag LIBPMDA to trace Bar refreshes
//

const char BarMod::theBarId = 'b';

BarMod::~BarMod()
{
}

BarMod::BarMod(MetricList *metrics, 
	       SoNode *obj, 
	       BarMod::Direction dir,
	       BarMod::Grouping group,
	       float xScale, float yScale, float zScale,
	       float xSpace, float zSpace)
: Modulate(metrics),
  _blocks(),
  _dir(dir),
  _mod(BarMod::yScale),
  _group(group),
  _colScale(0.0, 0.0, 0.0),
  _selectCount(0),
  _infoValue(0),
  _infoMetric(0),
  _infoInst(0),
  _xScale(xScale),
  _yScale(yScale),
  _zScale(zScale)
{
    generate(obj, xSpace, zSpace);
}

BarMod::BarMod(MetricList *metrics, 
	       const ColorScale &colScale,
	       SoNode *obj, 
	       BarMod::Direction dir,
	       BarMod::Modulation mod,
	       BarMod::Grouping group,
	       float xScale, float yScale, float zScale,
	       float xSpace, float zSpace)
: Modulate(metrics),
  _blocks(),
  _dir(dir),
  _mod(mod),
  _group(group),
  _colScale(colScale),
  _selectCount(0),
  _infoValue(0),
  _infoMetric(0),
  _infoInst(0),
  _xScale(xScale),
  _yScale(yScale),
  _zScale(zScale)
{
    generate(obj, xSpace, zSpace);
}

void
BarMod::generate(SoNode *obj, float xSpace, float zSpace)
{
    int	numMetrics = _metrics->numMetrics();
    int	numValues = _metrics->numValues();
    int	maxInst = 0;
    char	buf[32];
    int		m, i, v;

    _root = new SoSeparator;

    if (numValues > 0) {

	for (m = 0; m < numMetrics; m++)
	    if (_metrics->metric(m).numValues() > maxInst)
		maxInst = _metrics->metric(m).numValues();

	if (_dir == instPerCol) {
	    _cols = maxInst;
	    _rows = numMetrics;
	}
	else {
	    _cols = numMetrics;
	    _rows = maxInst;
	}

	_blocks.resize(numValues);

	for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
	    const QmcMetric &metric = _metrics->metric(m);
	    for (i = 0; i < metric.numValues(); i++, v++) {
		BarBlock &block = _blocks[v];
		pmsprintf(buf, sizeof(buf), "%c%d", theBarId, v);
		block._sep = new SoSeparator;
		block._sep->setName((SbName)buf);
		_root->addChild(block._sep);

		block._tran = new SoTranslation;
		block._sep->addChild(block._tran);

		block._color = new SoBaseColor;
		block._sep->addChild(block._color);

		block._scale = new SoScale;
		block._sep->addChild(block._scale);

		block._sep->addChild(obj);
	    }
	}

	regenerate(_xScale, _zScale, xSpace, zSpace);
	_infoValue = numValues;
	    
	add();
        if (pmDebugOptions.appl2)
            cerr << "BarMod::generate: Added " << numValues << " in " << _cols
		 << " cols and " << _rows << " rows." << endl;

    }

    // Invalid object
    else {
	_sts = -1;
    }
}

void
BarMod::refresh(bool fetchFlag)
{
    int m, i, v;

    if (status() < 0)
	return;

    for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
	QmcMetric &metric = _metrics->metric(m);

	if (fetchFlag)
	    metric.update();

	for (i = 0; i < metric.numValues(); i++, v++) {

	    BarBlock &block = _blocks[v];

	    if (metric.error(i) <= 0) {

		if (block._state != Modulate::error) {
		    block._color->rgb.setValue(_errorColor.getValue());
		    if (_mod != color)
			block._scale->scaleFactor.setValue(_xScale, 
							   theMinScale,
							   _zScale);
		    block._state = Modulate::error;
		}
	    }
	    else {
		double  unscaled    = metric.value(i);
		double  value       = unscaled * theScale;
                
		if (value > theNormError) {
		    if (block._state != Modulate::saturated) {
			block._color->rgb.setValue(Modulate::_saturatedColor);
			if (_mod != color)
			    block._scale->scaleFactor.setValue(_xScale, 
							       _yScale, 
							       _zScale);
			block._state = Modulate::saturated;
		    }
		}
		else {
		    if (block._state != Modulate::normal) {
			block._state = Modulate::normal;
			if (_mod == yScale)
			    block._color->rgb.setValue(_metrics->color(m).getValue());
		    }
		    else if (_mod != yScale)
			block._color->rgb.setValue(_colScale.step(unscaled).color().getValue());
		    if (_mod != color) {
			if (value < Modulate::theMinScale)
			    value = Modulate::theMinScale;
			else if (value > 1.0)
			    value = 1.0;
			block._scale->scaleFactor.setValue(_xScale,
							   _yScale * value,
							   _zScale);
		    }

		}
	    }
	}
    }
}

void
BarMod::selectAll()
{
    int	i;

    if (_selectCount == _blocks.size())
	return;

    theModList->selectAllId(_root, _blocks.size());

    if (pmDebugOptions.appl2)
	cerr << "BarMod::selectAll" << endl;

    for (i = 0; i < _blocks.size(); i++) {
	if (_blocks[i]._selected == false) {
	    _selectCount++;
	    theModList->selectSingle(_blocks[i]._sep);
	    _blocks[i]._selected = true;
	}
    }
}

int
BarMod::select(SoPath *path)
{
    int	metric, inst, value;

    findBlock(path, metric, inst, value, false);
    if (value < _blocks.size() && _blocks[value]._selected == false) {
	_blocks[value]._selected = true;
	_selectCount++;

	if (pmDebugOptions.appl2)
	    cerr << "BarMod::select: value = " << value
		 << ", count = " << _selectCount << endl;
    }
    return _selectCount;
}

int
BarMod::remove(SoPath *path)
{
    int	metric, inst, value;

    findBlock(path, metric, inst, value, false);
    if (value < _blocks.size() && _blocks[value]._selected == true) {
	_blocks[value]._selected = false;
	_selectCount--;

	if (pmDebugOptions.appl2)
	    cerr << "BarMod::remove: value = " << value
		 << ", count = " << _selectCount << endl;
    }

    else if (pmDebugOptions.appl2)
	cerr << "BarMod::remove: did not remove " << value 
	     << ", count = " << _selectCount << endl;

    return _selectCount;
}

void BarMod::infoText(QString &str, bool selected) const
{
    int		m = _infoMetric;
    int		i = _infoInst;
    int		v = _infoValue;
    bool	found = false;

    if (selected && _selectCount == 1) {
	for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
	    const QmcMetric &metric = _metrics->metric(m);
	    for (i = 0; i < metric.numValues(); i++, v++)
		if (_blocks[v]._selected) {
		    found = true;
		    break;
		}
	    if (found)
		break;
	}
    }

    if (v >= _blocks.size()) {
	if (pmDebugOptions.appl2)
	    cerr << "BarMod::infoText: infoText requested but nothing selected"
		 << endl;
	str = "";
    }
    else {
	const QmcMetric &metric = _metrics->metric(m);
	str = metric.spec(true, true, i);
	str.append(QChar('\n'));

	if (_blocks[v]._state == Modulate::error)
	    str.append(theErrorText);
	else if (_blocks[v]._state == Modulate::start)
	    str.append(theStartText);
	else {
	    QString value;
	    str.append(value.setNum(metric.realValue(i), 'g', 4));
	    str.append(QChar(' '));
	    if (metric.desc().units().size() > 0)
		str.append(metric.desc().units());
	    str.append(" [");
	    str.append(value.setNum(metric.value(i) * 100.0, 'g', 4));
	    str.append("% of expected max]");
	}
    }
}

void BarMod::launch(Launch &launch, bool all) const
{
    int		m, i, v;
    bool	needClose;
    bool	always = all;
    bool	keepGoing = true;

    if (status() < 0)
	return;

    if (_selectCount == _blocks.size())
	always = true;

    // Group by metric
    if (_group == groupByMetric || 
	(_group == groupByRow && _dir == instPerCol) ||
	(_group == groupByCol && _dir == instPerRow)) {

	for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
	    QmcMetric &metric = _metrics->metric(m);

	    // Do we have to check that an instance of this metric has
	    // been selected?
	    if (!always) {
		needClose = false;
		for (i = 0; i < metric.numValues(); i++, v++) {
		    if (_blocks[v]._selected) {
			if (needClose == false) {
			    launch.startGroup("point");
			    needClose = true;
			}
			if (_mod == yScale)
			    launch.addMetric(metric, _metrics->color(m), i);
			else
			    launch.addMetric(metric, _colScale, i);
		    }
		}
		if (needClose)
		    launch.endGroup();
	    }
	    else {
		launch.startGroup("point");
		for (i = 0; i < metric.numValues(); i++, v++) {
		    if (_mod == yScale)
			launch.addMetric(metric, _metrics->color(m), i);
		    else
			launch.addMetric(metric, _colScale, i);
		}
		launch.endGroup();
	    }
	}
    }

    // Group by instance, this gets a little tricky
    else {
	for (i = 0; keepGoing ; i++) {
	    needClose = false;
	    keepGoing = false;
	    for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
		QmcMetric &metric = _metrics->metric(m);
		if (metric.numValues() > i) {
		    if (always || _blocks[v+i]._selected) {
			if (needClose == false) {
			    launch.startGroup("point");
			    needClose = true;
			}
			if (_mod == yScale)
			    launch.addMetric(metric, _metrics->color(m), i);
			else
			    launch.addMetric(metric, _colScale, i);
		    }
		    keepGoing = true;
		}
		v += metric.numValues();
	    }
	    if (needClose)
		launch.endGroup();
	}
    }
}

void 
BarMod::selectInfo(SoPath *path)
{
    findBlock(path, _infoMetric, _infoInst, _infoValue);
}

void
BarMod::removeInfo(SoPath *)
{
    _infoValue = _blocks.size();
    _infoMetric = _infoInst = 0;
}

void
BarMod::dump(QTextStream &os) const
{
    int		m, i, v;

    os << "BarMod: ";

    if (_dir == instPerCol)
	os << "inst per col";
    else
	os << "inst per row";

    if (_mod == yScale)
	os << ", Y-Scale: ";
    else if (_mod == color)
	os << ", Color Only: ";
    else
	os << ", Color & Y-Scale: ";

    if (status() < 0)
        os << "Invalid metrics: " << pmErrStr(status()) << endl;
    else {
	os << endl;
	for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
	    QmcMetric &metric = _metrics->metric(m);
	    for (i = 0; i < metric.numValues(); i++, v++) {
		os << "    [" << v << "]: ";
		if (_blocks[v]._selected == true)
		    os << '*';
		else
		    os << ' ';
		dumpState(os, _blocks[v]._state);
		os << ": ";
		metric.dump(os, true, i);
	    }
	}
    }
}

void
BarMod::findBlock(SoPath *path, int &metric, int &inst, 
			int &value, bool idMetric)
{
    SoNode	*node;
    char	*str;
    int		m, i, v;
    char	c;

    for (i = path->getLength() - 1; i >= 0; --i) {
	node = path->getNode(i);
	str = (char *)(node->getName().getString());
	if (strlen(str) && str[0] == theBarId)
	    break;
    }

    if (i >= 0) {

	if (pmDebugOptions.appl2)
	    cerr << "BarMod::findBlock: Bar id = " << str << endl;

	sscanf(str, "%c%d", &c, &value);
	
	if (value == 0 || idMetric == false) {
	    metric = 0;
	    inst = 0;
	}
	else {
	    m = 0;
	    v = value;
	    while (m < _metrics->numMetrics()) {
		i = _metrics->metric(m).numValues();
		if (v < i) {
		    metric = m;
		    inst = v;
		    break;
		}
		else {
		    v -= i;
		    m++;
		}
	    }
	}
    }
    else {
	value = _blocks.size();
	metric = inst = 0;
    }

    if (pmDebugOptions.appl2)
	cerr << "BarMod::findBlock: metric = " << metric
	     << ", inst = " << inst << ", value = " << value << endl;

    return;
}

void
BarMod::regenerate(float xScale, float zScale, float xSpace, float zSpace)
{
    int		m, i, v;
    float	halfX = xScale / 2.0;
    float	halfZ = zScale / 2.0;

    if (status() < 0)
	return;

    _xScale = xScale;
    _zScale = zScale;

    _width = (unsigned int)((_cols * (_xScale + xSpace)) - xSpace);
    _depth = (unsigned int)((_rows * (_zScale + zSpace)) - zSpace);

    for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
	const QmcMetric &metric = _metrics->metric(m);
	for (i = 0; i < metric.numValues(); i++, v++) {
	    BarBlock &block = _blocks[v];

	    if (_dir == instPerCol)
		block._tran->translation.setValue(i * (_xScale+xSpace) + halfX,
						  0,
						  m * (_zScale+zSpace) + halfZ);
	    else
		block._tran->translation.setValue(m * (_xScale+xSpace) + halfX,
						  0,
						  i * (_zScale+zSpace) + halfZ);

	    block._color->rgb.setValue(_errorColor.getValue());
	    block._scale->scaleFactor.setValue(_xScale, _yScale, _zScale);
	    block._state = Modulate::start;
	    block._selected = false;
	}
    }
}

const char *
BarMod::dirStr() const
{
    const char *str = NULL;

    if (_dir == instPerCol)
	str = "instances in columns";
    else
	str = "instances in rows";

    return str;
}

const char *
BarMod::modStr() const
{
    const char *str = NULL;

    switch (_mod) {
    case yScale:
	str = "Y-Scale";
	break;
    case color:
	str = "Colored";
	break;
    case colYScale:
	str = "Colored Y-Scale";
	break;
    }
    return str;
}
