/****************************************************************************
**
** Copyright (C) 2007 - 2013 Urs Wolfer <uwolfer @ kde.org>
**
** This file is part of KDE.
**
** 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
** (at your option) 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.
**
** You should have received a copy of the GNU General Public License
** along with this program; see the file COPYING. If not, write to
** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
** Boston, MA 02110-1301, USA.
**
****************************************************************************/

#include "vncclientthread.h"

#include <QMutexLocker>
#include <QTimer>

//for detecting intel AMT KVM vnc server
static const QString INTEL_AMT_KVM_STRING= "Intel(r) AMT KVM";
static QString outputErrorMessageString;

QVector<QRgb> VncClientThread::m_colorTable;

void VncClientThread::setClientColorDepth(rfbClient* cl, VncClientThread::ColorDepth cd)
{
    switch(cd) {
    case bpp8:
        if (m_colorTable.isEmpty()) {
            m_colorTable.resize(256);
            int r,g,b;
            for (int i = 0; i < 256; ++i) {
                //pick out the red (3 bits), green (3 bits) and blue (2 bits) bits and make them maximum significant in 8bits
                //this gives a colortable for 8bit true colors
                r= (i & 0x07) << 5;
                g= (i & 0x38) << 2;
                b= i & 0xc0;
                m_colorTable[i] = qRgb(r, g, b);
            }
        }
        cl->format.depth = 8;
        cl->format.bitsPerPixel = 8;
        cl->format.redShift = 0;
        cl->format.greenShift = 3;
        cl->format.blueShift = 6;
        cl->format.redMax = 7;
        cl->format.greenMax = 7;
        cl->format.blueMax = 3;
        break;
    case bpp16:
        cl->format.depth = 16;
        cl->format.bitsPerPixel = 16;
        cl->format.redShift = 11;
        cl->format.greenShift = 5;
        cl->format.blueShift = 0;
        cl->format.redMax = 0x1f;
        cl->format.greenMax = 0x3f;
        cl->format.blueMax = 0x1f;
        break;
    case bpp32:
    default:
        cl->format.depth = 24;
        cl->format.bitsPerPixel = 32;
        cl->format.redShift = 16;
        cl->format.greenShift = 8;
        cl->format.blueShift = 0;
        cl->format.redMax = 0xff;
        cl->format.greenMax = 0xff;
        cl->format.blueMax = 0xff;
    }
}

rfbBool VncClientThread::newclient(rfbClient *cl)
{
    VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
    Q_ASSERT(t);

    //8bit color hack for Intel(r) AMT KVM "classic vnc" = vnc server built in in Intel Vpro chipsets.
    if (INTEL_AMT_KVM_STRING == cl->desktopName) {
        kDebug(5011) << "Intel(R) AMT KVM: switching to 8 bit color depth (workaround, recent libvncserver needed)";
        t->setColorDepth(bpp8);
    }
    setClientColorDepth(cl, t->colorDepth());

    const int width = cl->width, height = cl->height, depth = cl->format.bitsPerPixel;
    const int size = width * height * (depth / 8);
    if (t->frameBuffer)
        delete [] t->frameBuffer; // do not leak if we get a new framebuffer size
    t->frameBuffer = new uint8_t[size];
    cl->frameBuffer = t->frameBuffer;
    memset(cl->frameBuffer, '\0', size);

    switch (t->quality()) {
    case RemoteView::High:
        cl->appData.encodingsString = "copyrect zlib hextile raw";
        cl->appData.compressLevel = 0;
        cl->appData.qualityLevel = 9;
        break;
    case RemoteView::Medium:
        cl->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw";
        cl->appData.compressLevel = 5;
        cl->appData.qualityLevel = 7;
        break;
    case RemoteView::Low:
    case RemoteView::Unknown:
    default:
        cl->appData.encodingsString = "copyrect tight zrle ultra zlib hextile corre rre raw";
        cl->appData.compressLevel = 9;
        cl->appData.qualityLevel = 1;
    }

    SetFormatAndEncodings(cl);
    kDebug(5011) << "Client created";
    return true;
}

void VncClientThread::updatefb(rfbClient* cl, int x, int y, int w, int h)
{
//     kDebug(5011) << "updated client: x: " << x << ", y: " << y << ", w: " << w << ", h: " << h;
    VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
    Q_ASSERT(t);

    const int width = cl->width, height = cl->height;
    QImage img;
    switch(t->colorDepth()) {
    case bpp8:
        img = QImage(cl->frameBuffer, width, height, QImage::Format_Indexed8);
        img.setColorTable(m_colorTable);
        break;
    case bpp16:
        img = QImage(cl->frameBuffer, width, height, QImage::Format_RGB16);
        break;
    case bpp32:
        img = QImage(cl->frameBuffer, width, height, QImage::Format_RGB32);
        break;
    }

    if (img.isNull()) {
        kDebug(5011) << "image not loaded";
    }
    
    if (t->m_stopped) {
        return; // sending data to a stopped thread is not a good idea
    }

    t->setImage(img);

    t->emitUpdated(x, y, w, h);
}

void VncClientThread::cuttext(rfbClient* cl, const char *text, int textlen)
{
    const QString cutText = QString::fromUtf8(text, textlen);
    kDebug(5011) << cutText;

    if (!cutText.isEmpty()) {
        VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
        Q_ASSERT(t);

        t->emitGotCut(cutText);
    }
}

char *VncClientThread::passwdHandler(rfbClient *cl)
{
    kDebug(5011) << "password request";

    VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
    Q_ASSERT(t);

    t->passwordRequest();
    t->m_passwordError = true;

    return strdup(t->password().toUtf8());
}
 
rfbCredential *VncClientThread::credentialHandler(rfbClient *cl, int credentialType)
{
    kDebug(5011) << "credential request" << credentialType;

    VncClientThread *t = (VncClientThread*)rfbClientGetClientData(cl, 0);
    Q_ASSERT(t);

    rfbCredential *cred = 0;

    switch (credentialType) {
        case rfbCredentialTypeUser:
            t->passwordRequest(true);
            t->m_passwordError = true;

            cred = new rfbCredential;
            cred->userCredential.username = strdup(t->username().toUtf8());
            cred->userCredential.password = strdup(t->password().toUtf8());
            break;
        default:
            kError(5011) << "credential request failed, unspported credentialType:" << credentialType;
            t->outputErrorMessage(i18n("VNC authentication type is not supported."));
            break;
    }
    return cred;
}

void VncClientThread::outputHandler(const char *format, ...)
{
    va_list args;
    va_start(args, format);

    QString message;
    message.vsprintf(format, args);

    va_end(args);

    message = message.trimmed();

    kDebug(5011) << message;

    if ((message.contains("Couldn't convert ")) ||
            (message.contains("Unable to connect to VNC server")))
        outputErrorMessageString = i18n("Server not found.");

    if ((message.contains("VNC connection failed: Authentication failed, too many tries")) ||
            (message.contains("VNC connection failed: Too many authentication failures")))
        outputErrorMessageString = i18n("VNC authentication failed because of too many authentication tries.");

    if (message.contains("VNC connection failed: Authentication failed"))
        outputErrorMessageString = i18n("VNC authentication failed.");

    if (message.contains("VNC server closed connection"))
        outputErrorMessageString = i18n("VNC server closed connection.");

    // internal messages, not displayed to user
    if (message.contains("VNC server supports protocol version 3.889")) // see http://bugs.kde.org/162640
        outputErrorMessageString = "INTERNAL:APPLE_VNC_COMPATIBILTY";
}

VncClientThread::VncClientThread(QObject *parent)
        : QThread(parent)
        , frameBuffer(0)
        , cl(0)
        , m_stopped(false)
{
    outputErrorMessageString.clear(); //don't deliver error messages of old instances...
    QMutexLocker locker(&mutex);

    QTimer *outputErrorMessagesCheckTimer = new QTimer(this);
    outputErrorMessagesCheckTimer->setInterval(500);
    connect(outputErrorMessagesCheckTimer, SIGNAL(timeout()), this, SLOT(checkOutputErrorMessage()));
    outputErrorMessagesCheckTimer->start();
}

VncClientThread::~VncClientThread()
{
    if(isRunning()) {
        stop();
        terminate();
        const bool quitSuccess = wait(1000);
        kDebug(5011) << "Attempting to stop in deconstructor, will crash if this fails:" << quitSuccess;
    }

    if (cl) {
        // Disconnect from vnc server & cleanup allocated resources
        rfbClientCleanup(cl);
    }

    delete [] frameBuffer;
}

void VncClientThread::checkOutputErrorMessage()
{
    if (!outputErrorMessageString.isEmpty()) {
        kDebug(5011) << outputErrorMessageString;
        QString errorMessage = outputErrorMessageString;
        outputErrorMessageString.clear();
        // show authentication failure error only after the 3rd unsuccessful try
        if ((errorMessage != i18n("VNC authentication failed.")) || m_passwordError)
            outputErrorMessage(errorMessage);
    }
}

void VncClientThread::setHost(const QString &host)
{
    QMutexLocker locker(&mutex);
    m_host = host;
}

void VncClientThread::setPort(int port)
{
    QMutexLocker locker(&mutex);
    m_port = port;
}

void VncClientThread::setQuality(RemoteView::Quality quality)
{
    m_quality = quality;
    //set color depth dependent on quality
    switch(quality) {
    case RemoteView::Low:
        setColorDepth(bpp8);
        break;
    case RemoteView::High:
        setColorDepth(bpp32);
        break;
    case RemoteView::Medium:
    default:
        setColorDepth(bpp16);
    }
}

void VncClientThread::setColorDepth(ColorDepth colorDepth)
{
    m_colorDepth= colorDepth;
}

RemoteView::Quality VncClientThread::quality() const
{
    return m_quality;
}

VncClientThread::ColorDepth VncClientThread::colorDepth() const
{
    return m_colorDepth;
}

void VncClientThread::setImage(const QImage &img)
{
    QMutexLocker locker(&mutex);
    m_image = img;
}

const QImage VncClientThread::image(int x, int y, int w, int h)
{
    QMutexLocker locker(&mutex);

    if (w == 0) // full image requested
        return m_image;
    else
        return m_image.copy(x, y, w, h);
}

void VncClientThread::emitUpdated(int x, int y, int w, int h)
{
    emit imageUpdated(x, y, w, h);
}

void VncClientThread::emitGotCut(const QString &text)
{
    emit gotCut(text);
}

void VncClientThread::stop()
{
    QMutexLocker locker(&mutex);
    m_stopped = true;
}

void VncClientThread::run()
{
    QMutexLocker locker(&mutex);

    while (!m_stopped) { // try to connect as long as the server allows
        locker.relock();
        m_passwordError = false;
        locker.unlock();

        rfbClientLog = outputHandler;
        rfbClientErr = outputHandler;
        //24bit color dept in 32 bits per pixel = default. Will change colordepth and bpp later if needed
        cl = rfbGetClient(8, 3, 4);
        setClientColorDepth(cl, this->colorDepth());
        cl->MallocFrameBuffer = newclient;
        cl->canHandleNewFBSize = true;
        cl->GetPassword = passwdHandler;
        cl->GetCredential = credentialHandler;
        cl->GotFrameBufferUpdate = updatefb;
        cl->GotXCutText = cuttext;
        rfbClientSetClientData(cl, 0, this);

        locker.relock();
        cl->serverHost = strdup(m_host.toUtf8().constData());

        if (m_port < 0 || !m_port) // port is invalid or empty...
            m_port = 5900; // fallback: try an often used VNC port

        if (m_port >= 0 && m_port < 100) // the user most likely used the short form (e.g. :1)
            m_port += 5900;
        cl->serverPort = m_port;
        locker.unlock();

        kDebug(5011) << "--------------------- trying init ---------------------";

        if (rfbInitClient(cl, 0, 0))
            break;
        else
            cl = 0;

        locker.relock();
        if (m_passwordError)
            continue;

        return;
    }

    locker.relock();
    kDebug(5011) << "--------------------- Starting main VNC event loop ---------------------";
    while (!m_stopped) {
        locker.unlock();
        const int i = WaitForMessage(cl, 500);
        if (m_stopped || i < 0) {
            break;
        }
        if (i) {
            if (!HandleRFBServerMessage(cl)) {
                break;
            }
        }

        locker.relock();
        while (!m_eventQueue.isEmpty()) {
            ClientEvent* clientEvent = m_eventQueue.dequeue();
            locker.unlock();
            clientEvent->fire(cl);
            delete clientEvent;
            locker.relock();
        }
    }

    m_stopped = true;
}

ClientEvent::~ClientEvent()
{
}

void PointerClientEvent::fire(rfbClient* cl)
{
    SendPointerEvent(cl, m_x, m_y, m_buttonMask);
}

void KeyClientEvent::fire(rfbClient* cl)
{
    SendKeyEvent(cl, m_key, m_pressed);
}

void ClientCutEvent::fire(rfbClient* cl)
{
    SendClientCutText(cl, text.toUtf8().data(), text.size());
}

void VncClientThread::mouseEvent(int x, int y, int buttonMask)
{
    QMutexLocker lock(&mutex);
    if (m_stopped)
        return;

    m_eventQueue.enqueue(new PointerClientEvent(x, y, buttonMask));
}

void VncClientThread::keyEvent(int key, bool pressed)
{
    QMutexLocker lock(&mutex);
    if (m_stopped)
        return;

    m_eventQueue.enqueue(new KeyClientEvent(key, pressed));
}

void VncClientThread::clientCut(const QString &text)
{
    QMutexLocker lock(&mutex);
    if (m_stopped)
        return;

    m_eventQueue.enqueue(new ClientCutEvent(text));
}

#include "moc_vncclientthread.cpp"
