#include "imagewidget.h"
#include <QCoreApplication>
#include <QMessageBox>
#include <QTimer>
#include <QElapsedTimer>
#include <QOpenGLFramebufferObject>
#include <QOpenGLExtraFunctions>
#include <QOpenGLDebugLogger>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QPainter>
#include <QStandardPaths>
#include <QFloat16>
#include <QStyle>
#include "imageringlist.h"

int FILTERING = 1;
bool OpenGLES = false;
const int LUT_SIZE = 32;
bool BESTFIT = false;

struct RawImageType
{
    QOpenGLTexture::PixelFormat pixelFormat;
    QOpenGLTexture::TextureFormat textureFormat;
    QOpenGLTexture::PixelType dataType;
};

RawImageType getRawImageType(const RawImage *img)
{
    RawImageType type;
    switch(img->type())
    {
    case RawImage::UINT8:
        if(img->channels() >= 3)
            type.textureFormat = QOpenGLTexture::RGBA8_UNorm;
        else
            type.textureFormat = QOpenGLTexture::R8_UNorm;
        type.dataType = QOpenGLTexture::UInt8;
        break;
    case RawImage::UINT16:
        if(img->channels() >= 3)
            type.textureFormat = QOpenGLTexture::RGBA16_UNorm;
        else
            type.textureFormat = QOpenGLTexture::R16_UNorm;
        type.dataType = QOpenGLTexture::UInt16;
        break;
    case RawImage::FLOAT32:
        if(img->channels() >= 3)
            type.textureFormat = QOpenGLTexture::RGBA32F;
        else
            type.textureFormat = QOpenGLTexture::R32F;
        type.dataType = QOpenGLTexture::Float32;
        break;
    case RawImage::FLOAT16:
        if(img->channels() >= 3)
            type.textureFormat = QOpenGLTexture::RGBA16F;
        else
            type.textureFormat = QOpenGLTexture::R16F;
        type.dataType = QOpenGLTexture::Float16;
        break;
    default:
        qWarning() << "Invalid format" << img->type();
        break;
    }

    if(img->channels() >= 3)
        type.pixelFormat = QOpenGLTexture::RGBA;
    else
        type.pixelFormat = QOpenGLTexture::Red;

    return type;
}

ImageWidgetGL::ImageWidgetGL(Database *database, QWidget *parent) : QOpenGLWidget(parent)
    , m_database(database)
{
    setFocusPolicy(Qt::ClickFocus);
    m_updateTimer = new QTimer(this);
    m_updateTimer->setInterval(500);
    m_updateTimer->setSingleShot(true);
    connect(m_updateTimer, &QTimer::timeout, this, static_cast<void (QOpenGLWidget::*)()>(&ImageWidgetGL::update));
    setAcceptDrops(true);
    QTimer::singleShot(1000, [this](){
        if(!isValid())
        {
            QMessageBox::critical(this, tr("OpenGL error"), tr("Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed."));
            QCoreApplication::exit(-1);
        }
    });

    setMouseTracking(true);
    m_bestFit = BESTFIT;
}

ImageWidgetGL::~ImageWidgetGL()
{
    makeCurrent();
}

void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
{
    m_currentImg = index;

    if(!image || !image->valid())
    {
        m_imgWidth = 0;
        m_imgHeight = 0;
        m_error = tr("Failed to load image");
        m_rawImage.reset();
        update();
        return;
    }

    m_error.clear();
    makeCurrent();
    m_rawImage = image;
    m_imgWidth = image->width();
    m_imgHeight = image->height();

    bool tooBig = false;
    if((int)image->width() > m_maxTextureSize || (int)image->height() > m_maxTextureSize)
    {
        tooBig = true;
        m_swPaint = true;
    }

    if(!m_image)return;

    RawImageType rawImageType = getRawImageType(image.get());
    m_srgb = image->getLUT().size() > 0;
    m_bwImg = image->channels() == 1;

    f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    if(m_srgb)
    {
        m_lut->setData(0, 0, 0, LUT_SIZE, LUT_SIZE, LUT_SIZE, 0, QOpenGLTexture::RGBA, QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image->getLUT().data());
    }

    if(!tooBig)
    {
        while(f->glGetError() != GL_NO_ERROR);
        QElapsedTimer timer;
        timer.start();
        m_image->destroy();
        m_image->setAutoMipMapGenerationEnabled(false);
        m_image->setFormat(rawImageType.textureFormat);
        m_image->setSize(image->width(), image->height());
        m_image->setMipLevels([&](){ int c = 0; int s = std::min(m_imgWidth, m_imgHeight); while(s>>=1)c++; return c; }());
        m_image->allocateStorage();
        m_image->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);
        m_image->setWrapMode(QOpenGLTexture::ClampToEdge);
        m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data());
        m_image->bind();
        f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
        f->glGenerateMipmap(GL_TEXTURE_2D);
        qDebug() << "ImageWidgetGL::setImage" << timer.elapsed() << "ms";
        m_swPaint = f->glGetError() != GL_NO_ERROR;
    }

    m_unit_scale[0] = 1.0f;
    m_unit_scale[1] = 0.0f;
    if(image->type() == RawImage::FLOAT32)
    {
        auto unitScaling = image->unitScale();
        m_unit_scale[0] = unitScaling.first;
        m_unit_scale[1] = unitScaling.second;
    }

    if(m_debayerTex)
    {
        f->glDeleteTextures(1, &m_debayerTex);
        m_debayerTex = 0;
    }

    if(m_bestFit)bestFit();
    else setOffset(m_dx, m_dy);
}

void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
{
    m_wcs = wcs;
    m_grid.clear();

    if(m_drawGrid && m_wcs)
        m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
}

void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
{
    m_bestFit = false;
    if(zoom != 0)
        m_scaleStop = std::clamp(m_scaleStop + (zoom > 0 ? 1 : -1), -10, 10);
    else
        m_scaleStop = 0;

    QPointF focus(m_width * 0.5f, m_height * 0.5f);
    if(!mousePos.isNull())
        focus = mousePos;

    if(width() > m_imgWidth * m_scale)
        m_dx = -width() * 0.5f + m_imgWidth * m_scale * 0.5f;
    if(height() > m_imgHeight * m_scale)
        m_dy = -height() * 0.5f + m_imgHeight * m_scale * 0.5f;

    float newScale = std::sqrt(std::pow(2.0f, (float)m_scaleStop));
    float r = newScale / m_scale;
    m_scale = newScale;

    setOffset(m_dx * r + focus.x() * (r - 1), m_dy * r + focus.y() * (r - 1));
}

void ImageWidgetGL::bestFit()
{
    m_bestFit = true;
    m_scale = std::min((float)m_width/m_imgWidth, (float)m_height/m_imgHeight);
    setOffset(0, 0);
}

void ImageWidgetGL::allocateThumbnails(const QStringList &paths)
{
    makeCurrent();
    int count = paths.size();
    m_thumbnailCount = count;
    m_thumnails.clear();
    QStringList marked = m_database->getMarkedFiles();
    for(auto &path : paths)
    {
        QString name = QFileInfo(path).fileName();
        m_thumnails.push_back({name, path, QSize(0, 0), marked.contains(path), false});
    }

    m_thumbnailTexture->destroy();
    m_thumbnailTexture->setFormat(QOpenGLTexture::RGBA16F);
    m_thumbnailTexture->setSize(THUMB_SIZE, THUMB_SIZE);
    m_thumbnailTexture->setLayers(std::min((int)paths.size(), m_maxArrayLayers));
    m_thumbnailTexture->setAutoMipMapGenerationEnabled(false);
    m_thumbnailTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
    m_thumbnailTexture->setMipLevelRange(0, 0);
    m_thumbnailTexture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
    m_thumbnailTexture->allocateStorage();
}

QVector2D ImageWidgetGL::getImagePixelCoord(const QVector2D &pos)
{
    float dx = m_dx;
    float dy = m_dy;
    if(m_width > m_imgWidth * m_scale)
        dx = -width()*0.5f + m_imgWidth*m_scale * 0.5f;
    if(m_height > m_imgHeight * m_scale)
        dy = -height()*0.5f + m_imgHeight*m_scale * 0.5f;

    QVector2D offset(dx, dy);
    return (pos + offset) / m_scale;
}

void ImageWidgetGL::setBayerMask(int mask)
{
    m_firstRed[0] = mask & 0x1;
    m_firstRed[1] = (mask & 0x2) >> 1;
    if(m_debayerTex)
    {
        f->glDeleteTextures(1, &m_debayerTex);
        m_debayerTex = 0;
    }
    update();
}

void ImageWidgetGL::setColormap(int colormap)
{
    m_colormapIdx = colormap;
    update();
}

void ImageWidgetGL::setMTFParams(const MTFParam &params)
{
    m_mtfParams = params;
    update();
}

void ImageWidgetGL::setOffset(float dx, float dy)
{
    m_dx = std::clamp(dx, 0.0f, std::max(0.0f, m_imgWidth * m_scale - m_width));
    if(m_showThumbnails)
        m_dy = std::clamp(dy, 0.0f, std::max(0.0f, (float)((m_thumbnailCount / (m_width / THUMB_SIZE_BORDER) + 2) * THUMB_SIZE_BORDER_Y - m_height)));
    else
        m_dy = std::clamp(dy, 0.0f, std::max(0.0f, m_imgHeight * m_scale - m_height));
    updateScrollBars();
    update();
}

void ImageWidgetGL::superPixel(bool enable)
{
    m_superpixel = enable;
    update();
}

void ImageWidgetGL::invert(bool enable)
{
    m_invert = enable;
    update();
}

void ImageWidgetGL::falseColor(bool enable)
{
    m_falseColor = enable;
    update();
}

QImage ImageWidgetGL::renderToImage()
{
    if(m_imgWidth < 0)return QImage();
    makeCurrent();
    QOpenGLFramebufferObject fbo(m_imgWidth, m_imgHeight);
    fbo.bind();

    f->glViewport(0, 0, m_imgWidth, m_imgHeight);
    m_vao->bind();

    m_program->bind();
    m_program->setUniformValue("viewport", (float)m_imgWidth, (float)m_imgHeight);
    m_program->setUniformValue("offset", 0.0f, 0.0f);
    m_program->setUniformValue("zoom", 1.0f);

    if(m_superpixel && m_debayerTex)
    {
        f->glActiveTexture(GL_TEXTURE0);
        f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
    }
    else
        m_image->bind(0);
    f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    m_vao->release();

    fbo.bindDefault();
    return fbo.toImage(true);
}

void ImageWidgetGL::thumbnailLoaded(const Image *image)
{
    if(image->number() >= m_maxArrayLayers)
        return;

    makeCurrent();
    const RawImage *raw = image->thumbnail();
    if(!raw || !raw->valid())return;
    f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGBA, QOpenGLTexture::Float16, raw->data());
    f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    float a = raw->thumbAspect();
    int sizes[3] = { std::max(1, a > 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE * a)), std::max(1, a < 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE / a)), image->number() };
    m_sizesDirty = true;
    m_thumnails[image->number()].size = QSize(sizes[0], sizes[1]);
    if(!m_updateTimer->isActive())m_updateTimer->start();
}

void ImageWidgetGL::showThumbnail(bool enable)
{
    m_showThumbnails = enable;
    setOffset(m_dx, m_dy);
}

void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scale, const MTFParam &mtfParams, QWidget *widget)
{
    QPainter painter(widget);
    int width = widget->width();
    int height = widget->height();
    QImage img(width, height, QImage::Format_RGB32);
    img.fill(Qt::darkGray);
    int64_t ox = dx;
    int64_t oy = dy;

    auto mtf = [&mtfParams](int i, float x)
    {
        x = (x - mtfParams.blackPoint[i]) / (mtfParams.whitePoint[i] - mtfParams.blackPoint[i]);
        x = std::clamp(x, 0.0f, 1.0f);
        return ((mtfParams.midPoint[i] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[i] - 1.0f) * x - mtfParams.midPoint[i]);
    };

    int imgWidth = rawImage->width();
    int imgHeight = rawImage->height();

    auto convert = [&](auto *src)
    {
        float s = 1.0f;
        if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)
            s = (float)std::numeric_limits<std::remove_reference_t<decltype(*src)>>::max();

        float iscale = 1.0f / scale;
        float r[4];
        float g[4];
        float b[4];
        for(int64_t y = std::max((int64_t)0, -oy); y < height; y++)
        {
            uint32_t *pixels = (uint32_t*)(img.scanLine(y));
            float iptr;
            float fy = std::modf((y + oy) * iscale, &iptr);
            int64_t py = iptr;
            int64_t w = py * rawImage->widthSamples();
            int64_t w2 = w;
            if(py+1 < imgHeight)w2 += rawImage->widthSamples();
            if(py >= imgHeight)break;

            for(int64_t x = std::max((int64_t)0, -ox); x < width; x++)
            {
                float fx = std::modf((x + ox) * iscale, &iptr);
                int px = iptr;
                int px2 = px + 1 < imgWidth ? px + 1 : px;
                if(px >= imgWidth)break;


                if(rawImage->channels() > 1)
                {
                    r[0] = src[w + px * 4 + 0];
                    g[0] = src[w + px * 4 + 1];
                    b[0] = src[w + px * 4 + 2];
                    if(FILTERING)
                    {
                        r[1] = src[w + px2 * 4 + 0];
                        g[1] = src[w + px2 * 4 + 1];
                        b[1] = src[w + px2 * 4 + 2];
                        r[2] = src[w2 + px * 4 + 0];
                        g[2] = src[w2 + px * 4 + 1];
                        b[2] = src[w2 + px * 4 + 2];
                        r[3] = src[w2 + px2 * 4 + 0];
                        g[3] = src[w2 + px2 * 4 + 1];
                        b[3] = src[w2 + px2 * 4 + 2];
                    }
                }
                else
                {
                    r[0] = src[w + px];
                    if(FILTERING)
                    {
                        r[2] = src[w2 + px];
                        r[1] = src[w + px2];
                        r[3] = src[w2 + px2];
                    }
                }

                uint32_t rgb = 0xff000000;
                if(FILTERING)
                {
                    if(rawImage->channels() > 1)
                    {
                        rgb |= (uint8_t)(mtf(0, ((r[3] * fx + r[2] * (1.0f - fx)) * fy + (r[1] * fx + r[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f) << 16;
                        rgb |= (uint8_t)(mtf(1, ((g[3] * fx + g[2] * (1.0f - fx)) * fy + (g[1] * fx + g[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f) << 8;
                        rgb |= (uint8_t)(mtf(1, ((b[3] * fx + b[2] * (1.0f - fx)) * fy + (b[1] * fx + b[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f);
                    }
                    else
                    {
                        uint32_t v = (uint8_t)(mtf(0, ((r[3] * fx + r[2] * (1.0f - fx)) * fy + (r[1] * fx + r[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f);
                        rgb = 0xff000000 | (v << 16) | (v << 8) | v;
                    }
                }
                else
                {
                    if(rawImage->channels() > 1)
                    {
                        rgb |= (uint8_t)(mtf(0, r[0] / s) * 255.0f) << 16;
                        rgb |= (uint8_t)(mtf(1, g[0] / s) * 255.0f) << 8;
                        rgb |= (uint8_t)(mtf(1, b[0] / s) * 255.0f);
                    }
                    else
                    {
                        uint32_t v = (uint8_t)(mtf(0, r[0] / s) * 255.0f);
                        rgb = 0xff000000 | (v << 16) | (v << 8) | v;
                    }
                }
                pixels[x] = rgb;
            }
        }
    };

    if(rawImage)
    {
        switch(rawImage->type())
        {
        case RawImage::UINT8:
            convert(static_cast<uint8_t*>(rawImage->data()));
            break;
        case RawImage::UINT16:
            convert(static_cast<uint16_t*>(rawImage->data()));
            break;
        case RawImage::UINT32:
            convert(static_cast<uint32_t*>(rawImage->data()));
            break;
        case RawImage::FLOAT16:
            convert(static_cast<qfloat16*>(rawImage->data()));
            break;
        case RawImage::FLOAT32:
            convert(static_cast<float*>(rawImage->data()));
            break;
        case RawImage::FLOAT64:
            convert(static_cast<double*>(rawImage->data()));
            break;
        }
    }

    painter.drawImage(0, 0, img);
}

void ImageWidgetGL::drawGrid(bool enable)
{
    if(m_grid.empty && m_wcs)
        m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);

    if(enable != m_drawGrid)
    {
        m_drawGrid = enable;
        update();
    }
}

void ImageWidgetGL::paintGL()
{
    float dx = m_dx;
    float dy = m_dy;
    if(m_width > m_imgWidth * m_scale)
        dx = -width() * 0.5f + m_imgWidth * m_scale * 0.5f;
    if(m_height > m_imgHeight * m_scale)
        dy = -height() * 0.5f + m_imgHeight * m_scale * 0.5f;
    QBrush highlight = style()->standardPalette().highlight();

    f->glClear(GL_COLOR_BUFFER_BIT);
    f->glBlendFunc(GL_ONE, GL_ZERO);
    if(m_showThumbnails)
    {
        m_vaoThumb->bind();
        m_thumbnailTexture->bind(1);
        if(m_sizesDirty)
        {
            m_bufferSizes->bind();
            int i = 0;
            std::vector<int> sizes(m_thumbnailCount*3);
            for(auto &s : m_thumnails)
            {
                sizes[3*i]   = s.size.width();
                sizes[3*i+1] = s.size.height();
                sizes[3*i+2] = i;
                i++;
            }
            m_bufferSizes->allocate(&sizes[0], sizes.size()*sizeof(int));
            m_sizesDirty = false;
        }
        m_thumbnailProgram->bind();
        f->glUniform3i(m_thumbnailProgram->uniformLocation("viewport_row"), width(), height(), width()/THUMB_SIZE_BORDER);
        f->glUniform3i(m_thumbnailProgram->uniformLocation("thumb_size"), THUMB_SIZE_BORDER/2, THUMB_SIZE_BORDER, THUMB_SIZE_BORDER_Y);
        m_thumbnailProgram->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3);
        m_thumbnailProgram->setUniformValue("invert", m_invert);
        m_thumbnailProgram->setUniformValue("offset", 0, m_dy);

        QMatrix4x4 mvp;
        mvp.ortho(rect());
        m_thumbnailProgram->setUniformValue("mvp", mvp);

        const int w = width()/THUMB_SIZE_BORDER;
        const int off = (THUMB_SIZE_BORDER - THUMB_SIZE) / 2;
        int start = std::max((int)(m_dy / THUMB_SIZE_BORDER_Y * w - w), 0);
        int end = std::min((int)(m_dy + m_height) / THUMB_SIZE_BORDER_Y * w + w, m_thumbnailCount);

        fx->glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, m_thumbnailCount);
        m_vaoThumb->release();

        QPainter painter(this);
        for(int i=start; i < end; i++)
        {
            float x = (i % w) * THUMB_SIZE_BORDER;
            float y = i / w * THUMB_SIZE_BORDER_Y + THUMB_SIZE - m_dy + off;
            QRectF rect(x, y, THUMB_SIZE_BORDER, 32);
            painter.drawText(rect, Qt::AlignCenter | Qt::TextWrapAnywhere, QString(m_thumnails[i].name));
            if(m_thumnails[i].selected)
            {
                painter.save();
                QRectF thumbRect;
                painter.setPen(Qt::red);
                thumbRect.setSize(m_thumnails[i].size);
                thumbRect.moveCenter(QPointF(x + THUMB_SIZE_BORDER / 2, y - THUMB_SIZE / 2));
                painter.drawRect(thumbRect);
                painter.restore();
            }
            if(m_currentImg == i)
            {
                painter.save();
                painter.setPen(QPen(highlight, 2.0));
                painter.drawRect((i % w) * THUMB_SIZE_BORDER + off, i / w * THUMB_SIZE_BORDER_Y - m_dy + off, THUMB_SIZE, THUMB_SIZE);
                painter.restore();
            }
        }
    }
    else if(!m_error.isEmpty())
    {
        QPainter painter(this);
        painter.setPen(Qt::red);
        painter.setFont(QFont("Sans", 24, QFont::Bold));
        painter.drawText(0, 0, width(), height(), Qt::AlignCenter | Qt::AlignHCenter, m_error);
    }
    else
    {
        if(m_swPaint)
        {
            swPaint(m_rawImage, dx, dy, m_scale, m_mtfParams, this);
        }
        else
        {
            m_vao->bind();
            debayer();

            if(m_superpixel && m_debayerTex)
            {
                f->glActiveTexture(GL_TEXTURE0);
                f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
            }
            else
                m_image->bind(0);

            m_program->bind();
            m_program->setUniformValue("viewport", (float)width(), (float)height());
            m_program->setUniformValue("offset", std::floor(dx), std::floor(dy));
            m_program->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3);
            m_program->setUniformValue("unit_scale", m_unit_scale[0], m_unit_scale[1]);
            m_program->setUniformValue("zoom", 1.0f/m_scale);
            m_program->setUniformValue("bw", m_bwImg && !m_superpixel);
            m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
            m_program->setUniformValue("invert", m_invert);
            m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
            m_program->setUniformValue("lut_table", 2);
            m_program->setUniformValue("srgb", m_srgb);
            m_program->setUniformValue("colormapIdx", m_colormapIdx);
            f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
            m_vao->release();

            if(m_drawGrid && !m_grid.empty)
            {
                QPainter painter(this);
                painter.setRenderHint(QPainter::Antialiasing);
                painter.setRenderHint(QPainter::TextAntialiasing);

                painter.setPen(QPen(Qt::yellow, 1.0 / m_scale));
                QTransform tran;
                tran.translate(-std::floor(dx), -std::floor(dy));
                tran.scale(m_scale, m_scale);
                painter.setTransform(tran);
                painter.setClipRect(0, 0, m_imgWidth, m_imgHeight);
                painter.drawPath(m_grid.grid);
                painter.setPen(Qt::yellow);
                QFont font({"Arial", "serif-sans"});
                font.setPointSizeF(12 / m_scale);
                painter.setFont(font);
                for(auto &text : m_grid.text)
                    painter.drawText(QRectF(text.first, QSizeF(4000, 4000)), text.second);

                painter.setPen(QPen(Qt::green, 1.0 / m_scale));
                QFontMetricsF fontMetric = QFontMetricsF(font);
                for(auto &object : m_grid.objects)
                {
                    QRectF rect = fontMetric.boundingRect(object.name);
                    rect.moveCenter(object.pixel);
                    painter.setTransform(tran);
                    painter.drawText(rect, Qt::TextDontClip, object.name);

                    painter.translate(object.pixel);
                    painter.rotate(object.pos_ang);
                    painter.drawEllipse(QPointF(0, 0), object.maj_ax, object.maj_ax);
                }
            }
        }
    }
}

void ImageWidgetGL::resizeGL(int w, int h)
{
    m_width = w;
    m_height = h;
    f->glViewport(0, 0, w, h);
    if(m_bestFit)bestFit();
    updateScrollBars();
}

void ImageWidgetGL::initializeGL()
{
    f = context()->functions();
    fx = context()->extraFunctions();
    f->glClearColor(0.5f, 0.5f, 0.5f, 1.0f);

    if(fx == nullptr)
        QMessageBox::critical(this, tr("OpenGL error"), tr("Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed."));

    OpenGLES = context()->isOpenGLES();

    auto loadShader = [](const QString &file)
    {
        QFile fr(file);
        fr.open(QIODevice::ReadOnly);
        QByteArray src;
        if(OpenGLES)
        {
            src = "#version 300 es\n"
                  "precision highp float;\n"
                  "precision highp sampler2D;\n"
                  "precision highp sampler2DArray;\n"
                  "precision highp sampler3D;\n"
                  "#line 1\n";
        }
        else
        {
            src = "#version 330\n#line 1\n";
        }
        src.append(fr.readAll());
        return src;
    };

    m_vao = std::unique_ptr<QOpenGLVertexArrayObject>(new QOpenGLVertexArrayObject);
    m_vaoThumb = std::unique_ptr<QOpenGLVertexArrayObject>(new QOpenGLVertexArrayObject);
    m_vao->create();
    m_vaoThumb->create();
    m_vao->bind();

    QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this);
    logger->initialize();
    logger->startLogging();
    connect(logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage &message)
            {
                qDebug() << "OpenGL debug" << message;
            });

    qDebug() << "OpenGL Vendor:"   << (char*)f->glGetString(GL_VENDOR);
    qDebug() << "OpenGL Renderer:" << (char*)f->glGetString(GL_RENDERER);
    qDebug() << "OpenGL Version:"  << (char*)f->glGetString(GL_VERSION);
    f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize);
    f->glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &m_maxArrayLayers);
    qDebug() << "Max texture size:" << m_maxTextureSize << "max layers:" << m_maxArrayLayers;

    //MANUAL_MIPMAP_GEN = QString((const char*)f->glGetString(GL_VENDOR)).startsWith("ATI Technologies Inc", Qt::CaseInsensitive);

    qDebug() << "OpenGL context format" << context()->format();

    // each vertex is  x,y 2D position and s,t texture coordinates
    float vertexs[] = {-1.0f, -1.0f, 0.0f, 1.0f,
        1.0f, -1.0f, 1.0f, 1.0f,
        -1.0f,  1.0f, 0.0f, 0.0f,
        1.0f,  1.0f, 1.0f, 0.0f,};
    m_buffer = std::unique_ptr<QOpenGLBuffer>(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
    m_buffer->setUsagePattern(QOpenGLBuffer::StaticDraw);
    m_buffer->create();
    m_buffer->bind();
    m_buffer->allocate(vertexs, sizeof(vertexs));
    // f->glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(float)*4, 0);

    m_program = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShader(":/image.vert"));
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShader(":/image.frag"));

    if(!m_program->link())
    {
        qDebug() << "Link failed" << m_program->log();
    }

    m_program->bind();
    m_program->enableAttributeArray("qt_Vertex");
    m_program->setAttributeBuffer("qt_Vertex", GL_FLOAT, 0, 2, sizeof(float)*4);
    m_program->enableAttributeArray("qt_MultiTexCoord0");
    m_program->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4);
    m_program->setUniformValue("qt_Texture0", (GLuint)0);
    m_program->setUniformValue("lut_table", (GLuint)2);
    m_program->setUniformValue("colormap", (GLuint)3);
    m_program->setUniformValue("scale", 1.0f, 0.0f);

    m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
    m_debayerProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShader(":/debayer.vert"));
    m_debayerProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShader(":/debayer.frag"));

    m_debayerProgram->bind();
    m_debayerProgram->enableAttributeArray("qt_Vertex");
    m_debayerProgram->setAttributeBuffer("qt_Vertex", GL_FLOAT, 0, 2, sizeof(float)*4);
    m_debayerProgram->enableAttributeArray("qt_MultiTexCoord0");
    m_debayerProgram->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4);
    m_debayerProgram->setUniformValue("qt_Texture0", (GLuint)0);
    if(!m_debayerProgram->link())
    {
        qDebug() << "Link failed" << m_debayerProgram->log();
    }

    m_vaoThumb->bind();

    m_thumbnailProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
    m_thumbnailProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShader(":/thumb.vert"));
    m_thumbnailProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShader(":/thumb.frag"));

    m_thumbnailProgram->bind();
    m_thumbnailProgram->enableAttributeArray("qt_Vertex");
    m_thumbnailProgram->setAttributeBuffer("qt_Vertex", GL_FLOAT, 0, 2, sizeof(float)*4);
    m_thumbnailProgram->enableAttributeArray("qt_MultiTexCoord0");
    m_thumbnailProgram->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4);
    if(!m_thumbnailProgram->link())
    {
        qDebug() << "Link failed" << m_thumbnailProgram->log();
    }
    m_thumbnailProgram->setUniformValue("qt_Texture0", (GLuint)1);

    m_bufferSizes = std::unique_ptr<QOpenGLBuffer>(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
    m_bufferSizes->setUsagePattern(QOpenGLBuffer::StaticDraw);
    m_bufferSizes->create();
    m_bufferSizes->bind();
    m_bufferSizes->allocate(12);

    m_thumbnailProgram->enableAttributeArray("imageSize_num");
    fx->glVertexAttribIPointer(m_thumbnailProgram->attributeLocation("imageSize_num"), 3, GL_INT, 0, nullptr);
    fx->glVertexAttribDivisor(m_thumbnailProgram->attributeLocation("imageSize_num"), 1);
    m_vaoThumb->release();

    m_image = std::unique_ptr<QOpenGLTexture>(new QOpenGLTexture(QOpenGLTexture::Target2D));
    m_image->setFormat(QOpenGLTexture::RGB8U);
    m_image->allocateStorage();
    m_image->bind(0);
    m_image->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
    m_image->setMagnificationFilter(QOpenGLTexture::Linear);

    m_thumbnailTexture = std::unique_ptr<QOpenGLTexture>(new QOpenGLTexture(QOpenGLTexture::Target2DArray));

    m_lut = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target3D);
    m_lut->setSize(LUT_SIZE, LUT_SIZE, LUT_SIZE);
    m_lut->setMipLevelRange(0, 0);
    m_lut->setFormat(QOpenGLTexture::TextureFormat::RGBA16F);
    m_lut->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
    m_lut->allocateStorage();
    m_lut->bind(2);

    QImage colormap = loadColormap();
    m_colormap = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2DArray);
    m_colormap->setSize(colormap.width());
    m_colormap->setLayers(colormap.height());
    m_colormap->setFormat(QOpenGLTexture::RGBA8_UNorm);
    m_colormap->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
    m_colormap->setWrapMode(QOpenGLTexture::ClampToEdge);
    m_colormap->allocateStorage();
    for(int i=0; i<colormap.height(); i++)
        m_colormap->setData(0, i, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, colormap.scanLine(i));

    m_colormap->bind(3);

    if(m_rawImage)
        setImage(m_rawImage, m_currentImg);
}

void ImageWidgetGL::dragEnterEvent(QDragEnterEvent *event)
{
    if(event->mimeData()->hasUrls() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction))
        event->acceptProposedAction();
}

void ImageWidgetGL::dropEvent(QDropEvent *event)
{
    if(event->mimeData()->hasUrls() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction))
    {
        for(const QUrl &url : event->mimeData()->urls())
        {
            if(url.isLocalFile())
            {
                emit fileDropped(url.toLocalFile());
                event->accept();
                return;
            }
        }
    }
    event->ignore();
}

void ImageWidgetGL::mousePressEvent(QMouseEvent *event)
{
    if(m_showThumbnails && event->button() == Qt::LeftButton && event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))
        m_selecting = true;

    if(m_selecting)
    {
        thumbSelect(event);
    }
    else
    {
        if(event->button() == Qt::LeftButton)
            m_lastPos = event->position();
    }
}

void ImageWidgetGL::mouseMoveEvent(QMouseEvent *event)
{
    if(m_selecting)
    {
        thumbSelect(event);
    }
    else if(!m_lastPos.isNull())
    {
        QPointF off = event->position() - m_lastPos;
        m_lastPos = event->position();
        setOffset(m_dx - off.x(), m_dy - off.y());
        return;
    }

    if(!m_showThumbnails && m_rawImage)
    {
        QVector2D pix = getImagePixelCoord(QVector2D(event->pos()));
        double r,g,b;

        SkyPoint sky;
        if(m_wcs)
        {
            m_wcs->pixelToWorld(QPointF(pix.x(), pix.y()), sky);
        }

        if(m_rawImage->pixel(pix.x(), pix.y(), r, g, b))
        {
            if(m_bwImg)
                emit status(tr("L:%1").arg(r), tr("X:%3 Y:%4").arg((int)pix.x()).arg((int)pix.y()), sky.toString());
            else
                emit status(tr("R:%1 G:%2 B:%3").arg(r).arg(g).arg(b), tr("X:%3 Y:%4").arg((int)pix.x()).arg((int)pix.y()), sky.toString());
        }
    }
}

void ImageWidgetGL::mouseReleaseEvent(QMouseEvent *event)
{
    if(m_selecting)
    {
        m_selecting = false;
        event->accept();
        QStringList mark;
        QStringList unmark;
        for(auto &thumb : m_thumnails)
        {
            if(thumb.dirty)
            {
                if(thumb.selected)
                    mark.append(thumb.path);
                else
                    unmark.append(thumb.path);

                thumb.dirty = false;
            }
        }
        if(!mark.isEmpty())
            m_database->mark(mark);
        if(!unmark.isEmpty())
            m_database->unmark(unmark);
    }
    else
    {
        m_lastPos = QPointF();
    }
}

void ImageWidgetGL::wheelEvent(QWheelEvent *event)
{
    if(m_showThumbnails)
    {
        setOffset(0, m_dy - event->angleDelta().y());
    }
    else
    {
        if(std::abs(event->angleDelta().y()) > 15)
            zoom(event->angleDelta().y(), event->modifiers() & Qt::ShiftModifier ? QPointF() : event->position());
    }
}

void ImageWidgetGL::thumbSelect(QMouseEvent *event)
{
    QPoint p = event->pos();
    const int off = (THUMB_SIZE_BORDER - THUMB_SIZE) / 2;
    p.ry() += m_dy;
    const int w = width()/THUMB_SIZE_BORDER;
    int x = p.x() / THUMB_SIZE_BORDER;
    int y = p.y() / THUMB_SIZE_BORDER_Y;
    int i = y * w + x;
    event->accept();
    QRect rect(x * THUMB_SIZE_BORDER + off, y * THUMB_SIZE_BORDER_Y + off, THUMB_SIZE, THUMB_SIZE);
    if(x < w && i < m_thumbnailCount && rect.contains(p, true))
    {
        bool oldVal = m_thumnails[i].selected;
        bool newVal = oldVal;
        if(event->modifiers() == Qt::ShiftModifier)
            newVal = true;
        if(event->modifiers() == Qt::ControlModifier)
            newVal = false;

        if(newVal != oldVal)
        {
            m_thumnails[i].selected = newVal;
            m_thumnails[i].dirty = true;
            update();
        }
    }
}

void ImageWidgetGL::debayer()
{
    if(m_debayerTex > 0 || !m_superpixel || !m_bwImg || m_imgWidth < 0)return;

    QOpenGLFramebufferObject fbo(m_imgWidth, m_imgHeight, QOpenGLFramebufferObject::NoAttachment, GL_TEXTURE_2D, GL_RGBA16F);
    fbo.bind();

    f->glViewport(0, 0, m_imgWidth, m_imgHeight);

    m_debayerProgram->bind();
    f->glUniform2i(m_debayerProgram->uniformLocation("firstRed"), m_firstRed[0], m_firstRed[1]);
    m_image->bind(0);
    f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    fbo.release();
    f->glViewport(0, 0, m_width, m_height);
    m_debayerTex = fbo.takeTexture();
    f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
    f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    f->glGenerateMipmap(GL_TEXTURE_2D);
}

void ImageWidgetGL::updateScrollBars()
{
    if(m_showThumbnails)
        emit scrollBarsUpdate(0, 0, -1, m_dy, m_height, (m_thumbnailCount / (m_width / THUMB_SIZE_BORDER) + 2) * THUMB_SIZE_BORDER_Y - m_height);
    else
        emit scrollBarsUpdate(m_dx, m_width, m_imgWidth * m_scale - m_width, m_dy, m_height, m_imgHeight * m_scale - m_height);
}

QImage ImageWidget::loadColormap()
{
    QImage embedded(":/colormap.png");
    QStringList path = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
    if(path.size())
    {
        QImage user(path.first() + "/colormap.png");
        if(!user.isNull())
        {
            user = user.scaledToWidth(embedded.width(), Qt::SmoothTransformation);
            QImage tmp(embedded.width(), embedded.height() + user.height(), QImage::Format_RGBA8888);
            QPainter painter(&tmp);
            painter.drawImage(0, 0, embedded);
            painter.drawImage(0, embedded.height(), user);
            return tmp;
        }
    }
    return embedded.convertToFormat(QImage::Format_RGBA8888);
}
