/*
 * Copyright (C) 2005-06  Mike Murray
 *
 *  All rights reserved.
 *  This document may not be reused without
 *  express written permission from Mike Murray.
 *
 */

package net.sf.j3d.network.db;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Hashtable;
import java.util.Properties;

import javax.swing.JLabel;

import net.sf.j3d.network.Message;

/**
 * A Client object is used to send output produced by executing a Job
 * to a remote output host that can persist the data for later use.
 * The Client class provides a writeOutput method for sending output
 * and a sendQuery method for requesting persisted data.
 * A Client object also encapsulates a network.Server instance and keeps
 * track of login information.
 * 
 * @author Mike Murray
 */
public class Client {
	private static Client client;
	
	private String user, passwd;
	private String outputHost;
	private int outputPort;
	
	private long startTime;
	
	private net.sf.j3d.network.Server server;
	
	/**
	 * Prompts for username and password and constructs a new
	 * Client instance.
	 * 
	 * @param args {URL of properties file for net.sf.j3d.network.server instance}
	 */
	public static void main(String[] args) {
		final Properties p = new Properties();
		
		try {
			InputStream in = (new URL(args[0])).openStream();
			p.load(in);            
		} catch (MalformedURLException e) {
			System.out.println("Client: Malformed properties URL");
			System.exit(1);
		} catch (IOException e) {
			System.out.println("Client: IO error loading properties");
			System.exit(2);
		}
		
		String user, passwd;
		
//		if (args.length >= 3) {
//			user = args[1];
//			passwd = args[2];
//		} else {
			final LoginDialog l = new LoginDialog();
			
			Runnable r = new Runnable() {
				public void run() {
					String user = l.getUser();
					String passwd = l.getPassword();
					
					try {
						Client.client = new Client(p, user, passwd, null);
					} catch (IOException e) {
						System.out.println("Client: " + e);
					}
				}
			};
			
			l.showDialog(r);
//		}
	}
	
	/**
	 * Constructs a new Client object. This constructor will
	 * start the net.sf.j3d.network.Server thread.
	 * 
	 * @param p  Properties object to pass to Server.
	 * @param user  Username to use when sending job output.
	 * @param passwd  Password to use when sending job output.
	 * @param status  Label to display server status.
	 * @throws IOException  If an IOException occurs creating the Server instance.
	 */
	public Client(Properties p, String user, String passwd, JLabel status) throws IOException {
		this.user = user;
		this.passwd = passwd;
		
		this.outputHost = p.getProperty("servers.output.host", "localhost");
		this.outputPort = Integer.parseInt(p.getProperty("servers.output.port", "7788"));
		
		this.server = new net.sf.j3d.network.Server(p, null);
		this.setStatusLabel(status);
		this.server.start();
		this.startTime = System.currentTimeMillis();
	}
	
	public String getUser() { return this.user; }
	
	public String getPassword() { return this.passwd; }
	
	public void setOutputHost(String host) { this.outputHost = host; }
	public void setOutputPort(int port) { this.outputPort = port; }
	public String getOutputHost() { return this.outputHost; }
	public int getOutputPort() { return this.outputPort; }
	
	public void setStatusLabel(JLabel label) { if (this.server != null) this.server.setStatusLabel(label); }
	
	public net.sf.j3d.network.Server getServer() { return this.server; }
	
	/**
	 * Returns this time in milliseconds since the client was initialized.
	 */
	public long getUptime() { return System.currentTimeMillis() - this.startTime; }
	
	/**
	 * Returns the time in milliseconds (System.currentTimeMillis method) when the
	 * client was initialized.
	 */
	public long getStartTime() { return this.startTime; }
	
	/**
	 * Sends the specified Query to the output server.
	 * 
	 * @param q  Query object to send.
	 * @return  The Hashtable resulting from the query or null if an error occurs.
	 */
	public Hashtable sendQuery(Query q) {
		Socket s = null;
		
		try {
			s = new Socket(this.outputHost, this.outputPort);
			
			ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
			ObjectInputStream in = new ObjectInputStream(s.getInputStream());
			
			out.writeUTF(q.getClass().getName());
			q.writeExternal(out);
			
			out.flush();
			
			Hashtable h = (Hashtable) in.readObject();
			
			in.close();
			out.close();
			s.close();
			
			return h;
		} catch (ClassNotFoundException cnf) {
			System.out.println("Client: " + cnf);
			return null;
		} catch (UnknownHostException uh) {
			System.out.println("Client: Output host " + this.outputHost + ":"+ this.outputPort + ") not found.");
			return null;
		} catch (IOException ioe) {
			System.out.println("Client: " + ioe);
			return null;
		}
	}
	
	/**
	 * Sends the specified JobOutput object to the output server.
	 * 
	 * @param out  Output to send.
	 * @return  True if send is successful, false otherwise.
	 */
	public boolean writeOutput(JobOutput o) {
		Socket s = null;
		
		boolean done = false;
		int sleep = 3;
		
		for (int i = 0; !done; i++) {
			try {
				s = new Socket(this.outputHost, this.outputPort);
				
				if (Message.verbose)
					System.out.println("Client: Opened socket " + s);
				
				ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
				ObjectInputStream in = new ObjectInputStream(s.getInputStream());
				
				if (Message.verbose)
					System.out.println("Client: Writing " + o + "...");
				out.writeUTF(o.getClass().getName());
				o.writeExternal(out);
				
//				out.writeObject(o);
				
				done = true;
				
				in.close();
				out.close();
				s.close();
				
				return true;
			} catch (ConnectException ce) {
				if (i >= 4) {
					System.out.println("Client: Error connection to output host - giving up");
					return false;
				} else {
					System.out.println("Client: Error connecting to output host - retry in " + sleep + " sec.");
					try { Thread.sleep(sleep * 1000); } catch (InterruptedException ie) {}
				}
			} catch (UnknownHostException uh) {
				System.out.println("Client: Output host (" + this.outputHost + ":"+ this.outputPort + ") not found.");
				return false;
			} catch (IOException ioe) {
				System.out.println("Client: " + ioe);
				ioe.printStackTrace(System.out);
				return false;
			} catch (Exception e) {
				if (done)
					System.out.println("Client.writeOutput: " + e);
				else
					System.out.println("Client.writeOutput: Ended prematurely due to " + e);
			}
		}
		
		return done;
	}
	
	/**
	 * Sets the Client to be returned by the getCurrentClient method.
	 * 
	 * @param client  The Client instance to use.
	 */
	public static void setCurrentClient(Client client) { Client.client = client; }
	
	/**
	 * @return  The Client started by the Client.main method.
	 */
	public static Client getCurrentClient() { return Client.client; }
}
