///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel                                                   //
// Copyright (C) 2015, 2017-2019, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com>                     //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai>                      //
//                                                                               //
// 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 as version 3 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 V3 for more details.                               //
//                                                                               //
// You should have received a copy of the GNU General Public License             //
// along with this program. If not, see <http://www.gnu.org/licenses/>.          //
///////////////////////////////////////////////////////////////////////////////////

#include <math.h>
#include <QObject>
#include <QFontMetrics>
#include <QDataStream>
#include "gui/scaleengine.h"

/*
static double trunc(double d)
{
	return (d > 0) ? floor(d) : ceil(d);
}
*/

QString ScaleEngine::formatTick(double value, int decimalPlaces)
{
	if (m_tickFormatter) {
		return m_tickFormatter->formatTick(value);
    }
	else if (m_physicalUnit != Unit::TimeHMS)
	{
        if (m_physicalUnit == Unit::Scientific) {
            return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'e', m_fixedDecimalPlaces);
        } else {
            return QString("%1%2%3")
                .arg(m_truncated ? "'" : "")
                .arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces)
                .arg(m_truncated ? m_multiplierStr : "");
        }
	}
	else
	{
	    if (m_scale < 1.0f) { // sub second prints just as is
            return QString("%1").arg(m_makeOpposite ? -value : value, 0, 'f', decimalPlaces);
        }

		QString str;
		double actual = value * m_scale; // this is the actual value in seconds
		double orig = fabs(actual);
		double tmp;

        if (m_truncated) {
            str = "'";
        }

		if (orig >= 86400.0)
        {
			tmp = floor(actual / 86400.0);
			str += QString("%1.").arg(tmp, 0, 'f', 0);
			actual -= tmp * 86400.0;

            if (actual < 0.0) {
			    actual *= -1.0;
            }
		}

		if (orig >= 3600.0)
        {
			tmp = floor(actual / 3600.0);
			str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0'));
			actual -= tmp * 3600.0;

            if (actual < 0.0) {
			    actual *= -1.0;
            }
		}

		if (orig >= 60.0)
        {
			tmp = floor(actual / 60.0);
			str += QString("%1:").arg(tmp, 2, 'f', 0, QChar('0'));
			actual -= tmp * 60.0;

            if (actual < 0.0) {
			    actual *= -1.0;
            }
		}

		tmp = m_makeOpposite ? -actual : actual;
		const int fieldWidth = (str.isEmpty() ? 1 : 2) + (decimalPlaces == 0 ? 0 : 1 + decimalPlaces);
		str += QString("%1").arg(tmp, fieldWidth, 'f', decimalPlaces, QChar('0'));

		return str;
	}
}

void ScaleEngine::calcCharSize()
{
	QFontMetricsF fontMetrics(m_font);

	if(m_orientation == Qt::Vertical) {
		m_charSize = fontMetrics.height();
	} else {
		QString str("012345679.,-");
		int i;
		float size;
		float max = 0.0f;
		for(i = 0; i < str.length(); i++) {
			size = fontMetrics.horizontalAdvance(QString(str[i]));
			if(size > max)
				max = size;
		}
		m_charSize = max;
	}
}

void ScaleEngine::calcScaleFactor()
{
	double median, range, freqBase;

    double rangeMin = m_truncated ? m_rangeMin - m_truncationValue : m_rangeMin;
    double rangeMax = m_truncated ? m_rangeMax - m_truncationValue : m_rangeMax;

	median = ((rangeMax - rangeMin) / 2.0) + rangeMin;
	range = (rangeMax - rangeMin);
	freqBase = (median == 0 ? range : median);
	m_scale = 1.0;
    m_multiplierStr.clear();

	switch(m_physicalUnit) {
		case Unit::None:
		case Unit::Scientific:
			m_unitStr.clear();
			break;

		case Unit::Frequency:
			if(freqBase < 1000.0) {
				m_unitStr = QObject::tr("Hz");
                m_multiplierStr = "";
			} else if(freqBase < 1000000.0) {
				m_unitStr = QObject::tr("kHz");
                m_multiplierStr = "k";
				m_scale = 1000.0;
			} else if(freqBase < 1000000000.0) {
				m_unitStr = QObject::tr("MHz");
                m_multiplierStr = "M";
				m_scale = 1000000.0;
			} else if(freqBase < 1000000000000.0){
				m_unitStr = QObject::tr("GHz");
                m_multiplierStr = "G";
				m_scale = 1000000000.0;
			} else {
				m_unitStr = QObject::tr("THz");
                m_multiplierStr = "T";
				m_scale = 1000000000000.0;
			}
			break;

		case Unit::Information:
			if(median < 1024.0) {
				m_unitStr = QObject::tr("Bytes");
                m_multiplierStr = "";
			} else if(median < 1048576.0) {
				m_unitStr = QObject::tr("KiBytes");
                m_multiplierStr = "k";
				m_scale = 1024.0;
			} else if(median < 1073741824.0) {
				m_unitStr = QObject::tr("MiBytes");
                m_multiplierStr = "M";
				m_scale = 1048576.0;
			} else if(median < 1099511627776.0) {
				m_unitStr = QObject::tr("GiBytes");
                m_multiplierStr = "G";
				m_scale = 1073741824.0;
			} else if(median < 1125899906842624.0) {
				m_unitStr = QObject::tr("TiBytes");
                m_multiplierStr = "T";
				m_scale = 1099511627776.0;
			} else {
				m_unitStr = QObject::tr("PiBytes");
                m_multiplierStr = "P";
				m_scale = 1125899906842624.0;
			}
			break;

		case Unit::Percent:
			m_unitStr = QString("%");
			break;

		case Unit::Decibel:
			m_unitStr = QString("dB");
			break;

		case Unit::SUnits:
			m_unitStr = QString("S");
			break;

		case Unit::DecibelMilliWatt:
			m_unitStr = QString("dBm");
			break;

		case Unit::DecibelMicroVolt:
			m_unitStr = QString("dBµV");
			break;

		case Unit::AngleDegrees:
			m_unitStr = QString("°");
			break;

		case Unit::Time:
        case Unit::TimeHMS:
			if (median < 0.001) {
				m_unitStr = QString("µs");
                m_multiplierStr = "µ";
				m_scale = 0.000001;
			} else if (median < 1.0) {
				m_unitStr = QString("ms");
                m_multiplierStr = "m";
				m_scale = 0.001;
			} else if (median < 1000.0) {
				m_unitStr = QString("s");
                m_multiplierStr = "";
			} else {
                m_unitStr = QString("ks");
                m_multiplierStr = "k";
                m_scale = 1000.0;
			}
			break;

		case Unit::Volt:
			if (median < 1e-9) {
				m_unitStr = QString("pV");
                m_multiplierStr = "p";
				m_scale = 1e-12;
			} else if (median < 1e-6) {
				m_unitStr = QString("nV");
                m_multiplierStr = "n";
				m_scale = 1e-9;
			} else if (median < 1e-3) {
				m_unitStr = QString("µV");
                m_multiplierStr = "µ";
				m_scale = 1e-6;
			} else if (median < 1.0) {
				m_unitStr = QString("mV");
                m_multiplierStr = "m";
				m_scale = 1e-3;
			} else {
				m_unitStr = QString("V");
                m_multiplierStr = "";
			}
			break;
	}
}

double ScaleEngine::calcMajorTickUnits(double distance, int* retDecimalPlaces)
{
	double sign;
	double log10x;
	double exponent;
	double base;
	int decimalPlaces;

	if(distance == 0.0)
		return 0.0;

	sign = (distance > 0.0) ? 1.0 : -1.0;
	log10x = log10(fabs(distance));
	exponent = floor(log10x);
	base = pow(10.0, log10x - exponent);
	decimalPlaces = (int)(-exponent);
/*
	if((m_physicalUnit == Unit::Time) && (distance >= 1.0)) {
		if(retDecimalPlaces != NULL)
			*retDecimalPlaces = 0;
		if(distance < 1.0)
			return 1.0;
		else if(distance < 5.0)
			return 5.0;
		else if(distance < 10.0)
			return 10.0;
		else if(distance < 15.0)
			return 15.0;
		else if(distance < 30.0)
			return 30.0;
		else if(distance < 60.0)
			return 60.0;
		else if(distance < 5.0 * 60.0)
			return 5.0 * 60.0;
		else if(distance < 10.0 * 60.0)
			return 10.0 * 60.0;
		else if(distance < 15.0 * 60.0)
			return 15.0 * 60.0;
		else if(distance < 30.0 * 60.0)
			return 30.0 * 60.0;
		else if(distance < 3600.0)
			return 3600.0;
		else if(distance < 2.0 * 3600.0)
			return 2.0 * 3600.0;
		else if(distance < 3.0 * 3600.0)
			return 3.0 * 3600.0;
		else if(distance < 6.0 * 3600.0)
			return 6.0 * 3600.0;
		else if(distance < 12.0 * 3600.0)
			return 12.0 * 3600.0;
		else if(distance < 86000.0)
			return 86000.0;
		else if(distance < 2.0 * 86000.0)
			return 2.0 * 86000.0;
		else if(distance < 7.0 * 86000.0)
			return 7.0 * 86000.0;
		else if(distance < 10.0 * 86000.0)
			return 10.0 * 86000.0;
		else if(distance < 30.0 * 86000.0)
			return 30.0 * 86000.0;
		else return 90.0 * 86000.0;
	} */

	if(base <= 1.0) {
        base = 1.0;
    } else if(base <= 2.0) {
        base = 2.0;
    } else if(base <= 2.5) {
        base = 2.5;
        if(decimalPlaces >= 0)
            decimalPlaces++;
    } else if(base <= 5.0) {
        base = 5.0;
    } else {
        base = 10.0;
    }

	if(retDecimalPlaces != 0) {
		if(decimalPlaces < 0)
			decimalPlaces = 0;
		*retDecimalPlaces = decimalPlaces;
	}

	return sign * base * pow(10.0, exponent);
}

int ScaleEngine::calcTickTextSize(double distance)
{
	int tmp;
	int tickLen;
	int decimalPlaces;

    double rangeMin = m_truncated ? m_rangeMin - m_truncationValue : m_rangeMin;
    double rangeMax = m_truncated ? m_rangeMax - m_truncationValue : m_rangeMax;

	tickLen = 1;
	tmp = formatTick(rangeMin / m_scale, 0).length();

	if(tmp > tickLen)
		tickLen = tmp;
	tmp = formatTick(rangeMax / m_scale, 0).length();

	if(tmp > tickLen)
		tickLen = tmp;

	calcMajorTickUnits(distance, &decimalPlaces);

	return tickLen + decimalPlaces + 1;
}

void ScaleEngine::forceTwoTicks()
{
	Tick tick;
	QFontMetricsF fontMetrics(m_font);

    double rangeMin = m_truncated ? m_rangeMin - m_truncationValue : m_rangeMin;
    double rangeMax = m_truncated ? m_rangeMax - m_truncationValue : m_rangeMax;

	m_tickList.clear();
	tick.major = true;

	tick.pos = getPosFromValueTrunc(rangeMin);
	tick.text = formatTick(rangeMin / m_scale, m_decimalPlaces);
	tick.textSize = fontMetrics.boundingRect(tick.text).width();
	if(m_orientation == Qt::Vertical)
		tick.textPos = tick.pos - fontMetrics.ascent() / 2;
	else tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
	m_tickList.append(tick);

	tick.pos = getPosFromValueTrunc(rangeMax);
	tick.text = formatTick(rangeMax / m_scale, m_decimalPlaces);
	tick.textSize = fontMetrics.boundingRect(tick.text).width();
	if(m_orientation == Qt::Vertical)
		tick.textPos = tick.pos - fontMetrics.ascent() / 2;
	else tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
	m_tickList.append(tick);
}

void ScaleEngine::reCalc()
{
    if (m_physicalUnit == Unit::SUnits) {
        reCalcS();
    } else {
        reCalcStd();
    }
}

void ScaleEngine::reCalcStd()
{
	float majorTickSize;
	double rangeMinScaled;
	double rangeMaxScaled;
	int maxNumMajorTicks;
	int numMajorTicks;
	int step;
	int skip;
	double value;
	double value2;
	int i;
	int j;
	Tick tick;
	float pos;
	QString str;
	QFontMetricsF fontMetrics(m_font);
	float endPos;
	float lastEndPos;
	bool done;

	if (!m_recalc) {
		return;
    }

    updateTruncation();

	m_recalc = false;
	m_tickList.clear();

	calcScaleFactor();

    double rangeMin = m_truncated ? m_rangeMin - m_truncationValue : m_rangeMin;
    double rangeMax = m_truncated ? m_rangeMax - m_truncationValue : m_rangeMax;

	rangeMinScaled = rangeMin / m_scale;
	rangeMaxScaled = rangeMax / m_scale;

	if (m_orientation == Qt::Vertical)
	{
		maxNumMajorTicks = (int)(m_size / (fontMetrics.lineSpacing() * 1.3f));
		m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces);
	}
	else
	{
		majorTickSize = (calcTickTextSize((rangeMaxScaled - rangeMinScaled) / 20) + 2) * m_charSize;

		if (majorTickSize != 0.0) {
            maxNumMajorTicks = (int)(m_size / majorTickSize);
		} else {
            maxNumMajorTicks = 20;
		}

		m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces);
	}

	numMajorTicks = (int)((rangeMaxScaled - rangeMinScaled) / m_majorTickValueDistance);

	if (numMajorTicks == 0)
    {
		forceTwoTicks();
		return;
	}

	if(maxNumMajorTicks > 0)
		m_numMinorTicks = (int)(m_size / (maxNumMajorTicks * fontMetrics.height()));
	else
        m_numMinorTicks = 0;

    if (m_numMinorTicks < 1)
		m_numMinorTicks = 0;
	else if (m_numMinorTicks < 2)
		m_numMinorTicks = 1;
	else if (m_numMinorTicks < 5)
		m_numMinorTicks = 2;
	else if (m_numMinorTicks < 10)
		m_numMinorTicks = 5;
	else
        m_numMinorTicks = 10;

	m_firstMajorTickValue = floor(rangeMinScaled / m_majorTickValueDistance) * m_majorTickValueDistance;

	skip = 0;

	if (rangeMinScaled == rangeMaxScaled)
		return;

	while (true)
    {
		m_tickList.clear();

		step = 0;
		lastEndPos = -100000000;
		done = true;

		for (i = 0; true; i++)
        {
			value = majorTickValue(i);

			for (j = 1; j < m_numMinorTicks; j++)
            {
				value2 = value + minorTickValue(j);

				if (value2 < rangeMinScaled)
					continue;

                if (value2 > rangeMaxScaled)
					break;

                pos = getPosFromValueTrunc((value + minorTickValue(j)) * m_scale);

                if ((pos >= 0) && (pos < m_size))
                {
					tick.pos = pos;
					tick.major = false;
					tick.textPos = -1;
					tick.textSize = -1;
					tick.text.clear();
				}

				m_tickList.append(tick);
			}

			pos = getPosFromValueTrunc(value * m_scale);

			if (pos < 0.0)
				continue;

            if (pos >= m_size)
				break;

			tick.pos = pos;
			tick.major = true;
			tick.textPos = -1;
			tick.textSize = -1;
			tick.text.clear();

			if (step % (skip + 1) != 0)
            {
				m_tickList.append(tick);
				step++;
				continue;
			}

            step++;
			str = formatTick(value, m_decimalPlaces);
			tick.text = str;
			tick.textSize = fontMetrics.boundingRect(tick.text).width();

            if (m_orientation == Qt::Vertical)
            {
				tick.textPos = pos - fontMetrics.ascent() / 2;
				endPos = tick.textPos + fontMetrics.ascent();
			}
            else
            {
				tick.textPos = pos - fontMetrics.boundingRect(tick.text).width() / 2;
				endPos = tick.textPos + tick.textSize;
			}

			if (lastEndPos >= tick.textPos)
            {
				done = false;
				break;
			}
            else
            {
				lastEndPos = endPos;
			}

			m_tickList.append(tick);
		}

		if (done)
			break;

        skip++;
	}

	// make sure we have at least two major ticks with numbers
	numMajorTicks = 0;

	for (i = 0; i < m_tickList.count(); i++)
    {
		tick = m_tickList.at(i);

        if (tick.major)
			numMajorTicks++;
	}

	if (numMajorTicks < 2)
		forceTwoTicks();
}

void ScaleEngine::reCalcS()
{
	float majorTickSize;
	double rangeMinScaled;
	double rangeMaxScaled;
	int maxNumMajorTicks;
	int numMajorTicks;
	Tick tick;
	QString str;
	QFontMetricsF fontMetrics(m_font);

	if (!m_recalc) {
		return;
    }

    updateTruncation();

	m_recalc = false;
	m_tickList.clear();

    calcScaleFactor();

    double rangeMin = m_truncated ? m_rangeMin - m_truncationValue : m_rangeMin;
    double rangeMax = m_truncated ? m_rangeMax - m_truncationValue : m_rangeMax;

	rangeMinScaled = rangeMin / m_scale;
	rangeMaxScaled = rangeMax / m_scale;

	if (m_orientation == Qt::Vertical)
	{
		maxNumMajorTicks = (int)(m_size / (fontMetrics.lineSpacing() * 1.3f));
		m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces);
	}
	else
	{
		majorTickSize = (calcTickTextSize((rangeMaxScaled - rangeMinScaled) / 20) + 2) * m_charSize;

		if (majorTickSize != 0.0) {
            maxNumMajorTicks = (int)(m_size / majorTickSize);
		} else {
            maxNumMajorTicks = 20;
		}

		m_majorTickValueDistance = calcMajorTickUnits((rangeMaxScaled - rangeMinScaled) / maxNumMajorTicks, &m_decimalPlaces);
	}

	numMajorTicks = (int)((rangeMaxScaled - rangeMinScaled) / m_majorTickValueDistance);

	if (numMajorTicks == 0)
    {
		forceTwoTicks();
		return;
	}

	if (maxNumMajorTicks > 0)
		m_numMinorTicks = (int)(m_size / (maxNumMajorTicks * fontMetrics.height()));
	else
        m_numMinorTicks = 0;

    if (m_numMinorTicks < 1)
		m_numMinorTicks = 0;
	else if (m_numMinorTicks < 2)
		m_numMinorTicks = 1;
	else if (m_numMinorTicks < 5)
		m_numMinorTicks = 2;
	else if (m_numMinorTicks < 10)
		m_numMinorTicks = 5;
	else
        m_numMinorTicks = 10;

	m_firstMajorTickValue = floor(rangeMinScaled / m_majorTickValueDistance) * m_majorTickValueDistance;
	// skip = 0;

	if (rangeMinScaled == rangeMaxScaled)
		return;

    if (rangeMinScaled > -73.0) {
        return;
    }

    if (rangeMaxScaled < -73.0) {
        return;
    }

    // qDebug("ScaleEngine::reCalcS: m_numMinorTicks: %d maxNumMajorTicks: %d m_firstMajorTickValue: %le rangeMinScaled: %le rangeMaxScaled: %le",
    //     m_numMinorTicks, maxNumMajorTicks, m_firstMajorTickValue, rangeMinScaled, rangeMaxScaled);

    std::vector<double> sVals;
    std::vector<double> dbVals;

    int nbSVals = ((int) (-73.0 - rangeMinScaled)) / 6;
    double sFloor = -73.0 - (double) nbSVals*6;

    for (int i = 0; i < nbSVals; i++) {
        sVals.push_back(sFloor + 6*i);
    }

    sVals.push_back(-73.0);

    int nbDbVals = ((int) (rangeMaxScaled - -73.0)) / 10;

    for (int i = 1; i < nbDbVals; i++) {
        dbVals.push_back(-73.0 + 10*i);
    }

    truncS(maxNumMajorTicks, sVals, dbVals);

    double minTickShiftS = 0.0;
    double minTickShiftDb = 0.0;
    m_tickList.clear();

    if ((m_numMinorTicks > 1) && (sVals.size() > 1)) {
        minTickShiftS = (sVals[1] - sVals[0]) / m_numMinorTicks;
    }

    if ((m_numMinorTicks > 1) && (dbVals.size() > 1)) {
        minTickShiftDb = (dbVals[1] - dbVals[0]) / m_numMinorTicks;
    }

    for (auto sVal : sVals)
    {
        int s = (int) (sVal + 6*9 - -73.0) / 6;
        // qDebug("ScaleEngine::reCalcS: S : %le (%d)", sVal, s);
        tick.pos = getPosFromValue(sVal);
        tick.major = true;
        tick.textPos = -1;
        tick.textSize = -1;
        tick.text.clear();
        tick.text = QString("%1").arg(s);
        adjustText(fontMetrics, tick);
        m_tickList.append(tick);

        if (s == 9)
        {
            if (dbVals.size() != 0)
            {
                tick.pos = getPosFromValue(sVal + minTickShiftDb);
                tick.major = false;
                tick.textPos = -1;
                tick.textSize = -1;
                tick.text.clear();
                m_tickList.append(tick);
            }
        }
        else
        {
            for (int i = 1; i < m_numMinorTicks; i++)
            {
                tick.pos = getPosFromValue(sVal + i*(minTickShiftS / (m_numMinorTicks - 1)));
                tick.major = false;
                tick.textPos = -1;
                tick.textSize = -1;
                tick.text.clear();
                m_tickList.append(tick);
            }
        }
    }

    for (auto dbVal : dbVals)
    {
        int db = (int) (dbVal - -73.0);
        // qDebug("ScaleEngine::reCalcS: dB: %le (+%d)", dbVal, db);
        tick.pos = getPosFromValue(dbVal);
        tick.major = true;
        tick.textPos = -1;
        tick.textSize = -1;
        tick.text.clear();
        tick.text = QString("+%1").arg(db);
        adjustText(fontMetrics, tick);
        m_tickList.append(tick);

        for (int i = 1; i < m_numMinorTicks; i++)
        {
            tick.pos = getPosFromValue(dbVal + i*(minTickShiftDb / (m_numMinorTicks - 1)));
            tick.major = false;
            tick.textPos = -1;
            tick.textSize = -1;
            tick.text.clear();
            m_tickList.append(tick);
        }
    }
}

void ScaleEngine::truncS(int nbMaxTicks, std::vector<double>& sVals, std::vector<double>& dbVals)
{
    int nbAvailDb = nbMaxTicks - (int) sVals.size();

    if (nbAvailDb <= 0)
    {
        dbVals.clear();

        if (nbMaxTicks == (int) sVals.size()) {
            return;
        }

        std::vector<double> newSVals;
        int n = sVals.size() / nbMaxTicks;

        for (int i = 0; n*i < (int) sVals.size(); i++) {
            newSVals.push_back(sVals[sVals.size() -1 -n*i]);
        }

        sVals = newSVals;
    }

    if (nbAvailDb < (int) dbVals.size())
    {
        std::vector<double> newDbVals;
        int n = dbVals.size() / nbAvailDb;

        for (int i = 0; n*i < (int) dbVals.size(); i++) {
            newDbVals.push_back(dbVals[n*i]);
        }

        dbVals = newDbVals;
    }
}

void ScaleEngine::adjustText(const QFontMetricsF& fontMetrics, Tick& tick)
{
    tick.textSize = fontMetrics.boundingRect(tick.text).width();

    if (m_orientation == Qt::Vertical)
    {
        tick.textPos = tick.pos - fontMetrics.ascent() / 2;
    }
    else
    {
        tick.textPos = tick.pos - fontMetrics.boundingRect(tick.text).width() / 2;
    }
}

double ScaleEngine::majorTickValue(int tick)
{
	return m_firstMajorTickValue + (tick * m_majorTickValueDistance);
}

double ScaleEngine::minorTickValue(int tick)
{
	if(m_numMinorTicks < 1)
		return 0.0;

	return (m_majorTickValueDistance * tick) / m_numMinorTicks;
}

ScaleEngine::ScaleEngine() :
	m_orientation(Qt::Horizontal),
	m_charSize(8),
    m_size(1.0f),
    m_physicalUnit(Unit::None),
    m_rangeMin(-1.0),
    m_rangeMax(1.0),
    m_recalc(true),
    m_scale(1.0),
	m_majorTickValueDistance(1.0),
    m_firstMajorTickValue(1.0),
    m_numMinorTicks(1),
    m_decimalPlaces(1),
    m_fixedDecimalPlaces(2),
    m_makeOpposite(false),
    m_truncateMode(false),
    m_truncated(false),
    m_truncationValue(0.0),
    m_tickFormatter(nullptr)
{
}

void ScaleEngine::setOrientation(Qt::Orientation orientation)
{
	m_orientation = orientation;
	m_recalc = true;
}

void ScaleEngine::setFont(const QFont& font)
{
	m_font = font;
	m_recalc = true;
	calcCharSize();
}

void ScaleEngine::setSize(float size)
{
	if(size > 0.0f) {
		m_size = size;
	} else {
		m_size = 1.0f;
	}
	m_recalc = true;
}

void ScaleEngine::setRange(Unit::Physical physicalUnit, float rangeMin, float rangeMax)
{
	double tmpRangeMin;
	double tmpRangeMax;
/*
	if(rangeMin < rangeMax) {
		tmpRangeMin = rangeMin;
		tmpRangeMax = rangeMax;
	} else if(rangeMin > rangeMax) {
		tmpRangeMin = rangeMax;
		tmpRangeMax = rangeMin;
	} else {
		tmpRangeMin = rangeMin * 0.99;
		tmpRangeMax = rangeMin * 1.01 + 0.01;
	}
*/
	tmpRangeMin = rangeMin;
	tmpRangeMax = rangeMax;

	if ((tmpRangeMin != m_rangeMin) || (tmpRangeMax != m_rangeMax) || (m_physicalUnit != physicalUnit))
    {
		m_physicalUnit = physicalUnit;
		m_rangeMin = tmpRangeMin;
		m_rangeMax = tmpRangeMax;
		m_recalc = true;
	}
}

float ScaleEngine::getPosFromValue(double value)
{
	return ((value - m_rangeMin) / (m_rangeMax - m_rangeMin)) * (m_size - 1.0);
}

float ScaleEngine::getValueFromPos(double pos)
{
	return ((pos * (m_rangeMax - m_rangeMin)) / (m_size - 1.0)) + m_rangeMin;
}

float ScaleEngine::getPosFromValueTrunc(double value)
{
    double rangeMin = m_truncated ? m_rangeMin - m_truncationValue : m_rangeMin;
    double rangeMax = m_truncated ? m_rangeMax - m_truncationValue : m_rangeMax;
	return ((value - rangeMin) / (rangeMax - rangeMin)) * (m_size - 1.0);
}

float ScaleEngine::getValueFromPosTrunc(double pos)
{
    double rangeMin = m_truncated ? m_rangeMin - m_truncationValue : m_rangeMin;
    double rangeMax = m_truncated ? m_rangeMax - m_truncationValue : m_rangeMax;
	return ((pos * (rangeMax - rangeMin)) / (m_size - 1.0)) + rangeMin;
}

const ScaleEngine::TickList& ScaleEngine::getTickList()
{
	reCalc();
	return m_tickList;
}

QString ScaleEngine::getRangeMinStr()
{
    double rangeMin = m_truncated ? m_rangeMin - m_truncationValue : m_rangeMin;

	if (m_unitStr.length() > 0) {
		return QString("%1 %2").arg(formatTick(rangeMin / m_scale, m_decimalPlaces)).arg(m_unitStr);
    } else {
        return QString("%1").arg(formatTick(rangeMin / m_scale, m_decimalPlaces));
    }
}

QString ScaleEngine::getRangeMaxStr()
{
    double rangeMax = m_truncated ? m_rangeMax - m_truncationValue : m_rangeMax;

	if (m_unitStr.length() > 0) {
		return QString("%1 %2").arg(formatTick(rangeMax / m_scale, m_decimalPlaces)).arg(m_unitStr);
    } else {
        return QString("%1").arg(formatTick(rangeMax / m_scale, m_decimalPlaces));
    }
}

float ScaleEngine::getScaleWidth()
{
	float max;
	float len;
	int i;

	reCalc();
	max = 0.0f;
	for(i = 0; i < m_tickList.count(); i++) {
		len = m_tickList[i].textSize;
		if(len > max)
			max = len;
	}
	return max;
}

void ScaleEngine::setTruncateMode(bool mode)
{
    qDebug("ScaleEngine::setTruncateMode: %s", (mode ? "on" : "off"));
    m_truncateMode = mode;
    m_recalc = true;
    reCalc();
}

void ScaleEngine::updateTruncation()
{
    m_truncated = false;
    m_truncationValue = 0.0;

    if (!m_truncateMode) {
        return;
    }

    if (order(m_rangeMax) != order(m_rangeMin)) {
        return;
    }

    double width = m_rangeMax - m_rangeMin;
    int widthOrder = order(width);
    int maxOrder = order(m_rangeMax);

    if ((widthOrder < 0) || (maxOrder < 0) || (widthOrder == maxOrder)) {
        return;
    }

    for (int i = widthOrder+1; i <= maxOrder; i++)
    {
        int irangeMin = floor(m_rangeMin / pow(10, i));
        int irangeMax = floor(m_rangeMax / pow(10, i));

        if (irangeMin == irangeMax)
        {
            m_truncated = true;
            m_truncationValue = irangeMin * pow(10, i);
            break;
        }
    }

    qDebug("ScaleEngine::updateTruncation: m_truncationValue: %f", m_truncationValue);
}
