/*
 * Copyright © 2013 Keith Packard <keithp@keithp.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

package org.altusmetrum.altoslib_14;

import java.io.*;

public class AltosSelfFlash extends AltosProgrammer {
	File			file;
	FileInputStream		input;
	AltosHexfile		image;
	AltosLink		link;
	boolean			aborted;
	AltosFlashListener	listener;
	AltosRomconfig		rom_config;

	void action(String s, int percent) {
		if (listener != null && !aborted)
			listener.position(s, percent);
	}

	void action(int part, int total) {
		int percent = 100 * part / total;
		action(String.format("%d/%d (%d%%)",
				     part, total, percent),
		       percent);
	}

	byte[] read_memory(long addr, int len) throws InterruptedException, IOException {
		int b;
		byte[]	data = new byte[len];

		for (int offset = 0; offset < len; offset += 0x100) {
			link.printf("R %x\n", addr + offset);
			byte[]	reply = link.get_binary_reply(5000, 0x100);

			if (reply == null)
				throw new IOException("Read device memory timeout");
			for (b = 0; b < 0x100 && b + offset < len; b++)
				data[b+offset] = reply[b];
		}
		return data;
	}

	AltosHexfile read_hexfile(long addr, int len) throws InterruptedException {
		try {
			byte[] mem = read_memory(addr, len);

			AltosHexfile	hexfile = new AltosHexfile(mem, addr);

			if (image != null)
				hexfile.add_symbols(image);
			return hexfile;
		} catch (IOException ie) {
			return null;
		}
	}

	void write_memory(long addr, byte[] data, int start, int len) {
		int b;
		link.printf("W %x\n", addr);
		link.flush_output();
		for (b = 0; b < len; b++)
			link.putchar(data[start + b]);
		for (; b < 0x100; b++)
			link.putchar((byte) 0xff);
	}

	void reboot() {
		link.printf("a\n");
		link.flush_output();
	}

	public void flash() {
		try {
			if (!check_rom_config())
				throw new IOException("Invalid rom config settings");

			/*
			 * Store desired config values into image
			 */
			rom_config.write(image);

			int remain = image.data.length;
			long flash_addr = image.address;
			int image_start = 0;

			action(AltosFlashListener.flash_start, 0);
			action(0, image.data.length);
			while (remain > 0 && !aborted) {
				int this_time = remain;
				if (this_time > 0x100)
					this_time = 0x100;

				if (link != null) {
					/* write the data */
					write_memory(flash_addr, image.data, image_start, this_time);

					byte[] check = read_memory(flash_addr, this_time);
					for (int i = 0; i < this_time; i++)
						if (check[i] != image.data[image_start + i])
							throw new IOException(String.format("Flash write failed at 0x%x (%02x != %02x)",
											    image.address + image_start + i,
											    check[i], image.data[image_start + i]));
				} else {
					Thread.sleep(100);
				}

				remain -= this_time;
				flash_addr += this_time;
				image_start += this_time;

				action(image.data.length - remain, image.data.length);
			}
			if (!aborted) {
				action(AltosFlashListener.flash_done, 100);
			}
			close();
		} catch (IOException ie) {
			action(ie.getMessage(), -1);
			abort();
		} catch (InterruptedException ie) {
			abort();
		}
	}

	public void close() {
		if (link != null) {
			reboot();
			try {
				link.close();
			} catch (InterruptedException ie) {
			}
			link = null;
		}
	}

	synchronized public void abort() {
		aborted = true;
		close();
	}

	private AltosHexfile get_rom() throws InterruptedException {
		try {
			long base = AltosRomconfig.fetch_base(image);
			long bounds = AltosRomconfig.fetch_bounds(image);

			if (link.debug)
				System.out.printf("rom base %x bounds %x\n", base, bounds);
			return read_hexfile(base, (int) (bounds - base));
		} catch (AltosNoSymbol ns) {
			return null;
		}
	}

	public boolean check_rom_config() throws InterruptedException {
		if (link == null) {
			return true;
		}
		if (rom_config == null) {
			AltosHexfile hexfile = get_rom();
			if (hexfile != null)
				rom_config = new AltosRomconfig(hexfile);
		}
		return rom_config != null && rom_config.valid();
	}

	public void set_romconfig (AltosRomconfig romconfig) {
		rom_config = romconfig;
	}

	public AltosRomconfig target_romconfig(AltosUsbId usb_id, String usb_product) throws InterruptedException {
		if (!check_rom_config())
			return null;
		if (rom_config.usb_id == null)
			rom_config.usb_id = usb_id;
		if (rom_config.usb_product == null)
			rom_config.usb_product = usb_product;
		return rom_config;
	}

	public AltosRomconfig image_romconfig() {
		return new AltosRomconfig(image);
	}

	public AltosSelfFlash(File file, AltosLink link, AltosFlashListener listener)
		throws IOException, FileNotFoundException, InterruptedException {
		this.file = file;
		this.link = link;
		this.listener = listener;
		input = new FileInputStream(file);
		image = new AltosHexfile(input);
	}
}
