package tim.prune.function;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.NumberFormat;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;

import tim.prune.App;
import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.data.DoubleRange;

/**
 * Class to allow the download of OSM data (using the XAPI)
 * for the area covered by the data
 */
public class DownloadOsmFunction extends GenericFunction implements Runnable
{
	private JDialog _dialog = null;
	private JLabel[] _latLonLabels = null;
	private JProgressBar _progressBar = null;
	private JButton _okButton = null;
	private JFileChooser _fileChooser = null;
	private File _selectedFile = null;
	private boolean _cancelled = false;
	/** Number formatter */
	private final NumberFormat FORMAT_TWO_DP = NumberFormat.getNumberInstance();


	/**
	 * Constructor
	 * @param inApp application object for callback
	 */
	public DownloadOsmFunction(App inApp)
	{
		super(inApp);
		FORMAT_TWO_DP.setMaximumFractionDigits(2);
		FORMAT_TWO_DP.setMinimumFractionDigits(2);
	}

	/** Get the name key */
	public String getNameKey() {
		return "function.downloadosm";
	}

	/**
	 * Begin the function
	 */
	public void begin()
	{
		// Make dialog window
		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();
			_fileChooser = new JFileChooser();
			_fileChooser.setSelectedFile(new File("data.osm"));
		}
		initDialog();
		_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(0, 10));
		dialogPanel.add(new JLabel(I18nManager.getText("dialog.downloadosm.desc")), BorderLayout.NORTH);
		// grid of labels to show lat/long extent
		JPanel gridPanel = new JPanel();
		gridPanel.setLayout(new GridLayout(3, 3));
		_latLonLabels = new JLabel[4];
		for (int i=0; i<4; i++) {
			_latLonLabels[i] = new JLabel("0");
		}
		int lNum = 0;
		for (int i=0; i<4; i++) {
			gridPanel.add(new JLabel(" "));
			gridPanel.add(_latLonLabels[lNum++]);
		}
		// layout main panel
		JPanel mainPanel = new JPanel();
		mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
		mainPanel.add(gridPanel);
		_progressBar = new JProgressBar();
		_progressBar.setIndeterminate(true);
		mainPanel.add(_progressBar);
		dialogPanel.add(mainPanel, BorderLayout.CENTER);
		// button panel at bottom
		JPanel buttonPanel = new JPanel();
		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
		_okButton = new JButton(I18nManager.getText("button.ok"));
		ActionListener okListener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				finish();
			}
		};
		_okButton.addActionListener(okListener);
		buttonPanel.add(_okButton);
		JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
		cancelButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				_cancelled = true;
				_dialog.dispose();
			}
		});
		buttonPanel.add(cancelButton);
		dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
		dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
		return dialogPanel;
	}

	/**
	 * Initialise the values of the labels in the dialog
	 */
	private void initDialog()
	{
		// Get range of data
		String[] lats = expandRange(_app.getTrackInfo().getTrack().getLatRange());
		String[] lons = expandRange(_app.getTrackInfo().getTrack().getLonRange());
		_latLonLabels[0].setText(lats[1]); // max lat
		_latLonLabels[1].setText(lons[0]); // min lon
		_latLonLabels[2].setText(lons[1]); // max lon
		_latLonLabels[3].setText(lats[0]); // min lat
		_okButton.setEnabled(true);
		_progressBar.setVisible(false);
		_cancelled = false;
	}

	/**
	 * Expand the given range to reasonable limits
	 * @param inRange range of lat/long values
	 * @return expanded range as pair of Strings
	 */
	private String[] expandRange(DoubleRange inRange)
	{
		double mid = (inRange.getMaximum() + inRange.getMinimum()) / 2.0;
		double range = inRange.getRange();
		double max = 0.0, min = 0.0;
		// Expand range to at least 0.02 degree
		if (range < 0.02)
		{
			min = mid - 0.01;
			max = mid + 0.01;
		}
		else {
			// expand by 10% in both directions
			min = mid - range * 0.55;
			max = mid + range * 0.55;
		}
		// Round min down to 0.01 degree
		int minCents = (int) (100 * min);
		// Round max upwards likewise
		int maxCents = (int) (100 * max + 1);
		final String[] answer = new String[] {FORMAT_TWO_DP.format(minCents/100.0),
			FORMAT_TWO_DP.format(maxCents/100.0)};
		return answer;
	}

	/**
	 * Finish the dialog when OK pressed
	 */
	private void finish()
	{
		if (!_okButton.isEnabled()) return;
		_selectedFile = selectOsmFile();
		if (_selectedFile != null)
		{
			// Show progress bar
			_okButton.setEnabled(false);
			_progressBar.setVisible(true);
			new Thread(this).start();
		}
		else
			_dialog.dispose();
	}

	/**
	 * Select a file to save the OSM data to
	 * @return selected file or null if cancelled
	 */
	private File selectOsmFile()
	{
		File saveFile = null;
		boolean chooseAgain = false;
		do
		{
			chooseAgain = false;
			if (_fileChooser.showSaveDialog(_dialog) == JFileChooser.APPROVE_OPTION)
			{
				// OK pressed and file chosen
				File file = _fileChooser.getSelectedFile();
				// Check if file exists and if necessary prompt for overwrite
				Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
				if (!file.exists() || JOptionPane.showOptionDialog(_dialog,
						I18nManager.getText("dialog.save.overwrite.text"),
						I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
						JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
					== JOptionPane.YES_OPTION)
				{
					// new file or overwrite confirmed
					saveFile = file;
				}
				else
				{
					// file exists and overwrite cancelled - select again
					chooseAgain = true;
				}
			}
		} while (chooseAgain);
		return saveFile;
	}


	/**
	 * Do the actual download - launched to run in another thread
	 */
	public void run()
	{
		final String url = "http://xapi.openstreetmap.org/api/0.6/map?bbox=" +
			_latLonLabels[1].getText() + "," + _latLonLabels[3].getText() + "," +
			_latLonLabels[2].getText() + "," + _latLonLabels[0].getText();

		byte[] buffer = new byte[1024];
		InputStream inStream = null;
		FileOutputStream outStream = null;
		int numBytesRead = 0;
		try
		{
			inStream = new URL(url).openStream();
			outStream = new FileOutputStream(_selectedFile);
			// Loop and copy bytes to file
			while ((numBytesRead = inStream.read(buffer)) > -1 && !_cancelled)
			{
				outStream.write(buffer, 0, numBytesRead);
			}
		}
		catch (MalformedURLException mue) {}
		catch (IOException ioe) {
			_app.showErrorMessageNoLookup(getNameKey(), ioe.getClass().getName() + " - " + ioe.getMessage());
		}
		// clean up streams
		finally {
			try {inStream.close();} catch (Exception e) {}
			try {outStream.close();} catch (Exception e) {}
		}
		// close dialog
		_dialog.dispose();
	}
}
