#include <stdio.h>
#include <string.h>
#include "common.h"
#include "../mqrspec.h"

static unsigned char v4frame[] = {
	0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc0,0x91,0x90,0x91,0x90,0x91,0x90,0x91,0x90,0x91,
	0xc1,0xc0,0xc0,0xc0,0xc0,0xc0,0xc1,0xc0,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0xc1,0xc0,0xc1,0xc1,0xc1,0xc0,0xc1,0xc0,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0xc1,0xc0,0xc1,0xc1,0xc1,0xc0,0xc1,0xc0,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0xc1,0xc0,0xc1,0xc1,0xc1,0xc0,0xc1,0xc0,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0xc1,0xc0,0xc0,0xc0,0xc0,0xc0,0xc1,0xc0,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc0,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x91,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x91,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x91,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x91,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x91,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

static void test_newFrame(void)
{
	int width, i, y;
	unsigned char *frame;

	testStart("Test empty frames");
	for(i=1; i<MQRSPEC_VERSION_MAX; i++) {
		frame = MQRspec_newFrame(i);
		width = MQRspec_getWidth(i);
		for(y=0; y<width; y++) {
			assert_zero(memcmp(&frame[y * width], &v4frame[y * MQRSPEC_WIDTH_MAX], width), "Mismatch found in version %d, line %d.\n", i, y);
		}
		free(frame);
	}
	testFinish();
}

static void test_newframe_invalid(void)
{
	unsigned char *frame;

	testStart("Checking MQRspec_newFrame with invalid version.");
	frame = MQRspec_newFrame(0);
	assert_null(frame, "MQRspec_newFrame(0) returns non-NULL.");
	frame = MQRspec_newFrame(MQRSPEC_VERSION_MAX+1);
	assert_null(frame, "MQRspec_newFrame(0) returns non-NULL.");
	testFinish();
}

/* See Table 10 (pp.115) of Appendix 1, JIS X0510:2004 */
static unsigned int calcFormatInfo(int type, int mask)
{
	unsigned int data, ecc, b, code;
	int i, c;

	data = (type << 12) | (mask << 10);
	ecc = data;
	b = 1 << 14;
	for(i=0; b != 0; i++) {
		if(ecc & b) break;
		b = b >> 1;
	}
	c = 4 - i;
	code = 0x537 << c ; //10100110111
	b = 1 << (10 + c);
	for(i=0; i<=c; i++) {
		if(b & ecc) {
			ecc ^= code;
		}
		code = code >> 1;
		b = b >> 1;
	}

	return (data | ecc) ^ 0x4445;
}

/* See Table 10 of Appendix 1. (pp.115) */
static const int typeTable[4][3] = {
	{ 0, -1, -1},
	{ 1,  2, -1},
	{ 3,  4, -1},
	{ 5,  6,  7}
};

static void test_format(void)
{
	unsigned int format;
	int version, l, mask;
	int type;
	int err = 0;

	testStart("Format info test");
	for(version=1; version<=MQRSPEC_VERSION_MAX; version++) {
		for(l=QR_ECLEVEL_L; l<=QR_ECLEVEL_Q; l++) {
			for(mask=0; mask<4; mask++) {
				format = MQRspec_getFormatInfo(mask, version, (QRecLevel)l);
				type = typeTable[version - 1][l];
				if(type == -1) {
					if(format != 0) {
						printf("Error in version %d, level %d, mask %d\n",
								version, l, mask);
						err++;
					}
				} else {
					if(format != calcFormatInfo(type, mask)) {
						printf("Error in version %d, level %d, mask %d\n",
								version, l, mask);
						err++;
					}
				}
			}
		}
	}
	testEnd(err);
}

static void print_format(void)
{
	unsigned int format;
	int i, j;

	puts("\nPrinting hex strings of format information.");
	for(i=0; i<4; i++) {
		for(j=0; j<8; j++) {
			format = calcFormatInfo(j, i);
			printf("0x%04x, ", format);
		}
		printf("\n");
	}
}

/**
 * See Table 7 of Appendix 1.
 */
static int datalen[4][3] = {
	{ 20,   0,  0},
	{ 40,  32,  0},
	{ 84,  68,  0},
	{128, 112, 80},
};

static void test_dataLength(void)
{
	int v, l;
	int bits;
	int err = 0;

	testStart("Test dataLength");
	for(v=0; v<4; v++) {
		for(l=0; l<3; l++) {
			bits = MQRspec_getDataLengthBit(v+1, (QRecLevel)l);
			if(bits != datalen[v][l]) {
				printf("Error in version %d, level %d.\n", v, l);
				err++;
			}
		}
	}
	testEnd(err);
}

int main(int argc, char **argv)
{
	int tests = 4;
	testInit(tests);
	test_newFrame();
	test_newframe_invalid();
	test_format();
	test_dataLength();
	testReport(tests);

	if(argc > 1) {
		print_format();
	}

	return 0;
}
