package net.psammead.commonist;

import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.List;

import javax.swing.*;

import net.psammead.commonist.data.*;
import net.psammead.commonist.task.*;
import net.psammead.commonist.text.*;
import net.psammead.commonist.thumb.*;
import net.psammead.commonist.ui.*;
import net.psammead.commonist.util.*;
import net.psammead.mwapi.*;
import net.psammead.mwapi.connection.*;
import net.psammead.util.*;
import net.psammead.util.apple.AppleQuit;
import bsh.*;

/** the main application class */
public class Main {
	private static final Logger log = new Logger(Main.class);
	
	/** main entry point */
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				try { 
					new Main(null, "The Commonist").init();
				}
				catch (Exception e) {
					log.error("cannot start program", e);
				}
			}
		});
	}
	
	private MediaWiki 	mw;

	private Loader		loader;
	private	File		settingsDir;
	
	private Settings	settings;
	private Thumbnails	thumbnails;
	private FileCache	cache;
	private	Templates	templates;

	private MainWindow	mainWindow;
	private CommonUI	commonUI;
	private DirectoryUI	directoryUI;
	private	ImageListUI	imageListUI;
	private	StatusUI	statusUI;
	private UploadUI	uploadUI;

	private ChangeDirectoryTask changeDirectoryTask;
	private UploadFilesTask 	uploadFilesTask;
	
	public Main(Image programIcon, String programHeading) {
		settingsDir	= new File(new File(System.getProperty("user.home")), ".commonist");
		settingsDir.mkdirs();
		
		// HACK: running from webstart or load from the Filesystem
//		boolean fromWebStart	= !(new File("etc/licenses.txt").exists());
		
		File	projectDir	= new File(new File(System.getProperty("user.dir")), "etc");
		
		loader	= new Loader(settingsDir, projectDir, "/etc/");

		String	userLanguage	= System.getProperty("user.language");
		log.info("using user language: " + userLanguage);
		try { initMessages(userLanguage); }
		catch (IOException e) { throw new Error("cannot load messages.properties", e); }
		
		List<LicenseData>	licenses	= null;
		try { licenses	= initLicenses(); }
		catch (IOException e) { throw new Error("cannot load licenses.txt", e); }

		try { mw = new MediaWiki(); }
		catch (ConfigException e) { throw new Error("cannot instantiate MediaWiki", e); }
		mw.setLog(System.err);
		mw.setupProxy();
		
		addFamilies();
		List<String>	wikiList	= new Vector<String>(mw.supportedWikis());
		
		try { sourceStartup(); }
		catch (IOException e) { throw new Error("cannot load startup.bsh", e); }
		
		settings	= new Settings(
								settingsFile("settings.properties"));

		cache		= new FileCache(
								settingsFile("thumbnails.txt"), 
								settingsFile("cache"),
								Constants.THUMBNAIL_CACHE_SIZE);
								
		thumbnails	= new Thumbnails(cache);
		
		commonUI	= new CommonUI(wikiList, licenses);
		
		directoryUI	= new DirectoryUI(new DirectoryUI.Callback() {
			public void changeDirectory(File currentDirectory) {
				doChangeDirectory(currentDirectory);
			}
		});
		
		statusUI	= new StatusUI();
		
		uploadUI	= new UploadUI(new UploadUI.Callback() {
			public void startUpload()	{ doStartUpload();	}
			public void stopUpload()	{ doStopUpload();	}
		});
		
		imageListUI	= new ImageListUI(programHeading, programIcon);
		
		mainWindow	= new MainWindow(commonUI, directoryUI, imageListUI, statusUI, uploadUI,
								programHeading, programIcon, new MainWindow.Callback() {
			public void quit() { doQuit(); }
		});
		
		templates	= new Templates(loader);
		
		changeDirectoryTask	= null;
		uploadFilesTask		= null;
		
		// install AppleQuitHandler
		AppleQuit.install(new AppleQuit.AppleQuitHandler() {
			public void applicationQuit() { doQuit(); }
		});
	}
	
	//-------------------------------------------------------------------------
	//## life cycle
	
	/** startup, called after UI constructors */
	public void init() {
		log.info("starting up");
		
		try { cache.load(); }
		catch (IOException e) { log.error("cannot load cache", e); }
		
		try { settings.load(); }
		catch (IOException e) { log.error("cannot load settings", e); }
		
		thumbnails.loadSettings(settings);
		commonUI.loadSettings(settings);
		directoryUI.loadSettings(settings);
		mainWindow.loadSettings(settings);
		
		mainWindow.makeVisible();
		
		log.info("running");
	}

	/** shutdown */
	public void exit() {
		log.info("shutting down");
		
		thumbnails.saveSettings(settings);
		commonUI.saveSettings(settings);
		directoryUI.saveSettings(settings);
		mainWindow.saveSettings(settings);
		
		try { settings.save(); }
		catch (IOException e) { log.error("cannot save settings", e); }
		try { cache.save(); }
		catch (IOException e) { log.error("cannot save cache", e); }
		
		log.info("finished");
	}
	
	//-------------------------------------------------------------------------
	//## actions
	
	/** Action: quit the program */
	private void doQuit() {
		exit();
		System.exit(0);
	}
	
	/** 
	 * Action: change to a new directory
	 * load and display imageUIs for all files in the new directory
	 */
	private void doChangeDirectory(final File directory) {
		Task	old	= changeDirectoryTask;
		changeDirectoryTask	= new ChangeDirectoryTask(mainWindow, imageListUI, statusUI, thumbnails, directory);
		if (old != null)	old.replace(changeDirectoryTask);
		else				changeDirectoryTask.start();
	}
	
	/** Action: start uploading selected files */
	private void doStartUpload() {
		if (imageListUI.getData().getSelected().size() == 0) {
			log.info("uploading does not make sense when no file is selected");
			return;
		}
		Task	old	= uploadFilesTask;
		uploadFilesTask	= new UploadFilesTask(mw, templates, mainWindow, commonUI.getData(), imageListUI.getData(), statusUI);
		if (old != null)	old.replace(uploadFilesTask);
		else				uploadFilesTask.start();
	}
	
	/** Action: stop uploading selected files */
	private void doStopUpload() {
		if (uploadFilesTask == null)	return;
		uploadFilesTask.abort();
	}
	
	//-------------------------------------------------------------------------
	//## init
	
	/** loads all family files from $settingsDir/family/ */
	private void addFamilies() {
		File	dir		= settingsFile("family");
		if (!dir.exists()) {
			log.info("directory for additional wiki families not found: " + dir);
			return;
		}
		log.info("loading additional families from: " + dir);
		File[]	files	= dir.listFiles();
		for (int i=0; i<files.length; i++) {
			File	file	= files[i];
			if (!file.getName().endsWith(".family"))	continue;
			log.info("loading family: " + file);
			try {
				mw.loadFamily(file.toURI().toURL());
			}
			catch (ConfigException e) {
				log.error("could not load family from: " + file, e);
			}
			catch (MalformedURLException e) {
				log.error("malformed URL for family file: " + file, e);
			}
		}
	}

	/** load language file for the language or en if not successful and returns the used language */
	private void initMessages(String language) throws IOException {
		URL	defaultURL	= loader.mandatoryURL("messages.properties");
		URL	userLangURL	= loader.optionalURL("messages_" + language + ".properties");
		Messages.init(defaultURL, userLangURL);
	}
	
	/** load licenses */
	private List<LicenseData> initLicenses() throws IOException {
		URL				url		= loader.mandatoryURL("licenses.txt");
		ParsedLicenses	parsed	= new ParsedLicenses(url);

		List<LicenseData>	out	= new ArrayList<LicenseData>();
		out.add(new LicenseData("", ""));
		out.addAll(parsed.licenseDatas);
		return out;
	}
	
	/** loads and executes startup.bsh */
	private void sourceStartup() throws IOException {
		URL	url	= loader.optionalURL("startup.bsh");
		if (url == null)	{ log.info("skipping, not found: startup.bsh"); return; }
		
		try {
			Interpreter	interpreter	= new Interpreter();
			interpreter.set("mw", mw);
			
			Reader	in	= null;
			try {
				in	= new InputStreamReader(url.openStream(), "UTF-8");
				interpreter.eval(in, interpreter.getNameSpace(), url.toExternalForm());
			}
			finally {
				if (in != null)
				try { in.close(); }
				catch (Exception e) { log.error("cannot close", e); }
			}
		}
		catch (EvalError e) {
			log.error("could not load startup.bsh", e);
		}
	}
	
	//-------------------------------------------------------------------------
	//## resources
	
	/** returns a File in the settings directory */
	private File settingsFile(String path) {
		return new File(settingsDir, path); 
	}
}
