#include <QFrame>
#include <QFont>
#include <QPainter>
#include <QMouseEvent>
#include <QScrollBar>
#include <QApplication>

#include "QFitsViewingTools.h"
#include "QFitsMainWindow.h"
#include "QFitsMainView.h"
#include "QFitsSingleBuffer.h"
#include "QFitsGlobal.h"
#include "QFitsWidget2D.h"
#include "QFitsScroller.h"
#include "QFitsMarkers.h"
#include "fits.h"
#include "qtdpuser.h"
#include "guitools.h"

//------------------------------------------------------------------------------
//         QFitsViewingTools
//------------------------------------------------------------------------------
QFitsViewingTools::QFitsViewingTools(QFitsMainWindow *parent, int widget_size)
                                    : QWidget(parent), myParent(parent)
{
    setFixedWidth(widget_size);
    // find out how wide a string will be, and set the font accordingly
    int w = 21;
    bool found = false;

    int close_button_height = 12,
        posinfo_height = 40,
        plotframe_height = 80;

    while (!found) {
        w--;
        QFontMetrics f(QFont("Helvetica", w));
        if (f.boundingRect("00:00:00.000 -00�00'00.00\"").width() < widget_size) {
            found = true;
        }
        if (w < 5) {
            found = true;
        }
        if (found) posinfo_height = int(f.height() * 4.5);
    }

    QFitsSimplestButton *hideButton = new QFitsSimplestButton(QIcon(":/xicon.svg").pixmap(10, 10), this);
    hideButton->setGeometry(widget_size-close_button_height, 2,
                            close_button_height, close_button_height);
    hideButton->setToolTip("Close");
    connect(hideButton, SIGNAL(clicked()),
            this, SLOT(hide()));

    posinfo = new QLabel(this);
    posinfo->setAlignment(Qt::AlignCenter);
    posinfo->setFont(QFont("Helvetica", w));
    posinfo->setGeometry(0, 0, widget_size/2, posinfo_height/4);
    posinfo->setText("");

    valinfo = new QLabel(this);
    valinfo->setAlignment(Qt::AlignCenter);
    valinfo->setFont(QFont("Helvetica", w));
    valinfo->setGeometry((widget_size-close_button_height)/2, 0,
                         (widget_size-close_button_height)/2, posinfo_height/4);
    valinfo->setText("");

    worldinfo = new QLabel(this);
    worldinfo->setAlignment(Qt::AlignCenter);
    worldinfo->setFont(QFont("Helvetica", w));
    worldinfo->setGeometry(0, posinfo_height/4, widget_size, posinfo_height/4);
    worldinfo->setText("");

    extrainfo = new QLabel(this);
    extrainfo->setAlignment(Qt::AlignLeft);
    extrainfo->setFont(QFont("Helvetica", w));
    extrainfo->setGeometry(5, posinfo_height/2, widget_size - 5, posinfo_height/2);
    extrainfo->setText("");

    QFrame *magframe = new QFrame(this);
    magframe->setGeometry(0, posinfo_height, widget_size, widget_size);
    magframe->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
    magnifier = new QFitsMag(magframe, this);
    magnifier->setGeometry(magframe->contentsRect());

    QFrame *totframe = new QFrame(this);
    totframe->setGeometry(0, posinfo_height+widget_size, widget_size, widget_size);
    totframe->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
    total = new QFitsTotal(totframe, this);
    total->setGeometry(totframe->contentsRect());
    total->setFont(QFont("Helvetica", w));

    plotframe_height = widget_size / 1.875;

    QFrame *plotframe = new QFrame(this);
    plotframe->setGeometry(0, posinfo_height + 2*widget_size,
                           widget_size, plotframe_height);
    plotframe->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
    cuts_plot = new QFitsCutsPlot(plotframe, this);
    cuts_plot->setGeometry(plotframe->contentsRect());

    resize(widget_size, posinfo_height+2*widget_size+plotframe_height);
}

void QFitsViewingTools::refreshPosInfo(int x, int y) {
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL) {
        QImage  *bufImg = sb->getImage();
        QString postext(""),
                valtext(""),
                worldtext("");
        dpString fstring;
        int     spacing_width   = 2,
                spacing_height  = 2;

        if (sb->getViewMode() == ViewTable) {
            fstring.sprintf("(%*i, %*i)", spacing_width, x, spacing_height, y);
            postext = fstring.c_str();
        } else {
            if ((x > 0) && (y > 0) &&
                (x <= sb->getDpData()->fvalue->Naxis(1)) && (y <= sb->getDpData()->fvalue->Naxis(2)))
            {
                Fits    *f      = sb->getDpData()->fvalue;
                double  xpos    = 0.,
                        ypos    = 0.,
                        ra      = 0.,
                        dec     = 0.,
                        value   = 0.;

                if (f->Naxis(3) > 1) {
                    value = sb->getCubeDisplay2D()->ValueAt(sb->getCubeDisplay2D()->F_I(x, y));
                } else {
                    value = f->ValueAt(f->F_I(x, y));
                }

                // calculate here the width of the coordinate-string to be painted
                if (bufImg->width() < 10) {
                   spacing_width = 1;
                } else if (bufImg->width() < 100) {
                    spacing_width = 2;
                } else if (bufImg->width() < 1000) {
                    spacing_width = 3;
                } else if (bufImg->width() < 10000) {
                    spacing_width = 4;
                } else if (bufImg->width() < 100000) {
                    spacing_width = 5;
                } else if (bufImg->width() < 1000000) {
                    spacing_width = 6;
                }

                if (bufImg->height() < 10) {
                    spacing_height = 1;
                } else if (bufImg->height() < 100) {
                    spacing_height = 2;
                } else if (bufImg->height() < 1000) {
                    spacing_height = 3;
                } else if (bufImg->height() < 10000) {
                    spacing_height = 4;
                } else if (bufImg->height() < 100000) {
                    spacing_height = 5;
                } else if (bufImg->height() < 1000000) {
                    spacing_height = 6;
                }

                dpString hhh;
                hhh.sprintf("(%*i, %*i)", spacing_width, x, spacing_height, y);
                postext = hhh.c_str();
                if (f->membits == C16) {
                    dpCOMPLEX valueComplex;
                    if (f->Naxis(3) > 1) {
                        valueComplex = sb->getCubeDisplay2D()->cdata[sb->getCubeDisplay2D()->F_I(x, y)];
                    } else {
                        valueComplex = f->cdata[f->F_I(x, y)];
                    }
                    valtext = FormatComplexdpString(dpComplex(valueComplex.r, valueComplex.i), "%.2f").c_str();
                } else {
                    valtext = QString("%1").arg(value, 4); //.sprintf("%4g", value);
                }

                if (f->hasRefPix()) {
                    double cd1_1 = 0.0, cd2_2 = 0.0, cd1_2 = 0.0, cd2_1 = 0.0, crota2 = 0.0, cdelt1 = 1.0, cdelt2 = 1.0;

                    f->GetFloatKey("CROTA2", &crota2);
                    f->GetFloatKey("CDELT1", &cdelt1);
                    f->GetFloatKey("CDELT2", &cdelt2);
                    if (f->GetFloatKey("PC1_1", &cd1_1)) {
                        cd1_1 *= cdelt1;
                        f->GetFloatKey("PC1_2", &cd1_2);
                        cd1_2 *= cdelt1;
                        f->GetFloatKey("PC2_1", &cd2_1);
                        cd2_1 *= cdelt2;
                        f->GetFloatKey("PC2_2", &cd2_2);
                        cd2_2 *= cdelt2;
                    } else {
                        f->GetFloatKey("CD1_1", &cd1_1);
                        f->GetFloatKey("CD2_1", &cd2_1);
                        f->GetFloatKey("CD1_2", &cd1_2);
                        f->GetFloatKey("CD2_2", &cd2_2);
                    }

                    worldpos(x, y,
                             f->getCRVAL(1), f->getCRVAL(2),
                             f->getCRPIX(1), f->getCRPIX(2),
                             cdelt1, cdelt2,
                             crota2, cd1_1, cd2_1, cd1_2, cd2_2,
                             f->crtype,
                             &xpos, &ypos);

                    if (f->crtype[0] == '-') {
                        ra = xpos;
                        dec = ypos;
                        char sign = dec < 0 ? '-' : '+';
                        double adec = fabs(dec);
                        double rad = ra / 15.0;
                        int rah = (int)rad;
                        rad = (rad-rah) * 60.0;
                        int ram = (int)rad;
                        rad = (rad-ram) * 60.0;
                        double de = adec;
                        int deh = (int)de;
                        de = (de-deh) * 60.0;
                        int dem = (int)de;
                        de = (de-dem) * 60.0;

                        fstring.sprintf("%02i:%02i:%06.3f %c%02i:%02i:%05.2f",
                                        rah, ram, rad, sign, deh, dem, de);
                        worldtext = fstring.c_str();
                    } else {
                        fstring.sprintf("%f %f", xpos, ypos);
                        worldtext = fstring.c_str();
                    }
                }
            }
        }

        posinfo->setText(postext);
        valinfo->setText(valtext);
        worldinfo->setText(worldtext);
        cuts_plot->setCenter(x, y);
    }
}

void QFitsViewingTools::setDistanceInfo(const double &dx, const double &dy, const int &dix, const int &diy, const bool &inPixels) {
    if (dix == 0 && diy == 0) {
        updateRegionInfo();
    } else {
        int pxlen = QString::number(dix).length();
        QFontMetrics f(extrainfo->font());
        if (QString::number(diy).length() > pxlen) pxlen = QString::number(diy).length();
        QString info, tmpstr;
        dpString fstring;
        info += QChar(916); // greek Delta
        info += "x: ";
        fstring.sprintf("%*i", pxlen, dix);
        info += fstring.c_str();
        if (!inPixels) {
            info += " (";
            info += QString::number(dx, 'g', 4);
            info += "'')";
        }

        tmpstr = " " + QString::number(sqrt(dix*dix+diy*diy), 'g', 4) + "px\n";
        while (f.boundingRect(info + tmpstr + "M").width() < extrainfo->width()) tmpstr.prepend(" ");
        info += tmpstr.right(tmpstr.size() - 1);

//        info += "\t(";
//        info += QString::number(sqrt(dix*dix+diy*diy), 'g', 4);
//        info += ")\n";
        QString secondline = QChar(916);
        secondline += "y: ";
        fstring.sprintf("%*i", pxlen, diy);
        secondline += fstring.c_str();
        if (!inPixels) {
            secondline += " (";
            secondline += QString::number(dy, 'g', 4);
            secondline += "'')";
            tmpstr = " (" + QString::number(sqrt(dx*dx+dy*dy), 'g', 2) + "'')";
            while (f.boundingRect(secondline + tmpstr + "M").width() < extrainfo->width()) tmpstr.prepend(" ");
            secondline += tmpstr.right(tmpstr.size() - 1);
        }
        info += secondline;
        extrainfo->setText(info);
    }
}

void QFitsViewingTools::updateRegionInfo() {
    long x = 0, y = 0, n = 0;
    double total, average, stddev, median, meddev, flux_per_beam = 0.0, bmaj = 0.0, bmin = 0.0;
    char bunit[81];
    QString info = "";

    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL && sb->getDpData()->type == typeFits) {
        Fits *f = sb->getDpData()->fvalue;
        if (f->Naxis(3) > 1) {
            f = sb->getCubeDisplay2D();
        }
        if (f != NULL) {
            QFitsMarkers *m = sb->getSourceMarkers();
            if (m->hasSourceMarkers()) {
                n = m->getSizeSource();
                Fits dummy;
                dummy.create(n, 1, R8);
                if (!(buffersLock.tryLockForRead())) {
                    return;
                }

                for (int i = 0; i < n; i++) {
                    m->getMarkerSource(i, &x, &y);
                    if ((x >= 1) && (x <= sb->Naxis(1)) &&
                        (y >= 1) && (y <= sb->Naxis(2)))
                    {
                        dummy.r8data[i] = f->ValueAt(f->F_I(x, y));
                    }
                }
                buffersLock.unlock();
                dummy.recreate(n, 1);
                total = dummy.get_flux();
                if (f->GetFloatKey("BMAJ", &bmaj) && f->GetFloatKey("BMIN", &bmin) && f->GetStringKey("BUNIT", bunit))
                    flux_per_beam = total / (2. * M_PI * bmaj * bmin / fabs(f->getCDELT(1)) / fabs(f->getCDELT(2)) / 8. / log(2.));

                info = QString::number(n) + " px; Sum: " + QString::number(total) + "\n";
                if (flux_per_beam != 0.0) {
                    QString b = QString(bunit);
                    b = b.left(b.indexOf('/'));
                    info += "Flux [" + b + "]: " + QString::number(flux_per_beam);
                }
            }
        }
    }
    extrainfo->setText(info);
}

void QFitsViewingTools::clear() {
    magnifier->clear();
    total->clear();
    cuts_plot->clear();
}

void QFitsViewingTools::enterEvent(QEvent*) {
    if (dpuser_widget->input->hasFocus()) {
        // first take away focus from dpuserWidget,
        // so it can be set to bb afterwards
        setFocus();
    }

    QFitsBaseBuffer *bb = fitsMainWindow->getCurrentBuffer();
    if (bb != NULL) {
        bb->setFocus();
    }
}

void QFitsViewingTools::refreshViews() {
    magnifier->update();
    total->update();
    cuts_plot->update();
}

//------------------------------------------------------------------------------
//         QFitsMag
//------------------------------------------------------------------------------
QFitsMag::QFitsMag(QFrame *parent, QFitsViewingTools *p) : QWidget(parent) {
    myParent    = p;
    cenx        = 0;
    ceny        = 0;
    clearWidget = true;
    setAttribute(Qt::WA_OpaquePaintEvent);
}

void QFitsMag::paintEvent(QPaintEvent *) {
    if (clearWidget) {
        QPainter p(this);
        QColor c = QWidget::palette().color(QWidget::backgroundRole());
        p.setPen(c);
        p.setBrush(c);
        p.drawRect(rect());
        clearWidget = false;
        return;
    }
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL) {
        QImage subimage;

        switch(sb->getViewMode()) {
        case ViewWiregrid:
//#ifdef HAS_VTK
        case View3D:
//#endif
            subimage = directPixmap.toImage();
            break;
        case ViewImage:
        case ViewContour:
        case ViewTable:
            {
            QImage *sbImg   = sb->getImage();
            int mag_size    = 25,
                w           = mag_size,     // define center (x,y), width and height
                h           = mag_size,     // of subimage to copy to QFitsMag
                x           = cenx - 13,
                y           = sbImg->height() - ceny - 12,
                sub_left    = 0,
                sub_right   = mag_size,
                sub_top     = 0,
                sub_bottom  = mag_size,
                r           = 0,
                g           = 0,
                b           = 0;

            // check if subimage is partially beyond image
            if (x < 0) {
                // left_edge will be grey
                sub_left = -x;
                if (sub_left > mag_size) {
                    sub_left = mag_size;
                }
            }

            if (sbImg->width() < x + w) {
                // right edge will be grey
                sub_right = w-(x + w - sbImg->width());
                if (sub_right < 0) {
                    sub_right = 0;
                }
            }

            if (y < 0) {
                // top edge will be grey
                sub_top = -y;
                if (sub_top > mag_size) {
                    sub_top = mag_size;
                }
            }
            if (sbImg->height() < y + h) {
                // bottom edge will be grey
                sub_bottom = h-(y + h - sbImg->height());
                if (sub_bottom < 0) {
                    sub_bottom = 0;
                }
            }

            // copy subimage from buffer (even if it is beyond the borders)
            subimage = sbImg->copy(x, y, w, h);

            // convert the subimage to 32bit RGB data
            subimage = subimage.convertToFormat(QImage::Format_RGB32, Qt::AutoColor);

            // get default grey background color
            QApplication::palette().window().color().getRgb(&r, &g, &b);
            if (r == 255 && g == 255 && b == 255) {
                // FIXME: hack for MacOS: Avoid white background
                r = g = b = 237;
            }
            QRgb pix_val = qRgb(r, g, b);

            if ((sub_left == sub_right) || (sub_top == sub_bottom)) {
                // QFitsMag is completely outside the image
                subimage.fill(pix_val);
            } else if ((sub_left != 0) || (sub_right != mag_size) ||
                       (sub_top != 0) || (sub_bottom != mag_size))
            {
                // QFitsMag is somewhere at the border
                for (int i = 0; i < mag_size; i++) {
                    QRgb *scan_line = (QRgb*)(subimage.scanLine(i));
                    for (int j = 0; j < mag_size; j++) {
                        if ((i < sub_top) || (i >= sub_bottom) ||
                            (j < sub_left) || (j >= sub_right))
                        {
                            scan_line[j] = pix_val;
                        }
                    }
                }
            }
            }
            break;
        default:
            break;
        }

        if (!subimage.isNull()) {
            subimage = subimage.scaled(width(), height());

            double w = width() / 25.;

            QPixmap  pm = QPixmap::fromImage(subimage);
            QPainter p;
            p.begin(&pm);
            p.setPen(QColor(255, 255, 255));
            p.drawRect(QRectF(width() / 2. - w / 2., width() / 2. - w / 2., w + 1, w + 1));
//            p.drawRect(72, 72, 7, 7);
            p.setPen(QColor(0, 0, 0));
            p.drawRect(QRectF(width() / 2. - w / 2. - 1, width() / 2. - w / 2. - 1, w + 3, w + 3));
//            p.drawRect(71, 71, 9, 9);
            p.end();

            p.begin(this);
            p.drawPixmap(0, 0, pm);
            p.end();
        }
    }
}

void QFitsMag::setCenter(int x, int y) {
    cenx = x;
    ceny = y;
    update();
}

void QFitsMag::clear() {
    clearWidget = true;
    update();
}

void QFitsMag::setDirectPixmap(QPixmap &pix) {
    directPixmap = pix;
    update();
}

//------------------------------------------------------------------------------
//         QFitsTotal
//------------------------------------------------------------------------------
QFitsTotal::QFitsTotal(QFrame *parent, QFitsViewingTools *p) : QWidget(parent) {
    myParent        = p;
    zoom            = 0;
    mousex          = 0;
    mousey          = 0;
    clearWidget     = true;
    visibleAreaRect = QRect(0, 0, 0, 0);
    totalImageScaleFactor = 0.;
    setAttribute(Qt::WA_OpaquePaintEvent);
    setMouseTracking(true);
}

bool QFitsTotal::isCompletelyVisible() {
    bool ret = false;
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL) {
        ret = (sb->getWidthVisible()  >= sb->getImage()->width()) &&
              (sb->getHeightVisible() >= sb->getImage()->height());
        ret |= (sb->getWidthVisible()  == 1) &&
                (sb->getHeightVisible() == 1);
    }
    return ret;
}

void QFitsTotal::clear() {
    clearWidget = true;
    update();
}

void QFitsTotal::paintEvent(QPaintEvent *) {
    if (clearWidget) {
        QPainter p(this);
        QColor c = QWidget::palette().color(QWidget::backgroundRole());
        p.setPen(c);
        p.setBrush(c);
        p.drawRect(rect());
        clearWidget = false;
        return;
    }

    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL && sb->getDpData()->type == typeFits) {
        // setup painter
//@        QPixmap  pm(width(), height());
        QPainter p;
//@        p.begin(&pm);
        p.begin(this);
        p.setPen(QColor(0, 0, 0));
        p.setBrush(QColor(128,128,128));

        QFont theFont = font();
        QFontMetrics fm(theFont);
// TODO QT6
//        int fw = fm.horizontalAdvance("10000x10000 (100%)");
				int fw = fm.boundingRect("10000x10000 (100%)").width();
        theFont.setPointSizeF(font().pointSizeF() * width() / fw * .9);
        p.setFont(theFont);

        p.drawRect(rect());

        // scale and copy current buffer image to totalImage
        QImage  *sbImg           = sb->getImage();
        int     imgW             = sbImg->width(),
                imgH             = sbImg->height();

        if (imgW > imgH) {
            totalImage = sbImg->scaledToWidth(width());
            totalImageScaleFactor = (double)width() / imgW;
        } else {
            totalImage = sbImg->scaledToHeight(height());
            totalImageScaleFactor = (double)height() / imgH;
        }

        // position the totalImage in the center of QFitsTotal (top-left corner)
        int left_edge = (int)floor((width() - totalImage.width()) / 2 + 0.5);
        if (left_edge < 0) {
            left_edge = 0;
        }
        int top_edge = (int)floor((height() - totalImage.height()) / 2 + 0.5);
        if (top_edge < 0) {
            top_edge = 0;
        }
        p.drawImage(left_edge, top_edge, totalImage);

        // write green text (width, height, scaleTotal)
        p.setPen(QColor(0, 200, 0));    // green
        dpString text;
        text.sprintf("%ix%i  %i%%", imgW, imgH, (int)floor(totalImageScaleFactor * 100. + 0.5));
        p.drawText(rect(), Qt::AlignHCenter | Qt::AlignTop, text.c_str());

        if (!isCompletelyVisible()) {
            // draw a rectangle to show the visible rectangle in the main window
            calcVisibleRect();

            p.setBrush(Qt::NoBrush);

            QRect correctedRect(visibleAreaRect);
            correctedRect.setLeft(correctedRect.left() - 1);
            correctedRect.setTop(correctedRect.top() - 1);

            // white inner rect
            p.setPen(QColor(255, 255, 255));
            p.drawRect(correctedRect);



            // black outer rect (for better contrast on bright images)
            p.setPen(QColor(0, 0, 0));
            p.drawRect(correctedRect.left() - 1,
                       correctedRect.top() - 1,
                       correctedRect.width() + 2,
                       correctedRect.height() + 2);
        }

        if (sb->getDpData()->fvalue->hasRefPix()) {
        // draw N-E Vectors
            p.setRenderHint(QPainter::Antialiasing);
            p.setPen(QColor(0, 200, 0));    // green
            wcsinfo info = readWCSinfo(*sb->getDpData()->fvalue);
            p.translate(width() / 2, height() / 2);
            if (sb->getFlipX()) p.scale(-1.0, 1.0);
            if (info.xinc > 0.0) p.scale(-1.0, 1.0);
            if (sb->getFlipY()) p.scale(1.0, -1.0);
            p.rotate(-info.rot);
            p.rotate(-sb->getRotation());

            p.drawLine(0, 0, 0, -height() / 4);
            p.drawLine(0, -height() / 4, -width() / 40, -height() / 4 + width() / 20);
            p.drawLine(0, -height() / 4, width() / 40, -height() / 4 + width() / 20);

            p.drawLine(0, 0, -width() / 4, 0);
            p.drawLine(-width() / 4, 0, -width() / 4 + width() / 20, -height() / 40);
            p.drawLine(-width() / 4, 0, -width() / 4 + width() / 20, height() / 40);

            QFontMetrics metrics(font());
            int w = metrics.boundingRect("E").width(),
                h = metrics.boundingRect("E").height();

            QRect N(-w, -height() / 4 - h, 2*w, h);
//            QPoint N(0, -height() / 4);
            N = p.transform().mapRect(N);
            QRect E(-width() / 4 - 2*w, -h, 2*w, 2*h);
//            QPoint E(-width() / 4, 0);
            E = p.transform().mapRect(E);

            p.resetTransform();
            p.drawText(N, Qt::AlignCenter, "N");
            p.drawText(E, Qt::AlignCenter, "E");
//            p.drawText(N, "N");
//            p.drawText(E, "E");
        }

        p.end();

//@        p.begin(this);
//@        p.drawPixmap(0, 0, pm);
//@        p.end();
    }
}

void QFitsTotal::mouseMoveEvent(QMouseEvent *m) {
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL) {
        if (int(m->buttons()) == Qt::LeftButton) {
            if (cursor().shape() == Qt::PointingHandCursor) {
//printf("''' QFitsTotal::mouseMoveEvent() ''''''''''''''''''''''\n"); fflush(stdout);
                double  x = m->x() - mousex,
                        y = m->y() - mousey;
//printf("'''    mouse  act  x: %d, y: %d\n", m->x(), m->y()); fflush(stdout);
//printf("'''           stor x: %d, y: %d\n", mousex, mousey); fflush(stdout);
//printf("''' mouse     diff x: %g, y: %g\n", x, y); fflush(stdout);

                // get absolute cursor position relative to total-image and set center
                QImage  *sbImg  = sb->getImage();
                int     imgW    = sbImg->width()*totalImageScaleFactor,
                        imgH    = sbImg->height()*totalImageScaleFactor;
//printf("'''           img w: %d, h: %d\n", imgW, imgH); fflush(stdout);
                // The totalWidget is quadratic and has gray strip at side or top/bottom.
                // In order to transform from totalWidget- to sb-coordinates
                // this stripe has to be subtracted
                double offset = 0;
                if (imgW > imgH) {
                    offset = (double)(imgW - imgH) / 2.;
                    y -= offset;
                } else if (imgW < imgH) {
                    offset = 0;
                    x -= (double)(imgH - imgW) / 2.;
                }

                int wH = height();
                if ((sb->getRotation() == 90) || (sb->getRotation()== 270)) {
                    wH = width();
                }

                // call this here in order to assert, that rectangle is well positioned
                // and no invalid center values are processed (center is updated accordingly)
                correctVisibleRect(&x, &y);
//printf("'''\n"); fflush(stdout);
//printf("1)   tc_w2i  xIn:  %g, yIn:  %g\n", x, y); fflush(stdout);
                sb->transCoordWidget2Image(&x, &y, totalImageScaleFactor, wH - 2 * offset);
//printf("     tc_w2i  xOut: %g, yOut: %g\n", x, y); fflush(stdout);

//double offset = 0;
//if (imgW > imgH) {
//    offset = (double)(imgW - imgH) / 2.;
//    y -= offset;
//} else if (imgW < imgH) {
//    offset = 0;
//    x -= (double)(imgH - imgW) / 2.;
//}
//                x = -.5;
//                y = -.5;
                sb->setXcenter(x/*-.5*/);
                sb->setYcenter(y/*-.5*/);
                sb->setImageCenter(x, y);

                update();
//printf("'''''''''''''''''''''''''''''''''''''''''''''''\n");fflush(stdout);
            }
        } else {
            if ((visibleAreaRect.width() < width()) ||
                (visibleAreaRect.height() < height()))
            {
                if (visibleAreaRect.contains(m->x(), m->y())) {
                    setCursor(Qt::PointingHandCursor);
                } else {
                    setCursor(Qt::ArrowCursor);
                }
            } else
                setCursor(Qt::ArrowCursor);
        }
    }
}

void QFitsTotal::mousePressEvent(QMouseEvent *m) {
//#ifdef HAS_VTK
    if (m->button() == Qt::LeftButton) {
        emit(setQuickAndDirty());
    }
//#endif
    mousex = m->x() - visibleAreaRect.center().x();
    mousey = m->y() - visibleAreaRect.center().y();
}

void QFitsTotal::mouseReleaseEvent(QMouseEvent *m) {
//#ifdef HAS_VTK
    if (m->button() == Qt::LeftButton) {
        emit(setSlowAndNice());
    }
//#endif
    mousex = 0;
    mousey = 0;
}

void QFitsTotal::correctVisibleRect(double *x, double *y) {
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if ((sb == NULL) || isCompletelyVisible()) {
        visibleAreaRect = QRect(0, 0, 0, 0);
    } else {
        // calculate a rectangle showing the visible rectangle in the main window

        double  rectW   = 0,
                rectH   = 0;
        if ((sb->getViewMode() == ViewImage) ||
            (sb->getViewMode() == ViewContour) ||
            (sb->getViewMode() == ViewWiregrid))
        {
            rectW = sb->getWidthVisible() * totalImageScaleFactor;
            rectH = sb->getHeightVisible() * totalImageScaleFactor;
//#ifdef HAS_VTK
        } else if (sb->getViewMode() == View3D) {
            rectW = 2 * (sb->getWidthVisible() - 1);
            rectH = 2 * (sb->getHeightVisible() - 1);
//#endif
        }

        // assert that rectangle doesn't move outside image
        if (*x < (rectW /2.)) {
            *x = rectW / 2./* - 1*/;
        }
        if (*x > (totalImage.width() - rectW / 2.)) {
            *x = totalImage.width() - rectW / 2./* - 1*/;
        }
        if (*y < (rectH /2.)) {
            *y = rectH / 2.;
        }
        if (*y > (totalImage.height() - rectH / 2.)) {
            *y = totalImage.height() - rectH / 2./* - 1*/;
        }        
    }
}

void QFitsTotal::calcVisibleRect() {
//printf("ccccc calcVisibleRect() ccccccccccccccccccccccccccc\n");fflush(stdout);
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL) {
        // calculate a rectangle showing the visible rectangle in the main window
        double  x = sb->getXcenter(),
                y = sb->getYcenter();

//printf("c before trs img x: %g, y: %g (before transCoord) scale: %g\n", x, y, totalImageScaleFactor);fflush(stdout);
         sb->transCoordImage2Widget(&x, &y, totalImageScaleFactor);
//printf("c after      img x: %g, y: %g (after transCoord)\n", x, y);fflush(stdout);
         correctVisibleRect(&x, &y);
//printf("c after      img x: %g, y: %g (after transCoord)\n", x, y);fflush(stdout);
//printf("c              w: %g, h: %g\n", sb->getWidthVisible(), sb->getHeightVisible());fflush(stdout);

        double  rectW   = 0,
                rectH   = 0;
        if ((sb->getViewMode() == ViewImage) ||
            (sb->getViewMode() == ViewContour) ||
            (sb->getViewMode() == ViewWiregrid))
        {
            rectW = sb->getWidthVisible() * totalImageScaleFactor;
            rectH = sb->getHeightVisible() * totalImageScaleFactor;
//#ifdef HAS_VTK
        } else if (sb->getViewMode() == View3D) {
            rectW = 2 * (sb->getWidthVisible() - 1);
            rectH = 2 * (sb->getHeightVisible() - 1);
//#endif
        }

        // shift coordinates according to gray space left/right or top/bottom
        // of image in QFitsTotal
        if (totalImage.width() > totalImage.height()) {
            y += (height() - (double)totalImage.height()) / 2.;
//printf("c   offset Y img x: %g, y: %g\n", x, y);fflush(stdout);
        } else if (totalImage.width() < totalImage.height()) {
            x += (width() - (double)totalImage.width()) / 2.;
//printf("c   offset X img x: %g, y: %g\n", x, y);fflush(stdout);
        }

        double  rectX = x - rectW / 2.,
                rectY = y - rectH / 2.;

        int rectXi = (int)floor(rectX + 0.5),
            rectYi = (int)floor(rectY + 0.5),
            rectWi = (int)floor(rectW + 0.5),
            rectHi = (int)floor(rectH + 0.5);
        visibleAreaRect = QRect(rectXi, rectYi, rectWi, rectHi);

//printf("c   wid x: %g, y: %g\n", x, y);fflush(stdout);
//printf("c       w: %g, h: %g\n", rectW, rectH);fflush(stdout);
    }
//printf("ccccccccccccccccccccccccccccccccccccccccccccccccccc\n");fflush(stdout);
}

//------------------------------------------------------------------------------
//         QFitsCutsPlot
//------------------------------------------------------------------------------
QFitsCutsPlot::QFitsCutsPlot(QFrame *parent, QFitsViewingTools *p) : QWidget(parent) {
    myParent    = p;
    cenx        = 0,
    ceny        = 0;
    style       = cpHorizontal;
    cutswidth   = 30;
    rotation    = 0;
    minlimit    = 0.;
    maxlimit    = 1.;
    flipX       = false;
    flipY       = false;
    takeLimits  = false;
    clearWidget = true;
}

void QFitsCutsPlot::paintEvent(QPaintEvent*) {
    if (clearWidget) {
        QPainter p(this);
        QColor c = QWidget::palette().color(QWidget::backgroundRole());
        p.setPen(c);
        p.setBrush(c);
        p.drawRect(rect());
        clearWidget = false;
        return;
    }
//    QReadLocker locker(&buffersLock);
    if (!(buffersLock.tryLockForRead())) {
        return;
    }
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if ((sb != NULL) && (sb->getDpData()->type == typeFits)) {
        Fits *actualFits = NULL;
        if (sb->Naxis(0) == 3) {
            actualFits = sb->getCubeDisplay2D();
        } else {
            actualFits = sb->getDpData()->fvalue;
        }

        if (actualFits == NULL) {
            buffersLock.unlock();
            return;
        }

        QPainter p;
        p.begin(this);
//        p.setPen(QColor(0, 0, 0));
        p.setPen(QWidget::palette().color(QWidget::foregroundRole()));

        int  actualStyle = style;
        bool actualFlipX = flipX,
             actualFlipY = flipY;
        if (actualStyle < cpRadialAverage) {
            switch (rotation) {
                case 90:
                    if (actualStyle == cpHorizontal) {
                        actualStyle = cpVertical;
                        actualFlipY = actualFlipX;
                    } else if (actualStyle == cpVertical) {
                        actualStyle = cpHorizontal;
                        actualFlipX = !actualFlipY;
                    }
                    break;
                case 180:
                    actualFlipX = !actualFlipX;
                    actualFlipY = !actualFlipY;
                    break;
                case 270:
                    if (actualStyle == cpHorizontal) {
                        actualStyle = cpVertical;
                        actualFlipY = !actualFlipX;
                    } else if (actualStyle == cpVertical) {
                        actualStyle = cpHorizontal;
                        actualFlipX = actualFlipY;
                    }
                    break;
                default:
                    break;
            }
        }

        QPolygon    array;
        Fits        line,
                    xxx;
        int         cutStart    = 0,
                    cutInc      = 0;
        switch (actualStyle) {
            case cpHorizontal:
                {
                line.create(cutswidth, 1);
                if (actualFlipX) {
                    cutStart = cutswidth - 1;
                    cutInc = -1;
                } else {
                    cutStart = 0;
                    cutInc = 1;
                }
                for (int i = cenx - cutswidth / 2, j = cutStart;i <= cenx + cutswidth / 2; i++, j += cutInc) {
                    if ((i > 0) && (i <= actualFits->Naxis(1))) {
                        if ((j >= 0) && (j < cutswidth)) {
                            line.r4data[j] = actualFits->ValueAt(actualFits->F_I(i, ceny));
                        }
                    }
                }
                line.cblank();
                if (takeLimits) {
                    line.clip(minlimit, maxlimit);
                }
                line.norm();
                line *= (double)height() - 5;
                xxx.recreate(line.Naxis(1), 1);
                for (int i = 0; i < xxx.Naxis(1); i++) {
                    xxx.r4data[i] = (float)i * (float)width() / (float)xxx.Naxis(1);
                }
                array.resize(line.Naxis(1));
                for (int i = cenx - width() / 2, j = 0; j < xxx.Naxis(1); i++, j++) {
                    if ((cenx==-1) && (ceny == -1)) {
                        array.setPoint(j, (int)xxx.ValueAt(j), height()-2);
                    } else {
                        array.setPoint(j,
                                       (int)xxx.ValueAt(j),
                                       height() - (int)line.ValueAt(j) - 2);
                    }
                }
                p.drawPolyline(array);
                }
                break;
            case cpVertical:
                {
                line.create(cutswidth, 1);
                if (actualFlipY) {
                    cutStart = cutswidth - 1;
                    cutInc = -1;
                } else {
                    cutStart = 0;
                    cutInc = 1;
                }
                for (int i = ceny - cutswidth / 2, j = cutStart; i <= ceny + cutswidth / 2; i++, j += cutInc) {
                    if ((i > 0) && (i <= actualFits->Naxis(2))) {
                        if ((j >= 0) && (j < cutswidth)) {
                            line.r4data[j] = actualFits->ValueAt(actualFits->F_I(cenx, i));
                        }
                    }
                }
                line.cblank();
                if (takeLimits) {
                    line.clip(minlimit, maxlimit);
                }
                line.norm();
                line *= (double)height() - 5;
                xxx.create(line.Naxis(1), 1);
                for (int i = 0; i < xxx.Naxis(1); i++) {
                    xxx.r4data[i] = (float)i * (float)width() / (float)xxx.Naxis(1);
                }
                array.resize(line.Naxis(1));
                for (int i = cenx - width() / 2, j = 0; j < xxx.Naxis(1); i++, j++)
                    if ((cenx==-1) && (ceny == -1)) {
                        array.setPoint(j, (int)xxx.ValueAt(j), height()-2);
                    } else {
                        array.setPoint(j,
                                       (int)xxx.ValueAt(j),
                                       height() - (int)line.ValueAt(j) - 2);
                    }
                p.drawPolyline(array);
                }
                break;
            case cpRadialAverage:
                {
                actualFits->radial_avg(line, cenx, ceny, cutswidth, 3);
                line.cblank();
                line.extractRange(xxx, 1, 1, -1, -1, -1, -1);
                xxx.norm();
                xxx *= (double)width();

                Fits tmpFits;
                line.extractRange(tmpFits, 2, 2, -1, -1, -1, -1);
                if (takeLimits) {
                    tmpFits.clip(minlimit, maxlimit);
                }
                tmpFits.norm();
                tmpFits *= (double)height() - 5;
                array.resize(xxx.Naxis(1));
                for (int i = cenx - width() / 2, j = 0; j < xxx.Naxis(1); i++, j++)
                    if ((cenx==-1) && (ceny == -1)) {
                        array.setPoint(j, (int)xxx.ValueAt(j), height()-2);
                    } else {
                        array.setPoint(j, (int)xxx.ValueAt(j), height() - (int)tmpFits.ValueAt(j) - 2);
                    }
                p.drawPoints(array);
                }
                break;
            default:
                break;
        }

        p.end();
    }
    buffersLock.unlock();
}

void QFitsCutsPlot::setCenter(int x, int y) {
    cenx = x;
    ceny = y;
    update();
}

void QFitsCutsPlot::setCutsStyle(int ns) {
    style = (CutsPlotStyle)ns;
    update();
}

//void QFitsCutsPlot::setOrientation(int rot, bool x, bool y) {
//    rotation = rot;
//    flipX = x;
//    flipY = y;
//    update();
//}

void QFitsCutsPlot::setCutsWidth(int newwidth) {
    cutswidth = newwidth;
    update();
}

void QFitsCutsPlot::setLimits(double min, double max) {
    minlimit = min;
    maxlimit = max;
}

void QFitsCutsPlot::clear() {
    clearWidget = true;
    update();
}

void QFitsCutsPlot::setTakeLimits(bool take) {
    takeLimits = take;
}
