public class Configuration {

	/**
	 * The file which we loaded from
	 */
	private File file;

	/**
	 * The configuration map
	 */
	private JSONObject config = new JSONObject();

	/**
	 * Empty constructor
	 */
	public Configuration() {

	}

	/**
	 * Get a generic object, auto casting
	 * 
	 * @param key
	 *            The key
	 * @return The result
	 * @throws JSONException 
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(String key) {
		return (T) config.get(key);
	}

	/**
	 * Get a boolean
	 * 
	 * @param key
	 *            The key
	 * @return The result
	 * @throws JSONException 
	 */
	public boolean getBoolean(String key) {
		return config.getBoolean(key);
	}

	/**
	 * Get an integer
	 * 
	 * @param key
	 *            The key
	 * @return The integer
	 * @throws JSONException 
	 */
	public int getInteger(String key) {
		return config.getInt(key);
	}

	/**
	 * Get an integer
	 * 
	 * @param key
	 *            The key
	 * @return The integer
	 * @throws JSONException 
	 */
	public int getInteger(String key, int def) {
		return config.getInt(key, def);
	}

	/**
	 * Get a map which is automatically cast to the class specified
	 * 
	 * @param key
	 *            The key
	 * @return The map attached to the specified key
	 * @throws JSONException 
	 */
	public JSONObject getJSONObject(String key) {
		return config.getJSONObject(key);
	}

	/**
	 * Get an object
	 * 
	 * @param key
	 *            The key
	 * @return The object
	 * @throws JSONException 
	 */
	public Object getObject(String key) {
		return config.get(key);
	}

	/**
	 * Get a string result
	 * 
	 * @param key
	 *            The key
	 * @return The string containing the setting for the key.
	 * @throws JSONException 
	 */
	public String getString(String key) {
		return config.getString(key);
	}

	/**
	 * Load the configuration from a file
	 * 
	 * @param file
	 *            The file
	 * @throws IOException
	 *             If an error occurred while loading
	 * @throws JSONException 
	 */
	public void load(File file) throws IOException {
		this.file = file;
		try {
			FileInputStream fInput = new FileInputStream(file);
			try {
				this.config = new JSONObject(new JSONTokener(fInput));
			} finally {
				fInput.close();
			}
		} catch(RuntimeException e) {
			throw new IOException(e);
		}
	}
	
	/**
	 * Set the configuration file path
	 * @param file
	 * 			The file
	 */
	public void setFile(File file) {
		this.file = file;
	}

	/**
	 * Load from a string
	 * 
	 * @param contents
	 *            The JSON contents
	 * @throws IOException 
	 */
	public void load(String contents) throws IOException {
		try {
			this.config = new JSONObject(contents);
		} catch(RuntimeException e) {
			throw new IOException(e);
		}
	}

	/**
	 * Put a value
	 * 
	 * @param key
	 *            The key
	 * @param value
	 *            The value
	 * @throws JSONException 
	 */
	public void put(String key, Object value) {
		config.put(key, value);
	}

	/**
	 * Save to the original file
	 * 
	 * @throws IOException
	 *             If an error occurred
	 */
	public void save() throws IOException {
		if (file == null) {
			throw new IOException("File not set! Cannot save to file");
		}
		FileWriter writer = new FileWriter(file);
		try {
			writer.write(config.toString(4));
		} finally {
			writer.close();
		}
	}

	/**
	 * Check whether the configuration map contains a string
	 * 
	 * @param string
	 *            The string
	 * @return Whether it contains the string or not
	 */
	public boolean contains(String string) {
		return config.has(string);
	}

	@SuppressWarnings("unchecked")
	public <T> T getEnumValue(String key, Class<?> class1) {
		if(class1.isEnum()) {
			return (T) class1.getEnumConstants()[getInteger(key)];
		}
		return null;
	}

	public String getString(String key, String defaultValue) {
		return config.getString(key, defaultValue);
	}
} public class ScreenSnapper {

	/**
	 * A basic class which lets us execute a custom action based on the
	 * ScreenshotAction class
	 * 
	 * @author Nikki
	 * 
	 */
	@SuppressWarnings("serial")
	private class ActionMenuItem extends MenuItem implements ActionListener {
		/**
		 * The action id
		 */
		private int action;

		public ActionMenuItem(String s, int action) {
			super(s);
			this.action = action;
			addActionListener(this);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			hotkey(action);
		}
	}

	/**
	 * A wrapper for action ids
	 * 
	 * @author Nikki
	 * 
	 */
	public static class ScreenshotAction {
		private static final int CROP = 1;
		private static final int FULL = 2;
		private static final int CLIPBOARD = 3;
		public static final int ACTIVE = 4;
		public static final int FILE = 5;
	}

	/**
	 * A basic hack for class associations -> names
	 */
	private static HashMap<Class<?>, String> names = new HashMap<Class<?>, String>();

	/**
	 * Load the names and set the useragent
	 */
	static {
		System.setProperty("http.agent", Util.getHttpUserAgent());

		names.put(ImageUpload.class, "Images");
		names.put(TextUpload.class, "Text");
		names.put(URLUpload.class, "Urls");
		names.put(FileUpload.class, "Files");
		
		// Register the Password serializer/deserializer
	}

	/**
	 * Logging instance
	 */
	private static final Logger logger = Logger.getLogger(ScreenSnapper.class.getName());

	public static void main(String[] args) {
		// Initialize program
		ScreenSnapper instance = new ScreenSnapper();
		instance.initialize(ProgramOptions.parseSettings(args));
	}

	/**
	 * A map which contains uploader classes -> a list of available uploaders
	 */
	private HashMap<Class<? extends Upload>, Map<String, Uploader<? extends Upload>>> uploaders = new HashMap<Class<? extends Upload>, Map<String, Uploader<? extends Upload>>>();

	/**
	 * A map which contains the current uploader settings
	 */
	private HashMap<Class<? extends Upload>, Uploader<?>> uploaderAssociations = new HashMap<Class<? extends Upload>, Uploader<?>>();

	/**
	 * A map containing upload filters
	 */
	private HashMap<Class<? extends Upload>, LinkedList<UploadFilter<?>>> filters = new HashMap<Class<? extends Upload>, LinkedList<UploadFilter<?>>>();

	/**
	 * The basic service...
	 */
	private ExecutorService serv = Executors.newSingleThreadExecutor();

	/**
	 * The ExecutorService used to upload
	 */
	private ExecutorService uploadService = Executors.newSingleThreadExecutor();

	/**
	 * The tray icon
	 */
	private TrayIcon icon;

	/**
	 * The configuration instance
	 */
	private Configuration configuration = new Configuration();

	/**
	 * The selection window instances
	 */
	private SelectionWindow window = null;

	/**
	 * Defines whether the options panel is open
	 */
	private boolean optionsOpen;

	/**
	 * The hotkey manager instance
	 */
	private HotkeyManager keyManager;

	/**
	 * The history instance
	 */
	private History history;

	/**
	 * The last uploaded URL, used for clicking tray icon
	 */
	private String lastUrl;

	/**
	 * The number of retries used
	 */
	private int retries;

	/**
	 * The open settings window
	 */
	private JFrame settingsFrame;

	/**
	 * Initialize the program
	 * 
	 * @param map
	 *            Flag to reset configuration
	 */
	private void initialize(Map<String, Object> map) {
		// Check for a configuration option
		if (map.containsKey("dir")) {
			File file = new File(map.get("dir").toString());
			if (!file.exists()) {
				file.mkdirs();
			}
			Util.setWorkingDirectory(file);
		}
		// Verify the directory
		File local = Util.getWorkingDirectory();
		if (!local.exists()) {
			local.mkdirs();
		}
		// Load the Release info
		URL releaseUrl = Util.getResourceByName("/org/sleeksnap/release.json");
		JSONObject releaseInfo = new JSONObject();
		if(releaseUrl != null) {
			try {
				releaseInfo = new JSONObject(new JSONTokener(releaseUrl.openStream()));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		// Set the UI skin
		try {
			UIManager.setLookAndFeel(releaseInfo.getString("uiClass", Util.getSystemLookAndFeelClassName()));
		} catch (Exception e) {
			e.printStackTrace();
		}
		// Then start
		try {
			LoggingManager.configure();
		} catch(IOException e) {
			logger.log(Level.WARNING, "Unable to configure logger, file logging and logging panel will not work.", e);
			JOptionPane.showMessageDialog(null, "Unable to configure logger, file logging and logging panel will not work.", "Error", JOptionPane.ERROR_MESSAGE);
		}
		logger.info("Loading plugins...");
		try {
			loadUploaders();
			loadFilters();
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Failed to load plugins!", e);
		}
		// Load the settings
		logger.info("Loading settings...");
		try {
			loadSettings(map.containsKey("resetconfig"));
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Failed to load settings!", e);
		}
		// Load the selected language
		try {
			Language.load(map.containsKey("language") ? map.get("language").toString() : configuration.getString("language", Constants.Configuration.DEFAULT_LANGUAGE));
		} catch (IOException e) {
			logger.log(Level.SEVERE, "Failed to load language file!", e);
		}
		// Check the update mode
		UpdaterMode mode = configuration.getEnumValue("updateMode", UpdaterMode.class);
		if (mode != UpdaterMode.MANUAL) {
			UpdaterReleaseType type = configuration.getEnumValue("updateReleaseType", UpdaterReleaseType.class);
			Updater updater = new Updater();
			if (updater.checkUpdate(type, mode == UpdaterMode.PROMPT)) {
				return;
			}
		}
		// Load the history
		logger.info("Loading history...");
		File historyFile = new File(local, "history.json");
		history = new History(historyFile);
		if (historyFile.exists()) {
			logger.info("Using existing history file.");
			try {
				history.load();
			} catch (Exception e) {
				logger.log(Level.WARNING, "Failed to load history", e);
			}
		} else {
			logger.info("Using new history file.");
		}
		// Validate settings
		if (!configuration.contains("hotkeys") || !configuration.contains("uploaders")) {
			promptConfigurationReset();
		}
		// Register the hotkeys
		logger.info("Registering keys...");
		keyManager = new HotkeyManager(this);
		keyManager.initializeInput();
		logger.info("Opening tray icon...");
		initializeTray();
		logger.info("Ready.");
	}

	/**
	 * Prompt the user for a configuration reset
	 */
	public void promptConfigurationReset() {
		int option = JOptionPane.showConfirmDialog(null, Language.getString("settingsCorrupted"), Language.getString("errorLoadingSettings"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
		if (option == JOptionPane.YES_OPTION) {
			try {
				loadSettings(true);
			} catch (Exception e) {
				logger.log(Level.SEVERE, "Unable to load default settings!", e);
			}
		} else if (option == JOptionPane.NO_OPTION) {
			// If no, let them set the configuration themselves..
			openSettings();
		} else if (option == JOptionPane.CANCEL_OPTION) {
			// Exit, they don't want anything to do with it.
			System.exit(0);
		}
	}

	/**
	 * Perform a capture of the active window
	 */
	public void active() {
		try {
			upload(new ImageUpload(ScreenshotUtil.capture(WindowUtilProvider.getWindowUtil().getActiveWindow().getBounds())));
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Unable to take the active window screenshot", e);
			showException(e);
		}
	}

	/**
	 * Clear the screenshot selection window
	 */
	public void clearWindow() {
		window = null;
	}

	/**
	 * Upload content from the clipboard
	 */
	public void clipboard() {
		try {
			Object clipboard = ClipboardUtil.getClipboardContents();
			if (clipboard == null) {
				icon.displayMessage(Language.getString("invalidClipboard"), Language.getString("invalidClipboardTitle"), TrayIcon.MessageType.WARNING);
				return;
			}
			if (clipboard instanceof BufferedImage) {
				upload(new ImageUpload((BufferedImage) clipboard));
			} else if (clipboard instanceof File) {
				File file = (File) clipboard;
				String mime = FileUtils.getMimeType(file.getAbsolutePath());

				// A better way to upload images, it'll check the mime type!
				if (mime.startsWith("image")) {
					upload(new ImageUpload(ImageIO.read(file)));
				} else if (mime.startsWith("text") && configuration.getBoolean("plainTextUpload")) {
					upload(new TextUpload(FileUtils.readFile(file)));
				} else {
					upload(new FileUpload(file));
				}
			} else if (clipboard instanceof String) {
				String string = clipboard.toString();
				if (string.matches("((mailto\\:|(news|(ht|f)tp(s?))\\://){1}\\S+)")) {
					upload(new URLUpload(clipboard.toString()));
				} else {
					upload(new TextUpload(string));
				}
			}
		} catch (ClipboardException e) {
			logger.log(Level.SEVERE, "Unable to get clipboard contents", e);
			showException(e);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Perform a screenshot crop action
	 */
	public void crop() {
		if (window != null) {
			return;
		}
		
		window = new SelectionWindow(this, DisplayUtil.getRealScreenSize());
		window.pack();
		window.setAlwaysOnTop(true);
		window.setVisible(true);
	}

	/**
	 * Perform a full screenshot action
	 */
	public void full() {
		upload(new ImageUpload(ScreenshotUtil.capture(DisplayUtil.getRealScreenSize())));
	}

	/**
	 * Get the configuration file
	 * 
	 * @return The configuration file
	 */
	public Configuration getConfiguration() {
		return configuration;
	}

	/**
	 * Get the Hotkey Manager
	 * 
	 * @return The hotkey manager
	 */
	public HotkeyManager getKeyManager() {
		return keyManager;
	}

	/**
	 * Get the settings file for an uploader class
	 * 
	 * @param uploader
	 *            The uploader's class
	 * @return The settings file path
	 */
	public static File getSettingsFile(Class<?> uploader) {
		return getSettingsFile(uploader, "json");
	}

	/**
	 * Get the settings file for an uploader class
	 * 
	 * @param uploader
	 *            The uploader's class
	 * @return The settings file path
	 */
	public static File getSettingsFile(Class<?> uploader, String ext) {
		String name = uploader.getName();
		if (name.contains("$")) {
			name = name.substring(0, name.indexOf('$'));
		}
		File directory = new File(Util.getWorkingDirectory(), "config");
		if (!directory.exists()) {
			directory.mkdirs();
		}
		return new File(directory, name + "." + ext);
	}

	/**
	 * Get the tray icon instance
	 * 
	 * @return The instance of the Tray Icon
	 */
	public TrayIcon getTrayIcon() {
		return icon;
	}

	/**
	 * Get the default uploader associations
	 * 
	 * @return The uploader associations
	 */
	public Map<Class<? extends Upload>, Uploader<?>> getUploaderAssociations() {
		return uploaderAssociations;
	}

	/**
	 * Get the uploader which is mapped to the class type
	 * 
	 * @param cl
	 *            The type
	 * @return The uploader
	 */
	public Uploader<?> getUploaderFor(Class<?> cl) {
		return uploaderAssociations.get(cl);
	}

	/**
	 * Check and perform hotkeys, uses a separate service to not tie up the
	 * uploader
	 * 
	 * @param ident
	 *            The key id
	 */
	public void hotkey(final int ident) {
		serv.execute(new Runnable() {
			public void run() {
				switch (ident) {
				case ScreenshotAction.CROP:
					crop();
					break;
				case ScreenshotAction.FULL:
					full();
					break;
				case ScreenshotAction.CLIPBOARD:
					clipboard();
					break;
				case ScreenshotAction.FILE:
					selectFile();
					break;
				case ScreenshotAction.ACTIVE:
					active();
					break;
				}
			}
		});
	}

	/**
	 * Upload a file to the file service by selecting in another window
	 */
	public void selectFile() {
		JFileChooser chooser = new JFileChooser();
		chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
		int returnVal = chooser.showOpenDialog(null);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			File file = chooser.getSelectedFile();
			int confirm = JOptionPane.showConfirmDialog(null, Language.getString("uploadConfirm", file.getName()), Language.getString("uploadConfirmTitle"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
			if (confirm == JOptionPane.YES_OPTION) {
				JOptionPane.showMessageDialog(null, Language.getString("fileUploading"), Language.getString("fileUploadTitle"), JOptionPane.INFORMATION_MESSAGE);
				upload(new FileUpload(file.getAbsoluteFile()));
			} else {
				JOptionPane.showMessageDialog(null, Language.getString("fileUploadCanceled"), Language.getString("fileUploadTitle"), JOptionPane.INFORMATION_MESSAGE);
			}
		}
	}

	/**
	 * Initialize the tray menu
	 */
	private void initializeTray() {
		// Add uploaders from the list we loaded earlier
		PopupMenu tray = new PopupMenu();
		// Add the action menu
		tray.add(new ActionMenuItem(Language.getString("cropupload"), ScreenshotAction.CROP));
		tray.add(new ActionMenuItem(Language.getString("fullupload"), ScreenshotAction.FULL));
		
		if (Platform.isWindows() || Platform.isLinux()) {
			tray.add(new ActionMenuItem(Language.getString("activeupload"), ScreenshotAction.ACTIVE));
		}
		
		tray.addSeparator();
		
		tray.add(new ActionMenuItem(Language.getString("clipboardupload"), ScreenshotAction.CLIPBOARD));
		tray.add(new ActionMenuItem(Language.getString("fileupload"), ScreenshotAction.FILE));
		
		tray.addSeparator();
		
		MenuItem settings = new MenuItem(Language.getString("options"));
		settings.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (!openSettings()) {
					icon.displayMessage(Language.getString("error"), Language.getString("optionsOpenError"), TrayIcon.MessageType.ERROR);
				}
			}
		});
		tray.add(settings);
		
		MenuItem exit = new MenuItem(Language.getString("exit"));
		exit.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				shutdown();
			}
		});
		tray.add(exit);
		
		icon = new TrayIcon(Toolkit.getDefaultToolkit().getImage(Resources.ICON), Application.NAME + " v" + Version.getVersionString());
		icon.setPopupMenu(tray);
		icon.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (lastUrl != null) {
					try {
						Util.openURL(new URL(lastUrl));
					} catch (Exception e1) {
						showException(e1, "Unable to open URL");
					}
				}
			}
		});
		try {
			SystemTray.getSystemTray().add(icon);
		} catch (AWTException e1) {
			this.showException(e1);
		}
	}

	/**
	 * Clean up and shut down
	 */
	private void shutdown() {
		uploadService.shutdown();
		System.exit(0);
	}

	/**
	 * Load settings
	 */
	private void loadSettings(boolean resetConfig) throws Exception {
		File configFile = new File(Util.getWorkingDirectory(), Application.NAME.toLowerCase() + ".conf");
		if (!configFile.exists() || resetConfig) {
			configuration.setFile(configFile);
			loadDefaultConfiguration();
		} else {
			configuration.load(configFile);
		}
		if (configuration.contains("uploaders")) {
			JSONObject uploadConfig = configuration.getJSONObject("uploaders");

			if (convertUploadDefinition(uploadConfig, BufferedImage.class, ImageUpload.class) || convertUploadDefinition(uploadConfig, String.class, TextUpload.class) || convertUploadDefinition(uploadConfig, File.class, FileUpload.class) || convertUploadDefinition(uploadConfig, URL.class, URLUpload.class)) {
				logger.info("Converted upload definitions from old configuration.");
				configuration.save();
			}

			@SuppressWarnings("unchecked")
			Iterator<Object> it$ = uploadConfig.keys();
			while (it$.hasNext()) {
				String key = it$.next().toString();
				String className = uploadConfig.getString(key);

				@SuppressWarnings("unchecked")
				Class<? extends Upload> clType = (Class<? extends Upload>) Class.forName(key);
				if (clType != null) {
					setDefaultUploader(clType, className);
				}
			}
		}
		if (configuration.contains("startOnStartup") && configuration.getBoolean("startOnStartup")) {
			// Verify that the paths match, useful for upgrading since it won't
			// open the old file.
			File current = FileUtils.getJarFile(ScreenSnapper.class);
			if (!current.isDirectory()) {
				Updater.verifyAutostart(current, VerificationMode.INSERT);
			}
		}
	}

	public boolean convertUploadDefinition(JSONObject uploadConfig, Class<?> originalClass, Class<?> newClass) {
		if (uploadConfig.has(originalClass.getName())) {
			uploadConfig.put(newClass.getName(), uploadConfig.remove(originalClass.getName()));
			return true;
		}
		return false;
	}

	/**
	 * Restores the default configuration
	 * 
	 * @throws IOException
	 * @throws JSONException
	 */
	private void loadDefaultConfiguration() throws IOException {

		configuration.put("plainTextUpload", false);
		configuration.put("shortenurls", false);

		JSONObject uploaders = new JSONObject();

		// Default uploaders
		uploaders.put(ImageUpload.class.getName(), ImgurUploader.class.getName());
		uploaders.put(TextUpload.class.getName(), PasteeUploader.class.getName());
		uploaders.put(FileUpload.class.getName(), FilebinUploader.class.getName());
		uploaders.put(URLUpload.class.getName(), GoogleShortener.class.getName());

		configuration.put("uploaders", uploaders);

		JSONObject hotkeys = new JSONObject();

		// Hotkeys
		hotkeys.put("full", Platform.isMac() ? HotkeyManager.FULL_HOTKEY_MAC : HotkeyManager.FULL_HOTKEY);
		hotkeys.put("crop", Platform.isMac() ? HotkeyManager.CROP_HOTKEY_MAC : HotkeyManager.CROP_HOTKEY);
		hotkeys.put("clipboard", Platform.isMac() ? HotkeyManager.CLIPBOARD_HOTKEY_MAC : HotkeyManager.CLIPBOARD_HOTKEY);
		hotkeys.put("options", Platform.isMac() ? HotkeyManager.OPTIONS_HOTKEY_MAC : HotkeyManager.OPTIONS_HOTKEY);
		hotkeys.put("file", Platform.isMac() ? HotkeyManager.FILE_HOTKEY_MAC : HotkeyManager.FILE_HOTKEY);

		if (!Platform.isMac()) {
			hotkeys.put("active", "alt PRINTSCREEN");
		}

		configuration.put("hotkeys", hotkeys);

		configuration.put("updateMode", UpdaterMode.AUTOMATIC.ordinal());

		// Save it
		configuration.save();
	}

	/**
	 * Load upload filters
	 * 
	 * @throws Exception
	 *             If an error occurred while loading
	 */
	private void loadFilters() throws Exception {
		// Register any filters

		// PNG Compression will always be done last.
		registerFilter(new PNGCompressionFilter(this));

		// Watermarks will be done after everything else too.
		registerFilter(new WatermarkFilter());

		// Load custom filters
		File dir = new File(Util.getWorkingDirectory(), "plugins/filters");
		if (!dir.exists()) {
			dir.mkdirs();
		}
		ClassLoader loader = new URLClassLoader(new URL[] { dir.toURI().toURL() });
		for (File f : dir.listFiles()) {
			// TODO jar files.
			String name = f.getName();
			if (name.endsWith(".class") && !name.contains("$")) {
				try {
					Class<?> c = loader.loadClass(f.getName().replaceAll(".class", ""));
					UploadFilter<?> uploader = (UploadFilter<?>) c.newInstance();
					if (uploader == null)
						throw new Exception();

					registerFilter(uploader);
				} catch (Exception e) {
					JOptionPane.showMessageDialog(null, Language.getString("loadingError", name, e), Language.getString("filterLoadError", e), JOptionPane.ERROR_MESSAGE);
				}
			}
		}
	}

	/**
	 * Load the uploaders from inside the jar file and the regular file
	 * 
	 * @throws Exception
	 *             If an error occurred
	 */
	private void loadUploaders() throws Exception {
		// Generic uploaders
		registerUploaderClass(FTPUploader.class);
		registerUploaderClass(LocalFileUploader.class);
		// Image Uploaders
		registerUploaderClass(ImgurUploader.class);
		registerUploaderClass(ImmioUploader.class);
		registerUploaderClass(PuushUploader.class);
		registerUploaderClass(ImagebinUploader.class);
		registerUploaderClass(ImageScriptUploader.class);
		// Text uploaders
		registerUploaderClass(LaravelUploader.class);
		registerUploaderClass(Paste2Uploader.class);
		registerUploaderClass(PasteeUploader.class);
		registerUploaderClass(PastebinUploader.class);
		registerUploaderClass(PastebincaUploader.class);
		registerUploaderClass(PastieUploader.class);
		registerUploaderClass(SlexyUploader.class);
		registerUploaderClass(UpasteUploader.class);
		// URL Shorteners
		registerUploaderClass(GoogleShortener.class);
		registerUploaderClass(TinyURLShortener.class);
		registerUploaderClass(TUrlShortener.class);
		registerUploaderClass(IsgdShortener.class);
		registerUploaderClass(PostShortener.class);
		// File uploaders
		registerUploaderClass(FilebinUploader.class);
		registerUploaderClass(UppitUploader.class);

		// Load custom uploaders
		UploaderLoader loader = new UploaderLoader(this);
		loader.load();
	}

	/**
	 * Register an upload filter
	 * 
	 * @param filter
	 *            The filter to register
	 */
	public void registerFilter(UploadFilter<?> filter) {
		Class<? extends Upload> type = getFilterType(filter);
		LinkedList<UploadFilter<?>> filterList = filters.get(type);
		if (filterList == null) {
			filters.put(type, filterList = new LinkedList<UploadFilter<?>>());
		}
		filterList.addFirst(filter);
	}

	/**
	 * Gets a filter's parent class type
	 * 
	 * @param filter
	 * @return The class representing the filter's upload type
	 */
	@SuppressWarnings("unchecked")
	public Class<? extends Upload> getFilterType(UploadFilter<?> filter) {
		// Find the uploader type
		Type[] types = filter.getClass().getGenericInterfaces();
		for (Type type : types) {
			if (type instanceof ParameterizedType) {
				ParameterizedType parameterizedType = (ParameterizedType) type;
				if (parameterizedType.getRawType() == UploadFilter.class) {
					return (Class<? extends Upload>) parameterizedType.getActualTypeArguments()[0];
				}
			}
		}
		throw new RuntimeException("Attempted to load invalid filter!");
	}

	/**
	 * Open the settings panel
	 */
	public boolean openSettings() {
		if (optionsOpen) {
			settingsFrame.requestFocus();
			return true;
		}
		optionsOpen = true;

		settingsFrame = new JFrame("Sleeksnap Settings");

		OptionPanel panel = new OptionPanel(this);
		panel.getUploaderPanel().setImageUploaders(uploaders.get(ImageUpload.class).values());
		panel.getUploaderPanel().setTextUploaders(uploaders.get(TextUpload.class).values());
		panel.getUploaderPanel().setURLUploaders(uploaders.get(URLUpload.class).values());
		panel.getUploaderPanel().setFileUploaders(uploaders.get(FileUpload.class).values());
		panel.setHistory(history);
		panel.doneBuilding();

		settingsFrame.add(panel);
		settingsFrame.pack();
		settingsFrame.setVisible(true);
		settingsFrame.setResizable(false);
		try {
			settingsFrame.setIconImage(ImageIO.read(Util.getResourceByName("/icon32x32.png")));
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		Util.centerFrameOnMainDisplay(settingsFrame);
		settingsFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		settingsFrame.addWindowListener(new WindowAdapter() {
			public void windowClosed(WindowEvent e) {
				settingsFrame = null;
				optionsOpen = false;
				LogPanelHandler.unbind();
			}
		});
		return true;
	}

	/**
	 * Register an uploader
	 * 
	 * @param cl
	 *            The uploader class
	 * @throws Exception 
	 * 				If an error occurred while loading
	 */
	public Uploader<?> registerUploaderClass(Class<? extends Uploader<?>> cl) throws Exception {
		Uploader<?> uploader = initializeUploader(cl);
		if (uploader instanceof GenericUploader) {
			GenericUploader u = (GenericUploader) uploader;
			for (Uploader<?> up : u.getSubUploaders()) {
				Uploader<?> sub = registerUploader(up);
				sub.setParentUploader(u);
			}
			return u;
		}
		return registerUploader(uploader);
	}
	
	/**
	 * Register a normal uploader
	 * 
	 * @param uploader
	 * 			The uploader to register
	 * @return
	 * 			The uploader instance
	 */
	public Uploader<?> registerUploader(Uploader<?> uploader) {
		Class<? extends Upload> type = getUploaderType(uploader);
		// Check for the current list of types
		if (!uploaders.containsKey(type)) {
			uploaders.put(type, new HashMap<String, Uploader<? extends Upload>>());
		}
		uploader.onActivation();
		uploaders.get(type).put(uploader.getClass().getName(), uploader);
		return uploader;
	}
	
	public static final Gson GSON = new Gson();
	
	/**
	 * Initialize an uploader by class
	 * 
	 * @param uploaderClass
	 * 			The Uploader class to initialize
	 * @return
	 * 			The new Uploader instance
	 * @throws Exception
	 * 			If the uploader cannot be created
	 */
	@SuppressWarnings("unchecked")
	private Uploader<?> initializeUploader(Class<? extends Uploader<?>> uploaderClass) throws Exception {
		SettingsClass c = uploaderClass.getAnnotation(SettingsClass.class);
		
		if (c != null) {
			File file = getSettingsFile(uploaderClass);
			
			Object settingsInstance = null;
			
			if (file.exists()) {
				try {
					Reader reader = new FileReader(file);
					try {
						settingsInstance = GSON.fromJson(reader, c.value());
					} finally {
						reader.close();
					}
				} catch(Exception e) {
					// Delete a malformed file.
					file.delete();
				}
			}
			
			if (settingsInstance == null) {
				settingsInstance = c.value().newInstance();
			}
			
			Constructor<Uploader<?>> constructor = (Constructor<Uploader<?>>) uploaderClass.getConstructor(c.value());
			
			if (constructor == null) {
				throw new RuntimeException("Unable to find constructor with the defined setting type!");
			}
			
			Uploader<?> uploader = constructor.newInstance(settingsInstance);
			
			uploader.setSettingsInstance(settingsInstance);
			
			return uploader;
		}
		
		return uploaderClass.newInstance();
	}

	/**
	 * Attempt to get the upload type from the Superclass
	 * 
	 * @param uploader
	 *            The uploader to get the type from
	 * @return The type
	 */
	@SuppressWarnings("unchecked")
	public Class<? extends Upload> getUploaderType(Uploader<? extends Upload> uploader) {
		// Find the uploader type
		ParameterizedType parameterizedType = (ParameterizedType) uploader.getClass().getGenericSuperclass();
		Type[] args = parameterizedType.getActualTypeArguments();
		if (args.length == 0) {
			throw new RuntimeException("Uploader does not include a valid type");
		}
		return (Class<? extends Upload>) args[0];
	}

	/**
	 * Set a default type's uploader, checking if the settings are valid first
	 * 
	 * @param type
	 *            The class type
	 * @param name
	 *            The uploader name
	 */
	public void setDefaultUploader(final Class<? extends Upload> type, String name) {
		if (uploaders.containsKey(type)) {
			Map<String, Uploader<? extends Upload>> map = uploaders.get(type);
			if (map.containsKey(name)) {
				Uploader<? extends Upload> uploader = map.get(name);
				setDefaultUploader(uploader, false);
			} else {
				throw new RuntimeException("Invalid uploader " + name + "! Possible choices: " + map.values());
			}
		} else {
			throw new RuntimeException("No uploaders set for " + type.getName());
		}
	}

	/**
	 * Set a default uploader, includes loading the settings
	 * 
	 * @param uploader
	 *            The uploader
	 * @param settingsOverride
	 *            Whether to override the settings even if required fields
	 *            aren't set
	 */
	public void setDefaultUploader(final Uploader<?> uploader, boolean settingsOverride) {
		uploaderAssociations.put(getUploaderType(uploader), uploader);
	}

	/**
	 * Show a TrayIcon message for an exception
	 * 
	 * @param e
	 *            The exception
	 */
	public void showException(Exception e) {
		icon.displayMessage(Language.getString("error"), Language.getString("exceptionCause", e.getMessage()), MessageType.ERROR);
	}
	
	/**
	 * Show a TrayIcon message for an exception
	 * 
	 * @param e
	 *            The exception
	 */
	public void showException(Exception e, String errorMessage) {
		icon.displayMessage(Language.getString("error"), Language.getString("exceptionCauseWithMessage", errorMessage, e.getMessage()), MessageType.ERROR);
	}

	/**
	 * Upload an object
	 * 
	 * @param upload
	 *            The upload object
	 */
	public void upload(final Upload upload) {
		// Check associations
		if (!uploaderAssociations.containsKey(upload.getClass())) {
			icon.displayMessage(Language.getString("noUploaderTitle"), Language.getString("noUploader", upload.getClass().getName()), TrayIcon.MessageType.ERROR);
			return;
		}

		uploadService.execute(new Runnable() {
			public void run() {
				icon.setImage(Resources.ICON_BUSY_IMAGE);
				executeUpload(upload);
				icon.setImage(Resources.ICON_IMAGE);
			}
		});
	}

	/**
	 * Execute an upload
	 * 
	 * @param object
	 *            The object to upload
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void executeUpload(Upload object) {
		// Run the object through the filters
		if (filters.containsKey(object.getClass())) {
			for (UploadFilter filter : filters.get(object.getClass())) {
				try {
					object = filter.filter(object);
				} catch (FilterException e) {
					// FilterExceptions when thrown should interrupt the upload.
					showException(e, e.getErrorMessage());
					return;
				}
			}
		}
		// Then upload it
		Uploader uploader = uploaderAssociations.get(object.getClass());
		if (uploader != null) {
			try {
				String url = uploader.upload(object);
				if (url != null) {
					if (configuration.getBoolean("shortenurls")) {
						Uploader shortener = uploaderAssociations.get(URLUpload.class);
						if (shortener != null) {
							url = shortener.upload(new URLUpload(url));
						}
					}
					if (object instanceof ImageUpload) {
						if (configuration.getBoolean("savelocal") && !(uploader instanceof ImageLocalFileUploader)) {
							FileOutputStream output = new FileOutputStream(getLocalFile(DateUtil.getCurrentDate() + ".png"));
							try {
								ImageIO.write(((ImageUpload) object).getImage(), "png", output);
							} finally {
								output.close();
							}
						}
						((ImageUpload) object).getImage().flush();
						((ImageUpload) object).setImage(null);
					}
					url = url.trim();

					retries = 0;
					
					ClipboardUtil.setClipboard(url);

					lastUrl = url;
					history.addEntry(new HistoryEntry(url, uploader.getName()));
					icon.displayMessage(Language.getString("uploadComplete"), Language.getString("uploadedTo", url), TrayIcon.MessageType.INFO);
					logger.info("Upload completed, url: " + url);
				} else {
					icon.displayMessage(Language.getString("uploadFailed"), Language.getString("uploadFailedError"), TrayIcon.MessageType.ERROR);
					logger.severe("Upload failed to execute due to an unknown error");
				}
			} catch (UploaderConfigurationException e) {
				icon.displayMessage(Language.getString("uploaderConfigError"), Language.getString("uploaderConfigErrorMessage"), TrayIcon.MessageType.ERROR);
				logger.log(Level.SEVERE, "Upload failed to execute", e);
			} catch (Exception e) {
				retryUpload(object, e);
			}
		}
	}
	
	/**
	 * Retry the specified upload because of the specified cause
	 * 
	 * @param object
	 * 			The upload object
	 * @param cause
	 * 			The failure cause
	 */
	private void retryUpload(Upload object, Exception cause) {
		// Retry until retries > max
		StringBuilder msg = new StringBuilder("The upload failed to execute: ");
		msg.append(cause.getMessage());
		int max = configuration.getInteger("max_retries", Constants.Configuration.DEFAULT_MAX_RETRIES);
		if(retries++ < max) {
			logger.info("Retrying upload (" + (retries - 1) + " of " + max + " retries)...");
			msg.append("\nRetrying...");
			upload(object);
		} else {
			msg.append("\nReached retry limit, upload aborted.");
			logger.log(Level.SEVERE, "Upload failed to execute, retries: " + retries, cause);
			retries = 0;
		}
		icon.displayMessage(Language.getString("uploadFailed"), msg.toString(), TrayIcon.MessageType.ERROR);
	}

	/**
	 * Get the local file for image archiving
	 * 
	 * @param fileName
	 *            The file name
	 * @return The constructed File object
	 */
	public File getLocalFile(String fileName) {
		File dir = new File(Util.getWorkingDirectory(), "images");
		if (!dir.exists()) {
			dir.mkdirs();
		}
		return new File(dir, fileName);
	}

	/**
	 * Check if we have an uploader for a type
	 * 
	 * @param class1
	 *            The type to check
	 * @return True, if we have an uploader
	 */
	public boolean hasUploaderFor(Class<? extends Object> class1) {
		return uploaderAssociations.containsKey(class1);
	}
} @SuppressWarnings({ "serial" })
public class OptionPanel extends JPanel {

	private ScreenSnapper snapper;

	private JTabbedPane jTabbedPane1;

	private int previousTab = 0;

	private InfoPanel infoPanel;

	private UploaderPanel uploaderPanel;

	private HotkeyPanel hotkeyPanel;

	private HistoryPanel historyPanel;

	private LogPanel logPanel;

	public OptionPanel(ScreenSnapper snapper) {
		this.snapper = snapper;
		initComponents();
	}

	public void doneBuilding() {
		infoPanel.doneBuilding();
		uploaderPanel.doneBuilding();
		hotkeyPanel.doneBuilding();
		historyPanel.doneBuilding();
		logPanel.doneBuilding();
	}

	private void initComponents() {

		jTabbedPane1 = new JTabbedPane();

		// New panels
		infoPanel = new InfoPanel(this);
		infoPanel.initComponents();

		uploaderPanel = new UploaderPanel(this);
		uploaderPanel.initComponents();

		hotkeyPanel = new HotkeyPanel(this);
		hotkeyPanel.initComponents();

		historyPanel = new HistoryPanel(this);
		historyPanel.initComponents();

		logPanel = new LogPanel(this);
		logPanel.initComponents();

		setMinimumSize(new java.awt.Dimension(500, 300));

		jTabbedPane1.setTabPlacement(JTabbedPane.LEFT);
		jTabbedPane1.setCursor(new java.awt.Cursor(
				java.awt.Cursor.DEFAULT_CURSOR));

		jTabbedPane1.addTab("Main", infoPanel);

		jTabbedPane1.addTab("Uploaders", uploaderPanel);

		jTabbedPane1.addTab("Hotkeys", hotkeyPanel);

		jTabbedPane1.addTab("History", historyPanel);

		jTabbedPane1.addTab("Log", logPanel);
		
		initializeTab("Updater", UpdaterPanel.class);

		GroupLayout layout = new GroupLayout(this);
		this.setLayout(layout);
		layout.setHorizontalGroup(layout.createParallelGroup(
				GroupLayout.Alignment.LEADING).addComponent(jTabbedPane1,
				GroupLayout.PREFERRED_SIZE, 520, GroupLayout.PREFERRED_SIZE));
		layout.setVerticalGroup(layout.createParallelGroup(
				GroupLayout.Alignment.LEADING).addComponent(jTabbedPane1,
				GroupLayout.PREFERRED_SIZE, 470, GroupLayout.PREFERRED_SIZE));

		jTabbedPane1.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				int index = jTabbedPane1.getSelectedIndex();
				if (index == 4) {
					// Load the log
					File file = new File(Util.getWorkingDirectory(), "log.txt");
					try {
						String contents = StreamUtils
								.readContents(new FileInputStream(file));
						logPanel.setContents(contents);
					} catch (IOException ex) {
						ex.printStackTrace();
					}
				}
				if (previousTab == 2) {
					// Restart our input manager if we disabled it
					if (!snapper.getKeyManager().hasKeysBound()) {
						snapper.getKeyManager().initializeInput();
					}
				}
				previousTab = index;
			}
		});

		// Do any loading/initializing
		hotkeyPanel.loadCurrentHotkeys();
	}
	
	public void initializeTab(String name, Class<?> cl) {
		try {
			Constructor<?> c = cl.getConstructor(OptionPanel.class);
			if(c == null) {
				throw new NoSuchMethodException("Unable to find a valid constructor!");
			}
			
			OptionSubPanel panel = (OptionSubPanel) c.newInstance(this);
			
			panel.initComponents();
			
			jTabbedPane1.addTab(name, panel);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void setHistory(History history) {
		historyPanel.setHistory(history);
	}

	public UploaderPanel getUploaderPanel() {
		return uploaderPanel;
	}

	public ScreenSnapper getSnapper() {
		return snapper;
	}

	public Configuration getConfiguration() {
		return snapper.getConfiguration();
	}

	public void saveAll() {
		hotkeyPanel.savePreferences();
		uploaderPanel.savePreferences();
	}
} public class FileUpload implements Upload {

	/**
	 * The file in this upload
	 */
	private File file;
	
	public FileUpload(File file) {
		this.file = file;
	}
	
	/**
	 * Set the upload's file
	 * @param file
	 * 			The file to set
	 */
	public void setFile(File file) {
		this.file = file;
	}
	
	/**
	 * Get the file for the upload
	 * @return
	 * 		The file
	 */
	public File getFile() {
		return file;
	}
	
	/**
	 * Get this file as a FileInputStream
	 */
	@Override
	public InputStream asInputStream() {
		try {
			return new FileInputStream(file);
		} catch (FileNotFoundException e) {
			return null;
		}
	}
} public class ImageUpload implements Upload {
	
	/**
	 * The BufferedImage we are uploading
	 */
	private BufferedImage image;
	
	public ImageUpload(BufferedImage image) {
		this.image = image;
	}

	@Override
	public InputStream asInputStream() throws IOException {
		return ImageUtil.toInputStream(image);
	}
	
	/**
	 * Covert this image into a Base64 string
	 * @return
	 * 			The image in base64
	 * @throws IOException
	 * 			If an error occurred while writing/reading into base64
	 */
	public String toBase64() throws IOException {
		return ImageUtil.toBase64(image);
	}

	/**
	 * Set this upload's image
	 * @param image
	 * 			The image to set
	 */
	public void setImage(BufferedImage image) {
		this.image = image;
	}

	/**
	 * Get this upload's image
	 * @return
	 * 		The image of the upload
	 */
	public BufferedImage getImage() {
		return image;
	}
} public class TextUpload implements Upload {

	/**
	 * The upload text data
	 */
	private String text;
	
	public TextUpload(String text) {
		this.text = text;
	}
	
	@Override
	public InputStream asInputStream() {
		return new ByteArrayInputStream(text.getBytes());
	}
	
	/**
	 * Get this upload's contents
	 * 
	 * @return
	 * 			The upload contents
	 */
	public String getText() {
		return text;
	}
	
	@Override
	public String toString() {
		return text;
	}
} public class URLUpload implements Upload {
	
	/**
	 * The URL of this upload
	 */
	private URL url;
	
	public URLUpload(URL url) {
		this.url = url;
	}

	public URLUpload(String url) throws MalformedURLException {
		this.url = new URL(url);
	}

	@Override
	public InputStream asInputStream() throws IOException {
		throw new IOException("URLs cannot be transformed into InputStreams!");
	}
	
	/**
	 * Set the URL
	 * @param url
	 * 			The URL to set it to
	 */
	public void setURL(URL url) {
		this.url = url;
	}

	/**
	 * Get the URL
	 * @return
	 * 		The URL
	 */
	public URL getURL() {
		return url;
	}
	
	@Override
	public String toString() {
		return url.toString();
	}
} public interface Upload {

	/**
	 * Get this upload as an InputStream
	 * @return
	 * 			The InputStream containing the upload data
	 */
	public InputStream asInputStream() throws IOException;
} public abstract class Uploader<T extends Upload> {
	
	/**
	 * The parent uploader, used if this is a sub uploader of another generic uploader
	 */
	protected Uploader<?> parent;
	
	private Object settingsInstance;
	
	public Uploader() {
		
	}
	
	public Uploader(Uploader<?> parent) {
		this.parent = parent;
	}

	/**
	 * Get the uploader name
	 * 
	 * @return
	 * 		The uploader name
	 */
	public abstract String getName();

	/**
	 * Upload the specified object
	 * 
	 * @param t
	 *      The object
	 * @return
	 * 		The URL or Location
	 * @throws Exception
	 */
	public abstract String upload(T t) throws Exception;
	
	/**
	 * Can be overridden by the uploader to validate the settings.
	 * 
	 * If invalid, either throw an UploaderConfigurationException or display your own message.
	 * 
	 * @return
	 * 			true if valid, false if invalid.
	 */
	public boolean validateSettings() throws UploaderConfigurationException {
		return true;
	}
	
	/**
	 * Can be overidden to get a call when it is activated/set as default (On Load or User Settings)
	 * By default, if this is a sub uploader for a Generic uploader, it will call the parent's onActivation method
	 */
	public void onActivation() {
		if(parent != null) {
			parent.onActivation();
		}
	}
	
	/**
	 * Set this uploader's parent
	 */
	public void setParentUploader(Uploader<?> parent) {
		this.parent = parent;
	}
	
	public Uploader<?> getParentUploader() {
		return parent;
	}

	public boolean hasParent() {
		return parent != null;
	}

	/**
	 * Check if this class directly has settings
	 * @return
	 * 		True if this class has settings
	 */
	public boolean hasDirectSettings() {
		return getClass().isAnnotationPresent(SettingsClass.class);
	}

	/**
	 * Check if this uploader (or generic uploader parent) has settings
	 * @return
	 * 		True if this uploader has settings
	 */
	public boolean hasSettings() {
		return hasDirectSettings() || parent != null && parent.hasSettings();
	}

	/**
	 * Get the Settings annotation from an uploader
	 * 
	 * @return The settings, or null if it doesn't have any
	 */
	public SettingsClass getSettingsAnnotation() {
		SettingsClass settings = getClass().getAnnotation(SettingsClass.class);

		if (settings == null && parent != null) {
			settings = parent.getSettingsAnnotation();
		}
		return settings;
	}
	
	public void saveSettings(File file) throws IOException {
		Writer writer = new FileWriter(file);
		try {
			ScreenSnapper.GSON.toJson(settingsInstance, writer);
		} finally {
			writer.close();
		}
	}
	
	public Object getSettingsInstance() {
		return settingsInstance;
	}

	public void setSettingsInstance(Object settingsInstance) {
		this.settingsInstance = settingsInstance;
	}
} public class ParametersDialog extends JDialog {

	/**
	 * The serial version unique id for this class.
	 */
	private static final long serialVersionUID = 42399074877274640L;

	/**
	 * The labels that correspond with the parameters.
	 */
	private final JLabel[] labels;

	/**
	 * The input components that correspond with the parameters.
	 */
	private final UploaderSetting[] components;

	/**
	 * The OK button.
	 */
	private final JButton btnOk = new JButton("OK");

	/**
	 * The cancel button.
	 */
	private final JButton btnCancel = new JButton("Cancel");

	/**
	 * The settings array length
	 */
	private int length;

	/**
	 * The action listener
	 */
	private ActionListener actionListener;
	
	private Field[] requiredSettings;
	
	private Field[] optionalSettings;

	/**
	 * The map of name => field
	 */
	private Map<Field, UploaderSetting> fieldMap = new HashMap<Field, UploaderSetting>();

	/**
	 * The uploader name
	 */
	private Uploader<?> uploader;

	/**
	 * Creates a new parameters dialog with the specified parent and array of
	 * parameters.
	 * 
	 * @param parent
	 * 			The frame which opened this dialog
	 * @param uploader
	 * 			The uploader for this settings dialog
	 * @param settings
	 * 			The settings annotation
	 * @throws Exception 
	 */
	public ParametersDialog(JFrame parent, Uploader<?> uploader, Class<?> settingClass) throws Exception {
		super(parent);
		this.uploader = uploader;
		
		List<Field> required = new ArrayList<Field>();
		List<Field> optional = new ArrayList<Field>();
		
		for (Field field : settingClass.getFields()) {
			Setting setting = field.getAnnotation(Setting.class);
			
			if (setting != null) {
				if (setting.optional()) {
					optional.add(field);
				} else {
					required.add(field);
				}
			}
		}
		
		requiredSettings = required.toArray(new Field[required.size()]);
		optionalSettings = optional.toArray(new Field[optional.size()]);
		
		length = requiredSettings.length + optionalSettings.length;
		// Check the length for labels
		if (requiredSettings.length != 0) {
			length++;
		}
		if (optionalSettings.length != 0) {
			length++;
		}
		this.labels = new JLabel[length];
		this.components = createComponentArray(settingClass);

		initComponents();
	}

	/**
	 * Creates the component array from the parameters array.
	 * @param settingClass 
	 * 
	 * @return The component array.
	 * @throws Exception 
	 */
	private UploaderSetting[] createComponentArray(Class<?> settingClass) throws Exception {
		UploaderSetting[] components = new UploaderSetting[length];

		int i = 0;
		if (requiredSettings.length != 0) {
			labels[i] = new JLabel("Required settings");
			components[i] = new UploaderSetting(new JLabel(), null);
			i++;

			for (Field field : requiredSettings) {
				initializeSetting(components, i, field, field.getAnnotation(Setting.class));
				i++;
			}
		}

		if (optionalSettings.length != 0) {
			labels[i] = new JLabel("Optional settings");
			components[i] = new UploaderSetting(new JLabel(), null);
			i++;

			for (Field field : optionalSettings) {
				initializeSetting(components, i, field, field.getAnnotation(Setting.class));
				i++;
			}
		}

		return components;
	}
	
	@SuppressWarnings("serial")
	private static Map<Class<?>, UploaderSettingType> settingTypes = new HashMap<Class<?>, UploaderSettingType>() {{
		put(String.class, new TextSettingType());
		put(boolean.class, new CheckBoxSettingType());
		put(int.class, new NumberSpinnerSettingType());
	}};
	
	private static Map<Class<?>, UploaderSettingType> cachedTypes = new HashMap<Class<?>, UploaderSettingType>();
	
	/**
	 * Build the setting component/label
	 * @param components
	 * 			The component array to push the setting into
	 * @param index
	 * 			The current setting index
	 * @param name
	 * 			The setting name
	 * @param properties
	 * 			The current properties object
	 */
	@SuppressWarnings("unchecked")
	public void initializeSetting(UploaderSetting[] components, int index, Field field, Setting setting) throws Exception {
		Class<?> type = setting.type();
		
		Class<?> fieldType = field.getType();
		
		UploaderSettingType settingType = null;
		
		String[] defaults = setting.defaults();
		
		if (type == AutoDetectSettingType.class) {
			// Get the setting type class from the map
			
			if (fieldType == Enum.class || fieldType.getSuperclass() == Enum.class) {
				settingType = new EnumComboBoxSettingType((Class<? extends Enum<?>>) field.getType());
				// Set field data to enum value
				List<String> list = new ArrayList<String>();
				for(Object o : fieldType.getEnumConstants()) {
					list.add(o.toString());
				}
				defaults = list.toArray(new String[list.size()]);
			} else if (settingTypes.containsKey(fieldType)) {
				settingType = settingTypes.get(field.getType());
			} else if (fieldType.getSuperclass() != null && settingTypes.containsKey(fieldType.getSuperclass())) {
				settingType = settingTypes.get(fieldType.getSuperclass());
			} else {
				throw new Exception("Unable to auto detect setting type for " + field.getType());
			}
		} else {
			// Other type
			if (cachedTypes.containsKey(type)) {
				settingType = cachedTypes.get(type);
			} else {
				cachedTypes.put(type, settingType = (UploaderSettingType) type.newInstance());
			}
		}
		
		JComponent component = settingType.constructComponent(defaults);
		
		if (!setting.description().isEmpty()) {
			component.setToolTipText(setting.description());
		}
		
		labels[index] = new JLabel(setting.name() + ": ");
		components[index] = new UploaderSetting(component, settingType);
		
		try {
			Object value = field.get(uploader.getSettingsInstance());
			if (value != null) {
				settingType.setValue(component, field.get(uploader.getSettingsInstance()));
			}
		} catch (Exception e) {
			// Unable to get value
		}
		
		component.setMinimumSize(new Dimension(200, 0));
		
		fieldMap.put(field, components[index]);
	}

	/**
	 * Initialises the components.
	 */
	private void initComponents() {
		setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
		setTitle(uploader.getName() + " Settings");
		setResizable(false);

		GroupLayout layout = new GroupLayout(getContentPane());
		getContentPane().setLayout(layout);

		GroupLayout.SequentialGroup horizontalGroup = layout
				.createSequentialGroup();
		GroupLayout.SequentialGroup verticalGroup = layout
				.createSequentialGroup().addContainerGap();

		GroupLayout.ParallelGroup horizontalLabels = layout
				.createParallelGroup(GroupLayout.Alignment.TRAILING);
		GroupLayout.ParallelGroup horizontalComponents = layout
				.createParallelGroup(GroupLayout.Alignment.LEADING, false);

		for (int i = 0; i < components.length; i++) {
			boolean last = i == (components.length - 1);
			JComponent component = components[i].getComponent();
			JLabel label = labels[i];

			horizontalLabels.addComponent(label);

			horizontalComponents.addGroup(layout.createSequentialGroup()
					.addComponent(component,
							javax.swing.GroupLayout.DEFAULT_SIZE,
							javax.swing.GroupLayout.DEFAULT_SIZE,
							Short.MAX_VALUE));

			if (last) {
				verticalGroup
						.addGroup(layout
								.createParallelGroup(
										javax.swing.GroupLayout.Alignment.LEADING)
								.addGroup(
										layout.createSequentialGroup()
												.addGroup(
														layout.createParallelGroup()
																.addComponent(
																		component,
																		javax.swing.GroupLayout.PREFERRED_SIZE,
																		javax.swing.GroupLayout.DEFAULT_SIZE,
																		javax.swing.GroupLayout.PREFERRED_SIZE))
												.addPreferredGap(
														LayoutStyle.ComponentPlacement.RELATED)
												.addGroup(
														layout.createParallelGroup(
																javax.swing.GroupLayout.Alignment.BASELINE)
																.addComponent(
																		btnCancel)
																.addComponent(
																		btnOk)))
								.addComponent(label));
				verticalGroup.addContainerGap();
			} else {
				verticalGroup.addGroup(layout
						.createParallelGroup(GroupLayout.Alignment.BASELINE)
						.addComponent(component).addComponent(label));
				verticalGroup
						.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED);
			}
		}

		horizontalGroup
				.addGroup(horizontalLabels)
				.addPreferredGap(
						javax.swing.LayoutStyle.ComponentPlacement.RELATED)
				.addGroup(horizontalComponents);

		layout.setHorizontalGroup(layout
				.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
				.addGroup(
						layout.createSequentialGroup()
								.addContainerGap()
								.addGroup(
										layout.createParallelGroup(
												javax.swing.GroupLayout.Alignment.LEADING)
												.addGroup(
														javax.swing.GroupLayout.Alignment.TRAILING,
														layout.createSequentialGroup()
																.addComponent(
																		btnCancel,
																		javax.swing.GroupLayout.PREFERRED_SIZE,
																		80,
																		javax.swing.GroupLayout.PREFERRED_SIZE)
																.addPreferredGap(
																		javax.swing.LayoutStyle.ComponentPlacement.RELATED)
																.addComponent(
																		btnOk,
																		javax.swing.GroupLayout.PREFERRED_SIZE,
																		80,
																		javax.swing.GroupLayout.PREFERRED_SIZE))
												.addGroup(horizontalGroup))
								.addContainerGap(
										javax.swing.GroupLayout.DEFAULT_SIZE,
										Short.MAX_VALUE)));

		layout.setVerticalGroup(layout.createParallelGroup(
				GroupLayout.Alignment.LEADING).addGroup(verticalGroup));

		btnOk.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btnOkActionPerformed(e);
			}
		});

		btnCancel.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btnCancelActionPerformed(e);
			}
		});

		pack();
	}

	/**
	 * A listener for the OK button.
	 * 
	 * @param evt
	 *            The event.
	 */
	private void btnOkActionPerformed(ActionEvent evt) {
		if (!validateProperties()) {
			JOptionPane.showMessageDialog(this,
					"Some required settings are empty, please fill them out",
					"Fields missing", JOptionPane.ERROR_MESSAGE);
			return;
		}
		// everything is valid, let the caller know, then let them close this.
		actionListener.actionPerformed(evt);
	}
	
	/**
	 * Close this window
	 */
	public void closeWindow() {
		dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
	}

	/**
	 * A listener for the cancel button.
	 * 
	 * @param evt
	 *            The event.
	 */
	private void btnCancelActionPerformed(ActionEvent evt) {
		dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
	}

	/**
	 * Validate the settings (verify they aren't empty, more validation later)
	 * @return
	 * 		true if all settings are good
	 */
	public boolean validateProperties() {
		for (Field field : requiredSettings) {
			Object object = getComponentValue(fieldMap.get(field));
			
			if (object == null || object.toString().isEmpty()) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Export this dialog's settings to the settings class
	 * @param uploaderSettings 
	 * @return
	 * 		The new UploaderSettings object
	 */
	public void exportToSettingInstance() throws Exception {
		for(Entry<Field, UploaderSetting> entry : fieldMap.entrySet()) {
			Field field = entry.getKey();
			UploaderSetting component = entry.getValue();
			
			Object value = getComponentValue(component);
			
			field.set(uploader.getSettingsInstance(), value);
		}
	}
	
	/**
	 * Get the component's value based on type
	 * @param component
	 * 			The component to get the value from
	 * @return
	 * 			The component value
	 */
	public Object getComponentValue(UploaderSetting component) {
		if(component == null || component.getType() == null) {
			return null;
		}
		return component.getType().getValue(component.getComponent());
	}

	/**
	 * Set the "OK" action
	 * @param actionListener
	 * 			The aciton listener
	 */
	public void setOkAction(ActionListener actionListener) {
		this.actionListener = actionListener;
	}
	
} public class Util {
	/**
	 * An enum containing operating system types
	 * 
	 * @author Nikki
	 * 
	 */
	public static enum OperatingSystem {
		LINUX, SOLARIS, WINDOWS, MAC, UNKNOWN
	}

	/**
	 * The cached operating system
	 */
	public static final OperatingSystem SYSTEM = getPlatform();

	/**
	 * The saved working directory
	 */
	private static File workDir;

	/**
	 * A method to get the unix time...
	 * @return
	 * 		The current time in seconds
	 */
	public static long currentTimeSeconds() {
		return (System.currentTimeMillis() / 1000);
	}

	/**
	 * Get the last part of a class name
	 * 
	 * @param key
	 *            The full name
	 * @return The class name formatted
	 */
	public static String formatClassName(Class<?> key) {
		return key.getName().substring(key.getName().lastIndexOf('.') + 1);
	}

	/**
	 * Get the computer's user agent
	 * 
	 */
	public static String getHttpUserAgent() {
		return Application.NAME + " v" + Version.getVersionString();
	}

	/**
	 * Get the current platform
	 * 
	 * @return The current platform
	 */
	public static OperatingSystem getPlatform() {
		String osName = System.getProperty("os.name").toLowerCase();
		if (osName.contains("win"))
			return OperatingSystem.WINDOWS;
		if (osName.contains("mac"))
			return OperatingSystem.MAC;
		if (osName.contains("solaris"))
			return OperatingSystem.SOLARIS;
		if (osName.contains("sunos"))
			return OperatingSystem.SOLARIS;
		if (osName.contains("linux"))
			return OperatingSystem.LINUX;
		if (osName.contains("unix"))
			return OperatingSystem.LINUX;
		return OperatingSystem.UNKNOWN;
	}

	/**
	 * Get a resource, allows us to run it from source or jar
	 * 
	 * @param name
	 *            The resource name
	 * @return The URL of the resource, either in-jar or on the filesystem
	 */
	public static URL getResourceByName(String name) {
		if (Utils.class.getResource(name) != null) {
			return Utils.class.getResource(name);
		} else {
			File file = null;
			if ((file = new File("resources" + name)).exists() || (file = new File(Util.getWorkingDirectory(), "resources" + name)).exists()) {
				try {
					return file.toURI().toURL();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	/**
	 * Get the system architecture
	 * 
	 * @return The system architecture integer
	 */
	public static int getSystemArch() {
		String archs = System.getProperty("os.arch");
		return Integer.parseInt(archs.substring(archs.length() - 2));
	}

	/**
	 * Get the current working directory
	 * 
	 * @return The working directory for the application
	 */
	public static File getWorkingDirectory() {
		if (workDir == null)
			workDir = getWorkingDirectory(Constants.Application.NAME
					.toLowerCase());
		return workDir;
	}

	/**
	 * Get the AppData directory
	 * 
	 * @param applicationName
	 *            The application name
	 * @return The working directory
	 */
	public static File getWorkingDirectory(String applicationName) {
		String userHome = System.getProperty("user.home", ".");
		File workingDirectory;
		switch (getPlatform()) {
		case LINUX:
		case SOLARIS:
			File config = new File(userHome, ".config");
			if(config.exists()) {
				workingDirectory = new File(config, applicationName + '/');
				workingDirectory.mkdirs();
			} else {
				workingDirectory = new File(userHome, '.' + applicationName + '/');
			}
			break;
		case WINDOWS:
			String applicationData = System.getenv("APPDATA");
			if (applicationData != null)
				workingDirectory = new File(applicationData, "."
						+ applicationName + '/');
			else
				workingDirectory = new File(userHome,
						'.' + applicationName + '/');
			break;
		case MAC:
			workingDirectory = new File(userHome,
					"Library/Application Support/" + applicationName);
			break;
		default:
			workingDirectory = new File(userHome, applicationName + '/');
			break;
		}
		if ((!workingDirectory.exists()) && (!workingDirectory.mkdirs()))
			throw new RuntimeException(
					"The working directory could not be created: "
							+ workingDirectory);
		return workingDirectory;
	}

	/**
	 * Parse commandline arguments
	 * 
	 * @param args
	 *            The arg array from the main method, or manual args
	 * @return The map containing the args
	 */
	public static HashMap<String, Object> parseArguments(String[] args) {
		HashMap<String, Object> arguments = new HashMap<String, Object>();
		for (String s : args) {
			if (s.startsWith("--")) {
				s = s.substring(2);
			} else if (s.startsWith("-")) {
				s = s.substring(1);
			}
			int eqIdx = s.indexOf('=');
			String key = s.substring(0, eqIdx != -1 ? eqIdx : s.length());
			Object value = true;
			if (eqIdx != -1) {
				value = s.substring(s.indexOf('=') + 1);
			}
			arguments.put(key, value);
		}
		return arguments;
	}

	/**
	 * Set the working directory, used when Sleeksnap is self-contained (Uses a
	 * directory which you can have on a flash drive)
	 * 
	 * @param workingDirectory
	 *            The directory
	 */
	public static void setWorkingDirectory(File workingDirectory) {
		workDir = workingDirectory;
	}

	/**
	 * Trim the string's start/finish of the specified character
	 * 
	 * @param str
	 *            The string
	 * @param ch
	 *            The character
	 * @return The trimmed string
	 */
	public static String trim(String str, final char ch) {
		if ((str == null) || str.isEmpty())
			return str;
		else if (str.length() == 1)
			return str.charAt(0) == ch ? "" : str;
		try {
			if (str.charAt(0) == ch)
				str = str.substring(1);
			final int l = str.length() - 1;
			if (str.charAt(l) == ch)
				str = str.substring(0, l);
			return str;
		} catch (final Exception e) {
			return str;
		}
	}

	/**
	 * Make each word in a string uppercase
	 * 
	 * @param string
	 *            The string to parse
	 * @return The formatted string
	 */
	public static String ucwords(String string) {
		StringBuilder out = new StringBuilder();
		String[] split = string.split(" ");
		for (int i = 0; i < split.length; i++) {
			String str = split[i];
			out.append(Character.toUpperCase(str.charAt(0)));
			if (str.length() > 1) {
				out.append(str.substring(1).toLowerCase());
			}
			if (i < (split.length - 1)) {
				out.append(" ");
			}
		}
		return out.toString();
	}

	/**
	 * Center a frame on the main display
	 * @param frame
	 * 			The frame to center
	 */
	public static void centerFrameOnMainDisplay(JFrame frame) {
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsDevice[] screens = ge.getScreenDevices();
		if (screens.length < 1) {
			return; // Silently fail.
		}
		Rectangle screenBounds = screens[0].getDefaultConfiguration().getBounds();
	    int x = (int) ((screenBounds.getWidth() - frame.getWidth()) / 2);
	    int y = (int) ((screenBounds.getHeight() - frame.getHeight()) / 2);
	    frame.setLocation(x, y);
	}

	/**
	 * A list of popular browsers
	 */
	private static final String[] BROWSERS = new String[] {
			"xdg-open", "google-chrome", "chromium", "firefox", "opera", "epiphany", "konqueror", "conkeror", "midori", "kazehakase", "mozilla"
	};

	/**
	 * A list of popular file managers
	 */
	private static final String[] FILE_MANAGERS = new String[] {
			"xdg-open", "nautilus", "dolphin", "thunar", "pcmanfm", "konqueror"
	};

	/**
	 * Finds the first supported program in the list (for UNIX-like platforms
	 * only).
	 * @param kind The kind of program, used in the exception message if no
	 * suitable program could be found.
	 * @param names The array of program names to try.
	 * @return The first supported program from the array of names.
	 * @throws Exception if no supported program could be found.
	 */
	private static String findSupportedProgram(String kind, String[] names) throws Exception {
		for (String name : names) {
			Process process = Runtime.getRuntime().exec(new String[] { "which", name });
			if (process.waitFor() == 0)
				return name;
		}

		throw new Exception("Unable to find supported " + kind);
	}

	/**
	 * Open a URL using java.awt.Desktop or a couple different manual methods
	 * @param url
	 * 			The URL to open
	 * @throws Exception
	 * 			If an error occurs attempting to open the url
	 */
	public static void openURL(URL url) throws Exception {
		Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
		if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
			desktop.browse(url.toURI());
		} else {
			OperatingSystem system = Util.getPlatform();
			switch (system) {
				case MAC:
					Class.forName("com.apple.eio.FileManager").getDeclaredMethod(
							"openURL", new Class[] { String.class }).invoke(null,
							new Object[] { url.toString() });
					break;
				case WINDOWS:
					Runtime.getRuntime().exec(new String[] { "rundll32", "url.dll,FileProtocolHandler", url.toString() });
					break;
				default:
					String browser = findSupportedProgram("browser", BROWSERS);
					Runtime.getRuntime().exec(new String[] { browser, url.toString() });
					break;
			}
		}
	}

	/**
	 * Open a file using {@link Desktop} if supported, or a manual
	 * platform-specific method if not.
	 * @param file The file to open.
	 * @throws Exception if the file could not be opened.
	 */
	public static void openFile(File file) throws Exception {
		Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
		if (desktop != null && desktop.isSupported(Desktop.Action.OPEN)) {
			desktop.open(file);
		} else {
			OperatingSystem system = Util.getPlatform();
			switch (system) {
				case MAC:
				case WINDOWS:
					openURL(file.toURI().toURL());
					break;
				default:
					String fileManager = findSupportedProgram("file manager", FILE_MANAGERS);
					Runtime.getRuntime().exec(new String[] { fileManager, file.getAbsolutePath() });
					break;
			}
		}
	}
	
	/**
	 * A utility method used for parsing uploader data (like what is used in the Spinner type)
	 * @param defaults
	 * 			The string to parse
	 * @return
	 * 			The data map
	 */
	public static Map<String, String> parseDataList(String[] defaults) {
		Map<String, String> ret = new HashMap<String, String>();
		for(String s : defaults) {
			int idx = s.indexOf('=');
			if(idx != -1) {
				String key = s.substring(0, idx);
				String value = s.substring(idx + 1);
				
				ret.put(key, value);
			} else {
				ret.put(s, null);
			}
		}
		return ret;
	}
	
	/**
	 * A utility method for parsing response data (Useful for k: v etc)
	 * @param input
	 * 			The input string
	 * @param kvSeparator
	 * 			The value separating key and value
	 * @param entrySeparator
	 * 			The value separating entries
	 * @return
	 * 			The parsed map
	 */
	public static Map<String, String> parseKeyValues(String input, String kvSeparator, String entrySeparator) {
		Map<String, String> ret = new HashMap<String, String>();
		String[] split = input.split(entrySeparator);
		for(String s : split) {
			int idx = s.indexOf(kvSeparator);
			if(idx != -1) {
				String key = s.substring(0, idx);
				String value = s.substring(idx + 1);
				
				ret.put(key, value);
			} else {
				ret.put(s, null);
			}
		}
		return ret;
	}

	/**
	 * Implode a list of values (Like php's implode)
	 * @param list
	 * 			The list to implode
	 * @param glue
	 * 			The glue to use between values
	 * @return
	 * 			The imploded string
	 */
	public static String implodeList(List<?> list, String glue) {
		StringBuilder builder = new StringBuilder();
		for(Iterator<?> it = list.iterator(); it.hasNext();) {
			builder.append(it.next());
			if(it.hasNext()) {
				builder.append(glue);
			}
		}
		return builder.toString();
	}

	/**
	 * Gets the class name of the Swing look and feel which emulates the native
	 * style of the operating system.
	 * @return The system look and feel class name.
	 */
	public static String getSystemLookAndFeelClassName() {
		OperatingSystem platform = getPlatform();
		String clazz = UIManager.getSystemLookAndFeelClassName();
		
		/*
		 * On Linux, Java only attempts to use the GTK+ style if the desktop
		 * environment is GNOME. Otherwise, it falls back to the cross-platform
		 * style.
		 * 
		 * However, many other desktop environments use GTK+ (e.g. XFCE, LXDE,
		 * Unity, etc.) and even ones that don't (e.g. KDE) usually have some
		 * GTK+ compatibility so it still looks better than the cross-platform
		 * theme.
		 * 
		 * Therefore on Linux we override Java's choice and attempt to use GTK+
		 * if the cross-platform style was chosen.
		 */
		if (platform == OperatingSystem.LINUX && clazz.equals(UIManager.getCrossPlatformLookAndFeelClassName())) {
			clazz = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
		}
		
		return clazz;
	}
} public static class SortingUtil {
	public static List<Uploader<?>> sortUploaders(
			Collection<Uploader<?>> uploaders) {
		List<Uploader<?>> list = new LinkedList<Uploader<?>>(uploaders);
		Collections.sort(list, new Comparator<Uploader<?>>() {
			@Override
			public int compare(Uploader<?> o1, Uploader<?> o2) {
				return o1.getName().compareTo(o2.getName());
			}
		});
		return list;
	}
}
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map.Entry;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import org.json.JSONObject;
import org.sleeksnap.Configuration;
import org.sleeksnap.ScreenSnapper;
import org.sleeksnap.gui.OptionPanel;
import org.sleeksnap.upload.FileUpload;
import org.sleeksnap.upload.ImageUpload;
import org.sleeksnap.upload.TextUpload;
import org.sleeksnap.upload.URLUpload;
import org.sleeksnap.upload.Upload;
import org.sleeksnap.uploaders.Uploader;
import org.sleeksnap.uploaders.settings.ParametersDialog;
import org.sleeksnap.uploaders.settings.SettingsClass;
import org.sleeksnap.util.Util;
import org.sleeksnap.util.Utils.SortingUtil;
		}
		
        imageSettings.setText("Settings");
        imageSettings.setMaximumSize(new java.awt.Dimension(71, 20));
        imageSettings.setMinimumSize(new java.awt.Dimension(71, 20));
        imageSettings.setPreferredSize(new java.awt.Dimension(71, 20));

        textSettings.setText("Settings");
        textSettings.setMaximumSize(new java.awt.Dimension(71, 20));
        textSettings.setMinimumSize(new java.awt.Dimension(71, 20));
        textSettings.setPreferredSize(new java.awt.Dimension(90, 20));

        fileSettings.setText("Settings");
        fileSettings.setMaximumSize(new java.awt.Dimension(71, 20));
        fileSettings.setMinimumSize(new java.awt.Dimension(71, 20));
        fileSettings.setPreferredSize(new java.awt.Dimension(90, 20));

        urlSettings.setText("Settings");
        urlSettings.setMaximumSize(new java.awt.Dimension(71, 20));
        urlSettings.setMinimumSize(new java.awt.Dimension(71, 20));
        urlSettings.setPreferredSize(new java.awt.Dimension(90, 20));

		imageSettings.setText("Settings");
		imageSettings.setMaximumSize(new java.awt.Dimension(71, 20));
		imageSettings.setMinimumSize(new java.awt.Dimension(71, 20));
		imageSettings.setPreferredSize(new java.awt.Dimension(71, 20));

		imageSettings.addActionListener(new OpenSettingsListener(imageUploader));

		textSettings.addActionListener(new OpenSettingsListener(textUploader));

		fileSettings.addActionListener(new OpenSettingsListener(fileUploader));

		urlSettings.addActionListener(new OpenSettingsListener(urlShortener));

		javax.swing.GroupLayout uploaderPanelLayout = new javax.swing.GroupLayout(
				this);
		this.setLayout(uploaderPanelLayout);
        uploaderPanelLayout.setHorizontalGroup(
                uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(uploaderPanelLayout.createSequentialGroup()
                    .addContainerGap()
                    .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(uploaderPanelLayout.createSequentialGroup()
                            .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addComponent(localCopyCheckbox)
                                .addComponent(urlLabel)
                                .addComponent(shortenURLs)
                                .addComponent(automaticUpload))
                            .addGap(152, 152, 152))
                        .addGroup(uploaderPanelLayout.createSequentialGroup()
                            .addComponent(browseButton)
                            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 242, Short.MAX_VALUE)
                            .addComponent(saveButton, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addContainerGap())
                        .addGroup(uploaderPanelLayout.createSequentialGroup()
                            .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addGroup(uploaderPanelLayout.createSequentialGroup()
                                    .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                        .addComponent(imageLabel, javax.swing.GroupLayout.Alignment.LEADING)
                                        .addComponent(textLabel, javax.swing.GroupLayout.Alignment.LEADING)
                                        .addComponent(fileLabel, javax.swing.GroupLayout.Alignment.LEADING))
                                    .addGap(0, 0, Short.MAX_VALUE))
                                .addComponent(imageUploader, javax.swing.GroupLayout.Alignment.TRAILING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(textUploader, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(fileUploader, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(urlShortener, javax.swing.GroupLayout.Alignment.TRAILING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                            .addGap(18, 18, 18)
                            .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(fileSettings, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                    .addComponent(urlSettings, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                    .addComponent(textSettings, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                                .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, uploaderPanelLayout.createSequentialGroup()
                                    .addComponent(imageSettings, javax.swing.GroupLayout.PREFERRED_SIZE, 88, javax.swing.GroupLayout.PREFERRED_SIZE)
                                    .addContainerGap())))))
            );
            uploaderPanelLayout.setVerticalGroup(
                uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(uploaderPanelLayout.createSequentialGroup()
                    .addContainerGap()
                    .addComponent(imageLabel)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(imageUploader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(imageSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGap(11, 11, 11)
                    .addComponent(textLabel)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(textUploader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(textSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                    .addComponent(fileLabel)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(fileUploader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(fileSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                    .addComponent(urlLabel)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(urlShortener, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(urlSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGap(18, 18, 18)
                    .addComponent(shortenURLs)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addComponent(automaticUpload)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addComponent(localCopyCheckbox)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 140, Short.MAX_VALUE)
                    .addGroup(uploaderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(saveButton)
                        .addComponent(browseButton))
                    .addContainerGap())
            );
	}

	private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {
		try {
			Util.openFile(new File(Util.getWorkingDirectory(), "plugins/uploaders"));