/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
 */

#include "lib.h"
#include "str.h"

#include "sieve-common.h"
#include "sieve-error.h"
#include "sieve-code.h"

#include "sieve-binary-private.h"

/* Quick 'n dirty debug */
#if 0
#define debug_printf(...) printf ("lineinfo: " __VA_ARGS__)
#else
#define debug_printf(...)
#endif

/*
 * Opcodes
 */

enum {
	LINPROG_OP_COPY,
	LINPROG_OP_ADVANCE_PC,
	LINPROG_OP_ADVANCE_LINE,
	LINPROG_OP_SET_COLUMN,
	LINPROG_OP_SPECIAL_BASE
};

#define LINPROG_LINE_BASE   0
#define LINPROG_LINE_RANGE  4

/*
 * Lineinfo writer
 */

struct sieve_binary_debug_writer {
	struct sieve_binary_block *sblock;

	sieve_size_t address;
	unsigned int line;
	unsigned int column;
};

struct sieve_binary_debug_writer *
sieve_binary_debug_writer_init(struct sieve_binary_block *sblock)
{
	struct sieve_binary_debug_writer *dwriter;

	dwriter = i_new(struct sieve_binary_debug_writer, 1);
	dwriter->sblock = sblock;

	return dwriter;
}

void sieve_binary_debug_writer_deinit(
	struct sieve_binary_debug_writer **dwriter)
{
	i_free(*dwriter);
	*dwriter = NULL;
}

void sieve_binary_debug_emit(struct sieve_binary_debug_writer *dwriter,
			     sieve_size_t code_address, unsigned int code_line,
			     unsigned int code_column)
{
	i_assert(code_address >= dwriter->address);

	struct sieve_binary_block *sblock = dwriter->sblock;
	sieve_size_t address_inc = code_address - dwriter->address;
	int line_inc = (code_line > dwriter->line ?
			(int)(code_line - dwriter->line) :
			-(int)(dwriter->line - code_line));
	unsigned int sp_opcode = 0;

	/* Check for applicability of special opcode */
	if (line_inc > 0 &&
	    (LINPROG_LINE_BASE + LINPROG_LINE_RANGE - 1) >= line_inc) {
		sp_opcode = LINPROG_OP_SPECIAL_BASE +
			(line_inc - LINPROG_LINE_BASE) +
			(LINPROG_LINE_RANGE * address_inc);

		if (sp_opcode > 255)
			sp_opcode = 0;
	}

	/* Update line and address */
	if (sp_opcode == 0) {
		if (line_inc != 0) {
			(void)sieve_binary_emit_byte(sblock,
						     LINPROG_OP_ADVANCE_LINE);
			(void)sieve_binary_emit_unsigned(
				sblock, (unsigned int)line_inc);
		}

		if (address_inc > 0) {
			(void)sieve_binary_emit_byte(sblock,
						     LINPROG_OP_ADVANCE_PC);
			(void)sieve_binary_emit_unsigned(sblock, address_inc);
		}
	} else {
		(void)sieve_binary_emit_byte(sblock, sp_opcode);
	}

	/* Set column */
	if (dwriter->column != code_column) {
		(void)sieve_binary_emit_byte(sblock, LINPROG_OP_SET_COLUMN);
		(void)sieve_binary_emit_unsigned(sblock, code_column);
	}

	/* Generate matrix row */
	(void)sieve_binary_emit_byte(sblock, LINPROG_OP_COPY);

	dwriter->address = code_address;
	dwriter->line = code_line;
	dwriter->column = code_column;
}

/*
 * Debug reader
 */

struct sieve_binary_debug_reader {
	struct sieve_binary_block *sblock;

	sieve_size_t address, last_address;
	unsigned int line, last_line;

	unsigned int column;

	sieve_size_t state;
};

struct sieve_binary_debug_reader *
sieve_binary_debug_reader_init(struct sieve_binary_block *sblock)
{
	struct sieve_binary_debug_reader *dreader;

	dreader = i_new(struct sieve_binary_debug_reader, 1);
	dreader->sblock = sblock;

	return dreader;
}

void sieve_binary_debug_reader_deinit(
	struct sieve_binary_debug_reader **dreader)
{
	i_free(*dreader);
	*dreader = NULL;
}

void sieve_binary_debug_reader_reset(struct sieve_binary_debug_reader *dreader)
{
	dreader->address = 0;
	dreader->line = 0;
	dreader->column = 0;
	dreader->state = 0;
}

unsigned int
sieve_binary_debug_read_line(struct sieve_binary_debug_reader *dreader,
			     sieve_size_t code_address)
{
	size_t linprog_size;
	sieve_size_t address;
	unsigned int line;

	if (code_address < dreader->last_address)
		sieve_binary_debug_reader_reset(dreader);

	if (code_address >= dreader->last_address &&
	    code_address < dreader->address) {
		debug_printf("%08llx: NOOP [%08llx]\n",
			     (unsigned long long)dreader->state,
			     (unsigned long long)code_address);
		return dreader->last_line;
	}

	address = dreader->address;
	line = dreader->line;

	debug_printf("%08llx: READ [%08llx]\n",
		     (unsigned long long)dreader->state,
		     (unsigned long long)code_address);

	linprog_size = sieve_binary_block_get_size(dreader->sblock);
	while (dreader->state < linprog_size) {
		unsigned int opcode;
		unsigned int value;
		int line_inc;

		if (sieve_binary_read_byte(dreader->sblock,
					   &dreader->state, &opcode)) {
			switch (opcode) {
			case LINPROG_OP_COPY:
				debug_printf("%08llx: COPY ==> %08llx: %ld\n",
					     (unsigned long long)dreader->state,
					     (unsigned long long)address, line);

				dreader->last_address = dreader->address;
				dreader->last_line = dreader->line;

				dreader->address = address;
				dreader->line = line;

				if (code_address < address)
					return dreader->last_line;
				else if (code_address == address)
					return dreader->line;
				break;
			case LINPROG_OP_ADVANCE_PC:
				debug_printf("%08llx: ADV_PC\n",
					     (unsigned long long)dreader->state);
				if (!sieve_binary_read_unsigned(
					dreader->sblock, &dreader->state,
					&value)) {
					sieve_binary_debug_reader_reset(dreader);
					return 0;
				}
				debug_printf("        : + %d\n", value);
				address += value;
				break;
			case LINPROG_OP_ADVANCE_LINE:
				debug_printf("%08llx: ADV_LINE\n",
					     (unsigned long long)dreader->state);
				if (!sieve_binary_read_unsigned(
					dreader->sblock, &dreader->state,
					&value)) {
					sieve_binary_debug_reader_reset(dreader);
					return 0;
				}
				line_inc = (int)value;
				debug_printf("        : + %d\n", line_inc);
				line = (line_inc > 0 ?
					line + (unsigned int)line_inc :
					line - (unsigned int)-line_inc);
				break;
			case LINPROG_OP_SET_COLUMN:
				debug_printf("%08llx: SET_COL\n",
					     (unsigned long long)dreader->state);
				if (!sieve_binary_read_unsigned(
					dreader->sblock, &dreader->state,
					&value)) {
					sieve_binary_debug_reader_reset(dreader);
					return 0;
				}
				debug_printf("        : = %d\n", value);
				dreader->column = value;
				break;
			default:
				opcode -= LINPROG_OP_SPECIAL_BASE;

				address += (opcode / LINPROG_LINE_RANGE);
				line += LINPROG_LINE_BASE +
					(opcode % LINPROG_LINE_RANGE);

				debug_printf("%08llx: SPECIAL\n",
					     (unsigned long long)dreader->state);
				debug_printf("        :  +A %d +L %d\n",
					     (opcode / LINPROG_LINE_RANGE),
					      LINPROG_LINE_BASE +
						(opcode % LINPROG_LINE_RANGE));
				break;
			}
		} else {
			debug_printf("OPCODE READ FAILED\n");
			sieve_binary_debug_reader_reset(dreader);
			return 0;
		}
	}

	return dreader->line;
}

