package tim.prune.function.gpsies;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import tim.prune.App;
import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.function.browser.BrowserLauncher;
import tim.prune.gui.GuiGridLayout;
import tim.prune.save.GpxExporter;

/**
 * Function to upload track information up to Gpsies.com
 */
public class UploadGpsiesFunction extends GenericFunction
{
	/** Dialog object */
	private JDialog _dialog = null;
	/** Edit box for user name */
	private JTextField _usernameField = null;
	/** Edit box for password */
	private JPasswordField _passwordField = null;
	/** Name of track */
	private JTextField _nameField = null;
	/** Description */
	private JTextArea _descField = null;
	/** Private checkbox */
	private JCheckBox _privateCheckbox = null;
	/** Activity checkboxes */
	private JCheckBox[] _activityCheckboxes = null;
	/** Writer object for GPX export */
	private OutputStreamWriter _writer = null;
	/** upload button */
	private JButton _uploadButton = null;

	/** URL to post form to */
	private static final String GPSIES_URL = "http://www.gpsies.com/upload.do";
	/** Keys for describing activities */
	private static final String[] ACTIVITY_KEYS = {"trekking", "walking", "jogging",
		"biking", "motorbiking", "snowshoe", "sailing", "skating"};

	/**
	 * Constructor
	 * @param inApp App object
	 */
	public UploadGpsiesFunction(App inApp) {
		super(inApp);
	}

	/**
	 * @return name key
	 */
	public String getNameKey() {
		return "function.uploadgpsies";
	}

	/**
	 * Begin the function
	 */
	public void begin()
	{
		// Initialise dialog, show empty list
		if (_dialog == null)
		{
			_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
			_dialog.setLocationRelativeTo(_parentFrame);
			_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
			_dialog.getContentPane().add(makeDialogComponents());
			_dialog.pack();
		}
		// Show dialog
		_dialog.setVisible(true);
	}


	/**
	 * Create dialog components
	 * @return Panel containing all gui elements in dialog
	 */
	private Component makeDialogComponents()
	{
		JPanel dialogPanel = new JPanel();
		dialogPanel.setLayout(new BorderLayout());

		JPanel gridPanel = new JPanel();
		GuiGridLayout grid = new GuiGridLayout(gridPanel);
		grid.add(new JLabel(I18nManager.getText("dialog.gpsies.username")));
		_usernameField = new JTextField(15);
		grid.add(_usernameField);
		grid.add(new JLabel(I18nManager.getText("dialog.gpsies.password")));
		_passwordField = new JPasswordField(15);
		grid.add(_passwordField);
		// Track name and description
		grid.add(new JLabel(I18nManager.getText("dialog.gpsies.column.name")));
		_nameField = new JTextField(15);
		grid.add(_nameField);
		grid.add(new JLabel(I18nManager.getText("dialog.gpsies.description")));
		_descField = new JTextArea(5, 15);
		_descField.setLineWrap(true);
		_descField.setWrapStyleWord(true);
		grid.add(new JScrollPane(_descField));
		// Listener on all these text fields to enable/disable the ok button
		KeyAdapter keyListener = new KeyAdapter() {
			/** Key released */
			public void keyReleased(KeyEvent inE) {
				enableOK();
				if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {
					_dialog.dispose();
				}
			}
		};
		_usernameField.addKeyListener(keyListener);
		_passwordField.addKeyListener(keyListener);
		_nameField.addKeyListener(keyListener);
		// Listen for tabs on description field, to change focus not enter tabs
		_descField.addKeyListener(new KeyAdapter() {
			/** Key pressed */
			public void keyPressed(KeyEvent inE) {
				if (inE.getKeyCode() == KeyEvent.VK_TAB) {
					inE.consume();
					if (inE.isShiftDown()) {
						_nameField.requestFocusInWindow();
					}
					else {
						_privateCheckbox.requestFocusInWindow();
					}
				}
			}
		});
		// Listen for Ctrl-backspace on password field (delete contents)
		_passwordField.addKeyListener(new KeyAdapter() {
			/** Key released */
			public void keyReleased(KeyEvent inE) {
				if (inE.isControlDown() && (inE.getKeyCode() == KeyEvent.VK_BACK_SPACE
					|| inE.getKeyCode() == KeyEvent.VK_DELETE)) {
					_passwordField.setText("");
				}
			}
		});
		// Checkbox for private / public
		grid.add(new JLabel(I18nManager.getText("dialog.gpsies.keepprivate")));
		_privateCheckbox = new JCheckBox();
		_privateCheckbox.setSelected(true);
		grid.add(_privateCheckbox);

		// panel for activity type checkboxes
		JPanel activityPanel = new JPanel();
		activityPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
		ChangeListener checkListener = new ChangeListener() {
			public void stateChanged(ChangeEvent arg0) {
				enableOK();
			}
		};
		// Why not a simple grid layout here?
		GuiGridLayout actGrid = new GuiGridLayout(activityPanel, new double[] {1.0, 1.0}, new boolean[] {false, false});
		final int numActivities = ACTIVITY_KEYS.length;
		_activityCheckboxes = new JCheckBox[numActivities];
		for (int i=0; i<numActivities; i++)
		{
			_activityCheckboxes[i] = new JCheckBox(I18nManager.getText("dialog.gpsies.activity." + ACTIVITY_KEYS[i]));
			_activityCheckboxes[i].addChangeListener(checkListener);
			actGrid.add(_activityCheckboxes[i]);
		}
		grid.add(new JLabel(I18nManager.getText("dialog.gpsies.activities")));
		grid.add(activityPanel);
		JPanel midPanel = new JPanel();
		midPanel.setLayout(new BoxLayout(midPanel, BoxLayout.Y_AXIS));
		midPanel.add(gridPanel);
		dialogPanel.add(midPanel, BorderLayout.CENTER);

		// button panel at bottom
		JPanel buttonPanel = new JPanel();
		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
		_uploadButton = new JButton(I18nManager.getText("button.upload"));
		_uploadButton.setEnabled(false);
		_uploadButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				startUpload();
			}
		});
		buttonPanel.add(_uploadButton);
		JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
		cancelButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				_dialog.dispose();
			}
		});
		buttonPanel.add(cancelButton);
		dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
		dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
		return dialogPanel;
	}


	/**
	 * Check the inputs and enable or disable the upload button
	 */
	private void enableOK()
	{
		// Check for lengths of input fields - only username, password and filename are required
		boolean ok = (_usernameField.getText().length() > 0 && _nameField.getText().length() > 0);
		if (ok) {
			// also check password field
			char[] pass = _passwordField.getPassword();
			ok = pass.length > 0;
			for (int i=0; i<pass.length; i++) {pass[i] = '0';} // recommended by javadoc
			if (ok) {
				ok = false;
				for (int i=0; i<_activityCheckboxes.length; i++) {
					ok = ok || _activityCheckboxes[i].isSelected();
				}
			}
		}
		_uploadButton.setEnabled(ok);
	}


	/**
	 * Start the upload process (require separate thread?)
	 */
	private void startUpload()
	{
		BufferedReader reader = null;
		try
		{
			FormPoster poster = new FormPoster(new URL(GPSIES_URL));
			poster.setParameter("device", "Prune");
			poster.setParameter("username", _usernameField.getText());
			poster.setParameter("password", new String(_passwordField.getPassword()));
			boolean hasActivity = false;
			for (int i=0; i<ACTIVITY_KEYS.length; i++)
			{
				if (_activityCheckboxes[i].isSelected()) {
					hasActivity = true;
					poster.setParameter("trackTypes", ACTIVITY_KEYS[i]);
				}
			}
			if (!hasActivity) {poster.setParameter("trackTypes", "walking");} // default if none given
			poster.setParameter("filename", _nameField.getText());
			poster.setParameter("fileDescription", _descField.getText());
			poster.setParameter("startpointCountry", "DE");
			poster.setParameter("endpointCountry", "DE"); // both those will be corrected by gpsies
			poster.setParameter("status", (_privateCheckbox.isSelected()?"3":"1"));
			poster.setParameter("submit", "speichern"); // required
			// Use Pipes to connect the GpxExporter's output with the FormPoster's input
			PipedInputStream iStream = new PipedInputStream();
			PipedOutputStream oStream = new PipedOutputStream(iStream);
			_writer = new OutputStreamWriter(oStream);
			new Thread(new Runnable() {
				public void run() {
					boolean[] saveFlags = {true, true, true, true, false, true}; // export everything
					try {
						GpxExporter.exportData(_writer, _app.getTrackInfo(), _nameField.getText(), null, saveFlags, null);
					} catch (IOException e) {}
					finally {
						try {_writer.close();} catch (IOException e) {}
					}
				}
			}).start();
			poster.setParameter("formFile", "filename.gpx", iStream);

			BufferedInputStream answer = new BufferedInputStream(poster.post());
			int response = poster.getResponseCode();
			reader = new BufferedReader(new InputStreamReader(answer));
			String line = reader.readLine();
			// Try to extract gpsies page url from the returned message
			String pageUrl = null;
			if (response == 200 && line.substring(0, 2).toUpperCase().equals("OK"))
			{
				final int bracketPos = line.indexOf('[');
				if (bracketPos > 0 && line.endsWith("]")) {
					pageUrl = line.substring(bracketPos + 1, line.length()-1);
				}
			}
			if (pageUrl != null)
			{
				// OK received and managed to extract a Url from the return message.
				int userChoice = JOptionPane.showConfirmDialog(_app.getFrame(),
					I18nManager.getText("dialog.gpsies.confirmopenpage"),
					I18nManager.getText(getNameKey()), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
				if (userChoice == JOptionPane.OK_OPTION) {
					BrowserLauncher.launchBrowser(pageUrl);
				}
			}
			else {
				_app.showErrorMessageNoLookup(getNameKey(), I18nManager.getText("error.gpsies.uploadnotok")
					+ ": " + line);
			}
		}
		catch (MalformedURLException e) {}
		catch (IOException ioe) {
			_app.showErrorMessageNoLookup(getNameKey(), I18nManager.getText("error.gpsies.uploadfailed") + ": "
				+ ioe.getClass().getName() + " : " + ioe.getMessage());
		}
		finally {
			try {if (reader != null) reader.close();} catch (IOException e) {}
		}
		_dialog.dispose();
	}
}
