/* Copyright (C) 2000-2003 Constantin Kaplinsky.  All Rights Reserved.
 * Copyright (C) 2011 D. R. Commander.  All Rights Reserved.
 * Copyright 2014 Pierre Ossman for Cendio AB
 * Copyright (C) 2016-2025 m-privacy GmbH, Berlin
 * 
 * This 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 software 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 software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */
#include <rdr/OutStream.h>
#include <rfb/encodings.h>
#include <rfb/SConnection.h>
#include <rfb/PixelBuffer.h>
#include <rfb/MP.h>
#include <rfb/TightMPEncoder.h>
#include <rfb/TightConstants.h>
#include <rfb/SMsgWriter.h>

#include <rfb/LogWriter.h>

using namespace rfb;

static LogWriter vlog("TightMPEncoder");

bool TightMPEncoder::acceptSetLevel = true;
int TightMPEncoder::mpLevel = -1;

TightMPEncoder::TightMPEncoder(SConnection* conn) :
	Encoder(conn, encodingTight, (EncoderFlags)(EncoderUseNativePF | EncoderLossy), -1, 9),
	mpCompression(MP_COMPRESSION_DEFAULT)
{
}

TightMPEncoder::~TightMPEncoder()
{
}

bool TightMPEncoder::isSupported()
{
	if (!conn->client.supportsEncoding(encodingTight)) {
		vlog.debug("isSupported(): encodingTight not supported by client");
		return false;
	}

	// Any one of these indicates support for MP
	if (mpLevel != -1)
		return true;

//	vlog.debug("isSupported(): mpLevel == -1, not supported");

	// Tight support, but not MP
	return false;
}

void TightMPEncoder::setMPLevel(int level)
{
	if (acceptSetLevel)
		mpLevel = level;
//	vlog.verbose("mpLevel set to %i", level);
}

int TightMPEncoder::getMPLevel()
{
	return mpLevel;
}

void TightMPEncoder::setMPCompression(int value)
{
	mpCompression = value;
}

int TightMPEncoder::getMPCompression()
{
	return mpCompression;
}

inline int copyCompact(rdr::U32 value, rdr::U8 * buffer)
{
	rdr::U8 b;

	b = value & 0x7F;
	if (value <= 0x7F) {
		buffer[0] = b;
		return 1;
	} else {
		buffer[0] = b | 0x80;
		b = value >> 7 & 0x7F;
		if (value <= 0x3FFF) {
			buffer[1] = b;
			return 2;
		} else {
			buffer[1] = b | 0x80;
			buffer[2] = value >> 14 & 0xFF;
			return 3;
		}
	}
}

void TightMPEncoder::writeRect(const PixelBuffer* pb, const Palette& palette, bool needStartEnd, Rect startR)
{
	const rdr::U8* buffer;
	int stride;
	rdr::OutStream* os;
	bool needLock = false;
	int startRectSize;
	int headerOffset;
	int dataSize;
	rdr::U8 * hcDataStart;

	buffer = pb->getBuffer(pb->getRect(), &stride);
	os = conn->getOutStream();

	if (needStartEnd) {
		startRectSize = conn->writer()->getStartRectSize();
		/* rects might be sent by other encoders */
		needLock = true;
	} else {
		startRectSize = 0;
	}

	hc.clear();
	/* startRectSize, one byte for encoding, max. three for copyCompact */
	hc.skip(startRectSize + 4);
	if (unlikely(!hc.compress(buffer, stride, pb->getRect(), pb->getPF(), mpLevel, conn->client.mpCompression)))
		return;
	dataSize = hc.length() - (startRectSize + 4);
//	vlog.verbose2("compression %u, rect x: %u, y: %u, w: %u, h: %u, size %uB, compressed %uB", conn->client.mpCompression, startR.tl.x, startR.tl.y, pb->getRect().br.x, pb->getRect().br.y, pb->getRect().width() * pb->getRect().height() * 4, dataSize);

	/* size of copyCompact depends on value of dataSize, so calc offset */
	if (dataSize <= 0x7F) {
		headerOffset = 2;
	} else {
		if (dataSize <= 0x3FFF) {
			headerOffset = 1;
		} else {
			headerOffset = 0;
		}
	}
	hcDataStart = ((rdr::U8*) hc.data()) + headerOffset;

	if (needStartEnd)
		conn->writer()->copyStartRect(startR, encoding, hcDataStart);
	hcDataStart[startRectSize] = tightMP << 4;
	copyCompact(dataSize, hcDataStart + startRectSize + 1);

	/* full buffer assembled now */
	if (needLock)
		MUTEX_LOCK(&rectWriteMutex);
	os->writeBytes(hcDataStart, startRectSize + 4 - headerOffset + dataSize);
	if (needLock)
		MUTEX_UNLOCK(&rectWriteMutex);
}

void TightMPEncoder::writeSolidRect(int width, int height, const PixelFormat& pf, const rdr::U8* colour)
{
	rdr::OutStream* os;
	struct rdr::queueBuffer * outBuffer;

//	vlog.verbose2("writeSolidRect(): colour %#08X", *((rdr::U32 *) colour));
	os = conn->getOutStream();
	outBuffer = os->getQueueBuffer(8);
	if (likely(outBuffer)) {
//		MPCompressor::compressionCount[MP_COMPRESSION_SOLID]++;
		outBuffer->data[0] = tightMP << 4;
		outBuffer->data[1] = 6; /* payload 6 bytes, writeCompact() below 0x7F not needed */
		outBuffer->data[2] = MP_COMPRESSION_SOLID;
		outBuffer->data[3] = 0;
		memcpy(outBuffer->data + 4, colour, 4);
		outBuffer->used = 8;
		if (unlikely(!os->submitQueueBuffer(outBuffer, outBuffer))) {
			vlog.error("writeSolidRect(): failed to submit queueBuffer");
			os->returnQueueBuffer(outBuffer);
		}
	} else {
		vlog.error("writeRect(): failed to get queueBuffer");
	}
}
