/*
 * DISTHRO Plugin Toolkit (DPT)
 * Copyright (C) 2012 Filipe Coelho <falktx@gmail.com>
 *
 * 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 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.
 *
 * For a full copy of the license see the GPL.txt file
 */

#include "DistrhoDefines.h"

#ifdef DISTRHO_UI_OPENGL

# include "DistrhoUIOpenGLExt.h"

#include <cassert>
#include <cmath>
#include <vector>

#if DISTRHO_OS_LINUX
# include <X11/Xlib.h>
#endif

START_NAMESPACE_DISTRHO

// -------------------------------------------------
// Point

Point::Point(int x, int y)
    : _x(x),
      _y(y)
{
}

Point::Point(const Point& pos)
    : _x(pos._x),
      _y(pos._y)
{
}

int Point::getX() const
{
    return _x;
}

int Point::getY() const
{
    return _y;
}

void Point::setX(int x)
{
    _x = x;
}

void Point::setY(int y)
{
    _y = y;
}

Point& Point::operator=(const Point& pos)
{
    _x = pos._x;
    _y = pos._y;
    return *this;
}

Point& Point::operator+=(const Point& pos)
{
    _x += pos._x;
    _y += pos._y;
    return *this;
}

Point& Point::operator-=(const Point& pos)
{
    _x -= pos._x;
    _y -= pos._y;
    return *this;
}

bool Point::operator==(const Point& pos) const
{
    return (_x == pos._x && _y == pos._y);
}

bool Point::operator!=(const Point& pos) const
{
    return !operator==(pos);
}

// -------------------------------------------------
// Point

Size::Size(int width, int height)
    : _width(width),
      _height(height)
{
}

Size::Size(const Size& size)
    : _width(size._width),
      _height(size._height)
{
}

int Size::getWidth() const
{
    return _width;
}

int Size::getHeight() const
{
    return _height;
}

void Size::setWidth(int width)
{
    _width = width;
}

void Size::setHeight(int height)
{
    _height = height;
}

Size& Size::operator=(const Size& size)
{
    _width  = size._width;
    _height = size._height;
    return *this;
}

Size& Size::operator+=(const Size& size)
{
    _width  += size._width;
    _height += size._height;
    return *this;
}

Size& Size::operator-=(const Size& size)
{
    _width  -= size._width;
    _height -= size._height;
    return *this;
}

Size& Size::operator*=(int m)
{
    _width  *= m;
    _height *= m;
    return *this;
}

Size& Size::operator/=(int d)
{
    _width  /= d;
    _height /= d;
    return *this;
}

Size& Size::operator*=(float m)
{
    _width  *= m;
    _height *= m;
    return *this;
}

Size& Size::operator/=(float d)
{
    _width  /= d;
    _height /= d;
    return *this;
}

// -------------------------------------------------
// Rectangle

Rectangle::Rectangle(int x, int y, int width, int height)
    : _pos(x, y),
      _size(width, height)
{
}

Rectangle::Rectangle(int x, int y, const Size& size)
    : _pos(x, y),
      _size(size)
{
}

Rectangle::Rectangle(const Point& pos, int width, int height)
    : _pos(pos),
      _size(width, height)
{
}

Rectangle::Rectangle(const Point& pos, const Size& size)
    : _pos(pos),
      _size(size)
{
}

Rectangle::Rectangle(const Rectangle& rect)
    : _pos(rect._pos),
      _size(rect._size)
{
}

int Rectangle::getX() const
{
    return _pos._x;
}

int Rectangle::getY() const
{
    return _pos._y;
}

int Rectangle::getWidth() const
{
    return _size._width;
}

int Rectangle::getHeight() const
{
    return _size._height;
}

const Point& Rectangle::getPos() const
{
    return _pos;
}

const Size& Rectangle::getSize() const
{
    return _size;
}

bool Rectangle::contains(int x, int y) const
{
    return (x >= _pos._x && y >= _pos._y && x <= _pos._x+_size._width && y <= _pos._y+_size._height);
}

bool Rectangle::contains(const Point& pos) const
{
    return contains(pos._x, pos._y);
}

bool Rectangle::containsX(int x) const
{
    return (x >= _pos._x && x <= _pos._x+_size._width);
}

bool Rectangle::containsY(int y) const
{
    return (y >= _pos._y && y <= _pos._y+_size._height);
}

void Rectangle::setX(int x)
{
    _pos._x = x;
}

void Rectangle::setY(int y)
{
    _pos._y = y;
}

void Rectangle::setPos(int x, int y)
{
    _pos._x = x;
    _pos._y = y;
}

void Rectangle::setPos(const Point& pos)
{
    _pos = pos;
}

void Rectangle::move(int x, int y)
{
    _pos._x += x;
    _pos._y +=  y;
}

void Rectangle::move(const Point& pos)
{
    _pos += pos;
}

void Rectangle::setWidth(int width)
{
    _size._width = width;
}

void Rectangle::setHeight(int height)
{
    _size._height = height;
}

void Rectangle::setSize(int width, int height)
{
    _size._width  = width;
    _size._height = height;
}

void Rectangle::setSize(const Size& size)
{
    _size = size;
}

void Rectangle::grow(int m)
{
    _size *= m;
}

void Rectangle::grow(float m)
{
    _size *= m;
}

void Rectangle::grow(int width, int height)
{
    _size._width  += width;
    _size._height += height;
}

void Rectangle::grow(const Size& size)
{
    _size += size;
}

void Rectangle::shrink(int m)
{
    _size /= m;
}

void Rectangle::shrink(float m)
{
    _size /= m;
}

void Rectangle::shrink(int width, int height)
{
    _size._width  -= width;
    _size._height -= height;
}

void Rectangle::shrink(const Size& size)
{
    _size -= size;
}

Rectangle& Rectangle::operator=(const Rectangle& rect)
{
    _pos  = rect._pos;
    _size = rect._size;
    return *this;
}

Rectangle& Rectangle::operator+=(const Point& pos)
{
    _pos  += pos;
    return *this;
}

Rectangle& Rectangle::operator-=(const Point& pos)
{
    _pos  -= pos;
    return *this;
}

Rectangle& Rectangle::operator+=(const Size& size)
{
    _size += size;
    return *this;
}

Rectangle& Rectangle::operator-=(const Size& size)
{
    _size -= size;
    return *this;
}

// -------------------------------------------------
// Image

Image::Image(const char* data, int width, int height, GLenum format, GLenum type)
    : _data(data),
      _size(width, height),
      _format(format),
      _type(type)
{
}

Image::Image(const char* data, const Size& size, GLenum format, GLenum type)
    : _data(data),
      _size(size),
      _format(format),
      _type(type)
{
}

Image::Image(const Image& image)
    : _data(image._data),
      _size(image._size),
      _format(image._format),
      _type(image._type)
{
}

bool Image::isValid() const
{
    return (_data && getWidth() > 0 && getHeight() > 0);
}

int Image::getWidth() const
{
    return _size.getWidth();
}

int Image::getHeight() const
{
    return _size.getHeight();
}

const Size& Image::getSize() const
{
    return _size;
}

const char* Image::getData() const
{
    return _data;
}

GLenum Image::getFormat() const
{
    return _format;
}

GLenum Image::getType() const
{
    return _type;
}

Image& Image::operator=(const Image& image)
{
    _data   = image._data;
    _size   = image._size;
    _format = image._format;
    _type   = image._type;
    return *this;
}

// -------------------------------------------------
// ImageButton

ImageButton::ImageButton(const Image& imageNormal, const Image& imageHover, const Image& imageDown, const Point& pos)
    : _imageNormal(imageNormal),
      _imageHover(imageHover),
      _imageDown(imageDown),
      _curImage(&_imageNormal),
      _pos(pos),
      _area(pos, imageNormal.getSize())
{
}

ImageButton::ImageButton(const ImageButton& imageButton)
    : _imageNormal(imageButton._imageNormal),
      _imageHover(imageButton._imageHover),
      _imageDown(imageButton._imageDown),
      _curImage(&_imageNormal),
      _pos(imageButton._pos),
      _area(imageButton._area)
{
}

int ImageButton::getWidth() const
{
    return _area.getWidth();
}

int ImageButton::getHeight() const
{
    return _area.getHeight();
}

const Size& ImageButton::getSize() const
{
    return _area.getSize();
}

ImageButton& ImageButton::operator=(const ImageButton& imageButton)
{
    _imageNormal = imageButton._imageNormal;
    _imageHover  = imageButton._imageHover;
    _imageDown   = imageButton._imageDown;
    _curImage    = &_imageNormal;
    _pos  = imageButton._pos;
    _area = imageButton._area;
    return *this;
}

// -------------------------------------------------
// ImageKnob

ImageKnob::ImageKnob(const Image& image, const Point& pos, Orientation orientation)
    : _image(image),
      _pos(pos),
      _orientation(orientation),
      _isVertical(image.getHeight() > image.getWidth()),
      _layerSize(_isVertical ? image.getWidth() : image.getHeight()),
      _layerCount(_isVertical ? image.getHeight()/_layerSize : image.getWidth()/_layerSize),
      _area(_pos, _layerSize, _layerSize)
{
    _min   = 0.0f;
    _max   = 1.0f;
    _value = _min;
}

ImageKnob::ImageKnob(const ImageKnob& imageKnob)
    : _image(imageKnob._image),
      _pos(imageKnob._pos),
      _orientation(imageKnob._orientation),
      _isVertical(imageKnob._isVertical),
      _layerSize(imageKnob._layerSize),
      _layerCount(imageKnob._layerCount),
      _area(imageKnob._area)
{
    _min   = imageKnob._min;
    _max   = imageKnob._max;
    _value = imageKnob._value;
}

void ImageKnob::setOrientation(Orientation orientation)
{
    _orientation = orientation;
}

void ImageKnob::setRange(float min, float max)
{
    _min = min;
    _max = max;

    if (_value < _min)
        _value = _min;
    else if (_value > _max)
        _value = _max;
}

void ImageKnob::setValue(float value)
{
    if (value < _min)
        value = _min;
    else if (value > _max)
        value = _max;

    _value = value;
}

ImageKnob& ImageKnob::operator=(const ImageKnob& imageKnob)
{
    _image = imageKnob._image;
    _pos   = imageKnob._pos;
    _orientation = imageKnob._orientation;
    _isVertical  = imageKnob._isVertical;
    _layerSize   = imageKnob._layerSize;
    _layerCount  = imageKnob._layerCount;
    _area  = imageKnob._area;
    _min   = imageKnob._min;
    _max   = imageKnob._max;
    _value = imageKnob._value;
    return *this;
}

// -------------------------------------------------
// ImageSlider

ImageSlider::ImageSlider(const Image& image, const Point& startPos, const Point& endPos)
    : _image(image),
      _startPos(startPos),
      _endPos(endPos),
      _area(startPos.getX(), startPos.getY(),
            endPos.getX() > startPos.getX() ? endPos.getX() + image.getWidth() - startPos.getX() : image.getWidth(),
            endPos.getY() > startPos.getY() ? endPos.getY() + image.getHeight() - startPos.getY() : image.getHeight())
{
    _min   = 0.0f;
    _max   = 1.0f;
    _value = _min;
}

ImageSlider::ImageSlider(const ImageSlider& imageSlider)
    : _image(imageSlider._image),
      _startPos(imageSlider._startPos),
      _endPos(imageSlider._endPos),
      _area(imageSlider._area)
{
    _min   = imageSlider._min;
    _max   = imageSlider._max;
    _value = imageSlider._value;
}

int ImageSlider::getWidth() const
{
    return _image.getWidth();
}

int ImageSlider::getHeight() const
{
    return _image.getHeight();
}

void ImageSlider::setRange(float min, float max)
{
    _min = min;
    _max = max;

    if (_value < _min)
        _value = _min;
    else if (_value > _max)
        _value = _max;
}

void ImageSlider::setValue(float value)
{
    if (value < _min)
        value = _min;
    else if (value > _max)
        value = _max;

    _value = value;
}

ImageSlider& ImageSlider::operator=(const ImageSlider& imageSlider)
{
    _image    = imageSlider._image;
    _startPos = imageSlider._startPos;
    _endPos   = imageSlider._endPos;
    _area     = imageSlider._area;
    _min      = imageSlider._min;
    _max      = imageSlider._max;
    _value    = imageSlider._value;
    return *this;
}

// -------------------------------------------------

class OpenGLDialog
{
public:
    OpenGLDialog(PuglView* parentView, const Size& parentSize, const Image& image_, const char* title)
        : image(image_)
    {
#if DISTRHO_OS_LINUX
        bool addToDesktop = false;
#else
        bool addToDesktop = true;
#endif

        view   = puglCreate(0, title, image.getWidth(), image.getHeight(), false, addToDesktop);
        closed = bool(!view);

        if (closed)
            return;

        puglSetHandle(view, this);
        puglSetDisplayFunc(view, onDisplayCallback);
        puglSetKeyboardFunc(view, onKeyboardCallback);
        puglSetMotionFunc(view, onMotionCallback);
        puglSetMouseFunc(view, onMouseCallback);
        puglSetScrollFunc(view, onScrollCallback);
        puglSetSpecialFunc(view, onSpecialCallback);
        puglSetReshapeFunc(view, onReshapeCallback);
        puglSetCloseFunc(view, onCloseCallback);

#if DISTRHO_OS_LINUX
        Display* display    = view->impl->display;
        Window thisWindow   = view->impl->win;
        Window parentWindow = parentView->impl->win;

        int x = (parentSize.getWidth()-image.getWidth())/2;
        int y = (parentSize.getHeight()-image.getHeight())/2;
        Window childRet;

        if (XTranslateCoordinates(display, parentWindow, thisWindow, x, y, &x, &y, &childRet))
            XMoveWindow(display, thisWindow, x, y);

        XSetTransientForHint(display, thisWindow, parentWindow);
        XMapRaised(display, thisWindow);
#endif
    }

    ~OpenGLDialog()
    {
        if (view)
            puglDestroy(view);
    }

    bool idle()
    {
        if (view)
            puglProcessEvents(view);

        return !closed;
    }

    void close()
    {
        closed = true;
    }

    void raise()
    {
        if (view)
        {
#if DISTRHO_OS_LINUX
            Display* display = view->impl->display;
            Window   window  = view->impl->win;
            XRaiseWindow(display, window);
            XSetInputFocus(display, window, RevertToPointerRoot, CurrentTime);
#endif
        }
    }

protected:
    void onDisplay()
    {
        glClear(GL_COLOR_BUFFER_BIT);

        glRasterPos2i(0, image.getHeight());
        glDrawPixels(image.getWidth(), image.getHeight(), image.getFormat(), image.getType(), image.getData());
    }

    void onKeyboard(bool press, uint32_t key)
    {
        if (press && key == CHAR_ESCAPE)
            closed = true;
    }

    void onMotion(int, int)
    {
    }

    void onMouse(int, bool, int, int)
    {
    }

    void onReshape(int width, int height)
    {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, width, height, 0, 0, 1);
        glViewport(0, 0, width, height);

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
    }

    void onScroll(float, float)
    {
    }

    void onSpecial(bool, Key)
    {
    }

    void onClose()
    {
        closed = true;
    }

private:
    PuglView* view;
    bool closed;

    const Image image;

    // Callbacks
    static void onDisplayCallback(PuglView* view)
    {
        OpenGLDialog* _this_ = (OpenGLDialog*)puglGetHandle(view);
        _this_->onDisplay();
    }

    static void onKeyboardCallback(PuglView* view, bool press, uint32_t key)
    {
        OpenGLDialog* _this_ = (OpenGLDialog*)puglGetHandle(view);
        _this_->onKeyboard(press, key);
    }

    static void onMotionCallback(PuglView* view, int x, int y)
    {
        OpenGLDialog* _this_ = (OpenGLDialog*)puglGetHandle(view);
        _this_->onMotion(x, y);
    }

    static void onMouseCallback(PuglView* view, int button, bool press, int x, int y)
    {
        OpenGLDialog* _this_ = (OpenGLDialog*)puglGetHandle(view);
        _this_->onMouse(button, press, x, y);
    }

    static void onReshapeCallback(PuglView* view, int width, int height)
    {
        OpenGLDialog* _this_ = (OpenGLDialog*)puglGetHandle(view);
        _this_->onReshape(width, height);
    }

    static void onScrollCallback(PuglView* view, float dx, float dy)
    {
        OpenGLDialog* _this_ = (OpenGLDialog*)puglGetHandle(view);
        _this_->onScroll(dx, dy);
    }

    static void onSpecialCallback(PuglView* view, bool press, PuglKey key)
    {
        OpenGLDialog* _this_ = (OpenGLDialog*)puglGetHandle(view);
        _this_->onSpecial(press, (Key)key);
    }

    static void onCloseCallback(PuglView* view)
    {
        OpenGLDialog* _this_ = (OpenGLDialog*)puglGetHandle(view);
        _this_->onClose();
    }
};

enum ObjectType {
    OBJECT_NULL   = 0,
    OBJECT_BUTTON = 1,
    OBJECT_KNOB   = 2,
    OBJECT_SLIDER = 3
};

#if DISTRHO_OS_LINUX
struct LinuxData {
    Display* display;
    Window   window;

    char   colorData[8];
    XColor colorBlack;
    Pixmap pixmapBlack;
    Cursor cursorBlack;

    LinuxData()
        : display(nullptr),
          window(0),
          colorData{0},
          colorBlack{0, 0, 0, 0, 0, 0}
    {
    }

    ~LinuxData()
    {
        XFreeCursor(display, cursorBlack);
    }
};
#endif

struct OpenGLExtUIPrivateData {
    int initialPosX;
    int initialPosY;
    void*      lastObj;
    Point      lastCursorPos;
    ObjectType lastObjType;

    Image background;
    std::vector<ImageButton*> buttons;
    std::vector<ImageKnob*>   knobs;
    std::vector<ImageSlider*> sliders;
    OpenGLDialog* dialog;

#if DISTRHO_OS_LINUX
    LinuxData linuxData;
#endif

    OpenGLExtUIPrivateData()
        : initialPosX(0),
          initialPosY(0),
          lastObj(nullptr),
          lastCursorPos(0, 0),
          lastObjType(OBJECT_NULL),
          background(nullptr, 0, 0),
          dialog(nullptr)
    {
    }

    void showCursor()
    {
#if DISTRHO_OS_LINUX
        if (lastCursorPos != Point(-1, -2))
            XWarpPointer(linuxData.display, None, DefaultRootWindow(linuxData.display), 0, 0, 0, 0, lastCursorPos.getX(), lastCursorPos.getY());

        XUndefineCursor(linuxData.display, linuxData.window);
#endif
    }

    void hideCursor()
    {
#if DISTRHO_OS_LINUX
        Window root, child;
        int rootX, rootY, winX, winY;
        unsigned int mask;

        if (XQueryPointer(linuxData.display, DefaultRootWindow(linuxData.display), &root, &child, &rootX, &rootY, &winX, &winY, &mask))
            lastCursorPos = Point(rootX, rootY);
        else
            lastCursorPos = Point(-1, -2);

        XDefineCursor(linuxData.display, linuxData.window, linuxData.cursorBlack);
#endif
    }
};

// -------------------------------------------------

OpenGLExtUI::OpenGLExtUI()
    : OpenGLUI()
{
    data = new OpenGLExtUIPrivateData;
}

OpenGLExtUI::~OpenGLExtUI()
{
    assert(! data->dialog);
    delete data;
}

// -------------------------------------------------
// UI Callbacks

void OpenGLExtUI::d_uiIdle()
{
    if (data->dialog && ! data->dialog->idle())
    {
        delete data->dialog;
        data->dialog = nullptr;
    }

    OpenGLUI::d_uiIdle();
}

// -------------------------------------------------
// Extended Calls

void OpenGLExtUI::setBackgroundImage(const Image& image)
{
    data->background = image;
    d_uiRepaint();
}

void OpenGLExtUI::addImageButton(ImageButton* button)
{
    data->buttons.push_back(button);
}

void OpenGLExtUI::addImageKnob(ImageKnob* knob)
{
    data->knobs.push_back(knob);
}

void OpenGLExtUI::addImageSlider(ImageSlider* slider)
{
    data->sliders.push_back(slider);
}

void OpenGLExtUI::showImageModalDialog(const Image& image, const char* title)
{
    data->dialog = new OpenGLDialog(OpenGLUI::data->widget, Size(d_width(), d_height()), image, title);
}

// -------------------------------------------------
// Extended Callbacks

void OpenGLExtUI::imageButtonClicked(ImageButton*)
{
}

void OpenGLExtUI::imageKnobDragStarted(ImageKnob*)
{
}

void OpenGLExtUI::imageKnobDragFinished(ImageKnob*)
{
}

void OpenGLExtUI::imageKnobValueChanged(ImageKnob*, float)
{
}

void OpenGLExtUI::imageSliderValueChanged(ImageSlider*, float)
{
}

void OpenGLExtUI::imageSliderDragStarted(ImageSlider*)
{
}

void OpenGLExtUI::imageSliderDragFinished(ImageSlider*)
{
}

void OpenGLExtUI::d_onInit()
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, d_width(), d_height(), 0, 0, 1);
    glViewport(0, 0, d_width(), d_height());

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

#if DISTRHO_OS_LINUX
    data->linuxData.display = OpenGLUI::data->widget->impl->display;
    data->linuxData.window  = OpenGLUI::data->widget->impl->win;
    data->linuxData.pixmapBlack = XCreateBitmapFromData(data->linuxData.display, data->linuxData.window, data->linuxData.colorData, 8, 8);
    data->linuxData.cursorBlack = XCreatePixmapCursor(data->linuxData.display, data->linuxData.pixmapBlack, data->linuxData.pixmapBlack,
                                                      &data->linuxData.colorBlack, &data->linuxData.colorBlack, 0, 0);
#endif
}

void OpenGLExtUI::d_onDisplay()
{
    glClear(GL_COLOR_BUFFER_BIT);

    // Background
    if (data->background.isValid())
    {
        glRasterPos2i(0, d_height());
        glDrawPixels(d_width(), d_height(), data->background._format, data->background._type, data->background._data);
    }

    // Buttons
    if (data->buttons.size() > 0)
    {
        for (auto it = data->buttons.begin(); it != data->buttons.end(); it++)
        {
            ImageButton* button(*it);

            glRasterPos2i(button->_pos.getX(), button->_pos.getY() + button->_area.getHeight());
            glDrawPixels(button->getWidth(), button->getHeight(), button->_curImage->getFormat(), button->_curImage->getType(), button->_curImage->getData());
        }
    }

    // Knobs
    if (data->knobs.size() > 0)
    {
        for (auto it = data->knobs.begin(); it != data->knobs.end(); it++)
        {
            ImageKnob* knob(*it);

            float vper = (knob->_value - knob->_min) / (knob->_max - knob->_min);

            int layerDataSize   = knob->_layerSize * knob->_layerSize * 4;
            int imageDataSize   = layerDataSize * knob->_layerCount;
            int imageDataOffset = imageDataSize - layerDataSize - layerDataSize * rint(vper*(knob->_layerCount-1));

            glRasterPos2i(knob->_pos.getX(), knob->_pos.getY()+knob->_area.getHeight());
            glDrawPixels(knob->_layerSize, knob->_layerSize, knob->_image.getFormat(), knob->_image.getType(), knob->_image.getData() + imageDataOffset);
        }
    }

    // Sliders
    if (data->sliders.size() > 0)
    {
        for (auto it = data->sliders.begin(); it != data->sliders.end(); it++)
        {
            ImageSlider* slider(*it);

            float vper = (slider->_value - slider->_min) / (slider->_max - slider->_min);
            int x = slider->_area.getX();
            int y = slider->_area.getY();

            if (slider->_endPos.getX() > slider->_startPos.getX())
                // horizontal
                x +=  rint(vper * (slider->_area.getWidth()-slider->getWidth()));
            else
                // vertical
                y += slider->_area.getHeight() - rint(vper * (slider->_area.getHeight()-slider->getHeight()));

#if 0 // DEBUG
            glColor3i(160, 90, 161);
            glBegin(GL_QUADS);
                glTexCoord2f(0.0f, 0.0f); glVertex3f(slider->_area.getX(),                          slider->_area.getY(), 0);
                glTexCoord2f(1.0f, 0.0f); glVertex3f(slider->_area.getX()+slider->_area.getWidth(), slider->_area.getY(), 0);
                glTexCoord2f(1.0f, 1.0f); glVertex3f(slider->_area.getX()+slider->_area.getWidth(), slider->_area.getY()+slider->_area.getHeight(), 0);
                glTexCoord2f(0.0f, 1.0f); glVertex3f(slider->_area.getX(),                          slider->_area.getY()+slider->_area.getHeight(), 0);
            glEnd();
            glColor3i(160, 90, 161);
#endif

            glRasterPos2i(x, y);
            glDrawPixels(slider->getWidth(), slider->getHeight(), slider->_image._format, slider->_image._type, slider->_image._data);
        }
    }
}

void OpenGLExtUI::d_onKeyboard(bool press, uint32_t key)
{
    if (data->dialog)
        return;

    (void)press;
    (void)key;
}

void OpenGLExtUI::d_onMotion(int x, int y)
{
    if (data->dialog)
        return;

    // Buttons
    if (data->buttons.size() > 0)
    {
        for (auto it = data->buttons.begin(); it != data->buttons.end(); it++)
        {
            ImageButton* button(*it);

            if (button == data->lastObj)
                continue;

            if (button->_area.contains(x, y))
            {
                if (button->_curImage != &button->_imageHover)
                    d_uiRepaint();

                button->_curImage = &button->_imageHover;
            }
            else
            {
                if (button->_curImage != &button->_imageNormal)
                    d_uiRepaint();

                button->_curImage = &button->_imageNormal;
            }
        }
    }

    if (data->lastObjType == OBJECT_NULL || ! data->lastObj)
        return;

    // Knobs
    if (data->lastObjType == OBJECT_KNOB && data->knobs.size() > 0)
    {
        for (auto it = data->knobs.begin(); it != data->knobs.end(); it++)
        {
            ImageKnob* knob(*it);

            if (knob != data->lastObj)
                continue;

            if (knob->_orientation == ImageKnob::Horizontal)
            {
                int movX = x - data->initialPosX;

                if (movX != 0)
                {
                    int d = (d_uiGetModifiers() & MOD_SHIFT) ? 2000 : 200;
                    float value = knob->_value + (knob->_max - knob->_min) / d * movX;

                    if (value < knob->_min)
                        value = knob->_min;
                    else if (value > knob->_max)
                        value = knob->_max;

                    if (knob->_value != value)
                    {
                        knob->_value = value;
                        imageKnobValueChanged(knob, value);

                        d_uiRepaint();
                    }
                }
            }
            else if (knob->_orientation == ImageKnob::Vertical)
            {
                int movY = data->initialPosY - y;

                if (movY != 0)
                {
                    int d = (d_uiGetModifiers() & MOD_SHIFT) ? 2000 : 200;
                    float value = knob->_value + (knob->_max - knob->_min) / d * movY;

                    if (value < knob->_min)
                        value = knob->_min;
                    else if (value > knob->_max)
                        value = knob->_max;

                    if (knob->_value != value)
                    {
                        knob->_value = value;
                        imageKnobValueChanged(knob, value);

                        d_uiRepaint();
                    }
                }
            }

            data->initialPosX = x;
            data->initialPosY = y;

            return;
        }
    }

    // Sliders
    if (data->lastObjType == OBJECT_SLIDER && data->sliders.size() > 0)
    {
        for (auto it = data->sliders.begin(); it != data->sliders.end(); it++)
        {
            ImageSlider* slider(*it);

            if (slider != data->lastObj)
                continue;

            bool horizontal = slider->_endPos.getX() > slider->_startPos.getX();

            if ((horizontal && slider->_area.containsX(x)) || (slider->_area.containsY(y) && ! horizontal))
            {
                float vper;

                if (horizontal)
                    // horizontal
                    vper  = float(x - slider->_area.getX()) / slider->_area.getWidth();
                else
                    // vertical
                    vper  = float(y - slider->_area.getY()) / slider->_area.getHeight();

                float value = slider->_max - vper * (slider->_max - slider->_min);

                if (value < slider->_min)
                    value = slider->_min;
                else if (value > slider->_max)
                    value = slider->_max;

                if (slider->_value != value)
                {
                    slider->_value = value;
                    imageSliderValueChanged(slider, value);

                    d_uiRepaint();
                }
            }
            else if (y < slider->_area.getY())
            {
                if (slider->_value != slider->_max)
                {
                    slider->_value = slider->_max;
                    imageSliderValueChanged(slider, slider->_max);

                    d_uiRepaint();
                }
            }
            else
            {
                if (slider->_value != slider->_min)
                {
                    slider->_value = slider->_min;
                    imageSliderValueChanged(slider, slider->_min);

                    d_uiRepaint();
                }
            }

            return;
        }
    }
}

void OpenGLExtUI::d_onMouse(int button, bool press, int x, int y)
{
    if (data->dialog)
    {
        data->dialog->raise();
        return;
    }

    if ((!press) && data->lastObjType == OBJECT_BUTTON && data->lastObj && data->buttons.size() > 0)
    {
        for (auto it = data->buttons.begin(); it != data->buttons.end(); it++)
        {
            ImageButton* button(*it);

            if (button == data->lastObj)
            {
                if (button->_area.contains(x, y))
                    imageButtonClicked(button);

                button->_curImage = &button->_imageNormal;
                d_uiRepaint();

                break;
            }
        }
    }

    if (button != 1)
        return;

    if (data->lastObjType != OBJECT_NULL && data->lastObj)
    {
        if (data->lastObjType == OBJECT_KNOB)
        {
            data->showCursor();
            imageKnobDragFinished((ImageKnob*)data->lastObj);
        }
        else if (data->lastObjType == OBJECT_SLIDER)
            imageSliderDragFinished((ImageSlider*)data->lastObj);
    }

    data->initialPosX = 0;
    data->initialPosY = 0;
    data->lastObj     = nullptr;
    data->lastObjType = OBJECT_NULL;

    if (! press)
        return;

    // Buttons
    if (data->buttons.size() > 0)
    {
        for (auto it = data->buttons.begin(); it != data->buttons.end(); it++)
        {
            ImageButton* button(*it);

            if (button->_area.contains(x, y))
            {
                data->initialPosX = x;
                data->initialPosY = y;
                data->lastObj     = button;
                data->lastObjType = OBJECT_BUTTON;

                button->_curImage = &button->_imageDown;
                d_uiRepaint();

                return;
            }
        }
    }

    // Knobs
    if (data->knobs.size() > 0)
    {
        for (auto it = data->knobs.begin(); it != data->knobs.end(); it++)
        {
            ImageKnob* knob(*it);

            if (knob->_area.contains(x, y))
            {
                data->initialPosX = x;
                data->initialPosY = y;
                data->lastObj     = knob;
                data->lastObjType = OBJECT_KNOB;
                data->hideCursor();
                imageKnobDragStarted(knob);
                return;
            }
        }
    }

    // Sliders
    if (data->sliders.size() > 0)
    {
        for (auto it = data->sliders.begin(); it != data->sliders.end(); it++)
        {
            ImageSlider* slider(*it);

            if (slider->_area.contains(x, y))
            {
                data->initialPosX = x;
                data->initialPosY = y;
                data->lastObj     = slider;
                data->lastObjType = OBJECT_SLIDER;
                imageSliderDragStarted(slider);

                float vper;

                if (slider->_endPos.getX() > slider->_startPos.getX())
                    // horizontal
                    vper  = float(x - slider->_area.getX()) / slider->_area.getWidth();
                else
                    // vertical
                    vper  = float(y - slider->_area.getY()) / slider->_area.getHeight();

                float value = slider->_max - vper * (slider->_max - slider->_min);

                if (value < slider->_min)
                    value = slider->_min;
                else if (value > slider->_max)
                    value = slider->_max;

                if (slider->_value != value)
                {
                    slider->_value = value;
                    imageSliderValueChanged(slider, value);

                    d_uiRepaint();
                }

                return;
            }
        }
    }
}

void OpenGLExtUI::d_onReshape(int width, int height)
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, height, 0, 0, 1);
    glViewport(0, 0, width, height);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void OpenGLExtUI::d_onScroll(float dx, float dy)
{
    if (data->dialog)
        return;

    // unused
    (void)dx;
    (void)dy;
}

void OpenGLExtUI::d_onSpecial(bool press, Key key)
{
    if (data->dialog)
        return;

    // unused
    (void)press;
    (void)key;
}

void OpenGLExtUI::d_onClose()
{
    if (data->dialog)
    {
        data->dialog->close();
        delete data->dialog;
        data->dialog = nullptr;
    }

    if (data->lastObjType != OBJECT_NULL && data->lastObj)
    {
        if (data->lastObjType == OBJECT_KNOB)
        {
            data->showCursor();
            imageKnobDragFinished((ImageKnob*)data->lastObj);
        }
        else if (data->lastObjType == OBJECT_SLIDER)
            imageSliderDragFinished((ImageSlider*)data->lastObj);
    }

    data->initialPosX = 0;
    data->initialPosY = 0;
    data->lastObj     = nullptr;
    data->lastObjType = OBJECT_NULL;

    data->buttons.clear();
    data->knobs.clear();
    data->sliders.clear();
}

// -------------------------------------------------

END_NAMESPACE_DISTRHO

#endif // DISTRHO_UI_OPENGL
