/*
 * Copyright © 2010 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.
 */

/*
 * Deal with TeleDongle on a serial port
 */

package org.altusmetrum.altosuilib_14;

import java.io.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import org.altusmetrum.altoslib_14.*;
import libaltosJNI.*;

/*
 * This class reads from the serial port and places each received
 * line in a queue. Dealing with that queue is left up to other
 * threads.
 */

public class AltosSerial extends AltosLink  {

	static java.util.List<String> devices_opened = Collections.synchronizedList(new LinkedList<String>());

	public AltosDevice device;
	SWIGTYPE_p_altos_file altos;
	Thread input_thread;
	String line;
	byte[] line_bytes;
	int line_count;
	Frame frame;

	public int getchar() {
		if (altos == null)
			return ERROR;
		return libaltos.altos_getchar(altos, 0);
	}

	public void flush_output() {
		super.flush_output();
		if (altos != null) {
			if (libaltos.altos_flush(altos) != 0)
				close_serial();
		}
	}

	JDialog		timeout_dialog;

	private void start_timeout_dialog_internal() {

		Object[] options = { "Cancel" };

		JOptionPane	pane = new JOptionPane();
		pane.setMessage(String.format("Connecting to %s, %7.3f MHz as %s", device.toShortString(), frequency, callsign));
		pane.setOptions(options);
		pane.setInitialValue(null);

		timeout_dialog = pane.createDialog(frame, "Connecting...");

		timeout_dialog.setVisible(true);

		Object o = pane.getValue();
		if (o == null)
			return;
		if (options[0].equals(o))
			reply_abort = true;
		timeout_dialog.dispose();
		timeout_dialog = null;
	}

	/*
	 * These are required by the AltosLink implementation
	 */

	public boolean can_cancel_reply() {
		/*
		 * Can cancel any replies not called from the dispatch thread
		 */
		return !SwingUtilities.isEventDispatchThread();
	}

	public boolean show_reply_timeout() {
		if (!SwingUtilities.isEventDispatchThread() && frame != null) {
			Runnable r = new Runnable() {
					public void run() {
						start_timeout_dialog_internal();
					}
				};
			SwingUtilities.invokeLater(r);
			return true;
		}
		return false;
	}

	public void hide_reply_timeout() {
		Runnable r = new Runnable() {
				public void run() {
					timeout_dialog.setVisible(false);
				}
			};
		SwingUtilities.invokeLater(r);
	}

	private synchronized void close_serial() {
		synchronized (devices_opened) {
			devices_opened.remove(device.getPath());
		}
		if (altos != null) {
			libaltos.altos_free(altos);
			altos = null;
		}
		abort_reply();
	}

	public void close() {
		if (remote) {
			try {
				stop_remote();
			} catch (InterruptedException ie) {
			}
		}
		if (in_reply != 0)
			System.out.printf("Uh-oh. Closing active serial device\n");

		close_serial();

		if (input_thread != null) {
			try {
				input_thread.interrupt();
				input_thread.join();
			} catch (InterruptedException ie) {
			}
			input_thread = null;
		}
		if (debug)
			System.out.printf("Closing %s\n", device.getPath());
	}

	private void putc(char c) {
		if (altos != null)
			if (libaltos.altos_putchar(altos, c) != 0)
				close_serial();
	}

	public void putchar(byte c) {
		if (altos != null) {
			if (debug)
				System.out.printf(" %02x", (int) c & 0xff);
			if (libaltos.altos_putchar(altos, (char) c) != 0)
				close_serial();
		}
	}

	public void print(String data) {
		for (int i = 0; i < data.length(); i++)
			putc(data.charAt(i));
	}

	private void open() throws FileNotFoundException, AltosSerialInUseException {
		synchronized (devices_opened) {
			if (devices_opened.contains(device.getPath()))
				throw new AltosSerialInUseException(device);
			devices_opened.add(device.getPath());
		}
		altos = device.open();
		if (altos == null) {
			final String	message = device.getErrorString();
			close();
			throw new FileNotFoundException(String.format("%s (%s)",
								      device.toShortString(), message));
		}
		if (debug)
			System.out.printf("Open %s\n", device.getPath());
		input_thread = new Thread(this);
		input_thread.start();
		print("~\nE 0\n");
		set_monitor(false);
		flush_output();
	}

	public void set_frame(Frame in_frame) {
		frame = in_frame;
	}

	public AltosSerial(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException {
		device = in_device;
		frame = null;
		serial = device.getSerial();
		name = device.toShortString();
		open();
	}
}
