package org.wikiwebserver.distribute.se;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import org.wikiwebserver.distribute.interfaces.ConfigChangeListener;

public class ConfigManager implements Runnable {
    
    private static final boolean DEBUG = false;

	private static final String configFileName = ".node_config.xml";
	private static final Properties config = new Properties();
	private static long configModifiedTime = 0;
	
	private static List<ConfigChangeListener> configChangeListeners 
		= new LinkedList<ConfigChangeListener>();
	
	// Default configuration	
	static {
		
		setString("taskClassServerUrl", "http://www.wikiwebserver.org/");			
		
        setString("taskAssignmentUrl", 
            "http://www.wikiwebserver.org/org/wikiwebserver/handler/http/responder/NodeTaskAssignmentResponder.class");    
        
		setString("taskCommunicationUrl", 
				  "http://www.wikiwebserver.org/org/wikiwebserver/handler/http/responder/NodeTaskResponder.class");		
		
		setString("remoteWorkerClassName", 
				   "org.wikiwebserver.distribute.se.worker.RemoteWorkerImpl");
		
		File sharedFile = null;
		
		// Attempt to create sensible default
		try {
			File userHome = getHomeDirectory();
			
			File winMyDocs = new File(userHome, "My Documents");
			if (winMyDocs.exists()) userHome = winMyDocs;
			
			sharedFile = new File(userHome, "Shared on MyDisk.co.uk");
			sharedFile.mkdir();
		} 
		catch (Throwable ex) {}	

		if (sharedFile != null) {
			setString("nodePath", sharedFile.toString());
		}
		
	}
	
	public void run() {
		
		synchronized (this) {
			load();
			notifyAll();
		}
		
		// Save configuration when it has been modified
		long configSaveTime = 0;
		while (true) {
			if (configModifiedTime > configSaveTime) {
				configSaveTime = System.currentTimeMillis();
				save();
			}
			try { Thread.sleep(1000);
			} catch (InterruptedException ex) {}
		}		
	}
	
	public static long getConfigModifiedTime() {
		return configModifiedTime;
	}
	
	public static void setString(String property, String newValue) {
		String oldValue = getString(property);
		if (hasChanged(oldValue, newValue)) {
		    if (newValue == null) config.remove(property);
		    else config.setProperty(property, newValue);
			configModifiedTime = System.currentTimeMillis();
			fireConfigChangeEvent();
			if (DEBUG) {
			    System.out.println(property + " changed from " + oldValue + " to " + newValue);
			}
		}
	}	
	
	public static String getString(String property) {
		return config.getProperty(property);
	}
	
	public static String getString(String property, String def) {
		return config.getProperty(property, def);
	}
	
	public static long getLong(String property) {
		return Long.parseLong(getString(property));
	}	
	
	public static long getLong(String property, long def) {
		return Long.parseLong(getString(property, String.valueOf(def)));
	}	
	
	private static void load() {
		
		// Try to load configuration from working directory
		FileInputStream fin = null;
		try {
		    File configFile = new File(getHomeDirectory(), configFileName);
		    //System.out.println("Attempting to load config from: " + configFile.getAbsolutePath());
		    fin = new FileInputStream(configFile);
		    config.loadFromXML(fin);
		    configModifiedTime = configFile.lastModified();
		}
		catch (FileNotFoundException ex) {

			// Try to load configuration from class path (inside JAR?)
			InputStream cin = null;
			try {
				cin = RemoteWorkerApp.class.getClass().getResourceAsStream(configFileName);
				if (cin != null) {
					config.loadFromXML(cin);
				}
			} 
			catch (Exception ex2) {
				ex2.printStackTrace();
			}		   
			finally {
				try { cin.close(); } catch (Exception ex2) {}
			}
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		finally {
			try { fin.close(); } catch (Exception ex) {}
		}
	}

	
	private static void save() {
		
		// Save configuration to working directory
		File tmpFile = new File(getHomeDirectory(), configFileName + ".tmp");
		File finalFile = new File(getHomeDirectory(), configFileName);
		FileOutputStream fout = null;
		try {
		   fout = new FileOutputStream(tmpFile);
		   config.storeToXML(fout, "Node configuration");
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
		finally {
			try { fout.close(); } catch (Exception ex) {}
			if (tmpFile.length() > 0) {
				finalFile.delete();
				tmpFile.renameTo(finalFile);
				finalFile.setLastModified(configModifiedTime);
			}
		}
	}	
	
	public static File getHomeDirectory() {
		try {
			return new File(System.getProperty("user.home"));
		} catch (Throwable ex) {
		    return null;
		}		
	}
	
	public static File getFileForPath(String path) {
		
		File root = new File(getString("nodePath"));
		File file = new File(root, path);
		try {
			if (file.getCanonicalPath().startsWith(root.getCanonicalPath())) {
				return file;
			}
		} catch (IOException ex) {}
		
		throw new SecurityException("Attempt to access illigal path");
	}
	
    public static URL getTaskAssignmentUrl() {
        
        try {
            return new URL(getString("taskAssignmentUrl"));
            
        } catch (MalformedURLException ex) {
            String msg = "Malformed task assignment URL";
            RuntimeException rex = new RuntimeException(msg);
            rex.initCause(ex);
            throw rex;
        }
    }   	
	
	public static URL getTaskCommunicationUrl() {
		
		try {
			return new URL(getString("taskCommunicationUrl"));
			
		} catch (MalformedURLException ex) {
			String msg = "Malformed task communication URL";
			RuntimeException rex = new RuntimeException(msg);
			rex.initCause(ex);
			throw rex;
		}
	}	
	
	public static URL getFileSharingUrl() {
		
		try {
			URL taskCommunicationURL = getTaskAssignmentUrl();
			
			String name = (String)config.get("nodeName");
			if (name == null) return null;
			
			String url = taskCommunicationURL.getProtocol() + "://" +
						 taskCommunicationURL.getHost();
			
			if (taskCommunicationURL.getPort() != -1) {
				url += ":" + taskCommunicationURL.getPort();
			}
			
			String urlEncName = URLEncoder.encode(name, "utf8").replace("+", "%20");
			url += "/nodes/" + urlEncName + "/";
			
			return new URL(url);

		} catch (Exception ex) {
			String msg = "Malformed file sharing URL";
			RuntimeException rex = new RuntimeException(msg);
			rex.initCause(ex);
			throw rex;
		}
	}
	
	public static void addConfigChangeListener(ConfigChangeListener listener) {
		configChangeListeners.add(listener);
	}
	
	private static void fireConfigChangeEvent() {
		Iterator<ConfigChangeListener> i = configChangeListeners.iterator();
		while (i.hasNext()) {
			ConfigChangeListener listener = i.next();
			listener.configChanged();
		}
	}			
	
	private static boolean hasChanged(Object obj1, Object obj2) {
        if (obj1 == null && obj2 == null) return false;	    
		if (obj1 == null && obj2 != null) return true;
		if (obj1 != null && obj2 == null) return true;
		return !obj1.equals(obj2);
	}	
}
