/***************************************************************************
                            threads.cpp  -  threads
                             -------------------
    begin                : Thu May 09 17:01:44 CET 2002
    copyright            : (C) 2015 Timothy Pearson <kb9vqf@pearsoncomputing.net>
                           (C) 2002 by Tim Jansen
    email                : tim@tjansen.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "kvncview.h"

#include <kdebug.h>
#include <tdeapplication.h>

#include "vncviewer.h"
#include "threads.h"

#include <tqcstring.h>
#include <tqpainter.h>

#include <math.h>

extern void pnmscale_fractional(const TQImage& src, TQImage& dst, int x, int y, int w, int h);

int getPassword(char * &passwd);

extern rfbBool newclient(rfbClient *cl)
{
	int width = cl->width, height = cl->height, depth = cl->format.bitsPerPixel;
	int size = width * height * (depth / 8);
	uint8_t *buf = new uint8_t[size];
	memset(buf, '\0', size);
	cl->frameBuffer = buf;
	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;
	
	SetFormatAndEncodings(cl);
	
	return true;
}

extern void updatefb(rfbClient* cl, int x, int y, int w, int h)
{
// 	kdDebug(5011) << "updated client: x: " << x << ", y: " << y << ", w: " << w << ", h: " << h << endl;
	
	int width = cl->width, height = cl->height;
	
	TQImage img(cl->frameBuffer, width, height, 32, NULL, 256, TQImage::IgnoreEndian);
	
	if (img.isNull())
		kdDebug(5011) << "image not loaded" << endl;
	
	
	ControllerThreadObject *t = (ControllerThreadObject*)rfbClientGetClientData(cl, 0);
	
	t->setImage(img);
	t->queueDrawRegion(x, y, w, h);
}

extern char *passwd(rfbClient *cl)
{
	Q_UNUSED(cl)

	kdDebug(5011) << "password request" << endl;

	char *passwd;
	if (getPassword(passwd)) {
		return passwd;
	}
	else {
		return NULL;
	}
}

extern void authresults(rfbClient *cl, uint32_t authResult)
{
	kdDebug(5011) << "authentication result: " << authResult << endl;
	
	ControllerThreadObject *t = (ControllerThreadObject*)rfbClientGetClientData(cl, 0);

	t->authenticationResults(authResult);
}

extern void networkstat(rfbClient *cl, uint32_t statuscode)
{
	kdDebug(5011) << "network status: " << statuscode << endl;
	
	ControllerThreadObject *t = (ControllerThreadObject*)rfbClientGetClientData(cl, 0);

	t->networkStatus(statuscode);
}

extern void output(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	
	TQString message;
	message.vsprintf(format, args);
	
	va_end(args);
	
	kdDebug(5011) << message.local8Bit();
	
	if (message.contains("Could not open")) {
		kdDebug(5011) << "Server not found!" << endl;
	}
	
	if (message.contains("VNC authentication succeeded")) {
		kdDebug(5011) << "Password OK" << endl;
	}
}

ControllerThreadObject::ControllerThreadObject(KVncView *v, volatile bool &quitFlag) :
	cl(0L),
	m_view(v),
	m_status(REMOTE_VIEW_CONNECTING),
	m_quitFlag(quitFlag),
	m_scaling(false),
	m_scalingWidth(-1),
	m_scalingHeight(-1),
	m_resizeEntireFrame(false)
{
	TQMutexLocker locker(&mutex);

	cl = rfbGetClient(8, 3, 4);
}

ControllerThreadObject::~ControllerThreadObject() {
	TQMutexLocker locker(&mutex);

	rfbClientCleanup(cl);
}

void ControllerThreadObject::changeStatus(RemoteViewStatus s) {
	m_status = s;
	TQApplication::postEvent(m_view, new StatusChangeEvent(s));
}

void ControllerThreadObject::sendFatalError(ErrorCode s) {
	m_quitFlag = true;
	TQApplication::postEvent(m_view, new FatalErrorEvent(s));
}

void ControllerThreadObject::queueDrawRegion(int x, int y, int w, int h) {
	if (m_scaling) {
		// Rescale desktop

		int new_x;
		int new_y;
		int new_w;
		int new_h;

		mutex.lock();

		if (m_resizeEntireFrame) {
			m_scaledImage.create(m_scalingWidth, m_scalingHeight, 32);

			new_x = 0;
			new_y = 0;
			new_w = m_scalingWidth;
			new_h = m_scalingHeight;

			TQImage scaledBlock = m_image.smoothScale(new_w, new_h);
			bitBlt(&m_scaledImage, new_x, new_y, &scaledBlock, 0, 0, new_w, new_h);

			m_resizeEntireFrame = false;
		}
		else {
			// Extend redraw boundaries to avoid pixelation artifacts due to rounding errors
			x = x - round((2.0 * m_image.width()) / m_scalingWidth);
			y = y - round((2.0 * m_image.height()) / m_scalingHeight);
			w = w + round((4.0 * m_image.width()) / m_scalingWidth);
			h = h + round((4.0 * m_image.height()) / m_scalingHeight);

			if (x < 0) {
				x = 0;
			}
			if (y < 0) {
				y = 0;
			}
			if (w > m_image.width()) {
				w = m_image.width();
			}
			if (h > m_image.height()) {
				h = m_image.height();
			}

			new_x = round((x * m_scalingWidth) / m_image.width());
			new_y = round((y * m_scalingHeight) / m_image.height());
			new_w = round((w * m_scalingWidth) / m_image.width());
			new_h = round((h * m_scalingHeight) / m_image.height());

			TQImage scaledBlock(m_scalingWidth, m_scalingHeight, 32);
			pnmscale_fractional(m_image, scaledBlock, new_x, new_y, new_w, new_h);
			bitBlt(&m_scaledImage, new_x, new_y, &scaledBlock, new_x, new_y, new_w, new_h);
		}

		mutex.unlock();

		DrawScreenRegion(new_x, new_y, new_w, new_h);
	}
	else {
		DrawScreenRegion(x, y, w, h);
	}
}

void ControllerThreadObject::setImage(const TQImage &img) {
	TQMutexLocker locker(&mutex);

	m_image = img;
}

const TQImage ControllerThreadObject::image(int x, int y, int w, int h) {
	TQMutexLocker locker(&mutex);

	if (m_scaling) {
		return m_scaledImage.copy(x, y, w, h);
	}
	else {
		return m_image.copy(x, y, w, h);
	}
}

void ControllerThreadObject::setScaling(int w, int h) {
	bool scale;

	if (w <= 0) {
		scale = false;
	}
	else {
		scale = true;
	}

	if ((m_scalingWidth != w) || (m_scalingHeight = h) || (m_scaling != scale)) {
		m_resizeEntireFrame = true;
	}

	m_scaling = scale;
	m_scalingWidth = w;
	m_scalingHeight = h;
}

void ControllerThreadObject::run() {
	mutex.lock();

	rfbClientLog = output;
	rfbClientErr = output;
	cl->MallocFrameBuffer = newclient;
	cl->canHandleNewFBSize = true;
	cl->GetPassword = passwd;
	cl->AuthenticationResults = authresults;
	cl->NetworkStatus = networkstat;
	cl->GotFrameBufferUpdate = updatefb;
	rfbClientSetClientData(cl, 0, this);

	// make a copy of the host string...
	char *host = (char*) malloc(m_view->host().length());
	strcpy(host, m_view->host().ascii());

	cl->serverHost = host;

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

	mutex.unlock();

	if(!rfbInitClient(cl, 0, 0)) {
		sendFatalError(ERROR_INTERNAL);
		// Terminate thread
		TQThread::exit();
		return;
	}

	TQApplication::postEvent(m_view,
				new ScreenResizeEvent(cl->width,
						      cl->height));

	changeStatus(REMOTE_VIEW_CONNECTED);
	
	while (!m_quitFlag) {	
		int i = WaitForMessage(cl, 500);
		if (i < 0) {
			m_quitFlag = true;
			changeStatus(REMOTE_VIEW_DISCONNECTED);

			// Terminate thread
			TQThread::exit();
			return;
		}
		if (i) {
			if(!HandleRFBServerMessage(cl)) {
				m_quitFlag = true;
				changeStatus(REMOTE_VIEW_DISCONNECTED);

				// Terminate thread
				TQThread::exit();
				return;
			}
		}
	}

	m_quitFlag = true;
	changeStatus(REMOTE_VIEW_DISCONNECTED);

	// Terminate thread
	TQThread::exit();
}

void ControllerThreadObject::authenticationResults(int resultCode) {
	if (resultCode == rfbVncAuthOK) {
		changeStatus(REMOTE_VIEW_PREPARING);
	}
	else {
		sendFatalError(ERROR_AUTHENTICATION);

		// Terminate thread
		TQThread::exit();
	}
}

void ControllerThreadObject::networkStatus(int statusCode) {
	if (statusCode == rfbNetworkConnectionSuccess) {
		// Stage 1 OK...
		changeStatus(REMOTE_VIEW_AUTHENTICATING);
	}
	else if (statusCode == rfbNetworkRFBConnectionSuccess) {
		// Stage 2 OK!
	}
	else {
		if (statusCode == rfbNetworkConnectionClosed) {
			sendFatalError(ERROR_CONNECTION);
		}
		else if (statusCode == rfbNetworkConnectionFailed) {
			sendFatalError(ERROR_CONNECTION);
		}
		else if (statusCode == rfbNetworkNameResolutionFailed) {
			sendFatalError(ERROR_NAME);
		}
		else if (statusCode == rfbNetworkRFBServerNotValid) {
			sendFatalError(ERROR_IO);
		}
		else if (statusCode == rfbNetworkRFBProtocolFailure) {
			sendFatalError(ERROR_PROTOCOL);
		}
	
		// Terminate thread
		TQThread::exit();
	}
}

enum RemoteViewStatus ControllerThreadObject::status() {
	return m_status;
}

void ControllerThreadObject::queueMouseEvent(int x, int y, int buttonMask) {
	SendPointerEvent(cl, x, y, buttonMask);
}

void ControllerThreadObject::queueKeyEvent(unsigned int k, bool down) {
	SendKeyEvent(cl, k, down);
}

void ControllerThreadObject::queueClientCut(const TQString &text) {
	SendClientCutText(cl, (char*)text.ascii(), text.length());
}

#include "threads.moc"