/*
	Copyright (C) 2012 2013 2014 2015 Johan Mattsson

	This library is free software; you can redistribute it and/or modify 
	it under the terms of the GNU Lesser General Public License as 
	published by the Free Software Foundation; either version 3 of the 
	License, or (at your option) any later version.

	This library 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 
	Lesser General Public License for more details.
*/

namespace BirdFont {

public class FontData : Object {

	// Read pointer
	uint rp = 0;

	// Write pointer
	uint wp = 0;

	uint32 len = 0;
	uint32 padding = 0;
	
	uint32 capacity;
	
	public uint8* table_data = null;
	
	public uint offset = 0; // for debugging
	
	public FontData (uint32 size = 1024) {
		capacity = size;
		table_data = malloc (size);
	}
	
	~FontData () {
		if (table_data != null) {
			delete table_data;
			table_data = null;
		}
	}
	
	public int get_read_pos () {
		return (int) rp;
	}

	public int get_write_pos () {
		return (int) wp;
	}
		
	public void write_at (uint pos, uint8 new_data) throws GLib.Error
		requires (pos <= capacity) 
	{		
		if (unlikely (pos >= len)) {
			warning ("end of table reached");
			assert (false);
		}
		
		table_data[pos]= new_data;
	}
	
	public void write_table (OtfInputStream dis, uint32 offset, uint32 length) throws Error {
		uint32 l = length + (length % 4);  // padding after end of table
		uint8 b;
		
		if (length >= l) {
			expand (l);
		}
		
		if (table_data == null) {
			warning ("Failed to allocate memory for ttf data.");
			return;
		}
		
		seek (0);
		dis.seek (offset);
		
		wp = 0;
		while (wp < l) {
			b = dis.read_byte ();
			add (b);
		}
		
		rp = 0;
	}
	
	public uint length_with_padding () {
		return len;
	}	
	
	public uint length () {
		return len - padding;
	}
	
	public void pad () {
		while (len % 4 != 0) {
			add (0);
			padding++;
		}
	}
	
	/** Add additional checksum data to this checksum. */
	public void continous_checksum (ref uint32 current_checksum) {
		uint trp = rp;
		uint l;
		
		if (length_with_padding () % 4 != 0) {
			warning ("OtfTable is not padded to correct size.");
		}
				
		seek (0);

		l = (length () % 4 > 0) ? length () / 4 + 1 : length () / 4; 

		for (uint32 i = 0; i < l; i++) {
			current_checksum += read_uint32 ();
		}
		
		rp = trp;
		
		return;
	}

	public uint32 checksum () {
		uint32 sum = 0;
		continous_checksum (ref sum);
		return sum;
	}
	
	public void seek_end () {
		seek (len);
	}
		
	public void seek (uint i) {
		rp = i;
		wp = i;
	}

	public void seek_relative (uint i) {
		rp += i;
		wp += i;
	}

	/** Returns true if next byte is a CFF operator */
	public bool next_is_operator () {
		uint8 o = read ();
		seek_relative (-1);
		return (0 <= o <=21);
	}
	
	public uint8 read () {
		if (unlikely (rp >= len)) {
			warning ("end of table reached");
			return 0;
		}
		
		return table_data [rp++];
	}
	
	public Fixed read_fixed () {
		return (Fixed) read_uint32 ();
	}

	public uint32 read_ulong () {
		return read_uint32 ();
	}

	public uint16 read_ushort () {
		uint16 f;
		f = read () << 8;
		f += read ();
		return f;
	}
	
	public int16 read_int16 () {
		int16 f;
		f = read () << 8;
		f += read ();
		return f;		
	}
	
	public int16 read_short () throws Error {
		return read_int16 ();
	}
	
	public uint32 read_uint32 () {
		uint32 f;
		f = read () << 8 * 3;
		f += read () << 8 * 2;
		f += read () << 8 * 1;
		f += read () << 8 * 0;
		return f;
	}

	public int32 read_int32 () {
		int32 f;
		f = read () << 8 * 3;
		f += read () << 8 * 2;
		f += read () << 8 * 1;
		f += read () << 8 * 0;
		return f;
	}
	
	public uint64 read_uint64 () {
		uint64 f;
		f = (uint64) read () << 8 * 7;
		f += (uint64) read () << 8 * 6;
		f += (uint64) read () << 8 * 5;
		f += (uint64) read () << 8 * 4;
		f += (uint64) read () << 8 * 3;
		f += (uint64) read () << 8 * 2;
		f += (uint64) read () << 8 * 1;
		f += (uint64) read () << 8 * 0;
		return f;
	}

	public F2Dot14 read_f2dot14 () throws Error {
		F2Dot14 f = (F2Dot14) read_int16 ();
		return f;
	}

	public uint64 read_udate () throws Error {
		return read_uint64 ();
	}

	public uint8 read_byte () throws Error {
		return read ();
	}

	public char read_char () throws Error {
		return (char) read_byte ();
	}

	public string read_string (uint length) throws Error {
		StringBuilder str = new StringBuilder ();
		char c;
		for (int i = 0; i < length; i++) {
			c = read_char ();
			str.append_c (c);
		}
		return str.str;
	}

	public int read_charstring_value () throws Error {
		uint8 a, b;
		a = read ();
		
		if (32 <= a <= 246) {
			return a - 139;
		}
		
		b = read ();
		
		if (247 <= a <= 250) {	
			return (a - 247) * 256 + b + 108;
		}
		
		if (251 <= a <= 254) {
			return -((a - 251) * 256) - b - 108;
		}

		if (a == 255) {
			// Implement it
			warning ("fractions not implemented yet.");
		}

		stderr.printf (@"unexpected value: $a\n");
		warn_if_reached ();
		return 0;
	}
	
	public void add_fixed (Fixed f) throws Error {
		add_u32 (f);
	}

	public void add_short (int16 d) throws Error {
		add_16 (d);
	}
	
	public void add_ushort (uint16 d) throws Error {
		add_u16 (d);
	}
		
	public void add_ulong (uint32 d) throws Error {
		add_u32 (d);
	}

	public void add_long (int32 d) throws Error {
		add_32 (d);
	}

	public void add_32 (int32 i) throws GLib.Error {
		uint32 s = (uint16) (i >> 16);
		
		add_u16 ((uint16) s);
		add_u16 ((uint16) (i - (s << 16)));
	}
				
	public void add_byte (uint8 b) throws Error {
		add (b);
	}
	
	private void expand (uint extra_bytes = 1024) {
		capacity += extra_bytes;
		table_data = (uint8*) try_realloc (table_data, capacity);
		
		if (table_data == null) {
			warning ("Out of memory.");
			capacity = 0;
		}		
	}
	
	public void add (uint8 d) {
		if (unlikely (len == capacity)) {
			expand ();
		}
		
		table_data[wp] = d;

		if (wp == len) {
			len++;
		}
				
		wp++;
	}

	public void add_littleendian_u16 (uint32 i) {
		add ((int8) (i & 0x00FF));
		add ((int8) ((i & 0xFF00) >> 8));
	}
		
	public void add_u16 (uint16 d) {
		uint16 n = d >> 8;
		add ((uint8)n);
		add ((uint8)(d - (n << 8)));
	}

	public void add_16 (int16 i) {
		uint8 s = (uint8) (i >> 8);
		
		add ((uint8) s);
		add ((uint8) (i - (s << 8)));
	}

	public void add_littleendian_u32 (uint32 i) {
		add ((int8) (i & 0x000000FF));
		add ((int8) ((i & 0x0000FF00) >> 8));
		add ((int8) ((i & 0x00FF0000) >> 16));
		add ((int8) ((i & 0xFF000000) >> 24));
	}
		
	public void add_u32 (uint32 i) {
		uint32 s = (uint16) (i >> 16);
		
		add_u16 ((uint16) s);
		add_u16 ((uint16) (i - (s << 16)));
	}
	
	public void add_u64(uint64 i) {
		uint64 s = (uint32) (i >> 32);
		
		add_u32 ((uint32) s);
		add_u32 ((uint32)(i - (s << 32)));
	}
	
	public void add_str_littleendian_utf16 (string s) {
		add_str_utf16 (s, true);
	}
	
	public void add_str_utf16 (string s, bool little_endian = false) {
		int index = 0;
		unichar c;
		uint8 c0;
		uint8 c1;
		int l = 0;
		
		while (s.get_next_char (ref index, out c)) {
			if (c <= 0x7FFF) {
				c0 = (uint8) (c >> 8);
				c1 = (uint8) (c - (c0 << 8));
				
				if (little_endian) {
					add (c1);
					add (c0);
				} else {
					add (c0);
					add (c1);
				}
			} else if (c <= 0xFFFFF) {
				int high = (0xFFC00 & c) >> 10;
				int low = (0x03FF & c);
				
				high += 0xD800;
				low += 0xDC00;
				
				c0 = (uint8) (high >> 8);
				c1 = (uint8) (high - (c0 << 8));
				
				if (little_endian) {
					add (c1);
					add (c0);
				} else {
					add (c0);
					add (c1);
				}

				c0 = (uint8) (low >> 8);
				c1 = (uint8) (low - (c0 << 8));
				
				if (little_endian) {
					add (c1);
					add (c0);
				} else {
					add (c0);
					add (c1);
				}
			} else {
				continue;
			}
			
			l += 2;
		}
	}
	
	public static uint utf16_strlen (string s) {
		FontData fd = new FontData ();
		fd.add_str_utf16 (s);
		return fd.length_with_padding ();
	}

	public void add_macroman_str (string s) {
		int index = 0;
		unichar c;

		while (s.get_next_char (ref index, out c)) {
			if (32 <= c <= 127) {
				add ((uint8) c);
			} else {
				if (c == 196) add ((uint8) 128);
				if (c == 197) add ((uint8) 129);
				if (c == 199) add ((uint8) 130);
				if (c == 201) add ((uint8) 131);
				if (c == 209) add ((uint8) 132);
				if (c == 214) add ((uint8) 133);
				if (c == 220) add ((uint8) 134);
				if (c == 225) add ((uint8) 135);
				if (c == 224) add ((uint8) 136);
				if (c == 226) add ((uint8) 137);
				if (c == 228) add ((uint8) 138);
				if (c == 227) add ((uint8) 139);
				if (c == 229) add ((uint8) 140);
				if (c == 231) add ((uint8) 141);
				if (c == 233) add ((uint8) 142);
				if (c == 232) add ((uint8) 143);
				if (c == 234) add ((uint8) 144);
				if (c == 235) add ((uint8) 145);
				if (c == 237) add ((uint8) 146);
				if (c == 236) add ((uint8) 147);
				if (c == 238) add ((uint8) 148);
				if (c == 239) add ((uint8) 149);
				if (c == 241) add ((uint8) 150);
				if (c == 243) add ((uint8) 151);
				if (c == 242) add ((uint8) 152);
				if (c == 244) add ((uint8) 153);
				if (c == 246) add ((uint8) 154);
				if (c == 245) add ((uint8) 155);
				if (c == 250) add ((uint8) 156);
				if (c == 249) add ((uint8) 157);
				if (c == 251) add ((uint8) 158);
				if (c == 252) add ((uint8) 159);
				if (c == 8224) add ((uint8) 160);
				if (c == 176) add ((uint8) 161);
				if (c == 162) add ((uint8) 162);
				if (c == 163) add ((uint8) 163);
				if (c == 167) add ((uint8) 164);
				if (c == 8226) add ((uint8) 165);
				if (c == 182) add ((uint8) 166);
				if (c == 223) add ((uint8) 167);
				if (c == 174) add ((uint8) 168);
				if (c == 169) add ((uint8) 169);
				if (c == 8482) add ((uint8) 170);
				if (c == 180) add ((uint8) 171);
				if (c == 168) add ((uint8) 172);
				if (c == 8800) add ((uint8) 173);
				if (c == 198) add ((uint8) 174);
				if (c == 216) add ((uint8) 175);
				if (c == 8734) add ((uint8) 176);
				if (c == 177) add ((uint8) 177);
				if (c == 8804) add ((uint8) 178);
				if (c == 8805) add ((uint8) 179);
				if (c == 165) add ((uint8) 180);
				if (c == 181) add ((uint8) 181);
				if (c == 8706) add ((uint8) 182);
				if (c == 8721) add ((uint8) 183);
				if (c == 8719) add ((uint8) 184);
				if (c == 960) add ((uint8) 185);
				if (c == 8747) add ((uint8) 186);
				if (c == 170) add ((uint8) 187);
				if (c == 186) add ((uint8) 188);
				if (c == 937) add ((uint8) 189);
				if (c == 230) add ((uint8) 190);
				if (c == 248) add ((uint8) 191);
				if (c == 191) add ((uint8) 192);
				if (c == 161) add ((uint8) 193);
				if (c == 172) add ((uint8) 194);
				if (c == 8730) add ((uint8) 195);
				if (c == 402) add ((uint8) 196);
				if (c == 8776) add ((uint8) 197);
				if (c == 8710) add ((uint8) 198);
				if (c == 171) add ((uint8) 199);
				if (c == 187) add ((uint8) 200);
				if (c == 8230) add ((uint8) 201);
				if (c == 160) add ((uint8) 202);
				if (c == 192) add ((uint8) 203);
				if (c == 195) add ((uint8) 204);
				if (c == 213) add ((uint8) 205);
				if (c == 338) add ((uint8) 206);
				if (c == 339) add ((uint8) 207);
				if (c == 8211) add ((uint8) 208);
				if (c == 8212) add ((uint8) 209);
				if (c == 8220) add ((uint8) 210);
				if (c == 8221) add ((uint8) 211);
				if (c == 8216) add ((uint8) 212);
				if (c == 8217) add ((uint8) 213);
				if (c == 247) add ((uint8) 214);
				if (c == 9674) add ((uint8) 215);
				if (c == 255) add ((uint8) 216);
				if (c == 376) add ((uint8) 217);
				if (c == 8260) add ((uint8) 218);
				if (c == 8364) add ((uint8) 219);
				if (c == 8249) add ((uint8) 220);
				if (c == 8250) add ((uint8) 221);
				if (c == 64257) add ((uint8) 222);
				if (c == 64258) add ((uint8) 223);
				if (c == 8225) add ((uint8) 224);
				if (c == 183) add ((uint8) 225);
				if (c == 8218) add ((uint8) 226);
				if (c == 8222) add ((uint8) 227);
				if (c == 8240) add ((uint8) 228);
				if (c == 194) add ((uint8) 229);
				if (c == 202) add ((uint8) 230);
				if (c == 193) add ((uint8) 231);
				if (c == 203) add ((uint8) 232);
				if (c == 200) add ((uint8) 233);
				if (c == 205) add ((uint8) 234);
				if (c == 206) add ((uint8) 235);
				if (c == 207) add ((uint8) 236);
				if (c == 204) add ((uint8) 237);
				if (c == 211) add ((uint8) 238);
				if (c == 212) add ((uint8) 239);
				if (c == 63743) add ((uint8) 240);
				if (c == 210) add ((uint8) 241);
				if (c == 218) add ((uint8) 242);
				if (c == 219) add ((uint8) 243);
				if (c == 217) add ((uint8) 244);
				if (c == 305) add ((uint8) 245);
				if (c == 710) add ((uint8) 246);
				if (c == 732) add ((uint8) 247);
				if (c == 175) add ((uint8) 248);
				if (c == 728) add ((uint8) 249);
				if (c == 729) add ((uint8) 250);
				if (c == 730) add ((uint8) 251);
				if (c == 184) add ((uint8) 252);
				if (c == 733) add ((uint8) 253);
				if (c == 731) add ((uint8) 254);
				if (c == 711) add ((uint8) 255);
			}
		}
	}

	public static uint macroman_strlen (string s) {
		FontData fd = new FontData ();
		fd.add_macroman_str (s);
		return fd.length_with_padding ();
	}
		
	public void add_str (string s) {
		uint8[] data = s.data;
		for (int n = 0; n < data.length; n++) { 
			add (data[n]);
		}		
	}
	
	public void add_tag (string s) 
		requires (s.length == 4 && s.data.length == 4) {
		add_str (s);
	}

	public void add_charstring_value (int v) throws Error {
		int w;
		uint8 t;
		
		if (unlikely (!(-1131 <= v <= 1131))) {
			warning ("charstring value out of range");
			v = 0;		
		}
		
		if (-107 <= v <= 107) {
			add_byte ((uint8) (v + 139));
		} else if (107 < v <= 1131) {
			// positive value
			w = v;
			v -= 108;
			
			t = 0;
			while (v >= 256) {
				v -= 256;
				t++;
			}
			
			add_byte (t + 247);
			add_byte ((uint8) (w - 108 - (t * 256)));		
		} else if (-1131 <= v < -107) {
			// negative value
			v = -v - 108;
			add_byte ((uint8) ((v >> 8) + 251));
			add_byte ((uint8) (v & 0xFF));
		} else {
			// Todo add fraction
		}
	}
	
	public void append (FontData fd) {
		fd.seek (0);
		for (int i = 0; i < fd.length (); i++) {
			add (fd.read ());
		}
	}

	public static int32 to_fixed (double d) {
		int32 val = (int32) Math.floor (d);
		int32 mant = (int32) Math.floor (0x10000 * (d - val));
		val = (val << 16) | mant;
		return val;
	}

	public static double from_fixed (int32 val) {
		return val / 65536.0;
	}
 	
	public void dump () {
		for (uint32 i = 0; i < length_with_padding (); i++) {
			stdout.printf ("%x " , table_data[i]);
		}
		stdout.printf ("\n");
	}
}

}
