package tim.prune.function.edit;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableCellRenderer;

import tim.prune.App;
import tim.prune.I18nManager;
import tim.prune.config.Config;
import tim.prune.data.DataPoint;
import tim.prune.data.Field;
import tim.prune.data.FieldList;
import tim.prune.data.Track;
import tim.prune.data.Unit;

/**
 * Class to manage the display and editing of point data
 */
public class PointEditor
{
	private App _app = null;
	private JFrame _parentFrame = null;
	private JDialog _dialog = null;
	private JTable _table = null;
	private JLabel _fieldnameLabel = null;
	private JTextField _valueField = null;
	private JTextArea _valueArea = null;
	private JScrollPane _valueAreaPane = null;
	private Track _track = null;
	private DataPoint _point = null;
	private EditFieldsTableModel _model = null;
	private int _prevRowIndex = -1;


	/**
	 * Constructor
	 * @param inApp application object to inform of success
	 * @param inParentFrame parent frame
	 */
	public PointEditor(App inApp, JFrame inParentFrame)
	{
		_app = inApp;
		_parentFrame = inParentFrame;
	}


	/**
	 * Show the edit point dialog
	 * @param inTrack track object
	 * @param inPoint point to edit
	 */
	public void showDialog(Track inTrack, DataPoint inPoint)
	{
		_track = inTrack;
		_point = inPoint;
		_dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.pointedit.title"), true);
		_dialog.setLocationRelativeTo(_parentFrame);
		// Check field list
		FieldList fieldList = _track.getFieldList();
		int numFields = fieldList.getNumFields();
		// Create table model for point editor
		_model = new EditFieldsTableModel(numFields);
		for (int i=0; i<numFields; i++)
		{
			Field field = fieldList.getField(i);
			_model.addFieldInfo(field.getName(), _point.getFieldValue(field), i);
		}
		// Create Gui
		_dialog.getContentPane().add(makeDialogComponents());
		_dialog.pack();
		// Init right-hand side
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				_valueField.setVisible(false);
				_valueAreaPane.setVisible(false);
			}
		});
		_dialog.setVisible(true);
	}


	/**
	 * Make the dialog components
	 * @return the GUI components for the dialog
	 */
	private Component makeDialogComponents()
	{
		JPanel panel = new JPanel();
		panel.setLayout(new BorderLayout(20, 10));
		// Create GUI layout for point editor
		_table = new JTable(_model)
		{
			// Paint the changed fields orange
			public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
			{
				Component comp = super.prepareRenderer(renderer, row, column);
				boolean changed = ((EditFieldsTableModel) getModel()).getChanged(row);
				comp.setBackground(changed ? Color.orange : getBackground());
				return comp;
			}
		};
		_table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		_table.getSelectionModel().clearSelection();
		_table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e)
			{
				fieldSelected();
			}
		});
		_table.setPreferredScrollableViewportSize(new Dimension(_table.getWidth() * 2, _table.getRowHeight() * 6));
		JScrollPane tablePane = new JScrollPane(_table);
		tablePane.setPreferredSize(new Dimension(150, 100));

		// Label at top
		JLabel topLabel = new JLabel(I18nManager.getText("dialog.pointedit.intro"));
		topLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 3, 6));
		panel.add(topLabel, BorderLayout.NORTH);

		// listener for ok event
		ActionListener okListener = new ActionListener() {
			public void actionPerformed(ActionEvent e)
			{
				// update App with edit
				confirmEdit();
				_dialog.dispose();
			}
		};

		JPanel rightPanel = new JPanel();
		rightPanel.setLayout(new BorderLayout());
		JPanel rightiPanel = new JPanel();
		rightiPanel.setLayout(new BoxLayout(rightiPanel, BoxLayout.Y_AXIS));
		// Add GUI elements to rhs
		_fieldnameLabel = new JLabel(I18nManager.getText("dialog.pointedit.nofield"));
		rightiPanel.add(_fieldnameLabel);
		_valueField = new JTextField(11);
		// Add listener for enter button
		_valueField.addActionListener(okListener);
		rightiPanel.add(_valueField);
		rightPanel.add(rightiPanel, BorderLayout.NORTH);
		_valueArea = new JTextArea(5, 15);
		_valueArea.setLineWrap(true);
		_valueArea.setWrapStyleWord(true);
		_valueAreaPane = new JScrollPane(_valueArea);
		rightPanel.add(_valueAreaPane, BorderLayout.CENTER);

		// Put the table and the right-hand panel together in a grid
		JPanel mainPanel = new JPanel();
		mainPanel.setLayout(new GridLayout(0, 2, 10, 10));
		mainPanel.add(tablePane);
		mainPanel.add(rightPanel);
		panel.add(mainPanel, BorderLayout.CENTER);

		// Bottom panel for OK, cancel buttons
		JPanel lowerPanel = new JPanel();
		lowerPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
		JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
		cancelButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e)
			{
				_dialog.dispose();
			}
		});
		lowerPanel.add(cancelButton);
		JButton okButton = new JButton(I18nManager.getText("button.ok"));
		okButton.addActionListener(okListener);
		lowerPanel.add(okButton);
		panel.add(lowerPanel, BorderLayout.SOUTH);
		return panel;
	}


	/**
	 * When table selection changes, need to update model and go to the selected field
	 */
	private void fieldSelected()
	{
		int rowNum = _table.getSelectedRow();
		if (rowNum == _prevRowIndex) {return;} // selection hasn't changed
		// Check the current values
		if (_prevRowIndex >= 0)
		{
			Field prevField = _track.getFieldList().getField(_prevRowIndex);
			boolean littleField = prevField.isBuiltIn() && prevField != Field.DESCRIPTION;
			String newValue = littleField ? _valueField.getText() : _valueArea.getText();
			// Update the model from the current GUI values
			_model.updateValue(_prevRowIndex, newValue);
		}

		if (rowNum < 0)
		{
			_fieldnameLabel.setText("");
		}
		else
		{
			String currValue = _model.getValue(rowNum);
			Field  field     = _track.getFieldList().getField(rowNum);
			_fieldnameLabel.setText(makeFieldLabel(field, _point));
			_fieldnameLabel.setVisible(true);
			boolean littleField = field.isBuiltIn() && field != Field.DESCRIPTION;
			if (littleField) {
				_valueField.setText(currValue);
			}
			else {
				_valueArea.setText(currValue);
			}
			_valueField.setVisible(littleField);
			_valueAreaPane.setVisible(!littleField);
			if (littleField) {
				_valueField.requestFocus();
			}
			else {
				_valueArea.requestFocus();
			}
		}
		_prevRowIndex = rowNum;
	}

	/**
	 * @param inField field
	 * @param inPoint current point
	 * @return label string for above the entry field / area
	 */
	private static String makeFieldLabel(Field inField, DataPoint inPoint)
	{
		String label = I18nManager.getText("dialog.pointedit.table.field") + ": " + inField.getName();
		// Add units if the field is altitude / speed / vspeed
		if (inField == Field.ALTITUDE)
		{
			label += makeUnitsLabel(inPoint.hasAltitude() ? inPoint.getAltitude().getUnit() : Config.getUnitSet().getAltitudeUnit());
		}
		else if (inField == Field.SPEED)
		{
			label += makeUnitsLabel(inPoint.hasHSpeed() ? inPoint.getHSpeed().getUnit() : Config.getUnitSet().getSpeedUnit());
		}
		else if (inField == Field.VERTICAL_SPEED)
		{
			label += makeUnitsLabel(inPoint.hasVSpeed() ? inPoint.getVSpeed().getUnit() : Config.getUnitSet().getVerticalSpeedUnit());
		}
		return label;
	}

	/**
	 * @param inUnit units for altitude / speed
	 * @return addition to the field label to describe the units
	 */
	private static String makeUnitsLabel(Unit inUnit)
	{
		if (inUnit == null) return "";
		return " (" + I18nManager.getText(inUnit.getShortnameKey()) + ")";
	}

	/**
	 * Confirm the edit and inform the app
	 */
	private void confirmEdit()
	{
		// Apply the edits to the current field
		int rowNum = _table.getSelectedRow();
		if (rowNum >= 0)
		{
			Field currField = _track.getFieldList().getField(rowNum);
			boolean littleField = currField.isBuiltIn() && currField != Field.DESCRIPTION;
			String newValue = littleField ? _valueField.getText() : _valueArea.getText();
			_model.updateValue(_prevRowIndex, newValue);
		}

		// Package the modified fields into an object
		FieldList fieldList = _track.getFieldList();
		int numFields = fieldList.getNumFields();
		// Make lists for edit and undo, and add each changed field in turn
		FieldEditList editList = new FieldEditList();
		FieldEditList undoList = new FieldEditList();
		for (int i=0; i<numFields; i++)
		{
			if (_model.getChanged(i))
			{
				Field field = fieldList.getField(i);
				editList.addEdit(new FieldEdit(field, _model.getValue(i)));
				undoList.addEdit(new FieldEdit(field, _point.getFieldValue(field)));
			}
		}
		_app.completePointEdit(editList, undoList);
	}
}
