/*
	Simple iso-8850-2 to UTF-8 convert, $Revision: 1.3 $

	Author: Wojciech Muła
	e-mail: wojciech_mula@poczta.onet.pl
	www:    http://0x80.pl/

	License: public domain

	initial release 25-02-2009, last update $Date: 2009-02-26 19:23:17 $

	----------------------------------------------------------------------

	Program is faster around 30-40% then naive approach i.e. char by char --
	it tries to process 4 chars at the same time.

	Program is also faster then GNU iconv, speedup depends on size of
	file input. On my machnine I've measured speedup from 30% to 270%
	(for really Big file).


	Compilation:

		gcc -std=c99 -Wall -pedantic -O2 conv.c -o program

	Usage:

		$ program file		# read from file
		$ program		# read from stdin

*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>

// iso-8859-2 => Unicode
uint16_t unicode[256] = {
	/*   0 */ 0x0000, /*   1 */ 0x0001, /*   2 */ 0x0002, /*   3 */ 0x0003,
	/*   4 */ 0x0004, /*   5 */ 0x0005, /*   6 */ 0x0006, /*   7 */ 0x0007,
	/*   8 */ 0x0008, /*   9 */ 0x0009, /*  10 */ 0x000A, /*  11 */ 0x000B,
	/*  12 */ 0x000C, /*  13 */ 0x000D, /*  14 */ 0x000E, /*  15 */ 0x000F,
	/*  16 */ 0x0010, /*  17 */ 0x0011, /*  18 */ 0x0012, /*  19 */ 0x0013,
	/*  20 */ 0x0014, /*  21 */ 0x0015, /*  22 */ 0x0016, /*  23 */ 0x0017,
	/*  24 */ 0x0018, /*  25 */ 0x0019, /*  26 */ 0x001A, /*  27 */ 0x001B,
	/*  28 */ 0x001C, /*  29 */ 0x001D, /*  30 */ 0x001E, /*  31 */ 0x001F,
	/*  32 */ 0x0020, /*  33 */ 0x0021, /*  34 */ 0x0022, /*  35 */ 0x0023,
	/*  36 */ 0x0024, /*  37 */ 0x0025, /*  38 */ 0x0026, /*  39 */ 0x0027,
	/*  40 */ 0x0028, /*  41 */ 0x0029, /*  42 */ 0x002A, /*  43 */ 0x002B,
	/*  44 */ 0x002C, /*  45 */ 0x002D, /*  46 */ 0x002E, /*  47 */ 0x002F,
	/*  48 */ 0x0030, /*  49 */ 0x0031, /*  50 */ 0x0032, /*  51 */ 0x0033,
	/*  52 */ 0x0034, /*  53 */ 0x0035, /*  54 */ 0x0036, /*  55 */ 0x0037,
	/*  56 */ 0x0038, /*  57 */ 0x0039, /*  58 */ 0x003A, /*  59 */ 0x003B,
	/*  60 */ 0x003C, /*  61 */ 0x003D, /*  62 */ 0x003E, /*  63 */ 0x003F,
	/*  64 */ 0x0040, /*  65 */ 0x0041, /*  66 */ 0x0042, /*  67 */ 0x0043,
	/*  68 */ 0x0044, /*  69 */ 0x0045, /*  70 */ 0x0046, /*  71 */ 0x0047,
	/*  72 */ 0x0048, /*  73 */ 0x0049, /*  74 */ 0x004A, /*  75 */ 0x004B,
	/*  76 */ 0x004C, /*  77 */ 0x004D, /*  78 */ 0x004E, /*  79 */ 0x004F,
	/*  80 */ 0x0050, /*  81 */ 0x0051, /*  82 */ 0x0052, /*  83 */ 0x0053,
	/*  84 */ 0x0054, /*  85 */ 0x0055, /*  86 */ 0x0056, /*  87 */ 0x0057,
	/*  88 */ 0x0058, /*  89 */ 0x0059, /*  90 */ 0x005A, /*  91 */ 0x005B,
	/*  92 */ 0x005C, /*  93 */ 0x005D, /*  94 */ 0x005E, /*  95 */ 0x005F,
	/*  96 */ 0x0060, /*  97 */ 0x0061, /*  98 */ 0x0062, /*  99 */ 0x0063,
	/* 100 */ 0x0064, /* 101 */ 0x0065, /* 102 */ 0x0066, /* 103 */ 0x0067,
	/* 104 */ 0x0068, /* 105 */ 0x0069, /* 106 */ 0x006A, /* 107 */ 0x006B,
	/* 108 */ 0x006C, /* 109 */ 0x006D, /* 110 */ 0x006E, /* 111 */ 0x006F,
	/* 112 */ 0x0070, /* 113 */ 0x0071, /* 114 */ 0x0072, /* 115 */ 0x0073,
	/* 116 */ 0x0074, /* 117 */ 0x0075, /* 118 */ 0x0076, /* 119 */ 0x0077,
	/* 120 */ 0x0078, /* 121 */ 0x0079, /* 122 */ 0x007A, /* 123 */ 0x007B,
	/* 124 */ 0x007C, /* 125 */ 0x007D, /* 126 */ 0x007E, /* 127 */ 0x007F,
	/* 128 */ 0x0080, /* 129 */ 0x0081, /* 130 */ 0x0082, /* 131 */ 0x0083,
	/* 132 */ 0x0084, /* 133 */ 0x0085, /* 134 */ 0x0086, /* 135 */ 0x0087,
	/* 136 */ 0x0088, /* 137 */ 0x0089, /* 138 */ 0x008A, /* 139 */ 0x008B,
	/* 140 */ 0x008C, /* 141 */ 0x008D, /* 142 */ 0x008E, /* 143 */ 0x008F,
	/* 144 */ 0x0090, /* 145 */ 0x0091, /* 146 */ 0x0092, /* 147 */ 0x0093,
	/* 148 */ 0x0094, /* 149 */ 0x0095, /* 150 */ 0x0096, /* 151 */ 0x0097,
	/* 152 */ 0x0098, /* 153 */ 0x0099, /* 154 */ 0x009A, /* 155 */ 0x009B,
	/* 156 */ 0x009C, /* 157 */ 0x009D, /* 158 */ 0x009E, /* 159 */ 0x009F,
	/* 160 */ 0x00A0, /* 161 */ 0x0104, /* 162 */ 0x02D8, /* 163 */ 0x0141,
	/* 164 */ 0x00A4, /* 165 */ 0x013D, /* 166 */ 0x015A, /* 167 */ 0x00A7,
	/* 168 */ 0x00A8, /* 169 */ 0x0160, /* 170 */ 0x015E, /* 171 */ 0x0164,
	/* 172 */ 0x0179, /* 173 */ 0x00AD, /* 174 */ 0x017D, /* 175 */ 0x017B,
	/* 176 */ 0x00B0, /* 177 */ 0x0105, /* 178 */ 0x02DB, /* 179 */ 0x0142,
	/* 180 */ 0x00B4, /* 181 */ 0x013E, /* 182 */ 0x015B, /* 183 */ 0x02C7,
	/* 184 */ 0x00B8, /* 185 */ 0x0161, /* 186 */ 0x015F, /* 187 */ 0x0165,
	/* 188 */ 0x017A, /* 189 */ 0x02DD, /* 190 */ 0x017E, /* 191 */ 0x017C,
	/* 192 */ 0x0154, /* 193 */ 0x00C1, /* 194 */ 0x00C2, /* 195 */ 0x0102,
	/* 196 */ 0x00C4, /* 197 */ 0x0139, /* 198 */ 0x0106, /* 199 */ 0x00C7,
	/* 200 */ 0x010C, /* 201 */ 0x00C9, /* 202 */ 0x0118, /* 203 */ 0x00CB,
	/* 204 */ 0x011A, /* 205 */ 0x00CD, /* 206 */ 0x00CE, /* 207 */ 0x010E,
	/* 208 */ 0x0110, /* 209 */ 0x0143, /* 210 */ 0x0147, /* 211 */ 0x00D3,
	/* 212 */ 0x00D4, /* 213 */ 0x0150, /* 214 */ 0x00D6, /* 215 */ 0x00D7,
	/* 216 */ 0x0158, /* 217 */ 0x016E, /* 218 */ 0x00DA, /* 219 */ 0x0170,
	/* 220 */ 0x00DC, /* 221 */ 0x00DD, /* 222 */ 0x0162, /* 223 */ 0x00DF,
	/* 224 */ 0x0155, /* 225 */ 0x00E1, /* 226 */ 0x00E2, /* 227 */ 0x0103,
	/* 228 */ 0x00E4, /* 229 */ 0x013A, /* 230 */ 0x0107, /* 231 */ 0x00E7,
	/* 232 */ 0x010D, /* 233 */ 0x00E9, /* 234 */ 0x0119, /* 235 */ 0x00EB,
	/* 236 */ 0x011B, /* 237 */ 0x00ED, /* 238 */ 0x00EE, /* 239 */ 0x010F,
	/* 240 */ 0x0111, /* 241 */ 0x0144, /* 242 */ 0x0148, /* 243 */ 0x00F3,
	/* 244 */ 0x00F4, /* 245 */ 0x0151, /* 246 */ 0x00F6, /* 247 */ 0x00F7,
	/* 248 */ 0x0159, /* 249 */ 0x016F, /* 250 */ 0x00FA, /* 251 */ 0x0171,
	/* 252 */ 0x00FC, /* 253 */ 0x00FD, /* 254 */ 0x0163, /* 255 */ 0x02D9
};

uint16_t utf8[256];

// prepare utf8 table using unicode table
void prepare_utf8();

// convert n chars from 'in' (iso-8859-2) and save to 'out' (UTF-8)
// ('out' must have space for 2*n bytes)
// number of bytes saved in 'out' is returned
size_t convert(size_t n, uint8_t* in, uint8_t* out);

// convert given file, print results on stdout
void convertfile(FILE* f);

// main program
int main(int argc, char* argv[]) {
	FILE* file;
	int i;

	prepare_utf8();

	if (argc == 1) {	// by default read stdin
		convertfile(stdin);
	}
	else {			// or read text from files
		for (i=1; i < argc; i++) {
			file = fopen(argv[i], "rb");
			if (file == NULL) {
				printf("Can't open file '%s': %s\n", argv[i], strerror(errno));
				return 1;
			}

			convertfile(file);
			fclose(file);
		}
	}
	return 0;
}
//======================================================================

void prepare_utf8() {
	int i;
	uint16_t x;
	for (i=0; i < 256; i++) {
		x = unicode[i];
		if (x < 128)
			utf8[i] = x;
		else
			utf8[i] = ( (x >> 6)   | 0x00c0) |
			          (((x & 0x3f) | 0x0080) << 8); // byte 2
	}
}
//======================================================================

size_t convert(size_t n, uint8_t* in, uint8_t* out) {
	uint8_t c;

#define char_to_utf8(ch) \
	c = ch; \
	if (c < 0x80) { \
		out[j] = c; \
		j += 1; \
	} \
	else { \
		*((uint16_t*)&out[j]) = utf8[c]; \
		j += 2; \
	}

#if 1
	size_t i = 0;
	size_t j = 0;

	size_t blocks = n/4;


	// convert 4 chars at same time
	for (i=0; i < blocks * 4; i+=4) {
		uint32_t c4 = *(uint32_t*)&in[i];	// load 4 chars
		if ((c4 & 0x80808080) == 0) {		// all ASCII?
			*((uint32_t*)&out[j]) = c4;	// so simply copy
			j += 4;
		}
		else {	// or fall back to default method
			char_to_utf8(c4 & 0xff);
			char_to_utf8((c4 >> 8) & 0xff);
			char_to_utf8((c4 >> 16) & 0xff);
			char_to_utf8((c4 >> 24) & 0xff);
		} // if
	} // for

	// convert rest 0..3 chars
	for (/**/; i < n; i++) {
		char_to_utf8(in[i])
	}

	return j;

#else
	size_t i = 0;
	size_t j = 0;
	for (i=0; i < n; i++) {
		char_to_utf8(in[i])
	}

	return j;
#endif
}

//======================================================================
void convertfile(FILE* file) {
	static uint8_t buf[4096];
	static uint8_t out[4096 * 2];
	size_t readed, conv;
	while ((readed = fread(buf, 1, sizeof(buf), file))) {
		conv = convert(readed, buf, out);
		fwrite(out, conv, 1, stdout);
	}
}

// eof

