#include <QWidget>
#include <QPushButton>
#include <QPainter>
#include <QVBoxLayout>
#include <QSignalMapper>
#include <QLabel>
#include <QLineEdit>
#include <QMouseEvent>
#include <QtGui>
#include "ColorPicker.h"


static const int modeHands[8] = {
    1, 2, 3, 4, 4, 4, 4, 4
};


static const int modeHandAngles[6][4] = {
    { 0 }, { 0, 180 }, { 0, 180 - 30, 180 + 30 },
    { 0, 360 - 30, 30, 180 },  /* { 0, 180 - 30, 180, 180 + 30 }, */ { 360 - 45, 45, 180 - 45, 180 + 45 }
};


static const char* modeNames[6] = {
    "Monochromatic", "Complementary", "Analogous", "Accented Analogous", "Tetradic"
};


const char* ColorScheme::modeName()
{
    if (m_mode == 2 && m_secondaryHueDelta > 64 && m_secondaryHueDelta < (255 - 64))
        return "Triadic";
    if (m_mode == 3 && m_secondaryHueDelta > 64 && m_secondaryHueDelta < (255-64))
        return "Split-Complementary";
    return modeNames[m_mode];
}


int ColorScheme::hueCount()
{
    return modeHands[m_mode];
}


void ColorScheme::setHue(int a_index, float a_val)
{
    if (a_index == 0)
        m_primaryHue = unsigned(a_val * 255.0) & 0xff;
    else if (a_index == 1 && m_mode != 2)
        m_primaryHue = unsigned((a_val - 0.5) * 255.0) & 0xff;
    else if ((a_index == 2 && m_mode != 2) || (m_mode == 2 && a_index == 1))
        m_secondaryHueDelta = (unsigned(a_val * 255.0) - m_primaryHue) & 0xff;
    else if (m_mode == 3 || (m_mode == 2 && a_index == 2))
        m_secondaryHueDelta = (m_primaryHue - unsigned(a_val * 255.0)) & 0xff;
    else
        m_secondaryHueDelta = (unsigned((a_val - 0.5) * 255.0) - m_primaryHue) & 0xff;
}


float denormalizedHue(ColorScheme a_scheme, int a_index)
{
    if (a_index == 0)
        return float(a_scheme.m_primaryHue) / 255.0;
    if (a_index == 1 && a_scheme.m_mode != 2)
        return 0.5 + float(a_scheme.m_primaryHue) / 255.0;
    if ((a_index == 2 && a_scheme.m_mode != 2) || (a_scheme.m_mode == 2 && a_index == 1))
        return float((int)a_scheme.m_primaryHue + a_scheme.m_secondaryHueDelta) / 255.0;
    if (a_scheme.m_mode == 3 || (a_scheme.m_mode == 2 && a_index == 2))
        return float((int)a_scheme.m_primaryHue - (int)a_scheme.m_secondaryHueDelta) / 255.0;
    return 0.5 + float((int)a_scheme.m_primaryHue + (int)a_scheme.m_secondaryHueDelta) / 255.0;
}


float ColorScheme::hue(int a_index)
{
    float h = denormalizedHue(*this, a_index);
    while (h < 0.0) h += 1.0;
    while (h > 1.0) h -= 1.0;
    return h;
}


// Values from this come up the same as doing:   QColor::fromHsvF(hue, sat, 1.0).rgba()
uint32_t rgbFromHsvF(qreal h1, qreal s, qreal v, qreal /*a*/)
{
    uint16_t hue = h1 == qreal(-1.0) ? USHRT_MAX : qRound(h1 * 36000);
    h1 = hue == 36000 ? 0 : hue / qreal(6000.);
    s = uint16_t(qRound(s * USHRT_MAX)) / qreal(USHRT_MAX);
    v = uint16_t(qRound(v * USHRT_MAX)) / qreal(USHRT_MAX);
    const qreal h = h1;
    const int i = int(h);
    const qreal f = h - i;
    const qreal v2 = v * USHRT_MAX;
    uint8_t components[4] = {
        uint8_t(qRound(v2 * (qreal(1.0) - s)) >> 8),
        uint8_t(qRound(v2) >> 8),
        uint8_t(qRound(v2 * (qreal(1.0) - (s * f))) >> 8),
        uint8_t(qRound(v2 * (qreal(1.0) - (s * (qreal(1.0) - f)))) >> 8)
    };
    const int indexTable[10] = { 1, 2, 0, 0, 3, 1, 1, 2, 0, 0 };
    return 0xff000000 | (components[indexTable[i]] << 16) | (components[indexTable[i + 4]] << 8) | components[indexTable[i + 2]];
}


bool m_wheelCached = false;
QImage colorWheelCache;


void makeColorWheel(int siz, unsigned bgColor)
{
    const float twoPi = 2 * 3.142;
    const int cen = (siz / 2);
    const int rad = cen - 20;
    const int iter = siz * 10;

    QImage img(siz, siz, QImage::Format_ARGB32);
    img.fill(bgColor);
    uint32_t* bits = (uint32_t*)img.bits();
    int pitch = img.bytesPerLine() / sizeof(uint32_t);
    //double dSat = 1.0 / rad;
    for (int i = 0; i < iter; i++)
    {
        double hue = double(i) / iter;
        double c = ::cos(hue * twoPi);
        double s = ::sin(hue * twoPi);
        double x = cen + rad * c;
        double y = cen + rad * s;
        double sat = 1.0;
        for (int w = rad-30; w < rad; w++)
        {
            bits[int(y)*pitch + int(x)] = rgbFromHsvF(hue, sat, 1.0, 1.0);
            x -= c;
            y -= s;
			// Enable this if wanting to vary the saturation out from the center
            //sat -= dSat;
        }
    }
    // Smoothing technique where we scale it up a fraction and then back down again
    img = img.scaled(img.size() + QSize(6, 6), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    colorWheelCache = img.scaled(img.size() - QSize(6, 6), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    m_wheelCached = true;
}


static const int siz = 250;
static const int cen = (siz / 2);
static const int rad = (siz / 2) - 20;
//static const int iter = siz * 10;
static const int dotSiz = 18;// 16;
static const int dotAlpha = 96;// 64;

static const int titleHeight = 35;

static const int schemeWidth = 80;
static const int wheelWidth = siz * 2;// 600;

static const int paletteWidth = 150;
static const int paletteHeight = 350;

static const int xOff = schemeWidth;
static const int yOff = titleHeight;


class ModeButton : public QPushButton
{
public:
    ModeButton(QWidget* a_parent, int a_mode)
        : QPushButton(a_parent)
        , m_mode(a_mode)
    {
        setAutoExclusive(true);
        setCheckable(true);
        setFlat(true);
        setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
        setMinimumSize(45,45);
    }
    void paintEvent(QPaintEvent*)
    {
        QPainter p(this);
        QPixmap pix1("../color-dial-deselect.png");
        QPixmap pix2("../color-dial-select.png");
        QPixmap pixHand1("../color-pointer-deselect.png");
        QPixmap pixHand2("../color-pointer-select.png");
        bool checked = isChecked();
        int xoff = (width() - 55) / 2;
        int yoff = (height() - 55) / 2;
        p.drawPixmap(xoff, yoff, (checked) ? pix2 : pix1);
        QPixmap hands = (checked) ? pixHand2 : pixHand1;
        for (int j = 0; j < modeHands[m_mode]; j++)
        {
            QMatrix m;
            m = m.scale(0.5, 0.5);
            m = m.rotate(modeHandAngles[m_mode][j]);
            QPixmap newPix = hands.transformed(m, Qt::SmoothTransformation);
            QSize s = newPix.size() - hands.size() / 2;
            s = s / 2;
            p.drawPixmap(xoff - s.width(), yoff - s.height(), newPix);
        }
    }
private:
    int m_mode;
};


SchemeSelection::SchemeSelection(QWidget* a_parent) : QWidget(a_parent)
{
    QVBoxLayout* vbox = new QVBoxLayout(this);
    QSignalMapper* signalMapper = new QSignalMapper(this);
    connect(signalMapper, SIGNAL(mapped(int)), a_parent, SLOT(setMode(int)));
    for (int i = 0; i < ColorScheme::modes; i++) {
        modeButtons[i] = new ModeButton(this, i);
        connect(modeButtons[i], SIGNAL(clicked()), signalMapper, SLOT(map()));
        signalMapper->setMapping(modeButtons[i], i);
        vbox->addWidget(modeButtons[i]);
    }
    modeButtons[0]->setChecked(true);
    setLayout(vbox);
    vbox->setMargin(0);
    vbox->setSpacing(0);
}



// lineedit to name the scheme
// okay/cancel buttons
// styled titlebar


const unsigned bgColor1 = 0x444444;
const unsigned bgColor2 = 0x888888;
const int lineHeight = 50;

// Properties of a scheme:
//   mode (3-bits), primary hue (8-bits), angle / hue-delta (8-bits)
//   saturation (8-bits), stretch (5-bits)
ColorPicker::ColorPicker(QWidget* parent) : QDialog(parent), scheme(this)
{
    setModal(true);
    palette.m_mode = 0;
    palette.m_primaryHue = 0;
    palette.m_secondaryHueDelta = 10;
    palette.m_saturation = 0;
    palette.m_stretch = 0;
    scheme.setGeometry(0, 0, schemeWidth, paletteHeight + titleHeight);
    setFixedSize(schemeWidth + wheelWidth + paletteWidth, paletteHeight + titleHeight + lineHeight);
    m_hueMovingIdx = -1;

    QPushButton* okyBut = new QPushButton("Okay", this);
    QPushButton* canBut = new QPushButton("Cancel", this);
    okyBut->setGeometry(width() - 120, height() - 90, 100, 30);
    canBut->setGeometry(width() - 120, height() - 50, 100, 30);
    QLabel* nameLab = new QLabel("Palette's name: ", this);
    nameLab->setGeometry(10, height() - 50, 100, 30);
    QLineEdit* name = new QLineEdit(this);
    name->setGeometry(120, height() - 50, width() - 120 - 140, 30);
}


ColorPicker::~ColorPicker()
{
}


void ColorPicker::setMode(int a_mode)
{
    palette.m_mode = a_mode;
    update();
}


void ColorPicker::mousePressEvent(QMouseEvent* me)
{
    for (int h = palette.hueCount() - 1; h >= 0; h--)
    {
        int x = xOff + cen + (rad - dotSiz - 5) * ::cos(palette.hue(h) * twoPi);
        int y = yOff + cen + (rad - dotSiz - 5) * ::sin(palette.hue(h) * twoPi);
        QRect r(x - dotSiz / 2, y - dotSiz / 2, x + dotSiz / 2, y + dotSiz / 2);
        if (r.contains(me->pos()))
        {
            m_hueMovingIdx = h;
            return;
        }
    }
}


void ColorPicker::mouseReleaseEvent(QMouseEvent*)
{
    m_hueMovingIdx = -1;
}


void ColorPicker::mouseMoveEvent(QMouseEvent* me)
{
    if (m_hueMovingIdx != -1)
    {
        int x = me->pos().x() - (cen + xOff);
        int y = me->pos().y() - (cen + yOff);
        palette.setHue(m_hueMovingIdx, ::atan2(y, x) / twoPi);
        update();
    }
}


void ColorPicker::paintEvent(QPaintEvent*)
{
    QPainter p(this);
    p.fillRect(0, 0, width(), height(), QColor(bgColor1));

    QFont f("Segoe UI", 13, 200);
    p.setFont(f);
    p.setPen(Qt::white);
    //p.drawText(3 + schemeWidth, 15, "Color Wheel");
    p.drawText(3 + schemeWidth + wheelWidth, 15 + titleHeight, "Palette Preview");


    if (!m_wheelCached)
        makeColorWheel(siz, 0xFF000000 | bgColor2);
    p.drawImage(xOff, yOff, colorWheelCache);

    f.setPixelSize(32);
    p.setFont(f);
    p.drawText(3 + schemeWidth, 25, QString(palette.modeName()) + " Color Scheme");

    for (int h = 0; h < palette.hueCount(); h++)
    {
        int x = xOff + cen + (rad /*- dotSiz */ - 15 - 5) * ::cos(palette.hue(h) * twoPi);
        int y = yOff + cen + (rad /*- dotSiz */ - 15 - 5) * ::sin(palette.hue(h) * twoPi);
        if (h == 0) {
            p.setPen(QPen(QColor(255, 255, 255, dotAlpha + 20), 2));
            p.setBrush(QColor(192, 192, 192, dotAlpha + 20));
        }
        else {
            p.setPen(QPen(QColor(0, 0, 0, dotAlpha - 10), 2));
            p.setBrush(QColor(0, 0, 0, dotAlpha - 10));
        }
        p.drawEllipse(x - dotSiz / 2, y - dotSiz / 2, dotSiz, dotSiz);
    }

    QImage img(siz, siz, QImage::Format_ARGB32);
    img.fill(0xFF000000 | bgColor2);
    uint32_t* bits = (uint32_t*)img.bits();
    int pitch = img.bytesPerLine() / sizeof(uint32_t);
    float hue = palette.hue(0); // Get the primary color
    for (int j = 0; j < (siz - 120); j++)
    {
        for (int i = 0; i < (siz - 40); i++)
        {
            bits[int(j + 20)*pitch + int(i + 20)] = rgbFromHsvF(hue, j / float(siz - 120), i / float(siz - 40), 1.0);
        }
    }

    for (int j = 0; j < 16; j++)
    {
        for (int i = 0; i < 10; i++)
        {
            float t = i / float(9);
            for (int x = 0; x < 16; x++)
            {
                // shades, tones and tints   (black,grey,white)
                bits[int(j + siz - 80 + 10)*pitch + int(i * 20 + x + 25)] = rgbFromHsvF(hue, 1.0, 1.0 - t, 1.0); // shades
                bits[int(j + siz - 80 + 30)*pitch + int(i * 20 + x + 25)] = rgbFromHsvF(hue, 1.0 - t, 1.0 - (t*0.6), 1.0); // tones
                bits[int(j + siz - 80 + 50)*pitch + int(i * 20 + x + 25)] = rgbFromHsvF(hue, 1.0 - t, 1.0, 1.0); // tints
            }
        }
    }
    p.drawImage(schemeWidth + siz, titleHeight, img);


    for (int h = 0; h < palette.hueCount(); h++)
    {
        QColor c = QColor::fromHsvF(palette.hue(h), 1.0, 1.0);
        p.setPen(QPen(Qt::white));
        p.setBrush(c);
        p.drawRect(schemeWidth + wheelWidth + 20, titleHeight + 16 + 20 + 60 * h, 50, 50);

        QFont f("Segoe UI", 12, 175);
        p.setFont(f);
        QString x = QVariant(c.toRgb()).toString();
        p.drawText(schemeWidth + wheelWidth + 20 + 60, titleHeight + 16 + 20 + 60 * h + 16, x);
    }

    int colIdxForMode[5][5] = { { 0, 0, 0, 0, -1 }, { 0, 0, 0, 1, -1 }, { 0, 0, 1, 2, -1 }, { 0, 2, 3, 1, -1 }, { 0, 2, 3, 1, -1 } };
    for (int i = 0; i < 4; i++)
    {
        int idx = colIdxForMode[palette.m_mode][i];
        float sats[4] = { 1.0 - 0.65, 1.0 - 0.35, 1.0, 1.0 };
        float vals[4] = { 1.0, 1.0, 1.0 - 0.35, 1.0 - 0.65 };
        float baseTone = 0.15;
        float hue = palette.hue(idx);
        QColor c = QColor::fromHsvF(hue, 1.0 - baseTone, 1.0 - (baseTone*0.6));

        int unitSiz = (((siz*2) - 30) / 16) * 4;
        int unitW = unitSiz - 4;
        int x1 = schemeWidth + 20 + unitSiz * i;
        int x2 = x1 + unitW;

        while (idx == colIdxForMode[palette.m_mode][i + 1]) {
            x2 += unitSiz;
            i++;
        }

        int w = x2 - x1;
        p.fillRect(x1, titleHeight + siz + 10, w, 50, c);
        for (int s = 0; s < 4; s++)
            p.fillRect(x1 + (w / 4) * s, titleHeight + siz + 10 + 50, w / 4, 25, rgbFromHsvF(hue, sats[s], vals[s], 1.0));

    }
}
