/*
 * Copyright (C) 2015 Laurent CLOUET
 * Author Laurent CLOUET <laurent.clouet@nopnop.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package com.sheepit.client.standalone.swing.activity;

import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;

import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.FlatDarkLaf;

import com.sheepit.client.Configuration;
import com.sheepit.client.Configuration.ComputeType;
import com.sheepit.client.hardware.cpu.CPU;
import com.sheepit.client.hardware.gpu.GPU;
import com.sheepit.client.hardware.gpu.GPUDevice;
import com.sheepit.client.hardware.gpu.GPULister;
import com.sheepit.client.hardware.gpu.nvidia.Nvidia;
import com.sheepit.client.hardware.gpu.opencl.OpenCL;
import com.sheepit.client.network.Proxy;
import com.sheepit.client.os.OS;
import com.sheepit.client.standalone.GuiSwing;
import com.sheepit.client.standalone.swing.SwingTooltips;
import com.sheepit.client.standalone.swing.components.CollapsibleJPanel;
import org.jetbrains.annotations.NotNull;


public class Settings implements Activity {
	private static final String DUMMY_CACHE_DIR = "Auto detected";
	
	private GuiSwing parent;
	
	private JTextField login;
	private JPasswordField password;
	private JLabel cacheDirText;
	private File cacheDir;
	private JFileChooser cacheDirChooser;
	private JCheckBox useCPU;
	private List<JCheckBoxGPU> useGPUs;
	private JCheckBox useSysTray;
	private JCheckBox headlessCheckbox;
	private JSlider cpuCores;
	private JSlider ram;
	private JSpinner renderTime;
	private JSlider priority;
	private JTextField proxy;
	private JTextField hostname;
	
	private ButtonGroup themeOptionsGroup;
	private JRadioButton lightMode;
	private JRadioButton darkMode;
	
	private JCheckBox saveFile;
	private JCheckBox autoSignIn;
	JButton saveButton;
	
	private boolean haveAutoStarted;
	private boolean useSysTrayPrevState;
	private boolean isHeadlessPrevState;
	private boolean sessionStarted;	//indicates whether the settings activity gets shown on launch or mid-session.
									// it should only be false when the settings activity gets shown right after launch
	
	public Settings(GuiSwing parent_) {
		parent = parent_;
		cacheDir = null;
		useGPUs = new LinkedList<JCheckBoxGPU>();
		haveAutoStarted = false;
		sessionStarted = false;
	}
	
	@Override public void show() {
		Configuration config = parent.getConfiguration();
		parent.getSettingsLoader().merge(config, false);
		useSysTrayPrevState = config.isUseSysTray();
		isHeadlessPrevState = config.isHeadless();
		
		applyTheme(config.getTheme());    // apply the proper theme (light/dark)
		
		List<GPUDevice> gpus = GPU.listDevices(config);
		useGPUs.clear();    // Empty the auxiliary list (used in the list of checkboxes)
		
		GridBagConstraints constraints = new GridBagConstraints();
		int currentRow = 0;
		
		JLabel labelImage;
		try {
			// Include the version of the app as a watermark in the SheepIt logo.
			final BufferedImage watermark = ImageIO.read(getClass().getResource("/sheepit-logo.png"));
			String versionString = "v" + Configuration.jarVersion;
			drawVersionStringOnImage(watermark, versionString);

			labelImage = new JLabel(new ImageIcon(watermark));
		}
		catch (Exception e) {
			// If something fails, we just show the SheepIt logo (without any watermark)
			ImageIcon image = new ImageIcon(getClass().getResource("/sheepit-logo.png"));
			labelImage = new JLabel(image);
		}
		
		constraints.fill = GridBagConstraints.CENTER;
		
		constraints.gridwidth = 2;
		constraints.gridx = 0;
		constraints.gridy = currentRow;
		parent.getContentPanel().add(labelImage, constraints);
		
		++currentRow;
		
		constraints.gridy = currentRow;
		parent.getContentPanel().add(new JLabel(" "), constraints);    // Add a separator between logo and first panel
		
		currentRow++;
		
		// authentication
		CollapsibleJPanel authentication_panel = new CollapsibleJPanel(new GridLayout(2, 2), this);
		authentication_panel.setBorder(BorderFactory.createTitledBorder("Authentication"));
		
		JLabel loginLabel = new JLabel("Username:");
		login = new JTextField();
		login.setText(parent.getConfiguration().getLogin());
		login.setColumns(20);
		login.addKeyListener(new CheckCanStart());
		JLabel passwordLabel = new JLabel("Password:");
		password = new JPasswordField();
		password.setText(parent.getConfiguration().getPassword());
		password.setColumns(10);
		password.addKeyListener(new CheckCanStart());
		
		authentication_panel.add(loginLabel);
		authentication_panel.add(login);
		
		authentication_panel.add(passwordLabel);
		authentication_panel.add(password);
		
		constraints.gridx = 0;
		constraints.gridy = currentRow;
		constraints.fill = GridBagConstraints.HORIZONTAL;
		parent.getContentPanel().add(authentication_panel, constraints);
		
		// Theme selection panel
		CollapsibleJPanel themePanel = new CollapsibleJPanel(new GridLayout(1, 3), this);
		themePanel.setBorder(BorderFactory.createTitledBorder("Theme"));
		
		themeOptionsGroup = new ButtonGroup();
		
		lightMode = new JRadioButton("Light");
		lightMode.setActionCommand("light");
		lightMode.setSelected(config.getTheme().equals("light"));
		lightMode.addActionListener(new ApplyThemeAction());
		
		darkMode = new JRadioButton("Dark");
		darkMode.setActionCommand("dark");
		darkMode.setSelected(config.getTheme().equals("dark"));
		darkMode.addActionListener(new ApplyThemeAction());
		
		themePanel.add(lightMode);
		themePanel.add(darkMode);
		
		// Group both radio buttons to allow only one selected
		themeOptionsGroup.add(lightMode);
		themeOptionsGroup.add(darkMode);
		
		currentRow++;
		constraints.gridx = 0;
		constraints.gridy = currentRow;
		constraints.gridwidth = 2;
		
		parent.getContentPanel().add(themePanel, constraints);
		
		// directory
		CollapsibleJPanel directory_panel = new CollapsibleJPanel(new GridLayout(1, 3), this);
		directory_panel.setBorder(BorderFactory.createTitledBorder("Cache"));
		JLabel cacheLabel = new JLabel("Working directory:");
		cacheLabel.setToolTipText(SwingTooltips.WORKING_DIRECTORY.getText());
		directory_panel.add(cacheLabel);
		String destination = DUMMY_CACHE_DIR;
		if (config.isUserHasSpecifiedACacheDir()) {
			destination = config.getCacheDirForSettings().getName();
		}
		
		JPanel cacheDirWrapper = new JPanel();
		cacheDirWrapper.setLayout(new BoxLayout(cacheDirWrapper, BoxLayout.LINE_AXIS));
		cacheDirText = new JLabel(destination);
		cacheDirWrapper.add(cacheDirText);
		
		cacheDirWrapper.add(Box.createHorizontalGlue());
		
		cacheDirChooser = new JFileChooser();
		cacheDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		JButton openButton = new JButton("...");
		openButton.addActionListener(new ChooseFileAction());
		cacheDirWrapper.add(openButton);
		
		directory_panel.add(cacheDirWrapper);
		
		currentRow++;
		constraints.gridx = 0;
		constraints.gridy = currentRow;
		constraints.gridwidth = 2;
		
		parent.getContentPanel().add(directory_panel, constraints);
		
		// compute devices
		GridBagLayout gridbag = new GridBagLayout();
		GridBagConstraints compute_devices_constraints = new GridBagConstraints();
		CollapsibleJPanel compute_devices_panel = new CollapsibleJPanel(gridbag, this);
		
		compute_devices_panel.setBorder(BorderFactory.createTitledBorder("Compute devices"));
		compute_devices_panel.setToolTipText(SwingTooltips.COMPUTE_DEVICES.getText());
		
		ComputeType method = config.getComputeMethod();
		useCPU = new JCheckBox("CPU");
		boolean gpuChecked = false;
		
		if (method == ComputeType.CPU_GPU) {
			useCPU.setSelected(true);
			gpuChecked = true;
		}
		else if (method == ComputeType.CPU) {
			useCPU.setSelected(true);
			gpuChecked = false;
		}
		else if (method == ComputeType.GPU) {
			useCPU.setSelected(false);
			gpuChecked = true;
		}
		useCPU.addActionListener(new CpuChangeAction());
		
		compute_devices_constraints.gridx = 1;
		compute_devices_constraints.gridy = 0;
		compute_devices_constraints.fill = GridBagConstraints.BOTH;
		compute_devices_constraints.weightx = 1.0;
		compute_devices_constraints.weighty = 1.0;
		
		gridbag.setConstraints(useCPU, compute_devices_constraints);
		compute_devices_panel.add(useCPU);
		
		if (gpus.size() > 0) {
			for (GPUDevice gpu : gpus) {
				JCheckBoxGPU gpuCheckBox = new JCheckBoxGPU(gpu);
				gpuCheckBox.setToolTipText(gpu.getId());
				if (gpuChecked) {
					GPUDevice config_gpu = config.getGPUDevice();
					if (config_gpu != null && config_gpu.getId().equals(gpu.getId())) {
						gpuCheckBox.setSelected(gpuChecked);
					}
				}
				gpuCheckBox.addActionListener(new GpuChangeAction());
				
				compute_devices_constraints.gridy++;
				gridbag.setConstraints(gpuCheckBox, compute_devices_constraints);
				compute_devices_panel.add(gpuCheckBox);
				useGPUs.add(gpuCheckBox);
			}
			
			//When replacing gpus it can happen that the client can't find the one specified in the config anymore in which case config.getGPUDevice()
			//returns null
			if ((config.getComputeMethod() == ComputeType.GPU || config.getComputeMethod() == ComputeType.CPU_GPU) && config.getGPUDevice() != null) {
				GPULister gpu;
				
				if (config.getGPUDevice().getType().equals("CUDA")) {
					gpu = new Nvidia();
				}
				else if (config.getGPUDevice().getType().equals("OPENCL")) {
					gpu = new OpenCL();
				}
			}
			
			compute_devices_constraints.gridx = 1;
			compute_devices_constraints.weightx = 1.0;
			
			compute_devices_panel.add(new JLabel(" "), compute_devices_constraints);        // Add a space between lines
		}
		
		CPU cpu = new CPU();
		if (cpu.cores() > 1) { // if only one core is available, no need to show the choice
			double step = 1;
			double display = (double) cpu.cores() / step;
			while (display > 10) {
				step += 1.0;
				display = (double) cpu.cores() / step;
			}
			
			cpuCores = new JSlider(CPU.MIN_CORES, cpu.cores());
			cpuCores.setMajorTickSpacing((int) (step));
			cpuCores.setMinorTickSpacing(1);
			cpuCores.setPaintTicks(true);
			cpuCores.setSnapToTicks(true);
			cpuCores.addChangeListener(new ChangeListener() {
				@Override public void stateChanged(ChangeEvent e) {
					cpuCores.setToolTipText(String.valueOf(cpuCores.getValue()));
				}
			});
			cpuCores.setPaintLabels(true);
			cpuCores.setValue(config.getNbCores() != -1 ? (Math.max(config.getNbCores(), CPU.MIN_CORES)): cpuCores.getMaximum());
			JLabel coreLabel = new JLabel("CPU cores:");
			coreLabel.setToolTipText(SwingTooltips.CPU_CORES.getText());
			
			compute_devices_constraints.weightx = 1.0 / gpus.size();
			compute_devices_constraints.gridx = 0;
			compute_devices_constraints.gridy++;
			
			gridbag.setConstraints(coreLabel, compute_devices_constraints);
			compute_devices_panel.add(coreLabel);
			
			compute_devices_constraints.gridx = 1;
			compute_devices_constraints.weightx = 1.0;
			
			gridbag.setConstraints(cpuCores, compute_devices_constraints);
			compute_devices_panel.add(new JLabel(" "), compute_devices_constraints);        // Add a space between lines
			compute_devices_panel.add(cpuCores);
		}
		
		// max ram allowed to render
		OS os = OS.getOS();
		int all_ram = (int) os.getTotalMemory();
		ram = new JSlider(0, all_ram);
		int step = 1000000;
		double display = (double) all_ram / (double) step;
		while (display > 10) {
			step += 1000000;
			display = (double) all_ram / (double) step;
		}
		Hashtable<Integer, JLabel> labelTable = new Hashtable<Integer, JLabel>();
		for (int g = 0; g < all_ram; g += step) {
			labelTable.put(g, new JLabel("" + (g / 1000000)));
		}
		ram.setMajorTickSpacing(step);
		ram.setLabelTable(labelTable);
		ram.setPaintTicks(true);
		ram.addChangeListener(new ChangeListener() {
			@Override public void stateChanged(ChangeEvent e) {
				ram.setToolTipText(String.format("%,d MiB", (ram.getValue() / 1024)));
			}
		});
		ram.setPaintLabels(true);
		ram.setValue((int) (config.getMaxAllowedMemory() != -1 ? config.getMaxAllowedMemory() : os.getTotalMemory()));
		JLabel ramLabel = new JLabel("Memory:");
		ramLabel.setToolTipText(SwingTooltips.MEMORY.getText());
		
		compute_devices_constraints.weightx = 1.0 / gpus.size();
		compute_devices_constraints.gridx = 0;
		compute_devices_constraints.gridy++;
		
		gridbag.setConstraints(ramLabel, compute_devices_constraints);
		compute_devices_panel.add(ramLabel);
		
		compute_devices_constraints.gridx = 1;
		compute_devices_constraints.weightx = 1.0;
		
		gridbag.setConstraints(ram, compute_devices_constraints);
		compute_devices_panel.add(new JLabel(" "), compute_devices_constraints);        // Add a space between lines
		compute_devices_panel.add(ram);
		
		parent.getContentPanel().add(compute_devices_panel, constraints);
		
		// priority
		// ui display low -> high but the actual values are reversed
		// -19 is highest priority
		boolean high_priority_support = os.getSupportHighPriority();
		priority = new JSlider(high_priority_support ? -19 : 0, 19);
		Hashtable<Integer, JLabel> labelTablePriority = new Hashtable<>();
		labelTablePriority.put(high_priority_support ? -19 : 0, new JLabel("Low"));
		labelTablePriority.put(19, new JLabel("High"));
		priority.setLabelTable(labelTablePriority);
		
		priority.setMinorTickSpacing(1);
		priority.setPaintTicks(true);
		priority.setSnapToTicks(true);
		priority.addChangeListener(new ChangeListener() {
			@Override public void stateChanged(ChangeEvent e) {
				priority.setToolTipText(String.valueOf(priority.getValue()));
			}
		});
		priority.setPaintLabels(true);
		if (high_priority_support) {
			// inverse
			priority.setValue(-1 * config.getPriority());
		}
		else {
			priority.setValue(19 - config.getPriority());
		}
		JLabel priorityLabel = new JLabel("Priority");
		priorityLabel.setToolTipText(SwingTooltips.PRIORITY.getText());
		
		boolean showPrioritySlider = os.checkNiceAvailability();
		priority.setVisible(showPrioritySlider);
		priorityLabel.setVisible(showPrioritySlider);
		
		compute_devices_constraints.weightx = 1.0 / gpus.size();
		compute_devices_constraints.gridx = 0;
		compute_devices_constraints.gridy++;
		
		gridbag.setConstraints(priorityLabel, compute_devices_constraints);
		compute_devices_panel.add(priorityLabel);
		
		compute_devices_constraints.gridx = 1;
		compute_devices_constraints.weightx = 1.0;
		
		gridbag.setConstraints(priority, compute_devices_constraints);
		compute_devices_panel.add(priority);
		
		currentRow++;
		constraints.gridx = 0;
		constraints.gridy = currentRow;
		constraints.gridwidth = 2;
		parent.getContentPanel().add(compute_devices_panel, constraints);
		
		// other
		CollapsibleJPanel advanced_panel = new CollapsibleJPanel(new GridLayout(5, 2), this);
		advanced_panel.setBorder(BorderFactory.createTitledBorder("Advanced options"));
		
		JLabel useSysTrayLabel = new JLabel("Minimize to SysTray");
		useSysTrayLabel.setToolTipText(SwingTooltips.MINIMIZE_TO_SYSTRAY.getText());
		
		useSysTray = new JCheckBox();
		useSysTray.setSelected(config.isUseSysTray());
		advanced_panel.add(useSysTrayLabel);
		advanced_panel.add(useSysTray);
		
		JLabel headlessLabel = new JLabel("Block Eevee projects");
		headlessCheckbox = new JCheckBox();
		headlessCheckbox.setSelected(config.isHeadless());
		advanced_panel.add(headlessLabel);
		advanced_panel.add(headlessCheckbox);
		
		JLabel proxyLabel = new JLabel("Proxy:");
		proxyLabel.setToolTipText("http://login:password@host:port\n" + SwingTooltips.PROXY.getText());
		proxy = new JTextField();
		proxy.setToolTipText("http://login:password@host:port");
		proxy.setText(parent.getConfiguration().getProxy());
		proxy.addKeyListener(new CheckCanStart());
		
		advanced_panel.add(proxyLabel);
		advanced_panel.add(proxy);
		
		JLabel hostnameLabel = new JLabel("Computer name:");
		hostnameLabel.setToolTipText(SwingTooltips.COMPUTER_NAME.getText());
		hostname = new JTextField();
		hostname.setText(parent.getConfiguration().getHostname());
		
		advanced_panel.add(hostnameLabel);
		advanced_panel.add(hostname);
		
		JLabel renderTimeLabel = new JLabel("Max time per frame (in minute):");
		renderTimeLabel.setToolTipText(SwingTooltips.MAX_TIME_PER_FRAME.getText());
		int val = 0;
		if (parent.getConfiguration().getMaxRenderTime() > 0) {
			val = parent.getConfiguration().getMaxRenderTime() / 60;
		}
		renderTime = new JSpinner(new SpinnerNumberModel(val, 0, 1000, 1));
		
		advanced_panel.add(renderTimeLabel);
		advanced_panel.add(renderTime);
		
		currentRow++;
		constraints.gridx = 0;
		constraints.gridy = currentRow;
		constraints.gridwidth = 2;
		parent.getContentPanel().add(advanced_panel, constraints);
		
		// general settings
		JPanel general_panel = new JPanel(new GridLayout(1, 2));
		
		saveFile = new JCheckBox("Save settings", true);
		general_panel.add(saveFile);
		
		autoSignIn = new JCheckBox("Auto sign in", config.isAutoSignIn());
		autoSignIn.addActionListener(new AutoSignInChangeAction());
		general_panel.add(autoSignIn);
		
		currentRow++;
		constraints.gridx = 0;
		constraints.gridy = currentRow;
		constraints.gridwidth = 2;
		parent.getContentPanel().add(general_panel, constraints);
		
		currentRow++;
		constraints.gridy = currentRow;
		parent.getContentPanel().add(new JLabel(" "), constraints);    // Add a separator between last checkboxes and button
		
		currentRow++;
		String buttonText = "Start";
		if (parent.getClient() != null) {
			if (parent.getClient().isRunning()) {
				buttonText = "Save";
			}
		}
		saveButton = new JButton(buttonText);
		checkDisplaySaveButton();
		saveButton.addActionListener(new SaveAction());
		currentRow++;
		constraints.gridwidth = 2;
		constraints.gridx = 0;
		constraints.gridy = currentRow;
		parent.getContentPanel().add(saveButton, constraints);

		// Increase the size of the app Window to ensure it shows all the information with the Advanced Options panel opened.
		parent.setSize(520,850);
		
		if (haveAutoStarted == false && config.isAutoSignIn() && checkDisplaySaveButton()) {
			// auto start
			haveAutoStarted = true;
			new SaveAction().actionPerformed(null);
		}
	}

	private void drawVersionStringOnImage(final BufferedImage image, String versionString) {
		var watermarkWidth = image.getWidth();
		var watermarkHeight = image.getHeight();

		Graphics gph = image.getGraphics();
		gph.setFont(gph.getFont().deriveFont(12f));

		FontMetrics fontMetrics = gph.getFontMetrics();
		// move text to the very right of the image respecting its length
		int x = watermarkWidth - fontMetrics.stringWidth(versionString);
		// the bottom of the image, but give enough headroom for descending letters like g
		int y = watermarkHeight - fontMetrics.getMaxDescent();

		gph.drawString(versionString, x, y);
		gph.dispose();
	}

	public void resizeWindow() {}
	
	public boolean checkDisplaySaveButton() {
		boolean selected = useCPU.isSelected();
		for (JCheckBoxGPU box : useGPUs) {
			if (box.isSelected()) {
				selected = true;
			}
		}
		if (login.getText().isEmpty() || password.getPassword().length == 0 || Proxy.isValidURL(proxy.getText()) == false) {
			selected = false;
		}
		
		saveButton.setEnabled(selected);
		return selected;
	}
	
	private void applyTheme(String theme_) {
		try {
			if (theme_.equals("light")) {
				UIManager.setLookAndFeel(new FlatLightLaf());
			}
			else if (theme_.equals("dark")) {
				UIManager.setLookAndFeel(new FlatDarkLaf());
			}
			
			// Apply the new theme
			FlatLaf.updateUI();
		}
		catch (UnsupportedLookAndFeelException e1) {
			e1.printStackTrace();
		}
	}
	
	class ChooseFileAction implements ActionListener {
		
		@Override public void actionPerformed(ActionEvent arg0) {
			JOptionPane.showMessageDialog(parent.getContentPane(),
					"<html>The working directory has to be dedicated directory. <br />Caution, everything not related to SheepIt-Renderfarm will be removed.<br />You should create a directory specifically for it.</html>",
					"Warning: files will be removed!", JOptionPane.WARNING_MESSAGE);
			int returnVal = cacheDirChooser.showOpenDialog(parent.getContentPane());
			if (returnVal == JFileChooser.APPROVE_OPTION) {
				File file = cacheDirChooser.getSelectedFile();
				cacheDir = file;
				cacheDirText.setText(cacheDir.getName());
			}
		}
	}
	
	class CpuChangeAction implements ActionListener {
		
		@Override public void actionPerformed(ActionEvent e) {
			checkDisplaySaveButton();
		}
	}
	
	class GpuChangeAction implements ActionListener {
		
		@Override public void actionPerformed(ActionEvent e) {
			int counter = 0;
			for (JCheckBox box : useGPUs) {
				if (!box.isSelected()) {
					box.setSelected(false);
				}
				else {
					GPULister gpu;
					if (useGPUs.get(counter).getGPUDevice().getType().equals("CUDA")) {
						gpu = new Nvidia();
					}
					else if (useGPUs.get(counter).getGPUDevice().getType().equals("OPENCL")) {
						gpu = new OpenCL();
					}
				}
				
				// Simulate a radio button behavior with check buttons while only 1 GPU
				// can be selected at a time
				if (box.equals(e.getSource()) == false) {
					box.setSelected(false);
				}
				
				counter++;
			}
			checkDisplaySaveButton();
		}
	}
	
	class AutoSignInChangeAction implements ActionListener {
		
		@Override public void actionPerformed(ActionEvent e) {
			if (autoSignIn.isSelected()) {
				saveFile.setSelected(true);
			}
		}
	}
	
	class ApplyThemeAction implements ActionListener {
		@Override public void actionPerformed(ActionEvent e) {
			applyTheme(themeOptionsGroup.getSelection().getActionCommand());
		}
	}
	
	class SaveAction implements ActionListener {
		
		@Override public void actionPerformed(ActionEvent e) {
			if (parent == null) {
				return;
			}
			
			Configuration config = parent.getConfiguration();
			if (config == null) {
				return;
			}
			
			if (themeOptionsGroup.getSelection().getActionCommand() != null)
				config.setTheme(themeOptionsGroup.getSelection().getActionCommand());
			
			if (cacheDir != null) {
				File fromConfig = config.getStorageDir();
				if (fromConfig != null && fromConfig.getAbsolutePath().equals(cacheDir.getAbsolutePath()) == false) {
					config.setCacheDir(cacheDir);
				}
				else {
					// do nothing because the directory is the same as before
				}
			}
			
			GPUDevice selected_gpu = null;
			for (JCheckBoxGPU box : useGPUs) {
				if (box.isSelected()) {
					selected_gpu = box.getGPUDevice();
				}
			}

			ComputeType method = getComputeType(selected_gpu);
			config.setComputeMethod(method);
			
			if (selected_gpu != null) {
				config.setGPUDevice(selected_gpu);
			}
			
			int cpu_cores = -1;
			if (cpuCores != null) {
				cpu_cores = cpuCores.getValue();
			}
			
			if (cpu_cores > 0) {
				config.setNbCores(cpu_cores);
			}
			
			long max_ram = -1;
			if (ram != null) {
				max_ram = ram.getValue();
			}
			
			if (max_ram > 0) {
				config.setMaxAllowedMemory(max_ram);
			}
			
			int max_rendertime = -1;
			if (renderTime != null) {
				max_rendertime = (Integer) renderTime.getValue() * 60;
				config.setMaxRenderTime(max_rendertime);
			}
			
			// ui display low -> high but the actual values are reversed
			// -19 is highest priority
			OS os = OS.getOS();
			boolean high_priority_support = os.getSupportHighPriority();
			if (high_priority_support) {
				// inverse
				config.setUsePriority(-1 * priority.getValue());
			}
			else {
				config.setUsePriority(19 - priority.getValue());
			}
			
			config.setHeadless(headlessCheckbox.isSelected());
			
			String proxyText = null;
			if (proxy != null) {
				try {
					Proxy.set(proxy.getText());
					proxyText = proxy.getText();
				}
				catch (MalformedURLException e1) {
					System.err.println("Error: wrong url for proxy");
					System.err.println(e1);
					System.exit(2);
				}
			}
			config.setProxy(proxyText);
			
			parent.setCredentials(login.getText(), new String(password.getPassword()));
			
			String hostnameText = hostname.getText();
			if (hostnameText == null || hostnameText.isEmpty()) {
				hostnameText = parent.getConfiguration().getHostname();
			}
			config.setHostname(hostnameText);
			config.setAutoSignIn(autoSignIn.isSelected());
			config.setUseSysTray(useSysTray.isSelected());
//			config.setUIType(GuiSwing.type);
			
			
			
			if (saveFile.isSelected()) {
				parent.getSettingsLoader()
					.setSettings(config.getConfigFilePath(), login.getText(), new String(password.getPassword()), proxyText, hostnameText, method,
						selected_gpu, cpu_cores, max_ram, max_rendertime, getCachePath(config), autoSignIn.isSelected(), useSysTray.isSelected(),
						headlessCheckbox.isSelected(), GuiSwing.type, themeOptionsGroup.getSelection().getActionCommand(), config.getPriority());
				
				// wait for successful authentication (to store the public key)
				// or do we already have one?
				if (parent.getClient().getServer().getServerConfig() != null && parent.getClient().getServer().getServerConfig().getPublickey() != null) {
					parent.getSettingsLoader().saveFile();
					sessionStarted = true;
				}
			}
			
			boolean sysTrayChanged = useSysTray.isSelected() != useSysTrayPrevState;
			boolean headlessChanged = headlessCheckbox.isSelected() != isHeadlessPrevState;
			boolean restartRequired = sysTrayChanged || headlessChanged;
			if (restartRequired) {
				String warning = "The following changes require a restart to take effect: %s";
				List<String> changes = new LinkedList<>();
				
				if (sysTrayChanged) {
					changes.add("Minimize to SysTray");
				}
				
				if (headlessChanged && sessionStarted) {    //only show this when the setting gets changed in the middle of a session, not on launch
					changes.add("Block Eevee Projects");
				}
				
				if (changes.size() > 0) {
					warning = String.format(warning, String.join(", ", changes));
					JOptionPane.showMessageDialog(null, warning);
				}
			}
		}
		
		@NotNull private ComputeType getComputeType(GPUDevice selected_gpu) {
			ComputeType method = ComputeType.CPU;
			if (useCPU.isSelected() && selected_gpu == null) {
				method = ComputeType.CPU;
			}
			else if (useCPU.isSelected() == false && selected_gpu != null) {
				method = ComputeType.GPU;
			}
			else if (useCPU.isSelected() && selected_gpu != null) {
				method = ComputeType.CPU_GPU;
			}
			return method;
		}
		
		private String getCachePath(Configuration config) {
			String cachePath = null;
			if (config.isUserHasSpecifiedACacheDir() && config.getCacheDirForSettings() != null) {
				cachePath = config.getCacheDirForSettings().getAbsolutePath();
			}
			return cachePath;
		}
	}
	
	class JCheckBoxGPU extends JCheckBox {
		private GPUDevice gpu;
		
		public JCheckBoxGPU(GPUDevice gpu) {
			super(gpu.getModel());
			this.gpu = gpu;
		}
		
		public GPUDevice getGPUDevice() {
			return gpu;
		}
	}
	
	public class CheckCanStart implements KeyListener {
		
		@Override public void keyPressed(KeyEvent arg0) {
		}
		
		@Override public void keyReleased(KeyEvent arg0) {
			checkDisplaySaveButton();
		}
		
		@Override public void keyTyped(KeyEvent arg0) {
		}
		
	}
}
