package nl.nikhef.safaripark;

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.xml.stream.XMLStreamException;

import org.codejargon.feather.Feather;
import org.codejargon.feather.Provides;

import nl.nikhef.safaripark.devmgr.BaySelectionListener;
import nl.nikhef.safaripark.devmgr.DeviceManager;
import nl.nikhef.safaripark.dolly.DollyModel;
import nl.nikhef.safaripark.dolly.DollyPanel;
import nl.nikhef.safaripark.editpane.EditPane;
import nl.nikhef.safaripark.extra.ExtendedAbstractAction;
import nl.nikhef.safaripark.extra.GuiUtils;
import nl.nikhef.safaripark.extra.StatefulTask;
import nl.nikhef.safaripark.monitor.Monitor;
import nl.nikhef.safaripark.res.Resources;
import nl.nikhef.sfp.SFPDevice;
import nl.nikhef.sfp.SFPManager;
import nl.nikhef.sfp.ddmi.DDMILoader;
import nl.nikhef.sfp.ddmi.DDMI;

@SuppressWarnings("serial")
public class SaFariPark extends JFrame implements BaySelectionListener, WindowListener, DeferredListener {

	
	private static final int SCAN_DEVICE_DELAY = 2;
	
	private DeviceManager 	_devMgr;
	private EditPane   		_tabEdit;
	private ActiveLogArea   _logOut;
	private Monitor			_monitor;
	private StatusBar       _status;
	private Timer			_timer;
	private int  			_scanForNewDevices = SCAN_DEVICE_DELAY;
	private AppContext      _appCtx;
	
	private ModuleManager   _modMgr;
	private DollyPanel		_dp;
	private Properties		buildProps;
	
	private Action _selectOverlays = new ExtendedAbstractAction("Select overlays", Resources.getIcon("books"), "Select which SFP+ overlays to load") {
		
		@Override
		public void actionPerformed(ActionEvent e) 
		{
			
			_appCtx.ovlMgr.showDialog(SaFariPark.this);
		}
	};
	
	private Action _saveBinary = new ExtendedAbstractAction("Save binary content", Resources.getIcon("document-export-4"), "Save a binary export") {
		
		@Override
		public void actionPerformed(ActionEvent e) 
		{
			_modMgr.exportBinary(SaFariPark.this);
		}
	};

	private StatefulTask _task;
	
	
	

	
	

	@Inject
	public SaFariPark(AppContext appCtx) throws IOException, XMLStreamException 
	{
		
		_appCtx = appCtx;
		setLayout(new BorderLayout());
		
		loadBuildProps();
		
		setTitle("SaFariPark " + getFullRevision());
		setSize(640, 480);
		setIconImage(Resources.getImage("appicon"));
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		
		
		_devMgr = new DeviceManager(appCtx.sfpMgr);
		_devMgr.addDeviceSelectedListener(this);
		
		_tabEdit = new EditPane(appCtx.ctxCache);
		_logOut = new ActiveLogArea();
		_monitor = new Monitor(appCtx);
		_status = new StatusBar();
		_modMgr = new ModuleManager(_saveBinary, appCtx);
		_devMgr.addDeviceSelectedListener(_modMgr);
		DollyModel dm = new DollyModel(appCtx.ctxCache);
		appCtx.sfpMgr.addSFPProviderListener(dm);
		_dp = new DollyPanel(appCtx.ddmiLdr, dm, this);
		makeToolbar();
		JSplitPane splitTop = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
		splitTop.setDividerLocation(300);
		
		JSplitPane splitDevAndClone = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
		splitDevAndClone.setTopComponent(_devMgr);
		splitDevAndClone.setBottomComponent(_dp);
		splitDevAndClone.setDividerLocation(300);
		splitTop.setLeftComponent(splitDevAndClone);
		
		JSplitPane splitEditAndTabs = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
		splitTop.setRightComponent(splitEditAndTabs);
		splitEditAndTabs.setTopComponent(_tabEdit);
		
		
		JTabbedPane tp = new JTabbedPane();
		tp.addTab("Monitor", _monitor);
		tp.addTab("Logging", _logOut);
		
		splitEditAndTabs.setBottomComponent(tp);
		splitEditAndTabs.setResizeWeight(0.9);
		
		splitTop.setResizeWeight(0.1);
		add(splitTop, BorderLayout.CENTER);
		add(_status, BorderLayout.SOUTH);
		setExtendedState(JFrame.MAXIMIZED_BOTH);
		addWindowListener(this);
		
		
		_timer = new Timer(1000, new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				if (_scanForNewDevices == 0) { 
					_appCtx.sfpMgr.update(true);
					_scanForNewDevices = SCAN_DEVICE_DELAY;
				} else {
					_appCtx.sfpMgr.update(false);
					_scanForNewDevices--;
				}
				
			}
		});
		_timer.setRepeats(true);
	}
	
	
	
	private void loadBuildProps() {
		buildProps = new Properties();
		try {
			InputStream is = getClass().getResourceAsStream("build.properties");
			if (is != null) 
			{
				buildProps.load(is);
				return;
			}
		} catch (IOException e) {
		}	
		buildProps = null;
	}



	private String getFullRevision() {
		
		if (buildProps == null) {
			return "(development)";
		}
		
		return buildProps.getProperty("git.tag") + "-" + buildProps.getProperty("git.revision");
	}



	private void makeToolbar() 
	{
		JToolBar tb = new JToolBar();
		tb.setFloatable(false);
		tb.add(_selectOverlays);
		tb.add(_saveBinary);
		
		GuiUtils.toolbarTextButtions(tb);
		add(tb, BorderLayout.NORTH);
	}
	
	
	private static class SaFariParkModule 
	{
		
		@Provides
		@Singleton
		OverlayManager ovlMgr() {
			OverlayManager ovlMgr = new OverlayManager(); 
			ovlMgr.scanDirectory(new File("overlays"));
			return ovlMgr;
		}
		
		@Provides
		@Singleton
		DDMILoader ddmiLoader(OverlayManager ovl) throws IOException, XMLStreamException {
			DDMILoader loader = new DDMILoader();
			loader.loadOverlays(ovl.getOverlays());
			return loader;
		}
		
		
	}
	
	private Runnable _taskRunner = new Runnable() {

		@Override
		public void run() 
		{
			if (_task.execute()) {
				_task = null;
				_status.setProgress(0, 100);
			} else {
				_status.setProgress(_task.getProgress(), 100);
				SwingUtilities.invokeLater(this);
			}
		}
		
	};
	
	public void executeTask(StatefulTask task)
	{
		if (_task != null) return;
		_task = task;
		SwingUtilities.invokeLater(_taskRunner);
	}
	
	public static void main(String[] args) 
	{
		System.out.printf("Starting SaFariPark on %s Java %s (%s bit) and %s (OS=%s, %s)\n",
				System.getProperty("java.vendor"),
				System.getProperty("java.version"),
				System.getProperty("sun.arch.data.model") ,
				System.getProperty("os.name"),
				System.getProperty("os.version"),
				System.getProperty("os.arch"));
		
		Locale.setDefault(Locale.US);
		
		try {
			UIManager.setLookAndFeel(
			            UIManager.getSystemLookAndFeelClassName());
		} catch (ClassNotFoundException e) {
		} catch (InstantiationException e) {
		} catch (IllegalAccessException e) {
		} catch (UnsupportedLookAndFeelException e) {
		}

		LogManager.getLogManager().getLogger("").setLevel(Level.INFO);
		
		Logger globalLogger = Logger.getLogger("");
		Handler[] handlers = globalLogger.getHandlers();
		for(Handler handler : handlers) {
		    globalLogger.removeHandler(handler);
		}
		
		
		if (args.length == 2 && args[0].equals("--setdpi")){
			
			
			if (args[1].equals("auto")) {
				setDpi(-1);
			} else {
				setDpi(Integer.parseInt(args[1]));
			}
		}
		
		/*
		SaFariPark sfpE;
		try {
			sfpE = new SaFariPark();
			sfpE.setVisible(true);
			sfpE.init();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (XMLStreamException e) {
			e.printStackTrace();
		}*/
		
		Feather feather = Feather.with(new SaFariParkModule());
		SaFariPark sfpE = feather.instance(SaFariPark.class);
		sfpE.setVisible(true);
		sfpE.init();
	}
				 
	
	public static void setDpi(int dpi) {
		
		
		if (dpi == -1) {
			dpi = Toolkit.getDefaultToolkit().getScreenResolution();
		}
		
		
		// no high dpi screen, exit.
		//if (dpi < 100) return;
		//dpi = 45;
		
		float scale = (float)dpi / 96f;
		
		UIDefaults d = UIManager.getLookAndFeel().getDefaults();
		
		
		
		for (Entry<Object, Object> x : d.entrySet() )
		{
			
			Object o = UIManager.get(x.getKey());
			if (o == null) continue;
			if ((o instanceof Font)) {
				Font f = Font.class.cast(o);
				
				UIManager.put(x.getKey(), f.deriveFont(f.getSize2D() * scale));
			}
			if (x.getKey().toString().equals("Tree.rowHeight")) {
				UIManager.put(x.getKey(), (int)( Integer.class.cast(o).intValue() * scale));
			}
		}
	}

	public void init() {
		
		SwingUtilities.invokeLater(new Runnable() 
		{
			
			@Override
			public void run() {
				_tabEdit.initEditor(_appCtx.ddmiLdr);
				_timer.start();
			}
		});

	}
	
	public void deinit() {
		_timer.stop();
	}

	
	@Override
	public void baySelected(SFPDevice dev, int bay) {
		try {
			_tabEdit.populate(dev, bay);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean canSelectBay(SFPDevice dev, int bay) {
		if (_tabEdit.getContext().someAreDirty()) {
			if (JOptionPane.showConfirmDialog(this, 
					"Editor contains changes which are not yet applied\n"
					+ "Press OK to discard the changes and edit the selected device\n"
					+ "Press CANCEL to return to keep editing the current device", 
					"Warning", JOptionPane.OK_CANCEL_OPTION,
					JOptionPane.WARNING_MESSAGE) == JOptionPane.CANCEL_OPTION) {
				return false;
			}
		}
		return true;
	}






	@Override
	public void windowOpened(WindowEvent e) {
	}



	@Override
	public void windowClosing(WindowEvent e) {
		deinit();
	}



	@Override
	public void windowClosed(WindowEvent e) {
	}



	@Override
	public void windowIconified(WindowEvent e) {
	}


	@Override
	public void windowDeiconified(WindowEvent e) {
	}

	@Override
	public void windowActivated(WindowEvent e) {
	}

	@Override
	public void windowDeactivated(WindowEvent e) {
	}



	private int _max = 0;
	
	@Override
	public synchronized void deferredTasksUpdated(int tasksQueued)
	{
		if (tasksQueued > _max) {
			_max = tasksQueued;
		}
		_status.setProgress(_max - tasksQueued, _max);
		//else _status.setDone();
	}

	


	
}
