package org.newdawn.slick.tools.peditor;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;

import org.lwjgl.LWJGLException;
import org.newdawn.slick.CanvasGameContainer;
import org.newdawn.slick.InputListener;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.particles.ConfigurableEmitter;
import org.newdawn.slick.particles.ParticleIO;
import org.newdawn.slick.particles.ParticleSystem;
import org.newdawn.slick.util.InputAdapter;
import org.newdawn.slick.util.Log;

/**
 * The bootstrap and main frame for the particle editor Pedigree.
 *
 * @author kevin
 */
public class ParticleEditor extends JFrame {
	/** The canvas displaying the particles */
	private ParticleGame game;
	/** Create a new system */
	private JMenuItem newSystem = new JMenuItem("New System");
	/** Load a complete particle system */
	private JMenuItem load = new JMenuItem("Load System");
	/** Save a complete particle system */
	private JMenuItem save = new JMenuItem("Save System");
	/** Load a single particle emitter */
	private JMenuItem imp = new JMenuItem("Import Emitter");
	/** Clone a single particle emitter */
	private JMenuItem clone = new JMenuItem("Clone Emitter");
	/** Save a single particle emitter */
	private JMenuItem exp = new JMenuItem("Export Emitter");
	/** Toggle the HUD  */
	private JMenuItem hud = new JMenuItem("Toggle Overlay");
	/** Toggle the HUD  */
	private JMenuItem loadBackground = new JMenuItem("Load Background Image");
	/** Toggle the HUD  */
	private JMenuItem clearBackground = new JMenuItem("Clear Background Image");
	/** Toggle the graphice editor  */
	private JMenuItem whiskas = new JMenuItem("Show/Hide Graph Editor");
	/** Exit the editor */
	private JMenuItem quit = new JMenuItem("Exit");

	/** The visual list of emitters */
	private EmitterList emitters;
	/** The controls for the initial emission settings */
	private EmissionControls emissionControls;
	/** The positional controls for spawnng particles */
	private PositionControls positionControls;
	/** The global settings for the emitter */
	private SettingsPanel settingsPanel;
	/** The color controls for particles */
	private ColorPanel colorPanel;
	/** The limiting controls for particles */
	private LimitPanel limitPanel;
	/** The whiskas panel */
	private WhiskasPanel whiskasPanel;
	
	/** Control for the type of particle system blending */
	private JCheckBox additive = new JCheckBox("Additive Blending");
	/** Control for the type of particle point usage */
	private JCheckBox pointsEnabled = new JCheckBox("Use Points");
	/** The currently selected particle emitter */
	private ConfigurableEmitter selected;
	/** Chooser used to load/save/import/export */
	private JFileChooser chooser = new JFileChooser(new File("."));
	/** Reset the particle counts on the canvas */
	private JButton reset = new JButton("Reset Max");
	/** Play or Pause the current rendering */
	private JButton pause = new JButton("Play/Pause");
	/** The slider defining the movement of the system */
	private JSlider systemMove = new JSlider(-100,100,0);
	
	/** The graph editor frame **/
	private JFrame graphEditorFrame;
	/** The filter in use */
	private FileFilter xmlFileFilter;
	
	/**
	 * Create a new editor
	 * 
	 * @throws LWJGLException Indicates a failure to create an OpenGL context
	 * @throws SlickException 
	 */
	public ParticleEditor() throws LWJGLException, SlickException {
		super("Pedigree - Whiskas flavoured");

		xmlFileFilter = new FileFilter() {
			public boolean accept(File f) {
				if (f.isDirectory()) {
					return true;
				}
				
				if (f.getName().endsWith(".xml")) {
					return true;
				}
				
				return false;
			}

			public String getDescription() {
				return "XML Files";
			}
		};
		chooser.setFileFilter(xmlFileFilter);
		
//		try {
//			InputStream in = ParticleEditor.class.getClassLoader().getResourceAsStream("org/newdawn/slick/tools/peditor/data/icon.gif");
//			
//			setIconImage(ImageIO.read(in));
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
		
		emitters = new EmitterList(this);
		emissionControls = new EmissionControls();
		positionControls = new PositionControls();
		settingsPanel = new SettingsPanel(emitters);
		colorPanel = new ColorPanel();
		limitPanel = new LimitPanel(emitters);
		whiskasPanel= new WhiskasPanel( emitters, colorPanel, emissionControls );
		
		JPopupMenu.setDefaultLightWeightPopupEnabled(false);
		
		JMenu file = new JMenu("File");
		file.add(newSystem);
		file.addSeparator();
		file.add(load);
		file.add(save);
		file.addSeparator();
		file.add(imp);
		file.add(clone);
		file.add(exp);
		file.addSeparator();
		file.add(hud);
		file.add(whiskas);
		file.addSeparator();
		file.add(loadBackground);
		file.add(clearBackground);
		file.addSeparator();
		file.add(quit);

		loadBackground.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				loadBackground();
			}
		});
		clearBackground.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				clearBackground();
			}
		});
		
		newSystem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				createNewSystem();
			}
		});
		hud.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				game.setHud(!game.isHudOn());
			}
		});
		load.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				loadSystem();
			}
		});
		save.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				saveSystem();
			}
		});
		clone.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				cloneEmitter();
			}
		});
		exp.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				exportEmitter();
			}
		});
		imp.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				importEmitter();
			}
		});
		quit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});
		whiskas.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				graphEditorFrame.setVisible(!graphEditorFrame.isVisible());
			}
		});
		
		JMenuBar bar = new JMenuBar();
		bar.add(file);
		setJMenuBar(bar);
		
		game = new ParticleGame(this);
		final CanvasGameContainer container = new CanvasGameContainer(game);
		container.getContainer().setAlwaysRender(true);
		container.setSize(500,600);
		JPanel controls = new JPanel();
		controls.setLayout(null);
		emitters.setBounds(0,0,300,150);
		emitters.setBorder(BorderFactory.createTitledBorder("Emitters"));
		controls.add(emitters);
		JTabbedPane tabs = new JTabbedPane();
		tabs.setBounds(0,150,300,350);
		controls.add(tabs);
		
		tabs.add("Settings", settingsPanel);
		tabs.add("Emission", emissionControls);
		tabs.add("Position", positionControls);
		tabs.add("Rendering", colorPanel);
		tabs.add("Limit", limitPanel);
		tabs.add("Whiskas", whiskasPanel);
		
		JPanel panel = new JPanel();
		panel.setLayout(null);
		container.setBounds(0,0,500,600);
		controls.setBounds(500,20,300,575);
		reset.setBounds(90,500,90,25);
		controls.add(reset);
		systemMove.setBounds(180,500,120,25);
		controls.add(systemMove);
		pause.setBounds(0,500,90,25);
		controls.add(pause);
		additive.setBounds(500,0,150,25);
		panel.add(additive);
		pointsEnabled.setBounds(650,0,150,25);
		panel.add(pointsEnabled);
		panel.add(container);
		panel.add(controls);

		systemMove.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				game.setSystemMove(systemMove.getValue(),false);
			}
		});
		
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		additive.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				updateBlendMode();
			}
		});
		pointsEnabled.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				game.getSystem().setUsePoints(pointsEnabled.isSelected());
			}
		});
		reset.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				game.resetCounts();
			}
		});
		pause.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				game.setPaused(!game.isPaused());
			}
		});
		
		ConfigurableEmitter test = new ConfigurableEmitter("Default");
		emitters.add(test);
		game.addEmitter(test);
		
		additive.setSelected(true);
		
		setContentPane(panel);
		setSize(800,600);
		setResizable(false);
		setVisible(true);

		InputListener listener = new InputAdapter() {
			public void mousePressed(int x, int y, int button) {
				if (button != 0) {
					positionControls.setPosition(0,0);
				}
				systemMove.setValue(0);
				game.setSystemMove(0,true);
			}
			
			public void mouseMoved(int x, int y, int nx, int ny) {
				if (container.getContainer().getInput().isMouseButtonDown(0)) {
					int xp = nx - 250;
					int yp = ny - 300;
					positionControls.setPosition(xp,yp);
					systemMove.setValue(0);
					game.setSystemMove(0,true);
				}
			}
		};
		game.setListener(listener);
		
		// init graph window
		initGraphEditorWindow();

		emitters.setSelected(0);
		
		try {
			container.start();
		} catch (SlickException e1) {
			Log.error(e1);
		}
	}
	
	/**
	 * Load a background image to display behind the particle system
	 */
	private void loadBackground() {
		JFileChooser chooser = new JFileChooser(".");
		chooser.setDialogTitle("Open");
		int resp = chooser.showOpenDialog(this);
		if (resp == JFileChooser.APPROVE_OPTION) {
			game.setBackgroundImage(chooser.getSelectedFile());
		}
	}
	
	/** 
	 * Clear the background image in use
	 */
	private void clearBackground() {
		game.setBackgroundImage(null);
	}
	
	/**
	 * init the graph editor window
	 */
	private void initGraphEditorWindow()
	{
	    // create the window
	    GraphEditorWindow editor = new GraphEditorWindow();

	    whiskasPanel.setEditor(editor);

	    graphEditorFrame= new JFrame("Whiskas Gradient Editor");
	    graphEditorFrame.getContentPane().add(editor);
	    graphEditorFrame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
	    graphEditorFrame.pack();
	    graphEditorFrame.setSize(600, 300);
	    graphEditorFrame.setLocation(this.getX(), this.getY()+this.getHeight());
	    graphEditorFrame.setVisible(true);
	    
//		try {
//			InputStream in = ParticleEditor.class.getClassLoader().getResourceAsStream("org/newdawn/slick/tools/peditor/data/icon.gif");
//			
//			//graphEditorFrame.setIconImage(ImageIO.read(in));
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
	}
	
	/**
	 * Set the movement of the system
	 * 
	 * @param move The movement of the system
	 */
	public void setSystemMove(int move) {
		systemMove.setValue(move);
	}
	
	/**
	 * Import an emitter XML file
	 */
	public void importEmitter() {
		chooser.setDialogTitle("Open");
		int resp = chooser.showOpenDialog(this);
		if (resp == JFileChooser.APPROVE_OPTION) {
			File file = chooser.getSelectedFile();
			File path = file.getParentFile();
			
			try {
				final ConfigurableEmitter emitter = ParticleIO.loadEmitter(file);
				
				
				if (emitter.getImageName() != null) {
					File possible = new File(path, emitter.getImageName());
					
					if (possible.exists()) {
						emitter.setImageName(possible.getAbsolutePath());
					} else {
						chooser.setDialogTitle("Locate the image: "+emitter.getImageName());
						resp = chooser.showOpenDialog(this);
						FileFilter filter = new FileFilter() {
							public boolean accept(File f) {
								if (f.isDirectory()) {
									return true;
								}
								
								return (f.getName().equals(emitter.getImageName()));
							}
	
							public String getDescription() {
								return emitter.getImageName();
							}
						};
						chooser.addChoosableFileFilter(filter);
						if (resp == JFileChooser.APPROVE_OPTION) {
							File image = chooser.getSelectedFile();
							emitter.setImageName(image.getAbsolutePath());
							path = image.getParentFile();
						}
						chooser.resetChoosableFileFilters();
						chooser.addChoosableFileFilter(xmlFileFilter);
					}
				}
				
				addEmitter(emitter);
				emitters.setSelected(emitter);
			} catch (IOException e) {
				Log.error(e);
				JOptionPane.showMessageDialog(this, e.getMessage());
			}
		}
	}

	/**
	 * Clone the selected emitter
	 */
	public void cloneEmitter() {
		if (selected == null) {
			return;
		}
		
		try {
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			ParticleIO.saveEmitter(bout, selected);
			ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
			ConfigurableEmitter emitter = ParticleIO.loadEmitter(bin);
			emitter.name = emitter.name + "_clone";
			
			addEmitter(emitter);
			emitters.setSelected(emitter);
		} catch (IOException e) {
			Log.error(e);
			JOptionPane.showMessageDialog(this, e.getMessage());
		}
	}
	
	/**
	 * Export an emitter XML file
	 */
	public void exportEmitter() {
		if (selected == null) {
			return;
		}

		chooser.setDialogTitle("Save");
		int resp = chooser.showSaveDialog(this);
		if (resp == JFileChooser.APPROVE_OPTION) {
			File file = chooser.getSelectedFile();
			if (!file.getName().endsWith(".xml")) {
				file = new File(file.getAbsolutePath()+".xml");
			}
			
			try {
				ParticleIO.saveEmitter(file, selected);
			} catch (IOException e) {
				Log.error(e);
				JOptionPane.showMessageDialog(this, e.getMessage());
			}
		}
	}

	/**
	 * Create a completely new particle system
	 */
	public void createNewSystem() {
		game.clearSystem(additive.isSelected());
		pointsEnabled.setSelected(false);
		emitters.clear();
	}
	
	/**
	 * Load a complete particle system XML description
	 */
	public void loadSystem() {
		chooser.setDialogTitle("Open");
		int resp = chooser.showOpenDialog(this);
		if (resp == JFileChooser.APPROVE_OPTION) {
			File file = chooser.getSelectedFile();
			File path = file.getParentFile();
			
			try {
				ParticleSystem system = ParticleIO.loadConfiguredSystem(file);
				game.setSystem(system);
				emitters.clear();
				
				for (int i=0;i<system.getEmitterCount();i++) {
					final ConfigurableEmitter emitter = (ConfigurableEmitter) system.getEmitter(i);
					
					if (emitter.getImageName() != null) {
						File possible = new File(path, emitter.getImageName());
						if (possible.exists()) {
							emitter.setImageName(possible.getAbsolutePath());
						} else {
							chooser.setDialogTitle("Locate the image: "+emitter.getImageName());
							FileFilter filter = new FileFilter() {
								public boolean accept(File f) {
									if (f.isDirectory()) {
										return true;
									}
									
									return (f.getName().equals(emitter.getImageName()));
								}
	
								public String getDescription() {
									return emitter.getImageName();
								}
							};
							
							chooser.addChoosableFileFilter(filter);
							resp = chooser.showOpenDialog(this);
							if (resp == JFileChooser.APPROVE_OPTION) {
								File image = chooser.getSelectedFile();
								emitter.setImageName(image.getAbsolutePath());
								path = image.getParentFile();
							}
							chooser.setDialogTitle("Open");
							chooser.resetChoosableFileFilters();
							chooser.addChoosableFileFilter(xmlFileFilter);
						}
					}
					
					emitters.add(emitter);
				}
				additive.setSelected(system.getBlendingMode() == ParticleSystem.BLEND_ADDITIVE);
				pointsEnabled.setSelected(system.usePoints());
				
				emitters.setSelected(0);
			} catch (IOException e) {
				Log.error(e);
				JOptionPane.showMessageDialog(this, e.getMessage());
			}
		}
	}

	/**
	 * Save a complete particle system XML description
	 */
	public void saveSystem() {
		int resp = chooser.showSaveDialog(this);
		if (resp == JFileChooser.APPROVE_OPTION) {
			File file = chooser.getSelectedFile();
			if (!file.getName().endsWith(".xml")) {
				file = new File(file.getAbsolutePath()+".xml");
			}
			
			try {
				ParticleIO.saveConfiguredSystem(file, game.getSystem());
			} catch (IOException e) {
				Log.error(e);
				JOptionPane.showMessageDialog(this, e.getMessage());
			}
		}
	}
	
	/**
	 * Add a new emitter to the editor 
	 * 
	 * @param emitter The emitter to add
	 */
	public void addEmitter(ConfigurableEmitter emitter) {
		emitters.add(emitter);
		game.addEmitter(emitter);
	}
	
	/**
	 * Remove a particle emitter from the editor
	 * 
	 * @param emitter The emitter to be removed
	 */
	public void removeEmitter(ConfigurableEmitter emitter) {
		emitters.remove(emitter);
		game.removeEmitter(emitter);
	}
	
	/**
	 * Set the currently selected and edited particle emitter
	 * 
	 * @param emitter The emitter that should be selected or null for none
	 */
	public void setCurrentEmitter(ConfigurableEmitter emitter) {
		this.selected = emitter;
		
		if (emitter == null) {
			emissionControls.setEnabled(false);
			settingsPanel.setEnabled(false);
			positionControls.setEnabled(false);
			colorPanel.setEnabled(false);
			limitPanel.setEnabled(false);
			whiskasPanel.setEnabled(false);
		} else {
			emissionControls.setEnabled(true);
			settingsPanel.setEnabled(true);
			positionControls.setEnabled(true);
			colorPanel.setEnabled(true);
			limitPanel.setEnabled(true);
			whiskasPanel.setEnabled(true);
			
			emissionControls.setTarget(emitter);
			settingsPanel.setTarget(emitter);
			positionControls.setTarget(emitter);
			settingsPanel.setTarget(emitter);
			colorPanel.setTarget(emitter);
			limitPanel.setTarget(emitter);
			whiskasPanel.setTarget(emitter);
		}
	}
	
	/**
	 * Change the visual indicator for the current particle system 
	 * blend mode
	 */
	public void updateBlendMode() {
		if (additive.isSelected()) {
			game.getSystem().setBlendingMode(ParticleSystem.BLEND_ADDITIVE);
 		} else {
			game.getSystem().setBlendingMode(ParticleSystem.BLEND_COMBINE);
 		}
	}
	
	/**
	 * Entry point in the editor
	 * 
	 * @param argv The arguments passed on the command line
	 */
	public static void main(String[] argv) {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		
			new ParticleEditor();
		} catch (Exception e) {
			Log.error(e);
		}
	}
}
