/**
 * KKMulticopterFlashTool, a avrdude GUI for flashing KK boards and other
 *   equipment.
 *   Copyright (C) 2011 Christian Moll
 *
 *   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, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   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, see <http://www.gnu.org/licenses/>.
 */
package de.lazyzero.kkMulticopterFlashTool;

import static lu.tudor.santec.i18n.Translatrix._;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.SplashScreen;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import lu.tudor.santec.i18n.Translatrix;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;

import avr8_burn_o_mat.AVR;
import avr8_burn_o_mat.AvrdudeConfigFile;
import avr8_burn_o_mat.AvrdudeControl;
import avr8_burn_o_mat.AvrdudeControl.FileFormat;
import avr8_burn_o_mat.AvrdudeProgrammer;
import avr8_burn_o_mat.InterfaceTextOutput;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import de.lazyzero.kkMulticopterFlashTool.gui.ColorTweaks;
import de.lazyzero.kkMulticopterFlashTool.gui.ControllerPanel;
import de.lazyzero.kkMulticopterFlashTool.gui.EEpromResetPanel;
import de.lazyzero.kkMulticopterFlashTool.gui.EEpromSettingsPanel;
import de.lazyzero.kkMulticopterFlashTool.gui.FirmwarePanel;
import de.lazyzero.kkMulticopterFlashTool.gui.ProgrammerPanel;
import de.lazyzero.kkMulticopterFlashTool.gui.SimonkEditorPanel;
import de.lazyzero.kkMulticopterFlashTool.gui.kkMenu;
import de.lazyzero.kkMulticopterFlashTool.utils.ArduinoUpload;
import de.lazyzero.kkMulticopterFlashTool.utils.ButtonsStateListener;
import de.lazyzero.kkMulticopterFlashTool.utils.FileCorruptException;
import de.lazyzero.kkMulticopterFlashTool.utils.Firmware;
import de.lazyzero.kkMulticopterFlashTool.utils.Icons;
import de.lazyzero.kkMulticopterFlashTool.utils.XmlReaderFirmwares;
import de.lazyzero.kkMulticopterFlashTool.utils.EEprom.EEprom;
import de.lazyzero.kkMulticopterFlashTool.utils.EEprom.EEpromListener;

/**
 * @author Christian Moll
 * 
 */
public class KKMulticopterFlashTool extends JFrame implements
		InterfaceTextOutput, PropertyChangeListener {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public static String VERSION = "0.90";
	private static boolean isBeta = true;
	private static int betaVersion = 7;
	public static final String MODE_CHANGED = "changed";
	public static final String KKPLUSBOOT = "kkplusboot";
	public static final String FLYCAM_BLACKBOARD = "flycam_black";
	public static final String FLYCAM_BLACKBOARD_P = "flycam_blackp";
	public static final String SMARTLCD = "smartlcd";
	public static final String ESC = "esc";
	public static final String ESCBOOTLOADER = "escp";
	public static final String WIIESC = "wii_bl";
	public static final String WIIESC_EEPROM = "wii_eeprom";
	public static final Object WIIESC_BOOTLOADER = "wii_boot";
	public static final String ESC_LIGHT = "esc_light";
	public static final String OPENFLIGHT_V1 = "openflightV1";
	public static final String OPENFLIGHT_V2 = "openflightV2";
	public static final String OPENFLIGHT_V2_SM = "openflightSM";
	public static final String OPENFLIGHTPROG = "openflightPROG";
	public static final String i86 = "i86";
	public static final String i86l = "i86l";
	private static KKMulticopterFlashTool kk;

	private Locale						locale;
	private JPanel						mainPanel;
	private Properties					settings		 = new Properties();
	public static File SETTINGS_FILE	= new File(System.getProperty("user.dir"), "kkMulticopterFlashTool.properties");
	public static File LOG_FILE	= new File(System.getProperty("user.dir"), "kkLogging.txt");
	
	private JFrame						mainframe;
	private Dimension preferredSize	= new Dimension(830, 650);
	private AvrdudeConfigFile			 avrConfig;
	private AvrdudeProgrammer			 programmer;
	private AvrdudeControl				control;
	private boolean forceFlashing = false;

	private Vector<AvrdudeProgrammer>	 programmers;
	private static Vector<AVR>				   avrs			 = new Vector<AVR>();
	private AVR						   controller;

	private JTextArea					 output;
	private Color defaultFG;
	private JScrollPane				   outputPane;

	private ProgrammerPanel			   programmerPanel;
	private ControllerPanel			   controllerPanel;
	private FirmwarePanel				 firmwarePanel;
	private LinkedHashMap<String,String> firmwareRepositoryURL = new LinkedHashMap<String,String>();
	private Firmware firmware;
	private XmlReaderFirmwares firmwareReader;
	private boolean offlineMode;
	private String mode;
	
	private FileHandler logFile;
	private Logger logger = Logger.getLogger(KKMulticopterFlashTool.class.getName());
	private boolean enableTweak = true;
	public static boolean ENABLE_PORT_CHECK = true;
	private static boolean isPopupsEnabled;
	private static int countdown;
	private static boolean isHideDeprecated;
	private static boolean isShowDailyTGYEnabled;
	private static boolean isVerbose;
	private JTabbedPane tabbedPane = new JTabbedPane();
	//private TestPanel testPanel;
	private JPanel programmingPanel;
	private JPanel eepromPanel;
	private Component eepromResetPanel;
	private EEpromSettingsPanel eepromSettingsPanel;
	protected boolean successful;
	private Vector<ButtonsStateListener> listeners = new Vector<ButtonsStateListener>();
	private boolean isNoLogging = false;
	private boolean useDefaults = false;
	private SimonkEditorPanel simonkEditorPanel;

	public KKMulticopterFlashTool(String[] args) {

		kk = this;
		evaluateCommandlineOptions(args);
		
		//check if the OS is Mac OS X
		if (System.getProperty("os.name").toLowerCase().contains("mac")) {
			SETTINGS_FILE = new File(System.getProperty("user.home")+"/Library/Preferences/","kkMulticopterFlashTool.properties");
			LOG_FILE = new File(getTempFolder(),"kkLogging.txt");
		}
		
		if (!isNoLogging ) initLogger();
		
		SplashScreen splash = SplashScreen.getSplashScreen();
		if (splash==null) {
			logger.log(Level.INFO, "Splash not loaded.");
		}
		
		
		logger.log(Level.INFO, System.getProperty("user.home"));
		logger.log(Level.INFO, System.getProperty("os.name"));
		

		loadSettings();
		loadTranslation();

		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				saveSettings();
			}
		});
		
		firmwareReader = new XmlReaderFirmwares(firmwareRepositoryURL);

		this.init();
		if (enableTweak) ColorTweaks.tweakColors();
		this.initGUI();
		
		this.addPropertyChangeListener(this);
		
		checkVersion();
	}

	
	private void evaluateCommandlineOptions(String[] args) {
		
	        Options options = new Options();
	        
	        options.addOption("h", "help", false, "Show this help message.");
	        options.addOption("c", "color", false, "Use default colors of the Java VM.");
	        options.addOption("p", "portcheck", false, "Disable checking for port checking.");
	        options.addOption("n", "nologging", false, "Disable logging to file.");
	        options.addOption("d", "defaults", false, "Use default settings");
	     
	        try {
	            CommandLineParser parser = new PosixParser();
	            CommandLine cmd = parser.parse(options, args);
	            
	            if (cmd.hasOption("h")) {
	                HelpFormatter formatter = new HelpFormatter();
	                formatter.printHelp("KKmulticopter Flash Tool Version " + VERSION, options, true);
	                System.exit(0);
	            }
	           
	            if (cmd.hasOption("c")) {
	            	enableTweak = false;
	            }
	            
	            if (cmd.hasOption("p")) {
	            	ENABLE_PORT_CHECK = false;
	            }
	            
	            if (cmd.hasOption("n")) {
	            	isNoLogging = true;
	            }
	            
	            if (cmd.hasOption("d")) {
	            	useDefaults  = true;
	            }
	            
	            
	            	
	        } catch (ParseException exp) {
	           
	        }
	}


	private void initLogger() {
		try {
			logFile = new FileHandler(LOG_FILE.getAbsolutePath(), false);
			logFile.setFormatter(new SimpleFormatter());
			logFile.setLevel(Level.INFO);
			logger.addHandler(logFile);
			
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		logger.setLevel(Level.INFO);
	}


	
	
		
	private void checkVersion() {
		logger.log(Level.INFO, "Check the version: " + VERSION + (isBeta?" beta "+betaVersion:"") + " online is version: " + firmwareReader.getActualVersion());
		if (firmwareReader.getActualVersion()>Double.parseDouble(VERSION)) {
//			JOptionPane.showMessageDialog(this, _("update"));
			String[] choices = {_("downloads.download"), _("Cancel")};
			String updateTitle = String.format(_("updateTitle"), Double.toString(firmwareReader.getActualVersion()));
			String updateText = "<html>"+_("update") + "<br><br>" + _("updateChanges") + "<br>" + firmwareReader.getChangelog(); 
			int result = JOptionPane.showOptionDialog(this, updateText, updateTitle ,
				      JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, 
				      null, choices, choices[0]);
			if (result == 0) {
				kkMenu.openURL("http://kkflashtool.de");
				System.exit(0);
			}
		}
	}


	public void flashAVR() {
		boolean abort = check4USBlinker();
		if (!abort) {
			new Thread(){
				public void run(){
					
					boolean notFailed = true;
					boolean isUSBtiny = programmer.getType().equals("usbtiny")?true:false;
					firmwarePanel.setButtonsEnabled(false);
					updateButtons();
					
					
					try {
						control = new AvrdudeControl(KKMulticopterFlashTool.this, isUSBtiny);
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
						logger.log(Level.WARNING, e1.getMessage());
					}
					
					if(isVerbose) {
						control.addAdditionalOption("-vvvvv ");
					}
					
					control.setPort(programmerPanel.getPort());
					control.setProgrammer(programmer);
					if (programmerPanel.useBaudRate()) {
						control.addAdditionalOption("-b " + programmerPanel.getRate());
					}
					logger.log(Level.INFO, programmer.getType());
//					if (!programmer.getType().equals("stk500v2")){
//					if (!programmer.getType().equals("usbtiny")){
//						control.addAdditionalOption("-B 8");
//						logger.log(Level.INFO, "set additional Option -B 8");
//					}
//					}
					//if (controller.getCaption().equals("m48pa")||controller.getCaption().equals("m168p")||controller.getCaption().equals("m328p")){
					control.addAdditionalOption("-e");
					logger.log(Level.INFO, "set additional Option -e");
//					}
					
					if (forceFlashing) {
						control.addAdditionalOption("-F");
					}
					control.setDisableFuseCheck(false);
					
					//import additionalOptions from firmware
					control.addAndUpdateAdditionalOptions(firmware.getAdditionalOptions());
					//for BL-ESC the fuses are null and the next step is skipped.
					if (controller.getLfuse() != null && controller.getHfuse() != null) {
						//write the fuses.
						try {
							logger.log(Level.INFO, "write fuses: ");
							control.writeFuses(controller);
						} catch (Exception e) {
							notFailed = false; 
							e.printStackTrace();
							logger.log(Level.WARNING, "error.writefuses");
							err(_("error.writefuses"));
						}
						
						try {
							Thread.sleep(1500);
						} catch (InterruptedException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
							logger.log(Level.WARNING, e1.toString());
						}
					}
					
					//TODO add code to manipulate fuses
					//special code for enabling bootloader on simonk ESC
					if (controller.getCaption().equals(ESCBOOTLOADER) && notFailed) {
						logger.log(Level.INFO, "enable bootloader on simonk ESC: " + controller.getBootloaderAddress());
						
						try {
							control.readFuses(controller, getTempFolder());
							logger.log(Level.INFO, "hfuse: " + Integer.toHexString(Integer.parseInt(controller.getHfuse())) + " lfuse: " + Integer.toHexString(Integer.parseInt(controller.getLfuse())));
							if (controller.getBootloaderAddress()==512){
								controller.changeFuseNibble(AVR.HFUSE, AVR.LOWER_NIBBLE, "a");
								logger.log(Level.INFO, "fuses changed to: hfuse: " + Integer.toHexString(Integer.parseInt(controller.getHfuse())) + " lfuse: " + Integer.toHexString(Integer.parseInt(controller.getLfuse())));
							}
							
							//added a delay to test if avrispmkii will then work
							try {
								Thread.sleep(1500);
							} catch (InterruptedException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
								logger.log(Level.WARNING, e1.toString());
							}
							
							logger.log(Level.INFO, "write new fuses: ");
							control.writeFuses(controller);
							
						} catch (Exception e) {
							notFailed = false;
							err(_("error.readhfuse"));
							logger.log(Level.WARNING, "error.replacefuse");
							e.printStackTrace();
						}
						
						//clean up
						controller.setHfuse(null);
						controller.setLfuse(null);
						
						File fuseFile = new File(getTempFolder() + "/hfuse.hex");
						boolean isDeleted = false;
						if(fuseFile.exists() && fuseFile.isFile()){
							isDeleted = fuseFile.delete();
							logger.log(Level.INFO, "hfuse.hex deleted: " + isDeleted);
						}
						
						fuseFile = new File(getTempFolder() + "/lfuse.hex");
						if(fuseFile.exists() && fuseFile.isFile()){
							isDeleted = fuseFile.delete();
							logger.log(Level.INFO, "lfuse.hex deleted: " + isDeleted);
						}
						
						try {
							Thread.sleep(1500);
						} catch (InterruptedException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
							logger.log(Level.WARNING, e1.toString());
						}
					}
					
					try {
						if (notFailed) {
							logger.log(Level.INFO, "flash firmware");
							//TODO add flashing of eeprom here.
							try {	
								File file = firmware.getFile();
								println(_("flash.file") + ": "
										+ file.getAbsolutePath());
								control.writeFlash(controller, file.getAbsolutePath(), FileFormat.INTEL_HEX);
								if (isPopupsEnabled) {
									JOptionPane.showMessageDialog(kk, _("flash.successfull"), _("info"), JOptionPane.INFORMATION_MESSAGE);
								}
								println(_("flash.successfull"));
							} catch (FileCorruptException e) {
								err(_("flash.abort") + " " +_("flash.filecorrupt"));
							}
						} else {
							err(_("flash.abort"));
							logger.log(Level.WARNING, "flash.abort");
						}
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						err(_("error.writeflash"));
						logger.log(Level.WARNING, "error.writeflash");
					}
					firmwarePanel.setButtonsEnabled(true);
					updateButtons();
				}
			}.start();
			
		} else {
			err(_("flash.abort"));
		}
	}
	
	public void flashEEprom(final EEpromListener eepromListener) {
		logger.info("WRITE the EEprom!!!");
		clearText();
		boolean abort = check4USBlinker();
		if (!abort) {
			new Thread(){
				public void run(){
					boolean isUSBtiny = programmer.getType().equals("usbtiny")?true:false;
					
					try {
						control = new AvrdudeControl(KKMulticopterFlashTool.this, isUSBtiny);
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
						logger.log(Level.WARNING, e1.getMessage());
					}
					
					if(isVerbose) {
						control.addAdditionalOption("-vvvvv ");
					}
					
					control.setPort(programmerPanel.getPort());
					control.setProgrammer(programmer);
					if (programmerPanel.useBaudRate()) {
						control.addAdditionalOption("-b " + programmerPanel.getRate());
					}
					logger.log(Level.INFO, programmer.getType());
//					if (!programmer.getType().equals("usbtiny")){
//						control.addAdditionalOption("-B 8");
//						logger.log(Level.INFO, "set additional Option -B 8");
//					}
					if (forceFlashing) {
						control.addAdditionalOption("-F");
					}
					control.setDisableFuseCheck(false);
					
					logger.log(Level.INFO, "flash eeprom");
					try {
						if (controller.getCaption().equals(WIIESC_EEPROM)) {
							File file = firmware.getFile();
							println(_("flash.eeprom") + ": "
									+ file.getAbsolutePath());
							control.writeEEPROM(controller,  file.getAbsolutePath(), FileFormat.AUTO_DETECT);
						} else {
							println(_("flash.eeprom") + ": "
									+ EEprom.getDefaultOutputFile());
							control.writeEEPROM(controller,  EEprom.getDefaultOutputFile(), FileFormat.RAW);
							eepromListener.EEpromState(EEpromListener.WROTE);
						}
					} catch (FileCorruptException e) {
						err(_("flash.abort") + " " +_("flash.filecorrupt"));
						eepromListener.EEpromState(EEpromListener.FILE_CORRUPT);
					} catch (Exception e) {
						if (eepromListener != null) {
							eepromListener.EEpromState(EEpromListener.FLASH_FAILED);
						}
						err(_("flash.abort"));
						logger.warning(e.getMessage());
//					e.printStackTrace();
					}
				}
			}.start();
		} else {
			err(_("flash.abort"));
		}
		
	}
	
	public void readEEprom(final EEpromListener eepromListener) {
		logger.info("READ the EEprom!!!");
		clearText();
		boolean abort = check4USBlinker();
		if (!abort) {
			new Thread(){
				public void run(){
					boolean isUSBtiny = programmer.getType().equals("usbtiny")?true:false;
					
					try {
						control = new AvrdudeControl(KKMulticopterFlashTool.this, isUSBtiny);
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
						logger.log(Level.WARNING, e1.getMessage());
					}
					control.setPort(programmerPanel.getPort());
					control.setProgrammer(programmer);
					if (programmerPanel.useBaudRate()) {
						control.addAdditionalOption("-b " + programmerPanel.getRate());
					}
					logger.log(Level.INFO, programmer.getType());
					if (!programmer.getType().equals("usbtiny")){
						control.addAdditionalOption("-B 8");
						logger.log(Level.INFO, "set additional Option -B 8");
					}
					if (forceFlashing) {
						control.addAdditionalOption("-F");
					}
					control.setDisableFuseCheck(false);
					
					logger.log(Level.INFO, "flash eeprom");
					try {		
						println(_("read.eeprom") + ": "
								+ EEprom.getDefaultInputFile());
						control.readEEPROM(controller,  EEprom.getDefaultInputFile(), FileFormat.RAW);
						eepromListener.EEpromState(EEpromListener.READ);
					} catch (Exception e) {
						eepromListener.EEpromState(EEpromListener.FAILED);
						err(_("read.abort"));
						logger.warning(e.getMessage());
	//					e.printStackTrace();
					}
				}
			}.start(); 
		} else {
			eepromListener.EEpromState(EEpromListener.FAILED);
			err(_("read.abort"));
		}
		
	}
	

	private boolean check4USBlinker() {
		if (programmer.getId().equals("tgyusblinker") || programmer.getId().equals("arduinousblinker") || programmer.getId().equals("afrousb")) {
			if (programmerPanel.getPort().equals("usb")) {
				err(_("flash.LinkerWrongPort"));
				return true;
			}
		}
		return false;
	}


	public void loadSettings() {
		try {
			settings.load(new FileInputStream(SETTINGS_FILE));
			
			firmwareRepositoryURL.put("firmwareRepositoryURL", settings.getProperty("firmwareRepositoryURL", "http://www.chrmoll.de/_media/firmwares.xml.zip"));
			//check for depricated settings!!!
			if (firmwareRepositoryURL.containsValue("http://www.chrmoll.de/_media/firmwares.xml")) {
				firmwareRepositoryURL.put("firmwareRepositoryURL", "http://www.chrmoll.de/_media/firmwares.xml.zip");
			}
			firmwareRepositoryURL.put("tgydaily",settings.getProperty("tgydaily", "http://www.chrmoll.de/_media/tgy_daily.xml.zip"));
			
			offlineMode = new Boolean(settings.getProperty("offlineMode","false"));
			isPopupsEnabled = new Boolean(settings.getProperty("isPopupEnabled","true"));
			isHideDeprecated = new Boolean(settings.getProperty("isHideDeprecated","false"));
			isVerbose = new Boolean(settings.getProperty("isVerbose","false"));
			isShowDailyTGYEnabled = new Boolean(settings.getProperty("isShowDailyTGYEnabled", "false"));
			countdown = Integer.parseInt(settings.getProperty("countdown", "0"));
			
			if (isOfflineMode()) {
				mode = _("offline");
			} else {
				mode = _("online");
			}
			
			
			try {
				this.setLocale((String) settings.getProperty("locale.language",
						"en"), (String) settings.getProperty("locale.country",
						"US"));
			} catch (Exception e) {
			}

		} catch (Exception e) {
			
			e.printStackTrace();
			firmwareRepositoryURL.put("firmwareRepositoryURL", "http://www.chrmoll.de/_media/firmwares.xml.zip");
			offlineMode = false;
			
			saveSettings();
			e.printStackTrace();
		}
	}

	private void saveSettings() {
		if (!useDefaults) {
			try {
				settings.put("locale.language", locale.getLanguage());
				settings.put("locale.country", locale.getCountry());
				
				settings.put("offlineMode", offlineMode+"");
				settings.put("isPopupEnabled", isPopupsEnabled+"");
				settings.put("isHideDeprecated", isHideDeprecated+"");
				settings.put("isVerbose", isVerbose+"");
				settings.put("isShowDailyTGYEnabled", isShowDailyTGYEnabled+"");
				
				settings.put("countdown", countdown+"");
				
				settings.put("programmer", programmer.getId());
				settings.put("port", programmerPanel.getPort());
				settings.put("rate", programmerPanel.getRate());
				settings.put("defaultRate", programmerPanel.isDefaultRate()+"");
				settings.put("controller", controller.getName());
				
				Iterator<String> keys = firmwareRepositoryURL.keySet().iterator();
				while (keys.hasNext()) {
					String key = keys.next();
					settings.put(key, firmwareRepositoryURL.get(key));
				}
				
				try {
					settings.put("last.dir", firmware.getFile().getPath());
				} catch (Exception e) {
					// TODO: handle exception
				}
				
				settings.store(new FileOutputStream(SETTINGS_FILE), "");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private void init() {
		avrConfig = new AvrdudeConfigFile();
		try {
			avrConfig.readAvrdudeConfigFile(AvrdudeConfigFile.getConfigFileByOS());
		} catch (IOException e) {
			JOptionPane.showMessageDialog(this, _("error.avrdudeconfig"));
			e.printStackTrace();
		}
		programmers = avrConfig.getProgrammerList();
		
		avrs.add(new AVR("HobbyKing KK2.1 and KK2.1.5", "64kB flash", "m644p", 2048, null, null));
		avrs.add(new AVR("HobbyKing KK2.1 and KK2.1.5 (fuse restore)", "64kB flash", "m644p", 2048, "0xf7", "0xd7", "0xfc"));
		avrs.add(new AVR("HobbyKing KK2.1 and KK2.1.5 (with ATmega644)", "64kB flash", "m644", 2048, null, null));
		
		avrs.add(new AVR("HobbyKing KK2.0", "32kB flash", "m324pa", 1024, null, null));
		avrs.add(new AVR("HobbyKing KK2.0 (fuse restore)", "32kB flash", "m324pa", 1024, "0xf7", "0xd7", "0xfc"));
		avrs.add(new AVR("HobbyKing KK2.0 (brown out of 2.7V)", "32kB flash", "m324pa", 1024, "0xf7", "0xd7", "0xfd"));
		avrs.add(new AVR("HobbyKing KK2.0 (ATmega1284P)", "128kB flash", "m1284p", 4096, "0xd7", "0xd1", "0xfc"));//(Low=D7, High=D1, Ext=FC)
		
		avrs.add(new AVR("atmega 8-based brushless ESC", "8kB flash", ESC, 512, null, null));
		avrs.add(new AVR("atmega 8-based brushless ESC + enable Bootloader", "8kB flash", ESCBOOTLOADER, 512, null, null, 512));
		avrs.add(new AVR("atmega 8-based brushless ESC (fuse restore, external clock", "8kB flash", ESCBOOTLOADER, 512, "0x3f", "0xca"));
		avrs.add(new AVR("atmega 8-based brushless ESC (fuse restore, internal clock", "8kB flash", ESCBOOTLOADER, 512, "0x24", "0xda"));
		avrs.add(new AVR("WiiESC", "8kB flash", WIIESC, 512, null, null));
		avrs.add(new AVR("WiiESC Settings", "8kB flash", WIIESC_EEPROM, 512, null, null));
		avrs.add(new AVR("EscLight", "8kB flash", ESC_LIGHT, 512, null, null));
		
		avrs.add(new AVR("Orange RX3S V2/V3", "16kB flash", OPENFLIGHT_V2, 512, null, null));
		avrs.add(new AVR("Orange RX3SM", "16kB flash", OPENFLIGHT_V2_SM, 512, null, null));
		avrs.add(new AVR("Orange RX3S V1", "16kB flash", OPENFLIGHT_V1, 512, null, null));
		avrs.add(new AVR("OpenFlight Programming Box", "8kB flash", OPENFLIGHTPROG, 512, null, null));
		
		avrs.add(new AVR("KK Blackboard 168", "16kB flash", "m168", 512,"0xe2", "0xdd")); //Niall efuse:FF lfuse:E2 hfuse:DE
		avrs.add(new AVR("KK Blackboard 168P/PA", "16kB flash", "m168p", 512, "0xe2", "0xdd"));
		avrs.add(new AVR("KK Blackboard 328P", "32kB flash", "m328p", 1024, "0xe2", "0xd9"));
		avrs.add(new AVR("KK Plus 5.5d/e", "16kB flash", "m168p", 512, "0xe2", "0xde"));
//		avrs.add(new AVR("KK Plus 5.5d/e Bootloader restore", "16kB flash", KKPLUSBOOT, "0xe2", "0xde"));//Extended : 0xFF High: 0xDE Low: 0xE2
		avrs.add(new AVR("HobbyKing Quadcopter Control Board V3", "32kB flash", "m328p", 1024, "0xe2", "0xd9"));
		avrs.add(new AVR("HobbyKing Quadcopter Control Board V2/V2.1", "16kB flash", "m168p", 512, "0xe2", "0xdd"));
		avrs.add(new AVR("HobbyKing Quadcopter Control Board V1", "4kB flash", "m48p", 256, "0xe2", "0xdd"));
		avrs.add(new AVR("HobbyKing Hobbyking i86 or Eagle N6 V1", "16kB flash", i86, 512, "0xe2", "0xdd"));	
		avrs.add(new AVR("HobbyKing Hobbyking i86L or Eagle N6 V2", "16kB flash", i86l, 512, null, null));		
		avrs.add(new AVR("Flycam Blackboard", "16kB flash", FLYCAM_BLACKBOARD, 512, "0xe2", "0xdd"));//Extended : 0xFF High: 0xDE Low: 0xE2
		avrs.add(new AVR("Flycam Blackboard (Goodluckbuy version)", "16kB flash", FLYCAM_BLACKBOARD_P, 512, "0xe2", "0xdd"));//Extended : 0xFF High: 0xDE Low: 0xE2
		avrs.add(new AVR("Korean Blueboard", "16kB flash", "m168", 512, "0xe2", "0xdd"));
		avrs.add(new AVR("Korean Redboard (without chip update)", "4kB flash", "m48", 256, "0xe2", "0xdd"));
		
		avrs.add(new AVR("SmartLCD Pro", "4kB flash", SMARTLCD, 256, null, null));
		
//		avrs.add(new AVR("atmega 48", "4kB flash", "m48", 256, "0xe2", "0xdd"));
//		avrs.add(new AVR("atmega 48PA", "4kB flash", "m48p", 256, "0xe2", "0xdd"));
//		avrs.add(new AVR("atmega 88", "8kB flash", "m88", 512, "0xe2", "0xdd"));
//		avrs.add(new AVR("atmega 88P/PA", "8kB flash", "m88p", 512, "0xe2", "0xdd"));
//		avrs.add(new AVR("atmega 168", "16kB flash", "m168", 512, "0xe2", "0xdd"));
//		avrs.add(new AVR("atmega 168P/PA", "16kB flash", "m168p", 512, "0xe2", "0xdd"));
//		avrs.add(new AVR("atmega 328P", "32kB flash", "m328p", 1024, "0xe2", "0xd9"));
//		avrs.add(new AVR("atmega 328", "32kB flash", "m328", 1024, "0xe2", "0xd9"));
		
//		avrs.add(new AVR(ArduinoUSBLinkerUploader.ARDUINO, "32kB flash", "m328", 1024, null, null));
	}

	private void initGUI() {
		mainPanel = new JPanel();

		// create the CellContraints
		CellConstraints cc = new CellConstraints();

		// create the Layout for Panel this
		String panelColumns = "fill:pref:grow";
		String panelRows = "3dlu,pref,3dlu,pref,3dlu,pref:grow";
		FormLayout panelLayout = new FormLayout(panelColumns, panelRows);
		mainPanel.setLayout(panelLayout);
		programmingPanel = ceateProgrammingPanel();
		eepromPanel = createEEpromPanel();
		
		tabbedPane.add(programmingPanel);
		
		programmerPanel = new ProgrammerPanel(this, programmers);
		controllerPanel = new ControllerPanel(this, avrs);
		simonkEditorPanel = new SimonkEditorPanel(this);

		mainPanel.add(programmerPanel, cc.xy(1, 2));
		mainPanel.add(controllerPanel, cc.xy(1, 4));
		mainPanel.add(tabbedPane, cc.xy(1, 6));

		logger.log(Level.INFO, "Set the port from the properties [usb]: " + settings.getProperty("port", "usb"));
		
		programmerPanel.setPort(settings.getProperty("port", "usb"));
		programmerPanel.setRate(settings.getProperty("rate", "19200"));
		programmerPanel.setDefaultRate(new Boolean(settings.getProperty("defaultRate", "true")));
		programmerPanel.setProgrammer(settings.getProperty("programmer",
				"usbasp"));
		controllerPanel.setController(settings.getProperty("controller",
				"Blackboard"));
			
		tabbedPane.addTab(_("programmingPanel.title"), programmingPanel);
		tabbedPane.setBorder(new LineBorder(Color.WHITE));
		tabbedPane.setMnemonicAt(0, KeyEvent.VK_1);
		
		tabbedPane.addTab(_("simonkEditorPanel.title"), simonkEditorPanel);
		tabbedPane.setMnemonicAt(1, KeyEvent.VK_2);
		tabbedPane.setEnabledAt(tabbedPane.indexOfTab(_("simonkEditorPanel.title")), false);
		
		tabbedPane.addTab(_("eepromPanel.title"), eepromPanel);
		tabbedPane.setMnemonicAt(2, KeyEvent.VK_2);
		tabbedPane.setEnabledAt(tabbedPane.indexOfTab(_("eepromPanel.title")), false);
				
		tabbedPane.addChangeListener(new ChangeListener() {
			
			@Override
			public void stateChanged(ChangeEvent event) {
				JTabbedPane sourceTabbedPane = (JTabbedPane) event.getSource();
				if (sourceTabbedPane.getSelectedComponent().equals(eepromPanel)) {
					logger.info("changed to eepromPanel");
					eepromResetPanel.firePropertyChange(ControllerPanel.CONTROLLER_CHANGED, 0, 1);
				} else if (sourceTabbedPane.getSelectedComponent().equals(programmingPanel)) {
					logger.info("changed to programmingPanel");
				}
				
			}
		});
		
		
		mainframe = new JFrame(_("title") + " " + VERSION + (isBeta?" beta " + betaVersion:"") + " - " + mode);
//		mainframe.setPreferredSize(preferredSize);
//		mainframe.setSize(preferredSize);

//		mainframe.add(tabbedPane);
		mainframe.add(mainPanel);
		mainframe.setJMenuBar((new kkMenu(this, firmwareReader)).getBar());
		mainframe.setIconImage(Icons.getImage(Icons.app, 16));

		mainframe.pack();
		mainframe.setLocationRelativeTo(getRootPane());

	    mainframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		firmwarePanel.firePropertyChange(ControllerPanel.CONTROLLER_CHANGED, 0, 1);
		
	}

	private JPanel createEEpromPanel() {
		JPanel panel = new JPanel();
		CellConstraints cc = new CellConstraints();

		// create the Layout for Panel this
		String panelColumns = "pref:grow";
		String panelRows = "pref,3dlu,fill:pref:grow,3dlu,pref";
		FormLayout panelLayout =  new FormLayout(panelColumns, panelRows);
		panel.setLayout(panelLayout);
		
		eepromResetPanel = new EEpromResetPanel(this);
		eepromSettingsPanel = new EEpromSettingsPanel(this);
		
		panel.add(eepromResetPanel, cc.xy(1, 1));
		panel.add(eepromSettingsPanel, cc.xy(1, 3));
		// TODO Auto-generated method stub
		return panel;
	}


	private JPanel ceateProgrammingPanel() {
		JPanel panel = new JPanel();
		CellConstraints cc = new CellConstraints();

		// create the Layout for Panel this
		String panelColumns = "pref:grow";
		String panelRows = "pref,3dlu,fill:pref";
		FormLayout panelLayout =  new FormLayout(panelColumns, panelRows);
		panel.setLayout(panelLayout);
		
		firmwarePanel = new FirmwarePanel(this, firmwareReader);
		addButtonsStateListener(firmwarePanel);
		output = new JTextArea();
		output.setColumns(20);
		output.setEditable(false);
		output.setRows(15);
		defaultFG = output.getForeground();
		outputPane = new JScrollPane(output);
		outputPane.setAutoscrolls(true);
		outputPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		
		panel.add(firmwarePanel, cc.xy(1, 1));
		panel.add(outputPane, cc.xy(1, 3));
		
//		panel.setBorder(new LineBorder(Color.WHITE));
		
		return panel;
	}

	private void checkTabVisibility() {
		tabbedPane.setEnabledAt(tabbedPane.indexOfTab(_("eepromPanel.title")), false);
		tabbedPane.setEnabledAt(tabbedPane.indexOfTab(_("simonkEditorPanel.title")), false);
		if (controller.getCaption().equals(ESC) || controller.getCaption().equals(ESCBOOTLOADER)) {
			tabbedPane.setEnabledAt(tabbedPane.indexOfTab(_("simonkEditorPanel.title")), true);
		} else if (controller.getCaption().equals("m48") || controller.getCaption().equals("m48p")
				|| controller.getCaption().equals("m168") || controller.getCaption().equals("m168p") 
				|| controller.getCaption().equals("m328p")){
			tabbedPane.setEnabledAt(tabbedPane.indexOfTab(_("eepromPanel.title")), true);
		}
	}
	
	public void setLocale(String language, String country) {
		this.locale = new Locale(language, country);
		logger.log(Level.INFO, language + "_" + country);
	}
	
	public Locale getLocale() {
		return locale;
	}

	private void loadTranslation() {
		// load the supported locales and the default locale
		Translatrix
				.loadSupportedLocales("de.lazyzero.kkMulticopterFlashTool.gui.resources.supportedLocales");
		Translatrix.setLocale(Translatrix.getDefaultLocale());
		// load resources for core package
		@SuppressWarnings("rawtypes")
		Vector<Locale> locales = new Vector<Locale>(Translatrix.getSupportedLocales());

		if (locales.contains(locale)) {
			Translatrix.setLocale(locale);
		} else {
			locale = Translatrix.getLocale();
		}
		Translatrix.addBundle("de.lazyzero.kkMulticopterFlashTool.gui.resources.translatrix");

	}

	public boolean restart() {
		int response = JOptionPane.showConfirmDialog(mainframe,
				_("restart.dialog"), _("restart.title"),
				JOptionPane.YES_NO_OPTION);
		if (response == 0)
			return true;
		return false;
	}

	/**
	 * @return the mainframe
	 */
	public JFrame getMainframe() {
		return mainframe;
	}

	/**
	 * @return the settings
	 */
	public Properties getSettings() {
		return settings;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new KKMulticopterFlashTool(args);

		kk.mainframe.setVisible(true);
		kk.checkTabVisibility();
	}

	public void print(final String line) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				output.append(line);
			}
		});
	}

	public void println(final String line) {
		logger.log(Level.INFO, line);

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				output.append(line);
				output.append("\n");
			}
		});
	}
	
	public void err(final String line) {
		logger.log(Level.WARNING, line);

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				
				output.setForeground(Color.red);
				output.append(line);
				output.append("\n");
			}
		});
		if (isPopupsEnabled) {
			JOptionPane.showMessageDialog(this, line, _("error"), JOptionPane.ERROR_MESSAGE);
		}
	}

	public void clearText() {

		output.setForeground(defaultFG);
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				output.setText("");
			}
		});
	}
	
	@Override
	public String getText() {
		return output.getText();
	}


	/**
	 * @return the programmer
	 */
	public AvrdudeProgrammer getProgrammer() {
		return programmer;
	}

	/**
	 * @param programmer
	 *            the programmer to set
	 */
	public void setProgrammer(AvrdudeProgrammer programmer) {
		this.programmer = programmer;
	}

	/**
	 * @return the controller
	 */
	public AVR getController() {
		return controller;
	}

	/**
	 * @param controller
	 *            the controller to set
	 */
	public void setController(AVR controller) {
		this.controller = controller;
	}

	public void setFirmware(Firmware firmware) {
		this.firmware = firmware;
	}
	
	public void setHexFile(File file, boolean clearContent) {
		firmwarePanel.setHexFile(file, clearContent);
		switch2ProgrammingTab();
	}

	public void switch2ProgrammingTab() {
		tabbedPane.setSelectedIndex(tabbedPane.indexOfTab(_("programmingPanel.title")));
	}


	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if (evt.getPropertyName().equals(ControllerPanel.CONTROLLER_CHANGED)) {
			firmwarePanel.firePropertyChange(ControllerPanel.CONTROLLER_CHANGED, 0, 1);
			eepromResetPanel.firePropertyChange(ControllerPanel.CONTROLLER_CHANGED, 0, 1);
		}
		if (evt.getPropertyName().equals(MODE_CHANGED)) {
			firmwarePanel.firePropertyChange(ControllerPanel.CONTROLLER_CHANGED, 0, 1);
			if (isOfflineMode()) {
				mode = _("offline");
			} else {
				mode = _("online");
			}
			this.mainframe.setTitle(_("title") + " " + VERSION + " - " + mode);
		}
		if (evt.getPropertyName().equals(ProgrammerPanel.PROGRAMMER_CHANGED)) {
			//TODO Make controller dependent from Programmer.
		}
		checkTabVisibility();
	}



	public static String getTempFolder() {
		String tmpdir;
		if (System.getProperty("os.name").toLowerCase().contains("windows")) {
            tmpdir = System.getProperty("user.dir")+"\\tmp\\";
        } else if (System.getProperty("os.name").toLowerCase().contains("mac")) {
			tmpdir = System.getProperty("user.home")+"/Library/Preferences/kkMulticopterFlashTool/";
		} else {
        	tmpdir = System.getProperty("user.dir")+"/tmp/";
        }
		KKMulticopterFlashTool.getLogger().log(Level.INFO, tmpdir);
		return tmpdir;
	}


	public static boolean isOfflineMode() {
		try {
			return kk.offlineMode;
		} catch (Exception e) {
			return false;
		}
	}


	public static void setOfflineMode(boolean b) {
		kk.offlineMode = b;
	}


	public Vector<Firmware> getFirmwares() {
		return firmwareReader.getFirmwares();
	}

	public boolean isForceFlashing() {
		return forceFlashing;
	}


	public void setForceFlashing(boolean forceFlashing) {
		this.forceFlashing = forceFlashing;
	}
	
	public static Logger getLogger() {
		try {
			return kk.logger;
		} catch (Exception e) {
			return Logger.getLogger("Default-Logger");
		}
	}

	public static Vector<AVR> getControllers() {
		return avrs;
	}


	public void setSelectedTabIndex(int i) {
		tabbedPane.setSelectedIndex(i);
	}


	public static KKMulticopterFlashTool getInstance() {
		return kk;
	}


	public static void setPopupEnabled(boolean enablePopups) {
		isPopupsEnabled = enablePopups;
	}


	public static boolean isPopupsEnabled() {
		return isPopupsEnabled;
	}
	
	public static void setHideDeprecatedEnabled(boolean hideDeprecated) {
		isHideDeprecated = hideDeprecated;
	}

	public static boolean isHideDecprecatedEnabled() {
		return isHideDeprecated;
	}
	
	public static void setVerboseEnabled(boolean verbose) {
		isVerbose = verbose;
	}

	public static boolean isVerboseEnabled() {
		return isVerbose;
	}

	public static void setShowDailyTGYEnabled(boolean showDailyTGYEnabled) {
		isShowDailyTGYEnabled = showDailyTGYEnabled;
	}

	public static boolean isShowDailyTGYEnabled() {
		return isShowDailyTGYEnabled;
	}


	public static int getCountdown() {
		System.out.println("Countdown is " + countdown);
		return countdown;
	}


	public static void setCountdown(int value) {
		countdown = value;
	}
	
	public void addButtonsStateListener(ButtonsStateListener myListener) {
		listeners.add(myListener);
	}
	
	private void updateButtons() {
		for (ButtonsStateListener listener : listeners) {
			listener.updateButtons();
		}
	}


	public void flashArduino(ArduinoUpload arduinoUpload, Firmware arduinoFirmware, String arduinoPort) {
		programmerPanel.setProgrammer(arduinoUpload.getAvrProgrammer().getId());
		programmerPanel.setPort(arduinoPort);
		programmerPanel.setRate(arduinoUpload.getSpeed()+"");
		programmerPanel.setDefaultRate(false);
		
		setController(arduinoUpload.getAVR());
		
		setFirmware(arduinoFirmware);
		
		flashAVR();
		
		firePropertyChange(ControllerPanel.CONTROLLER_CHANGED, false, false);
	}


	

	

}
