package com.peterfranza;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;

public class TivoLocator {

	private static TivoLocator instance = null;
	
	public static synchronized TivoLocator getInstance() throws SocketException {
		if(instance == null) {
			instance = new TivoLocator();
		}
		return instance;
	}
	
	private List<TivoLocatorListener> listeners = new ArrayList<TivoLocatorListener>();
	private DatagramSocket socket;
	private Thread listenerThread;
	
	private TivoLocator() throws SocketException {
		socket = new DatagramSocket(2190);
		listenerThread = new Thread() {
			@Override
			public void run() {
				while(true) {
					try {
					DatagramPacket packet = new DatagramPacket(
							new byte[Short.MAX_VALUE], Short.MAX_VALUE);
					socket.receive(packet);
					processPacket(packet);
					} catch (SocketException se){
						System.out.println("Closing TivoLocator");
						return;
					} catch (Exception e) {
						System.err.println(e);
					}
				}
			}
		};
		listenerThread.setDaemon(true);
		listenerThread.start();
	}

	private void processPacket(DatagramPacket packet) {
		try {
			TivoInformation info = new TivoInformation(packet);
			for(TivoLocatorListener listener: listeners) {
				listener.processTivoHeartbeat(info);
			}
		} catch (Exception e) {
			System.err.println("Error parsing packet from " + packet.getAddress());
		}
	}
	
	public void close() {
		socket.close();
	}
	
	public void addListener(TivoLocatorListener listener) {
		listeners.add(listener);
	}
	
	public interface TivoLocatorListener {
		void processTivoHeartbeat(TivoInformation info);
	}
	
	public static class TivoInformation {

		private InetAddress address;
		private int tivoConnect;
		private String method;
		private String platform;
		private String machineName;
		private String identity;
		private String services;
		private String swVersion;
		
		public TivoInformation(DatagramPacket packet) throws Exception {
			address = packet.getAddress();
			String[] lines = convertToLines(packet.getData(), packet.getLength());
			tivoConnect = Integer.valueOf(fetchData(lines, "tivoconnect", true));
			method = fetchData(lines, "method", true);
			platform = fetchData(lines, "platform", true);
			machineName = fetchData(lines, "machine", false);
			identity = fetchData(lines, "identity", true);
			services = fetchData(lines, "services", false);
			swVersion = fetchData(lines, "swversion", false);
		}
		
		

		private String fetchData(String[] lines, String field, boolean required) throws Exception {
			for(String line: lines) {
				String[] parts = line.split("=");
				if(parts[0].equalsIgnoreCase(field)) {
					return parts[1].trim();
				}
			}
			
			if(required) {
				throw new Exception("Unable to locate required field: " + field);
			}
			
			return "";
		}



		private String[] convertToLines(byte[] data, int length) throws IOException {
			StringBuffer buf = new StringBuffer();
			for(int i = 0; i < length; i++) {
				buf.append( (char) data[i] );
			}
			
			BufferedReader reader = new BufferedReader(new StringReader(buf.toString().trim()));
			List<String> list = new ArrayList<String>();
			String line = reader.readLine();
			while(line != null) {
				list.add(line);
				line = reader.readLine();
			}
			
			String[] arr = new String[list.size()];
			for(int i = 0; i < list.size(); i++) {
				arr[i] = list.get(i).trim();
			}
			
			return arr;
		}



		public final int getTivoConnect() {
			return tivoConnect;
		}

		public final String getMethod() {
			return method;
		}

		public final String getPlatform() {
			return platform;
		}

		public final String getMachineName() {
			return machineName;
		}

		public final String getIdentity() {
			return identity;
		}

		public final String getServices() {
			return services;
		}

		public final InetAddress getAddress() {
			return address;
		}

		public final String getSwVersion() {
			return swVersion;
		}
		
	}
	
}
