package j3deditor.bin.editor;

import j3deditor.bin.InnerFrame;

import j3deditor.bin.editor.tools.NewGeometryDialog;

import j3deditor.bin.hierarchy.HierarchyNode;

import j3deditor.bin.util.ProgressIndicator;

import j3deditor.bin.hierarchy.util.HierarchyEvent;
import j3deditor.bin.hierarchy.util.HierarchyEventListener;

import j3deditor.bin.hierarchy.J3DeScene;

import j3deditor.bin.hierarchy.J3DeShape3D;
import j3deditor.bin.hierarchy.util.Copyable;
import j3deditor.bin.hierarchy.util.Cuttable;
import j3deditor.bin.hierarchy.util.Deletable;
import j3deditor.bin.hierarchy.util.ObjImporter;
import j3deditor.bin.hierarchy.util.Pasteable;
import j3deditor.bin.hierarchy.util.ProjectLoader;
import j3deditor.bin.hierarchy.util.ProjectSaver;

import j3deditor.bin.hierarchy.util.SGFExporter;
import j3deditor.bin.hierarchy.util.SGSExporter;

import j3deditor.bin.util.Utilities;

import java.awt.FileDialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.SwingWorker;


/**
 * <code>JInternalFrame</code> for scene-editor.
 *
 * @author Risto Seene
 */
public class EditorFrame extends InnerFrame{


	/** panel inside this internal frame */
	private EditorPanel editor;

	/**root node of the scene this editor manipulates */
	private J3DeScene scene;

	/** listens changes in hierarchy made with menu items of this frame */
	private HierarchyEventListener listener;


	/** <code>JMenuItem</code> for saving the scene */
	private JMenuItem save;

	/** <code>JMenuItem</code> for saving the scene */
	private JMenuItem saveas;

	private JMenu imp;

	private JMenu exp;

	/** <code>JMenuItem</code> for exporting the scene with <code>SceneGraphStreamWriter</code> */
	private JMenuItem sgsw;

	/** <code>JMenuItem</code> for exporting the scene with <code>SceneGraphFileWriter</code> */
	private JMenuItem sgfw;

	/** <code>JMenuItem</code> for importing objects from OBJ file */
	private JMenuItem obj;

	/** <code>JMenuItem</code> for closing this window */
	private JMenuItem close;


	private JMenu hmenu;

	/** <code>JMenuItem</code> for updating the hierarchy tree */
	private JMenuItem update;

	/** <code>JMenuItem</code> for cutting selected nodes */
	private JMenuItem cut;

	/** <code>JMenuItem</code> for copying selected nodes */
	private JMenuItem copy;

	/** <code>JMenuItem</code> for pasting selected nodes */
	private JMenuItem paste;

	/** <code>JMenuItem</code> for deleting selected nodes */
	private JMenuItem delete;


	private JMenu gmenu;

	/** <code>JCheckBoxMenuItem</code> for toggling between Scene Editor and Geometry Editor */
	private JCheckBoxMenuItem edit;

	private JMenuItem newgeom;

	private JCheckBoxMenuItem texture;


	/**
	 * Creates an instance of <code>EditorFrame</code>
	 * for a new scene.
	 *
	 * @param parent
	 *	<code>JDesktopPane</code> this frame will be attached to
	 */
	public EditorFrame(JDesktopPane parent){
		this.scene = (J3DeScene)(Utilities.hierarchy.addNewScene());

		initMenu();

		setFrameIcon(Utilities.image.getImageIcon("icons/translate.png"));
		setSize(3 * parent.getWidth() / 4, 3 * parent.getHeight() / 4);
		initContent();
		setTitle(scene.toString());

		showWindow(parent);
	}


	/**
	 * Initializes menubar and all its items
	 */
	private void initMenu(){
		JMenuBar menubar = new JMenuBar();

		JMenu filemenu = new JMenu(Utilities.translator.translate(getClass(), "filemenu"));
		save = new JMenuItem(Utilities.translator.translate(getClass(), "save"));
		saveas = new JMenuItem(Utilities.translator.translate(getClass(), "saveas"));
		exp = new JMenu(Utilities.translator.translate(getClass(), "export"));
		sgsw = new JMenuItem(Utilities.translator.translate(getClass(), "sgswriter"));
		sgfw = new JMenuItem(Utilities.translator.translate(getClass(), "sgfwriter"));
		imp = new JMenu(Utilities.translator.translate(getClass(), "import"));
		obj = new JMenuItem(Utilities.translator.translate(getClass(), "obj"));
		close = new JMenuItem(Utilities.translator.translate(getClass(), "close"));

		hmenu = new JMenu(Utilities.translator.translate(getClass(), "hmenu"));
		update = new JMenuItem(Utilities.translator.translate(getClass(), "update"));
		cut = new JMenuItem(Utilities.translator.translate(getClass(), "cut"));
		copy = new JMenuItem(Utilities.translator.translate(getClass(), "copy"));
		paste = new JMenuItem(Utilities.translator.translate(getClass(), "paste"));
		delete = new JMenuItem(Utilities.translator.translate(getClass(), "delete"));

		gmenu = new JMenu(Utilities.translator.translate(getClass(), "gmenu"));
		edit = new JCheckBoxMenuItem(Utilities.translator.translate(getClass(), "edit"), false);
		newgeom = new JMenuItem(Utilities.translator.translate(this.getClass(), "newgeom"));
		texture = new JCheckBoxMenuItem(Utilities.translator.translate(this.getClass(), "texture"), false);


		setJMenuBar(menubar);

		menubar.add(filemenu);
		filemenu.add(save);
		filemenu.add(saveas);
		filemenu.addSeparator();
		filemenu.add(imp);
		imp.add(obj);
		filemenu.add(exp);
		exp.add(sgsw);
		exp.add(sgfw);
		filemenu.addSeparator();
		filemenu.add(close);

		save.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuFileSave();
							return null;
						}
					};
					worker.execute();
				}
			});
		saveas.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuFileSaveAs();
							return null;
						}
					};
					worker.execute();
				}
			});
		sgsw.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuFileExport(1);
							return null;
						}
					};
					worker.execute();
				}
			});
		sgfw.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuFileExport(2);
							return null;
						}
					};
					worker.execute();
				}
			});
		obj.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuFileImport();
							return null;
						}
					};
					worker.execute();
				}
			});
		close.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							doDefaultCloseAction();
							return null;
						}
					};
					worker.execute();
				}
			});


		menubar.add(hmenu);
		hmenu.add(update);
		hmenu.addSeparator();
		hmenu.add(cut);
		hmenu.add(copy);
		hmenu.add(paste);
		hmenu.add(delete);

		update.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuHierarchyUpdate();
							return null;
						}
					};
					worker.execute();
				}
			});
		cut.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuHierarchyCut();
							return null;
						}
					};
					worker.execute();
				}
			});
		copy.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuHierarchyCopy();
							return null;
						}
					};
					worker.execute();
				}
			});
		paste.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuHierarchyPaste();
							return null;
						}
					};
					worker.execute();
				}
			});
		delete.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuHierarchyDelete();
							return null;
						}
					};
					worker.execute();
				}
			});


		menubar.add(gmenu);
		gmenu.add(edit);
		gmenu.add(newgeom);
		gmenu.addSeparator();
		gmenu.add(texture);
		texture.setEnabled(false);

		edit.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuGeometryEdit();
							return null;
						}
					};
					String info = Utilities.translator.translate(this.getClass(), "initializing");
					worker.addPropertyChangeListener(new ProgressIndicator(info));
					worker.execute();
				}
			});
		newgeom.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuNewGeometry();
							return null;
						}
					};
					worker.execute();
				}
			});
		texture.addActionListener(new ActionListener(){

				public void actionPerformed(ActionEvent e){
					SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){

						public Void doInBackground(){
							menuTextureEdit();
							return null;
						}
					};
					//String info = Utilities.translator.translate(this.getClass(), "initializing");
					//worker.addPropertyChangeListener(new ProgressIndicator(info));
					worker.execute();
				}
			});

	}


	/**
	 * Initializes the content of this frame
	 */
	private void initContent(){
		editor = new EditorPanel(scene, this);
		listener = editor;
		add(editor);
	}


	/**
	 * Sent to this frame to notify that some hierarchy nodes
	 * are selected/deselected to update <code>Hierarchy</code> menu.
	 */
	public void selectionChanged(){
		HierarchyNode[] nodes = scene.getSelectedObjects();
		boolean cutable = false;
		boolean copyable = false;
		boolean pasteable = false;
		boolean deletable = false;
		boolean geom = false;

		try{
			for(int i = 0; i < nodes.length; i++){
				if(nodes[i] instanceof Cuttable){
					cutable = true;
				}
				if(nodes[i] instanceof Copyable){
					copyable = true;
				}
				if(nodes[i] instanceof Pasteable && scene.getCopiedObjects().length != 0){
					pasteable = true;
				}
				if(nodes[i] instanceof Deletable){
					deletable = true;
				}
			}

			if(nodes.length == 1 && nodes[0] instanceof J3DeShape3D){
				geom = true;
			}
		} catch(Throwable t){
		}

		cut.setEnabled(cutable);
		copy.setEnabled(copyable);
		paste.setEnabled(pasteable);
		delete.setEnabled(deletable);
		gmenu.setEnabled(geom);
	}


	/**
	 * Overrides the closing operation of this frame.
	 */
	public void doDefaultCloseAction(){
		String question = Utilities.translator.translate(this.getClass(), "saveQ");
		String scene = this.scene.toString();
		int option = Utilities.out.throwQuestion(question + " " + scene + "?");
		if(option == -1 || option == 2){
			return;
		}
		else if(option == 0){
			this.menuFileSave();
		}
		this.scene.deleteScene();
		super.doDefaultCloseAction();
	}


	private void menuFileSave(){
		if(scene.getFile() != null){
			String info = Utilities.translator.translate(this.getClass(), "savingProj");
			ProjectSaver saver = new ProjectSaver(scene);
			saver.addPropertyChangeListener(new ProgressIndicator(info));
			saver.saveProject(scene.getFile());
		}
		else {
			menuFileSaveAs();
		}
	}

	private void menuFileSaveAs(){
		String saveProj = Utilities.translator.translate(this.getClass(), "saveProj");
		FileDialog fd = new FileDialog(new JFrame(), saveProj, FileDialog.SAVE);
		fd.setFile(scene.toString() + ".j3de");
		fd.setVisible(true);
		String file = fd.getDirectory() + fd.getFile();

		if(fd.getFile() != null){
			if(!file.endsWith(".j3de")){
				file = file + ".j3de";
			}
			String info = Utilities.translator.translate(this.getClass(), "savingProj");
			ProjectSaver saver = new ProjectSaver(scene);
			saver.addPropertyChangeListener(new ProgressIndicator(info));
			saver.saveProject(file);
			scene.setFile(file);

			String name = file.substring(file.lastIndexOf(java.io.File.separator) + 1, file.lastIndexOf("."));
			scene.setName(name);
			this.setTitle(name);
			editor.updateHierarchy();
		}
	}

	private void menuFileImport(){
		String in = Utilities.translator.translate(this.getClass(), "import");
		FileDialog fd = new FileDialog(new JFrame(), in, FileDialog.LOAD);
		fd.setVisible(true);
		String source = fd.getDirectory() + fd.getFile();

		if(fd.getFile() != null){
			String info = Utilities.translator.translate(this.getClass(), "importOBJ");
			ObjImporter loader = new ObjImporter(scene);
			loader.addHierarchyListener(editor);
			loader.addPropertyChangeListener(new ProgressIndicator(info));
			loader.loadFile(source);
		}
	}

	private void menuFileExport(int type){
		String out = Utilities.translator.translate(this.getClass(), "export");
		FileDialog fd = new FileDialog(new JFrame(), out, FileDialog.SAVE);
		fd.setFile(scene.toString());
		fd.setVisible(true);
		String file = fd.getDirectory() + fd.getFile();

		String info = Utilities.translator.translate(this.getClass(), "exportSG");
		if(fd.getFile() != null && type == 1){
			SGSExporter exporter = new SGSExporter(scene);
			exporter.addPropertyChangeListener(new ProgressIndicator(info));
			exporter.exportFile(file);
		}
		else if(fd.getFile() != null && type == 2){
			SGFExporter exporter = new SGFExporter(scene);
			exporter.addPropertyChangeListener(new ProgressIndicator(info));
			exporter.exportFile(file);
		}
	}


	private void menuHierarchyUpdate(){
		editor.updateHierarchy();
	}

	private void menuHierarchyCut(){
		scene.cutObjects();
		listener.hierarchyEventOccured(new HierarchyEvent(this, HierarchyEvent.OBJECT_REMOVAL));
	}

	private void menuHierarchyCopy(){
		scene.copyObjects();
		listener.hierarchyEventOccured(new HierarchyEvent(this, HierarchyEvent.OBJECT_ADDITION));
	}

	private void menuHierarchyPaste(){
		scene.pasteContent(scene.getCopiedObjects());
		listener.hierarchyEventOccured(new HierarchyEvent(this, HierarchyEvent.OBJECT_ADDITION));
	}

	private void menuHierarchyDelete(){
		scene.deleteObjects(scene.getSelectedObjects());
		listener.hierarchyEventOccured(new HierarchyEvent(this, HierarchyEvent.OBJECT_REMOVAL));
	}


	private void menuGeometryEdit(){
		try{
			if(edit.getState()){
				boolean hasTex = editor.openGeometryEditor();
				this.imp.setEnabled(false);
				this.exp.setEnabled(false);
				this.hmenu.setEnabled(false);
				this.newgeom.setEnabled(false);
				this.texture.setEnabled(hasTex);
			}
			else {
				editor.openSceneEditor();
				this.imp.setEnabled(true);
				this.exp.setEnabled(true);
				this.hmenu.setEnabled(true);
				this.newgeom.setEnabled(true);
				this.texture.setSelected(false);
				this.texture.setEnabled(false);
			}
		} catch(Throwable t){
			Utilities.out.throwError(t);
		}
	}

	private void menuNewGeometry(){
		try{
			HierarchyNode[] objects = this.scene.getSelectedObjects();
			if(objects != null && objects.length == 1 && objects[0] instanceof J3DeShape3D){
				J3DeShape3D object = (J3DeShape3D)objects[0];
				new NewGeometryDialog(object);
			}
		} catch(Throwable t){
			Utilities.out.throwError(t);
		}
	}

	private void menuTextureEdit(){
		if(editor.getGeometryEditor() != null){
			if(texture.getState()){
				editor.getGeometryEditor().openTextureEditor();
			}
			else {
				editor.getGeometryEditor().closeTextureEditor();
			}
		}
	}


	/**
	 * Opens the specified scene.
	 * 
	 * @param filePath
	 * 	file of the scene that will be opened
	 */
	public void openScene(String filePath){
		String info = Utilities.translator.translate(this.getClass(), "loading");
		ProjectLoader loader = new ProjectLoader(this.scene);
		loader.addHierarchyListener(this.listener);
		loader.addPropertyChangeListener(new ProgressIndicator(info));
		loader.loadProject(filePath);
	}

	/**
	 * Must be called in the process of disposing this frame.
	 */
	@Override
	public void cleanUp(){
		this.editor.cleanUp();
		this.editor = null;
		this.scene = null;
		super.cleanUp();
	}
}
