/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.prefs;

import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.prefs.BackingStoreException;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;

import javax.swing.JFrame;

import com.cburch.logisim.Main;
import com.cburch.logisim.circuit.RadixOption;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.gui.start.Startup;
import com.cburch.logisim.util.LocaleListener;
import com.cburch.logisim.util.LocaleManager;
import com.cburch.logisim.util.PropertyChangeWeakSupport;

public class AppPreferences {
	// class variables for maintaining consistency between properties,
	// internal variables, and other classes
	private static Preferences prefs = null;
	private static MyListener myListener = null;
	private static PropertyChangeWeakSupport propertySupport
		= new PropertyChangeWeakSupport(AppPreferences.class);

	// Template preferences
	public static final int TEMPLATE_UNKNOWN = -1;
	public static final int TEMPLATE_EMPTY = 0;
	public static final int TEMPLATE_PLAIN = 1;
	public static final int TEMPLATE_CUSTOM = 2;

	public static final String TEMPLATE = "template";
	public static final String TEMPLATE_TYPE = "templateType";
	public static final String TEMPLATE_FILE = "templateFile";

	private static int templateType = TEMPLATE_PLAIN;
	private static File templateFile = null;

	private static Template plainTemplate = null;
	private static Template emptyTemplate = null;
	private static Template customTemplate = null;
	private static File customTemplateFile = null;
	
	// International preferences
	public static final String SHAPE_SHAPED = "shaped";
	public static final String SHAPE_RECTANGULAR = "rectangular";
	public static final String SHAPE_DIN40700 = "din40700";

	public static final PrefMonitor<String> GATE_SHAPE
		= create(new PrefMonitorStringOpts("gateShape",
			new String[] { SHAPE_SHAPED, SHAPE_RECTANGULAR, SHAPE_DIN40700 },
			SHAPE_SHAPED));
	public static final PrefMonitor<String> LOCALE
		= create(new LocalePreference());
	public static final PrefMonitor<Boolean> ACCENTS_REPLACE
		= create(new PrefMonitorBoolean("accentsReplace", false));
	
	// Window preferences
	public static final String TOOLBAR_HIDDEN = "hidden";
	public static final String TOOLBAR_DOWN_MIDDLE = "downMiddle";
	
	public static final PrefMonitor<Boolean> SHOW_TICK_RATE
		= create(new PrefMonitorBoolean("showTickRate", false));
	public static final PrefMonitor<String> TOOLBAR_PLACEMENT
		= create(new PrefMonitorStringOpts("toolbarPlacement", new String[] {
					Direction.NORTH.toString(), Direction.SOUTH.toString(),
					Direction.EAST.toString(), Direction.WEST.toString(),
					TOOLBAR_DOWN_MIDDLE, TOOLBAR_HIDDEN },
				Direction.NORTH.toString()));
	
	// Layout preferences
	public static final String ADD_AFTER_UNCHANGED = "unchanged";
	public static final String ADD_AFTER_EDIT = "edit";

	public static final PrefMonitor<Boolean> PRINTER_VIEW
		= create(new PrefMonitorBoolean("printerView", false));
	public static final PrefMonitor<Boolean> ATTRIBUTE_HALO
		= create(new PrefMonitorBoolean("attributeHalo", true));
	public static final PrefMonitor<Boolean> COMPONENT_TIPS
		= create(new PrefMonitorBoolean("componentTips", true));
	public static final PrefMonitor<Boolean> MOVE_KEEP_CONNECT
		= create(new PrefMonitorBoolean("keepConnected", true));
	public static final PrefMonitor<Boolean> ADD_SHOW_GHOSTS
		= create(new PrefMonitorBoolean("showGhosts", true));
	public static final PrefMonitor<String> ADD_AFTER
		= create(new PrefMonitorStringOpts("afterAdd",
			new String[] { ADD_AFTER_EDIT, ADD_AFTER_UNCHANGED }, ADD_AFTER_EDIT));
	public static PrefMonitor<String> POKE_WIRE_RADIX1;
	public static PrefMonitor<String> POKE_WIRE_RADIX2;
	
	static {
		RadixOption[] radixOptions = RadixOption.OPTIONS;
		String[] radixStrings = new String[radixOptions.length];
		for (int i = 0; i < radixOptions.length; i++) {
			radixStrings[i] = radixOptions[i].getSaveString();
		}
		POKE_WIRE_RADIX1 = create(new PrefMonitorStringOpts("pokeRadix1",
				radixStrings, RadixOption.RADIX_2.getSaveString()));
		POKE_WIRE_RADIX2 = create(new PrefMonitorStringOpts("pokeRadix2",
				radixStrings, RadixOption.RADIX_10_SIGNED.getSaveString()));
	}

	// Experimental preferences
	public static final String ACCEL_DEFAULT = "default";
	public static final String ACCEL_NONE = "none";
	public static final String ACCEL_OPENGL = "opengl";
	public static final String ACCEL_D3D = "d3d";

	public static final PrefMonitor<String> GRAPHICS_ACCELERATION
		= create(new PrefMonitorStringOpts("graphicsAcceleration",
				new String[] { ACCEL_DEFAULT, ACCEL_NONE, ACCEL_OPENGL, ACCEL_D3D },
				ACCEL_DEFAULT));
	
	// hidden window preferences - not part of the preferences dialog, changes
	// to preference does not affect current windows, and the values are not
	// saved until the application is closed
	public static final String RECENT_PROJECTS = "recentProjects";
	private static final RecentProjects recentProjects = new RecentProjects();
	public static final PrefMonitor<Double> TICK_FREQUENCY
		= create(new PrefMonitorDouble("tickFrequency", 1.0));
	public static final PrefMonitor<Boolean> LAYOUT_SHOW_GRID
		= create(new PrefMonitorBoolean("layoutGrid", true));
	public static final PrefMonitor<Double> LAYOUT_ZOOM
		= create(new PrefMonitorDouble("layoutZoom", 1.0));
	public static final PrefMonitor<Boolean> APPEARANCE_SHOW_GRID
		= create(new PrefMonitorBoolean("appearanceGrid", true));
	public static final PrefMonitor<Double> APPEARANCE_ZOOM
		= create(new PrefMonitorDouble("appearanceZoom", 1.0));
	public static final PrefMonitor<Integer> WINDOW_STATE
		= create(new PrefMonitorInt("windowState", JFrame.NORMAL));
	public static final PrefMonitor<Integer> WINDOW_WIDTH
		= create(new PrefMonitorInt("windowWidth", 640));
	public static final PrefMonitor<Integer> WINDOW_HEIGHT
		= create(new PrefMonitorInt("windowHeight", 480));
	public static final PrefMonitor<String> WINDOW_LOCATION
		= create(new PrefMonitorString("windowLocation", "0,0"));
	public static final PrefMonitor<Double> WINDOW_MAIN_SPLIT
		= create(new PrefMonitorDouble("windowMainSplit", 0.25));
	public static final PrefMonitor<Double> WINDOW_LEFT_SPLIT
		= create(new PrefMonitorDouble("windowLeftSplit", 0.5));
	public static final PrefMonitor<String> DIALOG_DIRECTORY
		= create(new PrefMonitorString("dialogDirectory", ""));
	
	//
	// methods for accessing preferences
	//
	private static class MyListener implements PreferenceChangeListener,
			LocaleListener {
		public void preferenceChange(PreferenceChangeEvent event) {
			Preferences prefs = event.getNode();
			String prop = event.getKey();
			if (ACCENTS_REPLACE.getIdentifier().equals(prop)) {
				getPrefs();
				LocaleManager.setReplaceAccents(ACCENTS_REPLACE.getBoolean());
			} else if (prop.equals(TEMPLATE_TYPE)) {
				int oldValue = templateType;
				int value = prefs.getInt(TEMPLATE_TYPE, TEMPLATE_UNKNOWN);
				if (value != oldValue) {
					templateType = value;
					propertySupport.firePropertyChange(TEMPLATE, oldValue, value);
					propertySupport.firePropertyChange(TEMPLATE_TYPE, oldValue, value);
				}
			} else if (prop.equals(TEMPLATE_FILE)) {
				File oldValue = templateFile;
				File value = convertFile(prefs.get(TEMPLATE_FILE, null));
				if (value == null ? oldValue != null : !value.equals(oldValue)) {
					templateFile = value;
					if (templateType == TEMPLATE_CUSTOM) {
						customTemplate = null;
						propertySupport.firePropertyChange(TEMPLATE, oldValue, value);
					}
					propertySupport.firePropertyChange(TEMPLATE_FILE, oldValue, value);
				}
			}
		}
		
		public void localeChanged() {
			Locale loc = LocaleManager.getLocale();
			String lang = loc.getLanguage();
			if (LOCALE != null) {
				LOCALE.set(lang);
			}
		}
	}

	private static <E> PrefMonitor<E> create(PrefMonitor<E> monitor) {
		return monitor;
	}
	
	public static void clear() {
		Preferences p = getPrefs(true);
		try { p.clear(); } catch (BackingStoreException e) { }
	}
	
	static Preferences getPrefs() {
		return getPrefs(false);
	}
	
	private static Preferences getPrefs(boolean shouldClear) {
		if (prefs == null) {
			synchronized(AppPreferences.class) {
				if (prefs == null) {
					Preferences p = Preferences.userNodeForPackage(Main.class);
					if (shouldClear) {
						try { p.clear(); } catch (BackingStoreException e) { }
					}
					myListener = new MyListener();
					p.addPreferenceChangeListener(myListener);
					prefs = p;

					setTemplateFile(convertFile(p.get(TEMPLATE_FILE, null)));
					setTemplateType(p.getInt(TEMPLATE_TYPE, TEMPLATE_PLAIN));
				}
			}
		}
		return prefs;
	}
	
	private static File convertFile(String fileName) {
		if (fileName == null || fileName.equals("")) {
			return null;
		} else {
			File file = new File(fileName);
			return file.canRead() ? file : null;
		}
	}
	
	//
	// PropertyChangeSource methods
	//
	public static void addPropertyChangeListener(PropertyChangeListener listener) {
		propertySupport.addPropertyChangeListener(listener);
	}
	public static void removePropertyChangeListener(PropertyChangeListener listener) {
		propertySupport.removePropertyChangeListener(listener);
	}
	public static void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		propertySupport.addPropertyChangeListener(propertyName, listener);
	}
	public static void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		propertySupport.removePropertyChangeListener(propertyName, listener);
	}
	
	static void firePropertyChange(String property, boolean oldVal, boolean newVal) {
		propertySupport.firePropertyChange(property, oldVal, newVal);
	}
	static void firePropertyChange(String property, Object oldVal, Object newVal) {
		propertySupport.firePropertyChange(property, oldVal, newVal);
	}

	//
	// accessor methods
	//
	public static int getTemplateType() {
		getPrefs();
		int ret = templateType;
		if (ret == TEMPLATE_CUSTOM && templateFile == null) {
			ret = TEMPLATE_UNKNOWN;
		}
		return ret;
	}
	
	public static void setTemplateType(int value) {
		getPrefs();
		if (value != TEMPLATE_PLAIN && value != TEMPLATE_EMPTY && value != TEMPLATE_CUSTOM) {
			value = TEMPLATE_UNKNOWN;
		}
		if (value != TEMPLATE_UNKNOWN && templateType != value) {
			getPrefs().putInt(TEMPLATE_TYPE, value);
		}
	}
	
	public static File getTemplateFile() {
		getPrefs();
		return templateFile;
	}
	
	public static void setTemplateFile(File value) {
		getPrefs();
		setTemplateFile(value, null);
	}
	
	public static void setTemplateFile(File value, Template template) {
		getPrefs();
		if (value != null && !value.canRead()) value = null;
		if (value == null ? templateFile != null : !value.equals(templateFile)) {
			try {
				customTemplateFile = template == null ? null : value;
				customTemplate = template;
				getPrefs().put(TEMPLATE_FILE, value == null ? "" : value.getCanonicalPath());
			} catch (IOException ex) { }
		}
	}
	
	public static void handleGraphicsAcceleration() {
		String accel = GRAPHICS_ACCELERATION.get();
		try {
			if (accel == ACCEL_NONE) {
				System.setProperty("sun.java2d.opengl", "False");
				System.setProperty("sun.java2d.d3d", "False");
			} else if (accel == ACCEL_OPENGL) {
				System.setProperty("sun.java2d.opengl", "True");
				System.setProperty("sun.java2d.d3d", "False");
			} else if (accel == ACCEL_D3D) {
				System.setProperty("sun.java2d.opengl", "False");
				System.setProperty("sun.java2d.d3d", "True");
			}
		} catch (Throwable t) { }
	}
	
	//
	// template methods
	//
	public static Template getTemplate() {
		getPrefs();
		switch (templateType) {
		case TEMPLATE_PLAIN: return getPlainTemplate();
		case TEMPLATE_EMPTY: return getEmptyTemplate();
		case TEMPLATE_CUSTOM: return getCustomTemplate();
		default: return getPlainTemplate();
		}
	}
	
	public static Template getEmptyTemplate() {
		if (emptyTemplate == null) emptyTemplate = Template.createEmpty();
		return emptyTemplate;
	}
	
	private static Template getPlainTemplate() {
		if (plainTemplate == null) {
			ClassLoader ld = Startup.class.getClassLoader();
			InputStream in = ld.getResourceAsStream("resources/logisim/default.templ");
			if (in == null) {
				plainTemplate = getEmptyTemplate(); 
			} else {
				try {
					try {
						plainTemplate = Template.create(in);
					} finally {
						in.close();
					}
				} catch (Throwable e) {
					plainTemplate = getEmptyTemplate();
				}
			}
		}
		return plainTemplate;
	}
	
	private static Template getCustomTemplate() {
		File toRead = templateFile;
		if (customTemplateFile == null || !(customTemplateFile.equals(toRead))) {
			if (toRead == null) {
				customTemplate = null;
				customTemplateFile = null;
			} else {
				FileInputStream reader = null;
				try {
					reader = new FileInputStream(toRead);
					customTemplate = Template.create(reader);
					customTemplateFile = templateFile;
				} catch (Throwable t) {
					setTemplateFile(null);
					customTemplate = null;
					customTemplateFile = null;
				} finally {
					if (reader != null) {
						try { reader.close(); } catch (IOException e) { }
					}
				}
			}
		}
		return customTemplate == null ? getPlainTemplate() : customTemplate;
	}
	
	//
	// recent projects
	//
	public static List<File> getRecentFiles() {
		return recentProjects.getRecentFiles();
	}
	
	public static void updateRecentFile(File file) {
		recentProjects.updateRecent(file);
	}
	
	//
	// LocalePreference
	//
	private static class LocalePreference extends PrefMonitorString {
		public LocalePreference() {
			super("locale", "");

			String localeStr = this.get();
			if (localeStr != null && !localeStr.equals("")) {
				LocaleManager.setLocale(new Locale(localeStr));
			}
			LocaleManager.addLocaleListener(myListener);
			myListener.localeChanged();
		}

		@Override
		public void set(String value) {
			if (findLocale(value) != null) {
				super.set(value);
			}
		}
		
		private static Locale findLocale(String lang) {
			Locale[] check;
			for (int set = 0; set < 2; set++) {
				if (set == 0) check = new Locale[] { Locale.getDefault(), Locale.ENGLISH };
				else check = Locale.getAvailableLocales();
				for (int i = 0; i < check.length; i++) {
					Locale loc = check[i];
					if (loc != null && loc.getLanguage().equals(lang)) {
						return loc;
					}
				}
			}
			return null;
		}
	}
}
