/**
 * 
 */
package de.lazyzero.kkMulticopterFlashTool;

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

import java.awt.Color;
import java.awt.Dimension;
import java.awt.SplashScreen;
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.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
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.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.border.LineBorder;
import javax.swing.plaf.ColorUIResource;

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 lu.tudor.santec.i18n.Translatrix;
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.FirmwarePanel;
import de.lazyzero.kkMulticopterFlashTool.gui.ProgrammerPanel;
import de.lazyzero.kkMulticopterFlashTool.gui.kkMenu;
import de.lazyzero.kkMulticopterFlashTool.utils.Firmware;
import de.lazyzero.kkMulticopterFlashTool.utils.Icons;
import de.lazyzero.kkMulticopterFlashTool.utils.XmlReaderFirmwares;

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

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public static String VERSION = "0.40";
	public static final String MODE_CHANGED = "changed";
	public static final String KKPLUSBOOT = "kkplusboot";
	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 CellConstraints			   cc;
	private Dimension preferredSize	= new Dimension(800, 605);
	private AvrdudeConfigFile			 avrConfig;
	private AvrdudeProgrammer			 programmer;
	private AvrdudeControl				control;
	private boolean forceFlashing = false;

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

	private JTextArea					 output;
	private JScrollPane				   outputPane;

	private ProgrammerPanel			   programmerPanel;
	private ControllerPanel			   controllerPanel;
	private FirmwarePanel				 firmwarePanel;
	private URL firmwareRepositoryURL = null;
	private URL firmwareRepositoryMirrorURL = null;
	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 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");
		}
		
		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, firmwareRepositoryMirrorURL);

		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.");
	     
	        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;
	            }
	            
	            	
	        } 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 + " online is version: " + firmwareReader.getActualVersion());
		if (firmwareReader.getActualVersion()>Double.parseDouble(VERSION)) {
			JOptionPane.showMessageDialog(this, _("update"));
		}
	}


	public void flashAVR() {
		new Thread(){
			public void run(){
				
				boolean failed = true;
				try {
					control = new AvrdudeControl(KKMulticopterFlashTool.this);
				} 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("stk500v2")){
					control.addAdditionalOption("-B 8");
					logger.log(Level.INFO, "set additional Option -B 8");
				//}
				if (controller.getCaption().equals("m48pa")||controller.getCaption().equals("m168p")){
					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());
				
				

				try {
					logger.log(Level.INFO, "write fuses: ");
					control.writeFuses(controller);
				} catch (Exception e) {
					failed = false; 
					e.printStackTrace();
					logger.log(Level.WARNING, "error.writefuses");
					println(_("error.writefuses"));
				}

				try {
					Thread.sleep(1500);
				} catch (InterruptedException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
					logger.log(Level.WARNING, e1.toString());
				}
				
				try {
					if (failed) {
						logger.log(Level.INFO, "flash firmware");
						println(_("flash.file") + ": "
								+ firmware.getFile().getAbsolutePath());
						control.writeFlash(controller, firmware.getFile()
								.getAbsolutePath(), FileFormat.INTEL_HEX);
					} else {
						println(_("flash.abort"));
						logger.log(Level.WARNING, "flash.abort");
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					println(_("error.writeflash"));
					logger.log(Level.WARNING, "error.writeflash");
				}
			}
		}.start();
		
		
	}

	public void loadSettings() {
		try {
			settings.load(new FileInputStream(SETTINGS_FILE));
			
			firmwareRepositoryURL = new URL(settings.getProperty("firmwareRepositoryURL", "http://www.lazyzero.de/_media/firmwares.xml"));
			firmwareRepositoryMirrorURL = new URL(settings.getProperty("firmwareRepositoryMirrorURL", "http://www.michael-rossberg.de/kkmulticopter/firmwares.xml"));
			offlineMode = new Boolean(settings.getProperty("offlineMode","false"));
			
			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) {
			try {
				firmwareRepositoryURL = new URL("http://www.lazyzero.de/_media/firmwares.xml");
				firmwareRepositoryMirrorURL = new URL("http://www.michael-rossberg.de/kkmulticopter/firmwares.xml");
				offlineMode = false;
			} catch (MalformedURLException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			saveSettings();
			e.printStackTrace();
		}
	}

	private void saveSettings() {
		try {
			settings.put("locale.language", locale.getLanguage());
			settings.put("locale.country", locale.getCountry());

			settings.put("offlineMode", offlineMode+"");
			
			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());

			settings.put("firmwareRepositoryURL", firmwareRepositoryURL.toString());
			settings.put("firmwareRepositoryMirrorURL", firmwareRepositoryMirrorURL.toString());
			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("Blackboard 168", "16kB flash", "m168", "0xe2", "0xde")); //Niall efuse:FF lfuse:E2 hfuse:DE
		avrs.add(new AVR("Blackboard 168P/PA", "16kB flash", "m168p", "0xe2", "0xde"));
		avrs.add(new AVR("Blackboard 328P", "32kB flash", "m328p", "0xe2", "0xd9"));
		avrs.add(new AVR("KK Plus 5.5d/e", "16kB flash", "m168p", "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 V1", "4kB flash", "m48pa", "0xe2", "0xdf"));
		avrs.add(new AVR("HobbyKing Quadcopter Control Board V2", "16kB flash", "m168p", "0xe2", "0xde"));
		avrs.add(new AVR("Korea Blueboard", "16kB flash", "m168", "0xe2", "0xde"));
		avrs.add(new AVR("Korea Redboard (without chip update)", "4kB flash", "m48", "0xe2", "0xdf"));
		avrs.add(new AVR("atmega 48", "4kB flash", "m48", "0xe2", "0xdf"));
		avrs.add(new AVR("atmega 48PA", "4kB flash", "m48pa", "0xe2", "0xdf"));
		avrs.add(new AVR("atmega 88", "8kB flash", "m88", "0xe2", "0xdf"));
		avrs.add(new AVR("atmega 88P/PA", "8kB flash", "m88p", "0xe2", "0xdf"));
		avrs.add(new AVR("atmega 168", "16kB flash", "m168", "0xe2", "0xde"));
		avrs.add(new AVR("atmega 168P/PA", "16kB flash", "m168p", "0xe2", "0xde"));
		avrs.add(new AVR("atmega 328P", "32kB flash", "m328p", "0xe2", "0xd9"));
	}

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

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

		// create the Layout for Panel this
		String panelColumns = "3dlu,fill:pref:grow,3dlu";
		String panelRows = "3dlu,pref,3dlu,pref,3dlu,pref,3dlu,fill:pref";
		FormLayout panelLayout = new FormLayout(panelColumns, panelRows);
		mainPanel.setLayout(panelLayout);

		programmerPanel = new ProgrammerPanel(this, programmers);
		controllerPanel = new ControllerPanel(this, avrs);
		firmwarePanel = new FirmwarePanel(this, firmwareReader);

		output = new JTextArea();
		output.setColumns(20);
		output.setEditable(false);
		output.setRows(15);
		outputPane = new JScrollPane(output);
		outputPane.setAutoscrolls(true);
		outputPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

		mainPanel.add(programmerPanel, cc.xy(2, 2));
		mainPanel.add(controllerPanel, cc.xy(2, 4));
		mainPanel.add(firmwarePanel, cc.xy(2, 6));
		mainPanel.add(outputPane, cc.xy(2, 8));

		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"));
		
		
		mainframe = new JFrame(_("title") + " " + VERSION + " - " + mode);
		mainframe.setPreferredSize(preferredSize);
		mainframe.setSize(preferredSize);

		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);
		

	}

	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
		Vector locales = 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;
		else
			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);
	}

	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 clearText() {

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				output.setText("");
			}
		});
	}
	
	@Override
	public String getText() {
		// TODO Auto-generated method stub
		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;
	}

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if (evt.getPropertyName().equals(ControllerPanel.CONTROLLER_CHANGED)) {
			firmwarePanel.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);
		}
	}



	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() {
		// TODO Auto-generated method stub
		return kk.offlineMode;
	}


	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() {
		return kk.logger;
	}




	
	

}
