/***************************************************************************
        File                 : Spectrogram.cpp
        Project              : SciDAVis
--------------------------------------------------------------------
        Copyright            : (C) 2006 by Ion Vasilief
        Email (use @ for *)  : ion_vasilief*yahoo.fr
        Description          : SciDAVis's Spectrogram Class
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  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.                           *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/

#include "Spectrogram.h"
#include "ColorButton.h"
#include <math.h>
#include <qpen.h>
#include <qmessagebox.h>

#include <qwt_scale_widget.h>

Spectrogram::Spectrogram()
    : QwtPlotSpectrogram(),
      d_matrix(0),
      color_axis(QwtPlot::yRight),
      color_map_policy(Default),
      color_map(QwtLinearColorMap())
{
}

Spectrogram::Spectrogram(Matrix *m)
    : QwtPlotSpectrogram(QString(m->name())),
      d_matrix(m),
      color_axis(QwtPlot::yRight),
      color_map_policy(Default),
      color_map(QwtLinearColorMap())
{
    setData(MatrixData(m));
    double step = fabs(data().range().maxValue() - data().range().minValue()) / 5.0;

    QwtValueList contourLevels;
    for (double level = data().range().minValue() + step; level < data().range().maxValue();
         level += step)
        contourLevels += level;

    setContourLevels(contourLevels);
}

void Spectrogram::updateData(Matrix *m)
{
    if (!m)
        return;

    QwtPlot *plot = this->plot();
    if (!plot)
        return;

    setData(MatrixData(m));
    setLevelsNumber(levels());

    QwtScaleWidget *colorAxis = plot->axisWidget(color_axis);
    if (colorAxis)
        colorAxis->setColorMap(data().range(), colorMap());

    plot->setAxisScale(color_axis, data().range().minValue(), data().range().maxValue());
    plot->replot();
}

void Spectrogram::setLevelsNumber(int levels)
{
    double step = fabs(data().range().maxValue() - data().range().minValue()) / (double)levels;

    QwtValueList contourLevels;
    for (double level = data().range().minValue() + step; level < data().range().maxValue();
         level += step)
        contourLevels += level;

    setContourLevels(contourLevels);
}

bool Spectrogram::hasColorScale()
{
    QwtPlot *plot = this->plot();
    if (!plot)
        return false;

    QwtScaleWidget *colorAxis = plot->axisWidget(color_axis);
    return colorAxis->isColorBarEnabled();
}

void Spectrogram::showColorScale(int axis, bool on)
{
    if (hasColorScale() == on && color_axis == axis)
        return;

    QwtPlot *plot = this->plot();
    if (!plot)
        return;

    QwtScaleWidget *colorAxis = plot->axisWidget(color_axis);
    colorAxis->setColorBarEnabled(false);

    color_axis = axis;

    // We must switch main and the color scale axes and their respective scales
    int xAxis = this->xAxis();
    int yAxis = this->yAxis();
    int oldMainAxis = 0;
    if (axis == QwtPlot::xBottom || axis == QwtPlot::xTop) {
        oldMainAxis = xAxis;
        xAxis = 5 - color_axis;
    } else if (axis == QwtPlot::yLeft || axis == QwtPlot::yRight) {
        oldMainAxis = yAxis;
        yAxis = 1 - color_axis;
    }

    // First we switch axes
    setAxis(xAxis, yAxis);

    // Next we switch axes scales
    QwtScaleDiv *scDiv = plot->axisScaleDiv(oldMainAxis);
#if QWT_VERSION >= 0x050200
    if (axis == QwtPlot::xBottom || axis == QwtPlot::xTop)
        plot->setAxisScale(xAxis, scDiv->lowerBound(), scDiv->upperBound());
    else if (axis == QwtPlot::yLeft || color_axis == QwtPlot::yRight)
        plot->setAxisScale(yAxis, scDiv->lowerBound(), scDiv->upperBound());
#else
    if (axis == QwtPlot::xBottom || axis == QwtPlot::xTop)
        plot->setAxisScale(xAxis, scDiv->lBound(), scDiv->hBound());
    else if (axis == QwtPlot::yLeft || color_axis == QwtPlot::yRight)
        plot->setAxisScale(yAxis, scDiv->lBound(), scDiv->hBound());
#endif

    colorAxis = plot->axisWidget(color_axis);
    plot->setAxisScale(color_axis, data().range().minValue(), data().range().maxValue());
    colorAxis->setColorBarEnabled(on);
    colorAxis->setColorMap(data().range(), colorMap());
    if (!plot->axisEnabled(color_axis))
        plot->enableAxis(color_axis);
    colorAxis->show();
    plot->updateLayout();
}

int Spectrogram::colorBarWidth()
{
    QwtPlot *plot = this->plot();
    if (!plot)
        return 0;

    QwtScaleWidget *colorAxis = plot->axisWidget(color_axis);
    return colorAxis->colorBarWidth();
}

void Spectrogram::setColorBarWidth(int width)
{
    QwtPlot *plot = this->plot();
    if (!plot)
        return;

    QwtScaleWidget *colorAxis = plot->axisWidget(color_axis);
    colorAxis->setColorBarWidth(width);
}

Spectrogram *Spectrogram::copy()
{
    Spectrogram *new_s = new Spectrogram(matrix());
    new_s->setDisplayMode(QwtPlotSpectrogram::ImageMode,
                          testDisplayMode(QwtPlotSpectrogram::ImageMode));
    new_s->setDisplayMode(QwtPlotSpectrogram::ContourMode,
                          testDisplayMode(QwtPlotSpectrogram::ContourMode));
    new_s->setColorMap(colorMap());
    new_s->setAxis(xAxis(), yAxis());
    new_s->setDefaultContourPen(defaultContourPen());
    new_s->setLevelsNumber(levels());
    new_s->color_map_policy = color_map_policy;
    return new_s;
}

void Spectrogram::setGrayScale()
{
    color_map = QwtLinearColorMap(Qt::black, Qt::white);
    setColorMap(color_map);
    color_map_policy = GrayScale;

    QwtPlot *plot = this->plot();
    if (!plot)
        return;

    QwtScaleWidget *colorAxis = plot->axisWidget(color_axis);
    if (colorAxis)
        colorAxis->setColorMap(data().range(), colorMap());
}

void Spectrogram::setDefaultColorMap()
{
    color_map = defaultColorMap();
    setColorMap(color_map);
    color_map_policy = Default;

    QwtPlot *plot = this->plot();
    if (!plot)
        return;

    QwtScaleWidget *colorAxis = plot->axisWidget(color_axis);
    if (colorAxis)
        colorAxis->setColorMap(this->data().range(), this->colorMap());
}

void Spectrogram::setCustomColorMap(const QwtLinearColorMap &map)
{
    setColorMap(map);
    color_map = map;
    color_map_policy = Custom;

    QwtPlot *plot = this->plot();
    if (!plot)
        return;

    QwtScaleWidget *colorAxis = plot->axisWidget(color_axis);
    if (colorAxis)
        colorAxis->setColorMap(this->data().range(), this->colorMap());
}

QwtLinearColorMap Spectrogram::defaultColorMap()
{
    QwtLinearColorMap colorMap(Qt::blue, Qt::red);
    colorMap.addColorStop(0.25, Qt::cyan);
    colorMap.addColorStop(0.5, Qt::green);
    colorMap.addColorStop(0.75, Qt::yellow);
    return colorMap;
}

QString Spectrogram::saveToString()
{
    QString s = "<spectrogram>\n";
    s += "\t<matrix>" + QString(d_matrix->name()) + "</matrix>\n";

    if (color_map_policy != Custom)
        s += "\t<ColorPolicy>" + QString::number(color_map_policy) + "</ColorPolicy>\n";
    else {
        s += "\t<ColorMap>\n";
        s += "\t\t<Mode>" + QString::number(color_map.mode()) + "</Mode>\n";
        s += "\t\t<MinColor>" + COLORNAME(color_map.color1()) + "</MinColor>\n";
        s += "\t\t<MaxColor>" + COLORNAME(color_map.color2()) + "</MaxColor>\n";
        QwtArray<double> colors = color_map.colorStops();
        int stops = (int)colors.size();
        s += "\t\t<ColorStops>" + QString::number(stops - 2) + "</ColorStops>\n";
        for (int i = 1; i < stops - 1; i++) {
            s += "\t\t<Stop>" + QString::number(colors[i]) + "\t";
            s += COLORNAME(QColor(color_map.rgb(QwtDoubleInterval(0, 1), colors[i])));
            s += "</Stop>\n";
        }
        s += "\t</ColorMap>\n";
    }
    s += "\t<Image>" + QString::number(testDisplayMode(QwtPlotSpectrogram::ImageMode))
            + "</Image>\n";

    bool contourLines = testDisplayMode(QwtPlotSpectrogram::ContourMode);
    s += "\t<ContourLines>" + QString::number(contourLines) + "</ContourLines>\n";
    if (contourLines) {
        s += "\t\t<Levels>" + QString::number(levels()) + "</Levels>\n";
        bool defaultPen = defaultContourPen().style() != Qt::NoPen;
        s += "\t\t<DefaultPen>" + QString::number(defaultPen) + "</DefaultPen>\n";
        if (defaultPen) {
            s += "\t\t\t<PenColor>" + COLORNAME(defaultContourPen().color()) + "</PenColor>\n";
            s += "\t\t\t<PenWidth>" + QString::number(defaultContourPen().width())
                    + "</PenWidth>\n";
            s += "\t\t\t<PenStyle>" + QString::number(defaultContourPen().style() - 1)
                    + "</PenStyle>\n";
        }
    }
    QwtScaleWidget *colorAxis = plot()->axisWidget(color_axis);
    if (colorAxis && colorAxis->isColorBarEnabled()) {
        s += "\t<ColorBar>\n\t\t<axis>" + QString::number(color_axis) + "</axis>\n";
        s += "\t\t<width>" + QString::number(colorAxis->colorBarWidth()) + "</width>\n";
        s += "\t</ColorBar>\n";
    }
    s += "\t<Visible>" + QString::number(isVisible()) + "</Visible>\n";
    return s + "</spectrogram>\n";
}

double MatrixData::value(double x, double y) const
{
    int i = abs((int)floor((y - y_start) / dy - 1));
    int j = abs((int)floor((x - x_start) / dx));

    if (d_m && i < n_rows && j < n_cols)
        return d_m[i][j];
    else
        return 0.0;
}
