#include <qapplication.h>
#include <qpainterpath.h>
#include <qscreen.h>
#include <qlabel.h>

#include "framelesswindow.h"
#include "logger.h"

#define MAX_MOUSE_MOVEMENT 300

FramelessWindow::FramelessWindow(int cornerRadius, unsigned int attributes, QWidget* parent)
    : _cornerRadius(cornerRadius), _attributes((LUI_WINDOW_ATTRIBUTES)attributes), QWidget(parent)
{
    setAttribute(Qt::WA_TranslucentBackground);
    setWindowFlags(Qt::FramelessWindowHint);
    setFocusPolicy(Qt::StrongFocus);
    setMouseTracking(true);
    setFocus();
    
    // Create and properly set real displayed window widget
    _stretchLayout = new QVBoxLayout(this);
    _stretchLayout->setContentsMargins(30, 30, 30, 30);
    _windowWidget = new QWidget(this);
    _windowWidget->setObjectName("windowWidget");
    _windowWidget->setMouseTracking(true);
    _stretchLayout->addWidget(_windowWidget);
    _windowWidget->show();
    setLayout(_stretchLayout);

    // Set style sheet for window widget
    QString windowWidgetStyleSheet = "QWidget#windowWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_cornerRadius) + "px;}";
    _windowWidget->setStyleSheet(windowWidgetStyleSheet);

    // Set shadow for window widget
    _windowShadow = new QGraphicsDropShadowEffect(_windowWidget);
    _windowShadow->setBlurRadius(30);
    _windowShadow->setColor(QColor(0, 0, 0));
    _windowShadow->setOffset(0, 0);
    _windowWidget->setGraphicsEffect(_windowShadow);

    // Create window control buttons container widget & its layout
    _windowBtnWidget = new QWidget(_windowWidget);
    _windowBtnWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
    _windowBtnWidget->setMouseTracking(true);
    _windowBtnLayout = new QHBoxLayout(_windowBtnWidget);
    _windowBtnLayout->setContentsMargins(0, 0, 0, 0);
    _windowBtnLayout->setSpacing(10);
    _windowBtnLayout->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
    _windowBtnWidget->setLayout(_windowBtnLayout);

    // Create window control buttons
    _minimizeBtn = new QPushButton(_windowBtnWidget);
    _maximizeBtn = new QPushButton(_windowBtnWidget);
    _closeBtn = new QPushButton(_windowBtnWidget);

    _minimizeBtn->setFixedSize(12, 12);
    _maximizeBtn->setFixedSize(12, 12);
    _closeBtn->setFixedSize(12, 12);

    _minimizeBtn->setStyleSheet("QPushButton{border-radius: 6px; background-color: #c2c2c2;}"
        "QPushButton:hover{background-color: #e98b2a;}");
    _maximizeBtn->setStyleSheet("QPushButton{border-radius: 6px; background-color: #c2c2c2;}"
        "QPushButton:hover{background-color: #2d6d4b;}");
    _closeBtn->setStyleSheet("QPushButton{border-radius: 6px; background-color: #c2c2c2;}"
        "QPushButton:hover{background-color: #ab3b3a;}");

    _windowBtnLayout->addWidget(_minimizeBtn);
    _windowBtnLayout->addWidget(_maximizeBtn);
    _windowBtnLayout->addWidget(_closeBtn);
    if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MINIMIZE) == 0) {
        _minimizeBtn->show();
    }
    if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MAXIMIZE) == 0) {
        _maximizeBtn->show();
    }
    if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_CLOSE) == 0) {
        _closeBtn->show();
    }

    // Connect window control buttons
    connect(_minimizeBtn, &QPushButton::clicked, this, &QWidget::showMinimized);
    connect(_maximizeBtn, &QPushButton::clicked, this, &FramelessWindow::controlWindowScale);
    connect(_closeBtn, &QPushButton::clicked, this, &QWidget::close);
}

FramelessWindow::FramelessWindow(QWidget* parent)
    : FramelessWindow(0, LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_NO_ATTRIBUTES, parent)
{
}

FramelessWindow::FramelessWindow(int cornerRadius, QWidget* parent)
    : FramelessWindow(cornerRadius, LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_NO_ATTRIBUTES, parent)
{
}

FramelessWindow::FramelessWindow(unsigned int attributes, QWidget* parent)
    : FramelessWindow(0, attributes, parent)
{
}

FramelessWindow::~FramelessWindow() {}

void FramelessWindow::showEvent(QShowEvent* event) {
    // Initialize window UI after window is shown
    initializeWindowUI();
}

void FramelessWindow::initializeWindowUI() {
    if (_initialized) {
        return;
    }

    // Create a round cornered mask for window widget
    QPainterPath path;
    path.addRoundedRect(_windowWidget->rect(), _cornerRadius - 1, _cornerRadius - 1);
    QRegion region(path.toFillPolygon().toPolygon());
    _windowWidget->setMask(region);

    // Create a border for window widget (in order to hide zigzagged edges)
    _windowBorder = new QWidget(this);
    _windowBorder->setObjectName("windowBorder");
    QString windowBorderStyleSheet =
        "QWidget#windowBorder{background-color:#00FFFFFF;border:1.5px solid " + _borderColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_cornerRadius) + "px;}";
    _windowBorder->setStyleSheet(windowBorderStyleSheet);
    _windowBorder->setAttribute(Qt::WA_TransparentForMouseEvents);
    _windowBorder->move(_windowWidget->pos() - QPoint(1, 1));
    _windowBorder->resize(_windowWidget->size() + QSize(2, 2));
    _windowBorder->show();

    // Move button widget to the top right of the window widget
    _windowBtnWidget->move(_windowWidget->width() - _windowBtnWidget->width() - 18, 18);
    _windowBtnWidget->show();
    _windowBtnWidget->raise();

    // Set initialized state
    _initialized = true;
}

void FramelessWindow::resizeEvent(QResizeEvent* event) {
    // Resize window border
    if (_windowBorder != nullptr) {
        _windowBorder->move(_windowWidget->pos() - QPoint(1, 1));
        _windowBorder->resize(_windowWidget->size() + QSize(2, 2));
    }

    // Resize window mask
    QPainterPath path;
    path.addRoundedRect(_windowWidget->rect(), _cornerRadius - 1, _cornerRadius - 1);
    QRegion region(path.toFillPolygon().toPolygon());
    _windowWidget->setMask(region);

    // Move button widget to the top right of the window widget
    _windowBtnWidget->move(_windowWidget->width() - _windowBtnWidget->width() - 18, 18);
}

void FramelessWindow::controlWindowScale() {
    if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MAXIMIZE) != 0) {
        return;
    }
    if (!_maximized) {
        _lastWindowGeometry = frameGeometry();

        Logger::debug("Maximizing window:");
        Logger::debug("[+] current window position: " + std::to_string(x()) + ", " + std::to_string(y()));
        Logger::debug("[+] current window size: " + std::to_string(width()) + ", " + std::to_string(height()));
        Logger::debug("[+] current geometry: " + std::to_string(_lastWindowGeometry.x()) + ", " + std::to_string(_lastWindowGeometry.y()) + ", " + std::to_string(_lastWindowGeometry.width()) + ", " + std::to_string(_lastWindowGeometry.height()));
        Logger::debug("[+] current window widget position: " + std::to_string(_windowWidget->x()) + ", " + std::to_string(_windowWidget->y()));
        Logger::debug("[+] current window widget size: " + std::to_string(_windowWidget->width()) + ", " + std::to_string(_windowWidget->height()));
        Logger::debug("[+] current window border position: " + std::to_string(_windowBorder->x()) + ", " + std::to_string(_windowBorder->y()));
        Logger::debug("[+] current window border size: " + std::to_string(_windowBorder->width()) + ", " + std::to_string(_windowBorder->height()));

        _windowShadow->setEnabled(false);
        _windowBorder->hide();
        QString windowWidgetStyleSheet = "QWidget#windowWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";}";
        _windowWidget->setStyleSheet(windowWidgetStyleSheet);

        _stretchLayout->setContentsMargins(0, 0, 0, 0);

        showMaximized();

        QPainterPath path;
        path.addRect(_windowWidget->rect());
        QRegion mask(path.toFillPolygon().toPolygon());
        _windowWidget->setMask(mask);

        _maximized = true;
    }
    else {
        _stretchLayout->setContentsMargins(30, 30, 30, 30);

        showNormal();

        resize(_lastWindowGeometry.size());
        move(_lastWindowGeometry.topLeft());

        _windowShadow->setEnabled(true);
        _windowBorder->show();
        QString windowWidgetStyleSheet = "QWidget#windowWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_cornerRadius) + "px;}";
        _windowWidget->setStyleSheet(windowWidgetStyleSheet);

        QPainterPath path;
        path.addRoundedRect(_windowWidget->rect(), _cornerRadius - 1, _cornerRadius - 1);
        QRegion mask(path.toFillPolygon().toPolygon());
        _windowWidget->setMask(mask);

        Logger::debug("Restoring window:");
        Logger::debug("[+] current window position: " + std::to_string(x()) + ", " + std::to_string(y()));
        Logger::debug("[+] current window size: " + std::to_string(width()) + ", " + std::to_string(height()));
        Logger::debug("[+] current geometry: " + std::to_string(frameGeometry().x()) + ", " + std::to_string(frameGeometry().y()) + ", " + std::to_string(frameGeometry().width()) + ", " + std::to_string(frameGeometry().height()));
        Logger::debug("[+] current window widget position: " + std::to_string(_windowWidget->x()) + ", " + std::to_string(_windowWidget->y()));
        Logger::debug("[+] current window widget size: " + std::to_string(_windowWidget->width()) + ", " + std::to_string(_windowWidget->height()));

        _maximized = false;
    }
}

void FramelessWindow::updateMouseState(QMouseEvent* event) {
    _mouseState = MOUSE_STATE_NONE;
    if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_RESIZE) != 0) {
        return;
    }
    if (_maximized) {
        setCursor(Qt::ArrowCursor);
        return;
    }
    // Set mouse state according to abs distance from window border
    if (abs(event->globalPos().x() - (_windowWidget->frameGeometry().left() + frameGeometry().left())) < 5) {
        _mouseState |= MOUSE_STATE_RESIZE_LEFT;
    }
    if (abs(event->globalPos().x() - (_windowWidget->frameGeometry().right() + frameGeometry().left())) < 5) {
        _mouseState |= MOUSE_STATE_RESIZE_RIGHT;
    }
    if (abs(event->globalPos().y() - (_windowWidget->frameGeometry().top() + frameGeometry().top())) < 5) {
        _mouseState |= MOUSE_STATE_RESIZE_TOP;
    }
    if (abs(event->globalPos().y() - (_windowWidget->frameGeometry().bottom() + frameGeometry().top())) < 5) {
        _mouseState |= MOUSE_STATE_RESIZE_BOTTOM;
    }
    // Set cursor shape according to mouse state
    switch (_mouseState) {
        case MOUSE_STATE_RESIZE_LEFT:
        case MOUSE_STATE_RESIZE_RIGHT:
            setCursor(Qt::SizeHorCursor);
            break;
        case MOUSE_STATE_RESIZE_TOP:
        case MOUSE_STATE_RESIZE_BOTTOM:
            setCursor(Qt::SizeVerCursor);
            break;
        case MOUSE_STATE_RESIZE_LEFT | MOUSE_STATE_RESIZE_TOP:
        case MOUSE_STATE_RESIZE_RIGHT | MOUSE_STATE_RESIZE_BOTTOM:
            setCursor(Qt::SizeFDiagCursor);
            break;
        case MOUSE_STATE_RESIZE_LEFT | MOUSE_STATE_RESIZE_BOTTOM:
        case MOUSE_STATE_RESIZE_RIGHT | MOUSE_STATE_RESIZE_TOP:
            setCursor(Qt::SizeBDiagCursor);
            break;
        default:
            setCursor(Qt::ArrowCursor);
            break;
    }
}

void FramelessWindow::mousePressEvent(QMouseEvent* event) {
    if (event->button() == Qt::LeftButton) {
        _mousePressed = true;
        _lastMousePosition = event->globalPos().toPointF();
    }
}

void FramelessWindow::mouseReleaseEvent(QMouseEvent* event) {
    _mousePressed = false;
    QScreen* screen = QGuiApplication::screenAt(event->globalPos());
    Logger::debug("Current screen geometry:");
    Logger::debug("[+] screen position: " + std::to_string(screen->geometry().x()) + ", " + std::to_string(screen->geometry().y()));
    Logger::debug("[+] screen size: " + std::to_string(screen->geometry().width()) + ", " + std::to_string(screen->geometry().height()));
    if (abs(event->globalPos().y() - screen->geometry().top()) < 5) {
        controlWindowScale();
    }
    updateMouseState(event);
}

void FramelessWindow::mouseMoveEvent(QMouseEvent* event) {
    Logger::debug("Detected mouse move");
    Logger::debug("[+] mouse global position : " + std::to_string(event->globalPos().x()) + ", " + std::to_string(event->globalPos().y()));
    Logger::debug("[+] window geometry: " + std::to_string(frameGeometry().x()) + ", " + std::to_string(frameGeometry().y()) + ", " + std::to_string(frameGeometry().width()) + ", " + std::to_string(frameGeometry().height()));
    Logger::debug("[+] widget frame geometry: " + std::to_string(_windowWidget->frameGeometry().x()) + ", " + std::to_string(_windowWidget->frameGeometry().y()));
    Logger::debug("[+] widget frame size: " + std::to_string(_windowWidget->frameGeometry().width()) + ", " + std::to_string(_windowWidget->frameGeometry().height()));
    if (event->buttons() == Qt::NoButton) {
        _mousePressed = false;
    }
    if (abs(event->globalPos().x() - _lastMousePosition.x()) > MAX_MOUSE_MOVEMENT) {
        // Maybe moving window across monitors, avoid window disappear
        _lastMousePosition = event->globalPos().toPointF();
    }
    if (abs(event->globalPos().y() - _lastMousePosition.y()) > MAX_MOUSE_MOVEMENT) {
        // Maybe moving window across monitors, avoid window disappear
        _lastMousePosition = event->globalPos().toPointF();
    }
    if (!_mousePressed) {
        updateMouseState(event);
    }
    else {
        // Resize window according to mouse state
        switch (_mouseState) {
            case MOUSE_STATE_RESIZE_LEFT:
                resize(frameGeometry().width() - event->globalPos().x() + frameGeometry().left() + _windowWidget->frameGeometry().left(), frameGeometry().height());
                move(event->globalPos().x() - _windowWidget->frameGeometry().left(), frameGeometry().top());
                break;
            case MOUSE_STATE_RESIZE_RIGHT:
                resize(event->globalPos().x() - frameGeometry().left() + _windowWidget->frameGeometry().left(), frameGeometry().height());
                break;
            case MOUSE_STATE_RESIZE_TOP:
                resize(frameGeometry().width(), frameGeometry().height() - event->globalPos().y() + frameGeometry().top() + _windowWidget->frameGeometry().top());
                move(frameGeometry().left(), event->globalPos().y() - _windowWidget->frameGeometry().top());
                break;
            case MOUSE_STATE_RESIZE_BOTTOM:
                resize(frameGeometry().width(), event->globalPos().y() - frameGeometry().top() + _windowWidget->frameGeometry().top());
                break;
            case MOUSE_STATE_RESIZE_LEFT | MOUSE_STATE_RESIZE_TOP:
                resize(frameGeometry().width() - event->globalPos().x() + frameGeometry().left() + _windowWidget->frameGeometry().left(), frameGeometry().height() - event->globalPos().y() + frameGeometry().top() + _windowWidget->frameGeometry().top());
                move(event->globalPos().x() - _windowWidget->frameGeometry().left(), event->globalPos().y() - _windowWidget->frameGeometry().top());
                break;
            case MOUSE_STATE_RESIZE_LEFT | MOUSE_STATE_RESIZE_BOTTOM:
                resize(frameGeometry().width() - event->globalPos().x() + frameGeometry().left() + _windowWidget->frameGeometry().left(), event->globalPos().y() - frameGeometry().top() + _windowWidget->frameGeometry().top());
                move(event->globalPos().x() - _windowWidget->frameGeometry().left(), frameGeometry().top());
                break;
            case MOUSE_STATE_RESIZE_RIGHT | MOUSE_STATE_RESIZE_TOP:
                resize(event->globalPos().x() - frameGeometry().left() + _windowWidget->frameGeometry().left(), frameGeometry().height() - event->globalPos().y() + frameGeometry().top() + _windowWidget->frameGeometry().top());
                move(frameGeometry().left(), event->globalPos().y() - _windowWidget->frameGeometry().top());
                break;
            case MOUSE_STATE_RESIZE_RIGHT | MOUSE_STATE_RESIZE_BOTTOM:
                resize(event->globalPos().x() - frameGeometry().left() + _windowWidget->frameGeometry().left(), event->globalPos().y() - frameGeometry().top() + _windowWidget->frameGeometry().top());
                break;
            default:
                if (_maximized) {
                    qreal wRatio = (qreal)event->pos().x() / (qreal)_windowWidget->width();
                    controlWindowScale();
                    move(event->globalPos().x() - _windowWidget->width() * wRatio, event->globalPos().y() - 52);
                }
                else {
                    move(frameGeometry().left() + event->globalPos().x() - _lastMousePosition.x(), frameGeometry().top() + event->globalPos().y() - _lastMousePosition.y());
                }
                break;
        }
        _lastMousePosition = event->globalPos().toPointF();
    }
}

FramelessWindow::LUI_WINDOW_ATTRIBUTES FramelessWindow::getWindowAttributes() {
    return _attributes;
}

void FramelessWindow::setWindowAttributes(unsigned int attributes) {
    _attributes = (LUI_WINDOW_ATTRIBUTES)attributes;
    if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MINIMIZE) == 0) {
        _minimizeBtn->show();
    }
    else {
        _minimizeBtn->hide();
    }
    if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MAXIMIZE) == 0) {
        _maximizeBtn->show();
    }
    else {
        _maximizeBtn->hide();
    }
    if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_CLOSE) == 0) {
        _closeBtn->show();
    }
    else {
        _closeBtn->hide();
    }
}
