/*
 * Copyright 2008-2014, David Karnok 
 * The file is part of the Open Imperium Galactica project.
 * 
 * The code should be distributed under the LGPL license.
 * See http://www.gnu.org/licenses/lgpl.html for details.
 */

package hu.openig.tools;

import hu.openig.utils.XElement;

import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import javax.imageio.ImageIO;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSpinner;
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 javax.xml.stream.XMLStreamException;

/**
 * Display and edit the slots of ships.
 * @author akarnokd, 2014 szept. 1
 */
public class SlotEditor extends JFrame {
	/**
	 * 
	 */
	private static final String TECH_XML_FILE = "data/generic/campaign/main/tech.xml";
	/** */
	private static final long serialVersionUID = 8139560177720689047L;
	/** The raw tech XML. */
	XElement techXML;
	/** The items from the combobox. */
	List<XElement> techItems = new ArrayList<>();
	/** The items. */
	JComboBox<String> cbTech;
	/** Renders the equipment image. */
	EquipmentRender render;
	/** The currently selected slot. */
	String currentSlot;
	/** The current copy. */
	XElement clipBoard;
	/** Slot ID. */
	private JLabel txID;
	/** Slot X. */
	private JSpinner txX;
	/** Slot Y. */
	private JSpinner txY;
	/** Slot Width. */
	private JSpinner txW;
	/** Slot Height. */
	private JSpinner txH;
	/** Maximum items. */
	private JSpinner txMax;
	/** Default race. */
	private JTextField txRace;
	/** Skirmish race. */
	private JTextField txSkirmishRace;
	/** Tech level. */
	private JSpinner txLevel;
	/** Tech level. */
	private JSpinner txIndex;
	/** Items. */
	private JTextField txItems;
	/** Prerequisite. */
	private JTextField txRequires;
	/** Lab. */
	private JSpinner txCivil;
	/** Lab. */
	private JSpinner txMech;
	/** Lab. */
	private JSpinner txComp;
	/** Lab. */
	private JSpinner txAI;
	/** Lab. */
	private JSpinner txMil;
	/** Lab. */
	private JSpinner txResearch;
	/** Lab. */
	private JSpinner txProduction;
	/** Indicate that the main loading is in progress. */
	boolean mainLoad;
	/**
	 * Constructor, initializes the GUI.
	 */
	public SlotEditor() {
		super("Slot editor");
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		
		Container c = getContentPane();
		
		GroupLayout gl = new GroupLayout(c);
		c.setLayout(gl);
		gl.setAutoCreateContainerGaps(true);
		gl.setAutoCreateGaps(true);
		
		cbTech = new JComboBox<>();
		render = new EquipmentRender();
		
		
		JLabel lblRace = new JLabel("Race");
		JLabel lblSkirmishRace = new JLabel("SkirmishRace");
		JLabel lblLevel = new JLabel("Level");
		JLabel lblIndex = new JLabel("Index");
		JLabel lblRequires = new JLabel("Requires");
		
		JLabel lblID = new JLabel("Slot ID");
		JLabel lblX = new JLabel("Slot X");
		JLabel lblY = new JLabel("Slot Y");
		JLabel lblW = new JLabel("Slot Width");
		JLabel lblH = new JLabel("Slot Height");
		JLabel lblMax = new JLabel("Max");
		JLabel lblItems = new JLabel("Items");
		
		txRace = new JTextField();
		txSkirmishRace = new JTextField();
		txLevel = new JSpinner();
		txIndex = new JSpinner();
		txRequires = new JTextField();

		JLabel lblCivil = new JLabel("Civil");
		JLabel lblMech = new JLabel("Mech");
		JLabel lblComp = new JLabel("Comp");
		JLabel lblAI = new JLabel("AI");
		JLabel lblMil = new JLabel("Mil");
		
		txCivil = new JSpinner();
		txMech = new JSpinner();
		txComp = new JSpinner();
		txAI = new JSpinner();
		txMil = new JSpinner();
		
		JLabel lblResearch = new JLabel("Research cost");
		JLabel lblProduction = new JLabel("Production cost");
		
		txResearch = new JSpinner();
		txProduction = new JSpinner();
		
		txID = new JLabel();
		txX = new JSpinner();
		txY = new JSpinner();
		txW = new JSpinner();
		txH = new JSpinner();
		txMax = new JSpinner();
		txItems = new JTextField();
		
		JButton btnSave = new JButton("Save");
		JButton btnLoad = new JButton("Load");
		JButton btnCopy = new JButton(new ImageIcon(SlotEditor.class.getResource("/hu/openig/editors/res/Copy16.gif")));
		JButton btnPaste = new JButton(new ImageIcon(SlotEditor.class.getResource("/hu/openig/editors/res/Paste16.gif")));

		doLoad();
		
		gl.setHorizontalGroup(
			gl.createParallelGroup()
			.addGroup(
				gl.createSequentialGroup()
				.addComponent(cbTech)
				.addComponent(btnLoad)
				.addComponent(btnSave)
				.addGap(20)
				.addComponent(btnCopy)
				.addComponent(btnPaste)
			)
			.addGroup(
				gl.createSequentialGroup()
				.addGroup(
					gl.createParallelGroup()
					.addComponent(lblRace)
					.addComponent(lblSkirmishRace)
					.addComponent(lblRequires)
				)
				.addGroup(
					gl.createParallelGroup()
					.addComponent(txRace, 350, 350, 350)
					.addComponent(txSkirmishRace, 350, 350, 350)
					.addComponent(txRequires, 150, 150, 150)
				)
			)
			.addGroup(
				gl.createSequentialGroup()
				.addComponent(lblLevel)
				.addComponent(txLevel, 50, 50, 50)
				.addComponent(lblIndex)
				.addComponent(txIndex, 50, 50, 50)
			)
			.addGroup(
				gl.createSequentialGroup()
				.addComponent(lblCivil)
				.addComponent(txCivil, 50, 50, 50)
				.addComponent(lblMech)
				.addComponent(txMech, 50, 50, 50)
				.addComponent(lblComp)
				.addComponent(txComp, 50, 50, 50)
				.addComponent(lblAI)
				.addComponent(txAI, 50, 50, 50)
				.addComponent(lblMil)
				.addComponent(txMil, 50, 50, 50)
			)
			.addGroup(
				gl.createSequentialGroup()
				.addComponent(lblResearch)
				.addComponent(txResearch, 100, 100, 100)
				.addComponent(lblProduction)
				.addComponent(txProduction, 100, 100, 100)
			)
			.addGroup(
				gl.createSequentialGroup()
				.addComponent(render, 298, 298, 298)
				.addGroup(
					gl.createParallelGroup()
					.addComponent(lblID)
					.addComponent(lblX)
					.addComponent(lblY)
					.addComponent(lblW)
					.addComponent(lblH)
					.addComponent(lblMax)
					.addComponent(lblItems)
				)
				.addGroup(
					gl.createParallelGroup()
					.addComponent(txID, 200, 200, 200)
					.addComponent(txX, 50, 50, 50)
					.addComponent(txY, 50, 50, 50)
					.addComponent(txW, 50, 50, 50)
					.addComponent(txH, 50, 50, 50)
					.addComponent(txMax, 50, 50, 50)
					.addComponent(txItems, 350, 350, 350)
				)
			)
		);
		gl.setVerticalGroup(
			gl.createSequentialGroup()
			.addGroup(
				gl.createParallelGroup(Alignment.BASELINE)
				.addComponent(cbTech)
				.addComponent(btnLoad)
				.addComponent(btnSave)
				.addComponent(btnCopy)
				.addComponent(btnPaste)
			)
			.addGroup(
				gl.createParallelGroup(Alignment.BASELINE)
				.addComponent(lblRace)
				.addComponent(txRace)
			)
			.addGroup(
				gl.createParallelGroup(Alignment.BASELINE)
				.addComponent(lblSkirmishRace)
				.addComponent(txSkirmishRace)
			)
			.addGroup(
				gl.createParallelGroup(Alignment.BASELINE)
				.addComponent(lblLevel)
				.addComponent(txLevel)
				.addComponent(lblIndex)
				.addComponent(txIndex)
			)
			.addGroup(
				gl.createParallelGroup(Alignment.BASELINE)
				.addComponent(lblRequires)
				.addComponent(txRequires)
			)
			.addGroup(
				gl.createParallelGroup(Alignment.BASELINE)
				.addComponent(lblCivil)
				.addComponent(txCivil)
				.addComponent(lblMech)
				.addComponent(txMech)
				.addComponent(lblComp)
				.addComponent(txComp)
				.addComponent(lblAI)
				.addComponent(txAI)
				.addComponent(lblMil)
				.addComponent(txMil)
			)
			.addGroup(
				gl.createParallelGroup(Alignment.BASELINE)
				.addComponent(lblResearch)
				.addComponent(txResearch)
				.addComponent(lblProduction)
				.addComponent(txProduction)
			)
			.addGroup(
				gl.createParallelGroup()
				.addComponent(render, 128, 128, 128)
				.addGroup(
					gl.createSequentialGroup()
					.addGroup(
						gl.createParallelGroup(Alignment.BASELINE)
						.addComponent(lblID)
						.addComponent(txID)
					)
					.addGroup(
						gl.createParallelGroup(Alignment.BASELINE)
						.addComponent(lblX)
						.addComponent(txX)
					)
					.addGroup(
						gl.createParallelGroup(Alignment.BASELINE)
						.addComponent(lblY)
						.addComponent(txY)
					)
					.addGroup(
						gl.createParallelGroup(Alignment.BASELINE)
						.addComponent(lblW)
						.addComponent(txW)
					)
					.addGroup(
						gl.createParallelGroup(Alignment.BASELINE)
						.addComponent(lblH)
						.addComponent(txH)
					)
					.addGroup(
						gl.createParallelGroup(Alignment.BASELINE)
						.addComponent(lblMax)
						.addComponent(txMax)
					)
					.addGroup(
						gl.createParallelGroup(Alignment.BASELINE)
						.addComponent(lblItems)
						.addComponent(txItems)
					)
				)
			)
		);
		
		cbTech.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (mainLoad) {
					return;
				}
				int idx = cbTech.getSelectedIndex();
				if (idx >= 0) {
					currentSlot = null;
					render.current = techItems.get(idx);
					
					txRace.setText(render.current.get("race"));
					txSkirmishRace.setText(render.current.get("skirmish-race", ""));
					txLevel.setValue(render.current.getInt("level"));
					txIndex.setValue(render.current.getInt("index"));
					txRequires.setText(render.current.get("requires", ""));
					
					txCivil.setValue(render.current.getInt("civil", 0));
					txMech.setValue(render.current.getInt("mech", 0));
					txComp.setValue(render.current.getInt("comp", 0));
					txAI.setValue(render.current.getInt("ai", 0));
					txMil.setValue(render.current.getInt("mil", 0));
					
					txResearch.setValue(render.current.getInt("research-cost", 0));
					txProduction.setValue(render.current.getInt("production-cost", 0));
					
					render.repaint();
				}
				clearSlotText();
			}
		});
		btnSave.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					techXML.set("noNamespaceSchemaLocation", null);
					techXML.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
					techXML.set("xsi:noNamespaceSchemaLocation", "../../schemas/tech.xsd");
					techXML.save(TECH_XML_FILE);
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		});
		txX.addChangeListener(createChangeListener("x"));
		txY.addChangeListener(createChangeListener("y"));
		txW.addChangeListener(createChangeListener("width"));
		txH.addChangeListener(createChangeListener("height"));
		txMax.addChangeListener(createChangeListener("max"));
		txItems.getDocument().addDocumentListener(createTextChangeListener("items", txItems));
		
		txRace.getDocument().addDocumentListener(createTextChangeListener2("race", txRace, false));
		txSkirmishRace.getDocument().addDocumentListener(createTextChangeListener2("skirmish-race", txSkirmishRace, true));
		txLevel.addChangeListener(createChangeListener2("level", false));
		txIndex.addChangeListener(createChangeListener2("index", false));
		txRequires.getDocument().addDocumentListener(createTextChangeListener2("requires", txRequires, true));
		
		txCivil.addChangeListener(createChangeListener2("civil", true));
		txMech.addChangeListener(createChangeListener2("mech", true));
		txComp.addChangeListener(createChangeListener2("comp", true));
		txAI.addChangeListener(createChangeListener2("ai", true));
		txMil.addChangeListener(createChangeListener2("mil", true));

		txResearch.addChangeListener(createChangeListener2("research-cost", false));
		txProduction.addChangeListener(createChangeListener2("production-cost", false));

		MouseAdapter ma = new MouseAdapter() {
			boolean dragMode;
			int dragX;
			int dragY;
			@Override
			public void mousePressed(MouseEvent e) {
				if (render.current != null) {
					for (XElement xslot : render.current.childrenWithName("slot")) {
						String sid = xslot.get("id");
						int sx = xslot.getInt("x");
						int sy = xslot.getInt("y");
						int sw = xslot.getInt("width");
						int sh = xslot.getInt("height");
						
						if (e.getX() >= sx && e.getX() < sx + sw
								&& e.getY() >= sy && e.getY() < sy + sh
								) {
							currentSlot = sid;
							
							txID.setText(sid);
							txX.setValue(sx);
							txY.setValue(sy);
							txW.setValue(sw);
							txH.setValue(sh);
							txMax.setValue(xslot.getInt("max"));
							txItems.setText(xslot.get("items"));
							
							dragMode = e.getButton() == MouseEvent.BUTTON3;
							if (dragMode) {
								dragX = e.getX() - sx;
								dragY = e.getY() - sy;
							}
							render.repaint();
							return;
						}
					}
				}
				currentSlot = null;
				clearSlotText();
				render.repaint();
			}
			@Override
			public void mouseReleased(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON3) {
					dragMode = false;
				}
			}
			@Override
			public void mouseDragged(MouseEvent e) {
				if (dragMode) {
					if (render.current != null && currentSlot != null) {
						for (XElement xslot : render.current.childrenWithName("slot")) {
							String sid = xslot.get("id");
							if (Objects.equals(sid, currentSlot)) {
								int sx = e.getX() - dragX;
								int sy = e.getY() - dragY;
								xslot.set("x", sx);
								xslot.set("y", sy);
								txX.setValue(sx);
								txY.setValue(sy);
								render.repaint();
								break;
							}
						}
					}
				}
			}
		};
		render.addMouseListener(ma);
		render.addMouseMotionListener(ma);
		setResizable(false);
		
		btnLoad.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				doLoad();
				repaint();
			}
		});
		btnCopy.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (render.current != null) {
					clipBoard = render.current.copy();
				}
			}
		});
		btnPaste.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (render.current != null && clipBoard != null) {
					for (XElement xslot0 : clipBoard.childrenWithName("slot")) {
						String id0 = xslot0.get("id");
						for (XElement xslot1 : render.current.childrenWithName("slot")) {
							String id1 = xslot1.get("id");
							if (Objects.equals(id0, id1)) {
								xslot1.set("x", xslot0.get("x"));
								xslot1.set("y", xslot0.get("y"));
								xslot1.set("width", xslot0.get("width"));
								xslot1.set("height", xslot0.get("height"));
							}
						}
					}
					render.repaint();
				}			
			}
		});
	}
	/** Clears the slot text fields. */
	void clearSlotText() {
		txID.setText("");
		txX.setValue(0);
		txY.setValue(0);
		txW.setValue(0);
		txH.setValue(0);
		txMax.setValue(0);
		txItems.setText("");
	}
	/** Loads the tech file. */
	void doLoad() {
		mainLoad = true;
		try {
			techXML = XElement.parseXML(TECH_XML_FILE);
		} catch (XMLStreamException ex) {
			ex.printStackTrace();
		}

		render.current = null;
		currentSlot = null;
		techItems.clear();
		cbTech.removeAllItems();
		for (XElement xe : techXML.childrenWithName("item")) {
			if (xe.get("category", "").startsWith("SPACESHIP")) {
				techItems.add(xe);
				cbTech.addItem(xe.get("id"));
			}
		}
		cbTech.setSelectedIndex(-1);
		render.current = null;
		currentSlot = null;
		txRace.setText("");
		txSkirmishRace.setText("");
		txLevel.setValue(0);
		txRequires.setText("");
		txIndex.setValue(0);
		txResearch.setValue(0);
		txProduction.setValue(0);
		txCivil.setValue(0);
		txMech.setValue(0);
		txComp.setValue(0);
		txAI.setValue(0);
		txMil.setValue(0);
		mainLoad = false;
	}
	/**
	 * Creates a change listener which saves the value into the given field of the current tech slot.
	 * @param field the field attribute name
	 * @return the change listener
	 */
	private ChangeListener createChangeListener(final String field) {
		return new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				if (render.current != null && currentSlot != null) {
					for (XElement xslot : render.current.childrenWithName("slot")) {
						if (currentSlot.equals(xslot.get("id"))) {
							xslot.set(field, ((JSpinner)e.getSource()).getValue());
							render.repaint();
							break;
						}
					}					
				}
			}
		};
	}
	/**
	 * Creates a change listener which saves the value into the given field of the current tech slot.
	 * @param field the field attribute name
	 * @param tf the parent text field
	 * @return the change listener
	 */
	private DocumentListener createTextChangeListener(final String field, final JTextField tf) {
		return new DocumentListener() {
			@Override
			public void changedUpdate(DocumentEvent e) {
				stateChanged(e);
			}
			@Override
			public void insertUpdate(DocumentEvent e) {
				stateChanged(e);
			}
			@Override
			public void removeUpdate(DocumentEvent e) {
				stateChanged(e);
			}
			public void stateChanged(DocumentEvent e) {
				if (render.current != null && currentSlot != null) {
					for (XElement xslot : render.current.childrenWithName("slot")) {
						if (currentSlot.equals(xslot.get("id"))) {
							xslot.set(field, tf.getText());
							render.repaint();
							break;
						}
					}					
				}
			}
		};
	}
	/**
	 * Creates a change listener which saves the value into the given field of the current tech slot.
	 * @param field the field attribute name
	 * @param zeroIsNull store zero as a null value?
	 * @return the change listener
	 */
	private ChangeListener createChangeListener2(final String field, final boolean zeroIsNull) {
		return new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				if (render.current != null) {
					int value = (Integer)(((JSpinner)e.getSource()).getValue());
					if (value > 0 || !zeroIsNull) {
						render.current.set(field, value);
					} else {
						render.current.set(field, null);
					}
				}
			}
		};
	}
	/**
	 * Creates a change listener which saves the value into the given field of the current tech slot.
	 * @param field the field attribute name
	 * @param tf the parent text field
	 * @param emptyIsNull store a null if the text is empty?
	 * @return the change listener
	 */
	private DocumentListener createTextChangeListener2(final String field, final JTextField tf, final boolean emptyIsNull) {
		return new DocumentListener() {
			@Override
			public void changedUpdate(DocumentEvent e) {
				stateChanged(e);
			}
			@Override
			public void insertUpdate(DocumentEvent e) {
				stateChanged(e);
			}
			@Override
			public void removeUpdate(DocumentEvent e) {
				stateChanged(e);
			}
			public void stateChanged(DocumentEvent e) {
				if (render.current != null) {
					String text = tf.getText();
					render.current.set(field, text.isEmpty() && emptyIsNull ? null : text);
				}
			}
		};
	}
	/** Renders the technology with the slots. */
	final class EquipmentRender extends JComponent {
		/** */
		private static final long serialVersionUID = 4569061568803989171L;
		/** The current XML element. */
		XElement current;
		/** The image to be displayed. */
		BufferedImage img;
		/** The current image file. */
		String file;
		@Override
		public void paint(Graphics g) {
			if (current != null) {
				String imgFile = current.get("image");
				if (file == null || !file.equals(imgFile)) {
					file = imgFile;
					File input = new File("images/generic/" + imgFile + "_small.png");
					if (!input.canRead()) {
						input = new File("images/generic/" + imgFile + "_huge.png");
					}
					try {
						img = ImageIO.read(input);
					} catch (IOException ex) {
						System.out.println(input);
						ex.printStackTrace();
					}
				}
				if (img != null) {
					BufferedImage image = img;
					float fx = getWidth() * 1.0f / image.getWidth();
					float fy = getHeight() * 1.0f / image.getHeight();
					float f = Math.min(fx, fy);
					int dx = (int)((getWidth() - image.getWidth() * f) / 2);
					int dy = (int)((getHeight() - image.getHeight() * f) / 2);
					g.drawImage(image, dx, dy, (int)(image.getWidth() * f), (int)(image.getHeight() * f), null);
				}
				for (XElement xslot : current.childrenWithName("slot")) {
					String sid = xslot.get("id");
					int sx = xslot.getInt("x");
					int sy = xslot.getInt("y");
					int sw = xslot.getInt("width");
					int sh = xslot.getInt("height");
					if (Objects.equals(sid, currentSlot)) {
						g.setColor(Color.ORANGE);
					} else {
						g.setColor(Color.GREEN);
					}
					g.drawRect(sx, sy, sw, sh);
					g.drawRect(sx + 1, sy + 1, sw - 2, sh - 2);
				}
			} else {
				g.setColor(Color.WHITE);
				g.fillRect(0, 0, getWidth(), getHeight());
			}
		}
	}
	/**
	 * Main program, no arguments.
	 * @param args no arguments
	 */
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				SlotEditor frame = new SlotEditor();
				frame.pack();
				frame.setLocationRelativeTo(null);
				frame.setVisible(true);
			}
		});
	}
}