/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.gui.hex;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Arrays;
import java.util.StringTokenizer;

import com.cburch.hex.HexModel;

public class HexFile {
	private HexFile() { }
	
	private static final String RAW_IMAGE_HEADER = "v2.0 raw";
	private static final String COMMENT_MARKER = "#";
	
	private static class HexReader {
		private BufferedReader in;
		private int[] data;
		private StringTokenizer curLine;
		private long leftCount;
		private long leftValue;
		
		public HexReader(BufferedReader in) throws IOException {
			this.in = in;
			data = new int[4096];
			curLine = findNonemptyLine();
		}
		
		private StringTokenizer findNonemptyLine() throws IOException {
			String line = in.readLine();
			while (line != null) {
				int index = line.indexOf(COMMENT_MARKER);
				if (index >= 0) {
					line = line.substring(0, index);
				}

				StringTokenizer ret = new StringTokenizer(line);
				if (ret.hasMoreTokens()) return ret;
				line = this.in.readLine();
			}
			return null;
		}
		
		private String nextToken() throws IOException {
			if (curLine != null && curLine.hasMoreTokens()) {
				return curLine.nextToken();
			} else {
				curLine = findNonemptyLine();
				return curLine == null ? null : curLine.nextToken();
			}
		}
		
		public boolean hasNext() throws IOException {
			if (leftCount > 0) {
				return true;
			} else if (curLine != null && curLine.hasMoreTokens()) {
				return true;
			} else {
				curLine = findNonemptyLine();
				return curLine != null;
			}
		}
		
		public int[] next() throws IOException {
			int pos = 0;
			if (leftCount > 0) {
				int n = (int) Math.min(data.length - pos, leftCount);
				if (n == 1) {
					data[pos] = (int) leftValue;
					pos++;
					leftCount--;
				} else {
					Arrays.fill(data, pos, pos + n, (int) leftValue);
					pos += n;
					leftCount -= n;
				}
			}
			if (pos >= data.length) return data;
				
			for (String tok = nextToken(); tok != null; tok = nextToken()) {
				try {
					int star = tok.indexOf("*");
					if (star < 0) {
						leftCount = 1;
						leftValue = Long.parseLong(tok, 16);
					} else {
						leftCount = Long.parseLong(tok.substring(0, star));
						leftValue = Long.parseLong(tok.substring(star + 1), 16);
					}
				} catch (NumberFormatException e) {
					throw new IOException(Strings.get("hexNumberFormatError"));
				}
				
				int n = (int) Math.min(data.length - pos, leftCount);
				if (n == 1) {
					data[pos] = (int) leftValue;
					pos++;
					leftCount--;
				} else {
					Arrays.fill(data, pos, pos + n, (int) leftValue);
					pos += n;
					leftCount -= n;
				}
				if (pos >= data.length) return data;
			}
			
			if (pos >= data.length) {
				return data;
			} else {
				int[] ret = new int[pos];
				System.arraycopy(data, 0, ret, 0, pos);
				return ret;
			}
		}
	}

	public static void save(Writer out, HexModel src) throws IOException {
		long first = src.getFirstOffset();
		long last = src.getLastOffset();
		while (last > first && src.get(last) == 0) last--;
		int tokens = 0;
		long cur = 0;
		while (cur <= last) {
			int val = src.get(cur);
			long start = cur;
			cur++;
			while (cur <= last && src.get(cur) == val) cur++;
			long len = cur - start;
			if (len < 4) { cur = start + 1; len = 1; }
			try {
				if (tokens > 0) out.write(tokens % 8 == 0 ? '\n' : ' ');
				if (cur != start + 1) out.write((cur - start) + "*");
				out.write(Integer.toHexString(val));
			} catch (IOException e) {
				throw new IOException(Strings.get("hexFileWriteError"));
			}
			tokens++;
		}
		if (tokens > 0) out.write('\n');
	}
	
	public static void open(HexModel dst, Reader in) throws IOException {
		HexReader reader = new HexReader(new BufferedReader(in));
		long offs = dst.getFirstOffset();
		while (reader.hasNext()) {
			int[] values = reader.next();
			if (offs + values.length - 1 > dst.getLastOffset()) {
				throw new IOException(Strings.get("hexFileSizeError"));
			}
			dst.set(offs, values);
			offs += values.length;
		}
		dst.fill(offs, dst.getLastOffset() - offs + 1, 0);
	}
	
	public static int[] parse(Reader in) throws IOException {
		HexReader reader = new HexReader(new BufferedReader(in));
		int cur = 0;
		int[] data = new int[4096];
		while (reader.hasNext()) {
			int[] values = reader.next();
			if (cur + values.length > data.length) {
				int[] oldData = data;
				data = new int[Math.max(cur + values.length, 3 * data.length / 2)];
				System.arraycopy(oldData, 0, data, 0, cur);
			}
			System.arraycopy(values, 0, data, cur, values.length);
			cur += values.length;
		}
		if (cur != data.length) {
			int[] oldData = data;
			data = new int[cur];
			System.arraycopy(oldData, 0, data, 0, cur);
		}
		return data;
	}

	public static void open(HexModel dst, File src) throws IOException {
		BufferedReader in;
		try {
			in = new BufferedReader(new FileReader(src));
		} catch (IOException e) {
			throw new IOException(Strings.get("hexFileOpenError"));
		}
		try {
			String header = in.readLine();
			if (!header.equals(RAW_IMAGE_HEADER)) {
				throw new IOException(Strings.get("hexHeaderFormatError"));
			}
			open(dst, in);
			try {
				BufferedReader oldIn = in;
				in = null;
				oldIn.close();
			} catch (IOException e) {
				throw new IOException(Strings.get("hexFileReadError"));
			}
		} finally {
			try {
				if (in != null) in.close();
			} catch (IOException e) { }
		}
	}

	public static void save(File dst, HexModel src) throws IOException {
		FileWriter out;
		try {
			out = new FileWriter(dst);
		} catch (IOException e) {
			throw new IOException(Strings.get("hexFileOpenError"));
		}
		try {
			try {
				out.write(RAW_IMAGE_HEADER + "\n");
			} catch (IOException e) {
				throw new IOException(Strings.get("hexFileWriteError"));
			}
			save(out, src);
		} finally {
			try {
				out.close();
			} catch (IOException e) {
				throw new IOException(Strings.get("hexFileWriteError"));
			}
		}
	}
}
