#pragma once

#include <QtWidgets/QWidget>
#include <QObject>
#include <QColor>
#include <QHash>
#include <QPainter>
#include <QMenu>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QProxyStyle>
#include <QDialogButtonBox>
#include <QStyleOptionSlider>

#define arc_length_max 0.734f
#define arc_length_min 0.02f


class SpinAnimator: public QObject {
	Q_OBJECT
	Q_PROPERTY(float arc_length READ get_arc_length WRITE set_arc_length)
	Q_PROPERTY(int arc_rotation READ get_arc_rotation WRITE set_arc_rotation)
	Q_PROPERTY(int overall_rotation READ get_overall_rotation WRITE set_overall_rotation)
public:
	SpinAnimator(QObject* parent = 0, const int speed_factor=300) :
		QObject(parent),
		m_arc_length(arc_length_max),
		m_arc_rotation(0),
		m_overall_rotation(0),
		m_has_pending_updates(false),
		m_animation(this)
	{
		QPropertyAnimation *a;
#define S(property, duration) a = new QPropertyAnimation(this, QByteArray(#property), this); a->setEasingCurve(QEasingCurve::InOutCubic); a->setDuration(duration); a->setLoopCount(-1); m_animation.addAnimation(a);
		S(arc_length, 7 * speed_factor);
		a->setStartValue(arc_length_min);
        a->setKeyValueAt(0.25, arc_length_min);
        a->setKeyValueAt(0.5, arc_length_max);
        a->setKeyValueAt(0.75, arc_length_max);
        a->setEndValue(arc_length_min);

		S(arc_rotation, 7 * speed_factor);
        a->setStartValue(0);
        a->setKeyValueAt(0.25, 0);
        a->setKeyValueAt(0.5, 45);
        a->setKeyValueAt(0.75, 45);
        a->setEndValue(360);

		S(overall_rotation, 10 * speed_factor);
		a->setStartValue(0);
		a->setEndValue(360);
#undef S
	}
	~SpinAnimator() { m_animation.stop(); m_animation.clear(); }
	void start() { m_animation.start(); }
	void stop() { m_animation.stop(); m_arc_length = arc_length_max; m_arc_rotation = 0; m_overall_rotation = 0; notify_of_update(); }
	bool is_running() const { return m_animation.state() == QAbstractAnimation::Running; }
	void draw(QPainter &painter, QRect bounds, const QColor &color, const float thickness=0.f) {
		m_has_pending_updates = false;
		painter.save();
		painter.setRenderHint(QPainter::Antialiasing);
        QRectF rect(bounds);
		float width = thickness > 0.f ? thickness : std::max(3.f, std::min((float)rect.width() / 10.f, 18.f));
		QPen pen(color);
		pen.setWidthF(width);
        float ht = width / 2 + 1;
        rect.adjust(ht, ht, -ht, -ht);
        pen.setCapStyle(Qt::RoundCap);
		painter.setPen(pen);
        int rotated_by = (m_overall_rotation + m_arc_rotation) * 16;
		int arc_length = (int)(m_arc_length * 360 * 16);
        painter.drawArc(rect, -rotated_by, -arc_length);
		painter.restore();
	}

	float get_arc_length() const { return m_arc_length; }
	int get_arc_rotation() const { return m_arc_rotation; }
	int get_overall_rotation() const { return m_overall_rotation; }
public slots:
	void set_arc_length(float val) { m_arc_length = val; notify_of_update(); }
	void set_arc_rotation(int val) { m_arc_rotation = val; notify_of_update(); }
	void set_overall_rotation(int val) { m_overall_rotation = val; notify_of_update(); }
signals:
	void updated();
private:
	float m_arc_length;
	int m_arc_rotation, m_overall_rotation;
	bool m_has_pending_updates;
	void notify_of_update() { if (!m_has_pending_updates) { m_has_pending_updates = true; emit updated(); } }
	QParallelAnimationGroup m_animation;
};

class CalibreStyle : public QProxyStyle {
    protected:
        QByteArray desktop_environment;
        QDialogButtonBox::ButtonLayout button_layout;
        int transient_scroller;

    private:
        void draw_scrollbar(const QStyleOptionSlider *scroll_bar, QPainter * painter, const QWidget * widget) const;

    public:
        CalibreStyle(int transient_scroller);
        virtual int styleHint(StyleHint hint, const QStyleOption *option = 0, const QWidget *widget = 0, QStyleHintReturn *returnData = 0) const;
        virtual QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption * option = 0, const QWidget * widget = 0) const;
        virtual int pixelMetric(PixelMetric metric, const QStyleOption * option = 0, const QWidget * widget = 0) const;
        virtual void drawComplexControl(ComplexControl control, const QStyleOptionComplex * option, QPainter * painter, const QWidget * widget = 0) const;
        virtual void drawPrimitive(PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget = 0) const;
        virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;
        virtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &size, const QWidget *widget) const;
};

/*!
    \class QProgressIndicator
    \brief The QProgressIndicator class lets an application display a progress indicator to show that a lengthy task is under way.

    Progress indicators are indeterminate and do nothing more than spin to show that the application is busy.
    \sa QProgressBar
*/
class QProgressIndicator : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(QSize displaySize READ displaySize WRITE setDisplaySize)
public:
    QProgressIndicator(QWidget* parent = 0, int size = 64, int interval = 0);

    /*! Returns a Boolean value indicating whether the component is currently animated.
        \return Animation state.
        \sa startAnimation stopAnimation
     */
    bool isAnimated () const;

    virtual QSize sizeHint() const;
    QSize displaySize() const { return m_displaySize; }
public slots:
    /*! Starts the spin animation.
        \sa stopAnimation isAnimated
     */
    void startAnimation();
	void start();

    /*! Stops the spin animation.
        \sa startAnimation isAnimated
     */
    void stopAnimation();
	void stop();

    /*! Set the size of this widget (used by sizeHint)
     * \sa displaySize
     */
    void setDisplaySize(QSize size);
    void setDisplaySize(int size) { setDisplaySize(QSize(size, size)); }
	void setSizeHint(int size);
	void setSizeHint(QSize size);
signals:
	void running_state_changed(bool);
protected:
    virtual void paintEvent(QPaintEvent * event);
private:
    QSize m_displaySize;
	SpinAnimator m_animator;
};

void set_no_activate_on_click(QWidget *widget);
void draw_snake_spinner(QPainter &painter, QRect rect, int angle, const QColor & light, const QColor & dark);
void set_menu_on_action(QAction* ac, QMenu* menu);
QMenu* menu_for_action(const QAction *ac);
void set_image_allocation_limit(int megabytes);
int get_image_allocation_limit();
QImage image_from_hicon(void* hicon);
QImage image_from_hbitmap(void* hbitmap);
double contrast_ratio(const QColor & c1, const QColor & c2);
void install_translator(void *v);
QString utf16_slice(const QString &src, qsizetype pos, qsizetype n=-1);
void set_icon_theme(bool is_dark, bool has_dark_user_theme, bool has_light_user_theme, bool has_any_user_theme);
QIcon icon_from_name(QString name, const QByteArray fallback_data);
QIcon icon_from_paths(QString any_path, QString light_path, QString dark_path);
