package lpv.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import lpv.simulation.DefaultSimulation;
import lpv.simulation.GeometryInjectionStep;
import lpv.simulation.LightInjectionStep;
import lpv.simulation.SimulationStep;
import lpv.simulation.SimulationThread;
import lpv.simulation.injector.Injector;
import lpv.simulation.injector.WallInjector;
import lpv.simulation.propagator.AndreasKirschPropagator;
import lpv.simulation.propagator.BenjaminThautPropagator;
import lpv.simulation.propagator.CrytekSiggraphPropagator;
import lpv.simulation.propagator.ExperimentalPropagator;
import lpv.simulation.propagator.LeeSalzmanPropagator;
import lpv.simulation.propagator.NvidiaPropagator;
import lpv.simulation.propagator.Propagator;
import lpv.simulation.propagator.RafalWrobelPropagator;
import lpv.simulation.propagator.SSLPVPropagator;
import lpv.simulator.Simulator;

public class ControlPanel extends JPanel {
	
	
	private static final int INITIAL_SCALE_VALUE = 50;
	private static final float DEFAULT_PROPAGATION_FACTOR = 4.0f;
	private static final int DEFAULT_ITERATIONS = 16;
	private static final int DEFAULT_SLOW_DELAY = 100;

	private Simulator simulator;
	
	private JTextField propagationFactorField;
	private JButton injectButton;
	private JButton propagateRightButton;
	private JButton clearAllButton;
	private JButton runButton;
	private JButton runSlowMotionButton;
	private JList injectorList;
	private JButton addInjectorButton;
	private JButton removeInjectorButton;

	private DefaultListModel injectorListModel;
	private JTextField iterationsField;
	private JTextField slowDelayField;
	private JComboBox propagatorComboBox;
	private JTextField propagatorUrlField;
	private JSlider scaleSlider;
	private ScaleManager scale;
	private JLabel scaleLabel;
	private JCheckBox autoScaleCheckBox;
	private DefaultListModel blockerListModel;
	private JList blockerList;
	private JButton addBlockerButton;
	private JButton removeBlockerButton;
	private JSlider levelSlider;
	private JButton downsampleButton;
	protected ErrorDialog errorDialog;
	
	
	public ControlPanel(final JFrame owner, final Simulator simulator, final ScaleManager scale) {
		this.simulator = simulator; 
		this.scale = scale;
		this.errorDialog = null;
		
		setLayout(new BorderLayout());
		
		// Lists. 
		{
			JPanel listPanel = new JPanel();
			listPanel.setLayout(new GridLayout(2, 1));
			
			// Injector list.
			{
				JPanel injectorPanel = new JPanel();
				injectorPanel.setLayout(new BorderLayout());
				
				injectorPanel.add(new JLabel("Injectors:"), BorderLayout.NORTH);
				
				injectorList = new JList(injectorListModel = new DefaultListModel());
				injectorListModel.addElement(new WallInjector.Left(0, 1.0f));
				JScrollPane jsp = new JScrollPane(injectorList);
				injectorPanel.add(jsp, BorderLayout.CENTER);
				
				JPanel buttonPanel = new JPanel();
				buttonPanel.setLayout(new GridLayout(1, 2));
				buttonPanel.add(addInjectorButton = new JButton("Add"));
				buttonPanel.add(removeInjectorButton = new JButton("Remove"));
				injectorPanel.add(buttonPanel, BorderLayout.SOUTH);
				
				
				addInjectorButton.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent arg0) {
						InjectorDialog injectorDialog = new InjectorDialog(owner);
						injectorDialog.setVisible(true);
						if(injectorDialog.getInjector() != null) {
							injectorListModel.addElement(injectorDialog.getInjector());
						}
					}});
				
				removeInjectorButton.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent arg0) {
						int[] selected = injectorList.getSelectedIndices();
						for(int i = selected.length-1; i >= 0; i--) {
							injectorListModel.remove(selected[i]);
						}
					}});
				
				listPanel.add(injectorPanel);
			}
			
			// Blocker list.
			{
				JPanel blockerPanel = new JPanel();
				blockerPanel.setLayout(new BorderLayout());
				
				blockerPanel.add(new JLabel("Blockers:"), BorderLayout.NORTH);
				
				blockerList = new JList(blockerListModel = new DefaultListModel());
				JScrollPane jsp = new JScrollPane(blockerList);
				blockerPanel.add(jsp, BorderLayout.CENTER);
				
				JPanel buttonPanel = new JPanel();
				buttonPanel.setLayout(new GridLayout(1, 2));
				buttonPanel.add(addBlockerButton = new JButton("Add"));
				buttonPanel.add(removeBlockerButton = new JButton("Remove"));
				blockerPanel.add(buttonPanel, BorderLayout.SOUTH);
				
				
				addBlockerButton.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent arg0) {
						InjectorDialog blockerDialog = new InjectorDialog(owner);
						blockerDialog.setVisible(true);
						if(blockerDialog.getInjector() != null) {
							blockerListModel.addElement(blockerDialog.getInjector());
						}
					}});
				
				removeBlockerButton.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent arg0) {
						int[] selected = blockerList.getSelectedIndices();
						for(int i = selected.length-1; i >= 0; i--) {
							blockerListModel.remove(selected[i]);
						}
					}});
				
				listPanel.add(blockerPanel);
			}
			
			add(listPanel, BorderLayout.WEST);
		}
		
		// Simulator control.
		{
			JPanel simulatorPanel = new JPanel();
			simulatorPanel.setLayout(new GridLayout(13, 2, 5, 5));
			
			simulatorPanel.add(new JLabel("Octree level:"));
			simulatorPanel.add(levelSlider = new JSlider(JSlider.HORIZONTAL, 0, simulator.getAccumulation().getLevels()-1, 0));
			levelSlider.setMajorTickSpacing(1);
			levelSlider.setMinorTickSpacing(1);
			levelSlider.setSnapToTicks(true);
			levelSlider.setPaintLabels(true);
			levelSlider.addChangeListener(new ChangeListener() {
				@Override
				public void stateChanged(ChangeEvent e) {
					int val = levelSlider.getValue();
					simulator.setLevel(val);
				}});
			levelSlider.setValue(0);
			
			
			
			simulatorPanel.add(new JLabel("Rendering amplifier: (2^x)"));
			simulatorPanel.add(scaleSlider = new JSlider(JSlider.HORIZONTAL, -200, 200, 0));
			
			simulatorPanel.add(autoScaleCheckBox = new JCheckBox("Automatic scale", true));
			simulatorPanel.add(scaleLabel = new JLabel(""));
			
			scaleSlider.setMajorTickSpacing(50);
			scaleSlider.setMinorTickSpacing(10);
			scaleSlider.setPaintLabels(true);
			Hashtable<Integer, Component> labels = new Hashtable<Integer, Component>();
			labels.put(-200, new JLabel("Small"));
			labels.put(200, new JLabel("Large"));
			scaleSlider.setLabelTable(labels);
			scaleSlider.addChangeListener(new ChangeListener() {
				@Override
				public void stateChanged(ChangeEvent e) {
					int val = scaleSlider.getValue();
					float scaleValue = (float)Math.pow(2.0f, val / 10.0f);
					scale.setScale(scaleValue);
					scaleLabel.setText("2^" + val / 10.0f + " = " + scaleValue);
				}});
			scaleSlider.setValue(INITIAL_SCALE_VALUE);
			
			simulatorPanel.add(new JLabel("Propagator algorithm:"));
			simulatorPanel.add(propagatorComboBox = new JComboBox(PropagatorType.values()));
			propagatorComboBox.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					propagatorUrlField.setText(((PropagatorType)propagatorComboBox.getSelectedItem()).getUrl());
					propagationFactorField.setEnabled(((PropagatorType)propagatorComboBox.getSelectedItem()).supportsFactor());
				}});
			
			simulatorPanel.add(new JLabel("Propagator url:"));
			simulatorPanel.add(propagatorUrlField = new JTextField(PropagatorType.values()[0].getUrl(), 15));
			propagatorUrlField.setEditable(false);
			
			simulatorPanel.add(new JLabel("Propagation factor:"));
			simulatorPanel.add(propagationFactorField = new JTextField(String.valueOf(DEFAULT_PROPAGATION_FACTOR)));
			propagationFactorField.getDocument().addDocumentListener(new FloatValidator(propagationFactorField));
			
			simulatorPanel.add(injectButton = new JButton("Inject"));
			injectButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					new GeometryInjectionStep(getGeometryInjectors()).run(simulator);
					new LightInjectionStep(getLightInjectors()).run(simulator);
				}});
			
			
			simulatorPanel.add(propagateRightButton = new JButton("Propagate"));
			propagateRightButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					simulator.propagate(getPropagator());
					simulator.flip();
				}});
			
			simulatorPanel.add(clearAllButton = new JButton("Clear"));
			clearAllButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					simulator.clear();
				}});
			
			simulatorPanel.add(downsampleButton = new JButton("Downsample"));
			downsampleButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					simulator.downsample();
				}});

			
			JButton errorSampleButton;
			simulatorPanel.add(errorSampleButton = new JButton("Sample errors"));
			errorSampleButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					simulator.sampleErrors();
				}});
			
			JButton errorPlotButton;
			simulatorPanel.add(errorPlotButton = new JButton("Plot errors"));
			errorPlotButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					if(errorDialog == null) {
						errorDialog = new ErrorDialog(simulator.getErrors());
					}
					if(errorDialog.isVisible()) {
						SwingUtilities.invokeLater(new Runnable() {
							@Override
							public void run() {
								errorDialog.requestFocus();
							}
						});
					}
					errorDialog.setVisible(true);
				}});
			
			simulatorPanel.add(new JLabel());
			simulatorPanel.add(new JLabel());
			
			simulatorPanel.add(new JLabel("Iterations:"));
			simulatorPanel.add(iterationsField = new JTextField(String.valueOf(DEFAULT_ITERATIONS)));
			iterationsField.getDocument().addDocumentListener(new IntegerValidator(iterationsField));
			
			simulatorPanel.add(new JLabel("Slow delay (ms):"));
			simulatorPanel.add(slowDelayField = new JTextField(String.valueOf(DEFAULT_SLOW_DELAY)));
			slowDelayField.getDocument().addDocumentListener(new IntegerValidator(slowDelayField));
			
			simulatorPanel.add(runButton = new JButton("Run"));
			runButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					DefaultSimulation simulation = new DefaultSimulation(getIterations());
					simulation.addLightInjectors(getLightInjectors());
					simulation.addGeometryInjectors(getGeometryInjectors());
					simulation.setPropagator(getPropagator());
					for(SimulationStep step : simulation) {
						step.run(simulator);
					}
				}});
			
			simulatorPanel.add(runSlowMotionButton = new JButton("Run Slow"));
			runSlowMotionButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					setUIEnabled(false);
					DefaultSimulation simulation = new DefaultSimulation(getIterations());
					simulation.addLightInjectors(getLightInjectors());
					simulation.addGeometryInjectors(getGeometryInjectors());
					simulation.setPropagator(getPropagator());
					SimulationThread simulationThread = new SimulationThread(simulator, simulation, getSlowDelay());
					simulationThread.addCompletionHandler(new Runnable() {
						@Override
						public void run() {
							SwingUtilities.invokeLater(new Runnable() {
								@Override
								public void run() {
									setUIEnabled(true);
								}});
						}});
					simulationThread.start();
				}});
			
			JPanel tmp = new JPanel();
			tmp.setLayout(new FlowLayout(FlowLayout.LEFT));
			tmp.add(simulatorPanel);
			add(tmp, BorderLayout.CENTER);
		}
	}
	
	private void setUIEnabled(boolean enabled) {
		injectButton.setEnabled(enabled);
		propagateRightButton.setEnabled(enabled);
		clearAllButton.setEnabled(enabled);
		runButton.setEnabled(enabled);
		runSlowMotionButton.setEnabled(enabled);
		
		propagationFactorField.setEnabled(enabled);
		
		injectorList.setEnabled(enabled);
		addInjectorButton.setEnabled(enabled);
		removeInjectorButton.setEnabled(enabled);
		
		iterationsField.setEnabled(enabled);
		slowDelayField.setEnabled(enabled);
		
		propagatorComboBox.setEnabled(enabled);
	}
	
	protected List<Injector> getGeometryInjectors() {
		List<Injector> injectors = new ArrayList<Injector>();
		for(Object o : blockerListModel.toArray()) {
			injectors.add((Injector)o);
		}
		return injectors;
	}
	
	private List<Injector> getLightInjectors() {
		List<Injector> injectors = new ArrayList<Injector>();
		for(Object o : injectorListModel.toArray()) {
			injectors.add((Injector)o);
		}
		return injectors;
	}
	
	private int getIterations() {
		int iterations = Integer.parseInt(iterationsField.getText());
		if(iterations < 0) {
			throw new NumberFormatException("Negative values are not supported for string: " + iterationsField.getText());
		}
		return iterations;
	}
	
	private int getSlowDelay() {
		int delay = Integer.parseInt(slowDelayField.getText());
		if(delay < 0) {
			throw new NumberFormatException("Negative values are not supported for string: " + slowDelayField.getText());
		}
		return delay;
	}
	
	private Propagator getPropagator() {
		PropagatorType type = (PropagatorType)propagatorComboBox.getSelectedItem();
		float factor = Float.parseFloat(propagationFactorField.getText());
		switch(type) {
		case nvidia_nocopy:
			return new NvidiaPropagator(factor, false);
		case nvidia_copy:
			return new NvidiaPropagator(factor, true);
		case experimental:
			return new ExperimentalPropagator(factor);
		case lee_salzman:
			return new LeeSalzmanPropagator(factor);
		case benjamin_thaut:
			return new BenjaminThautPropagator(factor);
		case rafal_wrobel:
			return new RafalWrobelPropagator();
		case sslpv:
			return new SSLPVPropagator(factor);
		case andreas_kirsch:
			return new AndreasKirschPropagator();
		case crytek_siggraph:
			return new CrytekSiggraphPropagator(factor);
		default:
			return null;
		}
	}

	public void proposeScale(float scale) {
		if(autoScaleCheckBox.isSelected()) {
			float scaleLog2 = (float) (Math.log(scale) / Math.log(2.0));
			int scaleValue = (int) (scaleLog2 * 10.0f);
			scaleSlider.setValue(scaleValue);
		}
	}
	
}
