package com.mindoo.ec09.legacy.worldwind;

import gov.nasa.worldwind.Model;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.WorldWindow;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.WorldWindowGLCanvas;
import gov.nasa.worldwind.examples.ClickAndGoSelectListener;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.WorldMapLayer;
import gov.nasa.worldwind.util.StatusBar;
import gov.nasa.worldwind.view.FlyToOrbitViewStateIterator;
import gov.nasa.worldwind.view.OrbitView;
import gov.nasa.worldwind.view.ScheduledOrbitViewStateIterator;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.CompoundBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.mindoo.ec09.framework.views.ViewData;
import com.mindoo.ec09.legacy.AbstractView;

public class WorldWindView extends AbstractView {
	public static final String ACTION_ZOOM_IN="Zoom in";
	public static final String ACTION_ZOOM_OUT="Zoom out";

	private WorldWindowGLCanvas m_wwCanvas;
	private StatusBar m_statusBar;
	private JPanel m_panel;

	public WorldWindView() {
	}
	
	/**
	 * This view understands xml returned by google-maps, city-searches.<br>
	 * It tries to extracts the latitude and longitude from the coordinates-tag.<br>
	 * E.g. try:
	 * <code>http://maps.google.com/maps/geo?q=Kriegsstraße+1,Karlsruhe&output=xml</code>
	 * 
	 * @param data			the url to the document
	 */
	public WorldWindView(String title, ViewData data) {
		initialize(title, data);
	}
	
	public Component createComponent() {
		m_panel=new JPanel(new BorderLayout());
		m_panel.getActionMap().put(ACTION_ZOOM_IN, new AbstractAction() {
			private static final long serialVersionUID = -2032887916874679271L;

			public void actionPerformed(ActionEvent evt) {
				setZoom(Math.max(1000d,getZoom()*0.25d));
			}
		});
		m_panel.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_MASK), ACTION_ZOOM_IN);

		m_panel.getActionMap().put(ACTION_ZOOM_OUT, new AbstractAction() {
			private static final long serialVersionUID = -2204213401715189091L;

			public void actionPerformed(ActionEvent evt) {
				setZoom(getZoom()*4d);
			}
		});
		m_panel.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_MASK), ACTION_ZOOM_OUT);

		
		try {
			m_wwCanvas = new WorldWindowGLCanvas();
			m_wwCanvas.setPreferredSize(new Dimension(800, 800));

			Model m = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
			m_wwCanvas.setModel(m);
			m_wwCanvas.addSelectListener(new ClickAndGoSelectListener(m_wwCanvas, WorldMapLayer.class));
			
			/*LayerPanel layerPanel = new LayerPanel(m_wwCanvas, null);
			pane.add(layerPanel, BorderLayout.WEST);

			
			ViewControlPanel vcp = new ViewControlPanel(m_wwCanvas);
			layerPanel.add(vcp,  BorderLayout.NORTH);
	*/

			m_panel.add(m_wwCanvas, BorderLayout.CENTER);

			m_statusBar = new StatusBar();
			m_panel.add(m_statusBar, BorderLayout.PAGE_END);
			m_statusBar.setEventSource(m_wwCanvas);

			// load the data with some delay
			(new Thread() {
				public void run() {
					try {
						sleep(3000);
					} catch (InterruptedException e) {
					}

					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							dataChanged();
						}
					});
				}
			}).start();
			
		}
		catch (Throwable e) {
			e.printStackTrace();
		}

		return m_panel;
	}

	public Action getAction(String name) {
		if (m_panel!=null)
			return m_panel.getActionMap().get(name);

		return null;
	}

	public Iterator<String> getActionNames() {
		return new ArrayList<String>(Arrays.asList(new String[]{ACTION_ZOOM_IN,ACTION_ZOOM_OUT})).iterator();
	}

	@Override
	protected void dataChanged() {
		ViewData data=getData();
		if (m_wwCanvas!=null && data!=null) {
			InputStreamReader in=null;
			
			try {
				in=new InputStreamReader(data.openStream());

				// try to parse as xml-stream -> read from "coordinates"-tag
				SAXParserFactory factory = SAXParserFactory.newInstance();
				factory.setValidating(false);
	
				SAXParser saxParser = factory.newSAXParser(); 
				saxParser.parse(new InputSource(in), new DefaultHandler() {
					private boolean m_coordinatesTag;
					private StringBuffer m_coordinatesBuffer=new StringBuffer();
					
					public void startElement(String namespaceURI, String localName, 
							String qName, Attributes attrs) throws SAXException { 
						// get the tag-name
						String eName = localName; 
						if ("".equals(eName))
							eName = qName; // not namespace-aware
						
						if ("coordinates".equalsIgnoreCase(eName) && m_coordinatesBuffer.length()==0) {
							m_coordinatesTag=true;
						}
						else
							m_coordinatesTag=false;
					}
					
					public void characters(char ch[], int start, int length) throws SAXException {
						if (m_coordinatesTag) {
							m_coordinatesBuffer.append(ch,start,length);
						}
					}
					
					public void endElement (String uri, String localName, String qName) throws SAXException {
						if (m_coordinatesTag && m_coordinatesBuffer.length()>0) {
							m_coordinatesTag=false;
							
							String[] loc=m_coordinatesBuffer.toString().split(",");
							if (loc.length>=2) {
								try {
									// go to the location at 15 km height
									gotoLatLon(Double.valueOf(loc[1]).doubleValue(), Double.valueOf(loc[0]).doubleValue(), 15000, 0, 0);
								}
								catch (Throwable t) {
									t.printStackTrace();
								}
							}
						}
					}
					
					public InputSource resolveEntity (String publicId, String systemId) throws IOException, SAXException {
						return EMPTY_INPUT_SOURCE; // ignore any dtd
					}
					
					private InputSource EMPTY_INPUT_SOURCE=new InputSource(new InputStream() {
						@Override
						public int read() throws IOException {
							return -1;
						}
					});
				});
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ParserConfigurationException e) {
				e.printStackTrace();
			} catch (SAXException e) {
				e.printStackTrace();
			}
			finally {
				if (in!=null) {
					try {
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	// Public API
    /**
     * Move the current view position
     * @param lat the target latitude in decimal degrees
     * @param lon the target longitude in decimal degrees
     */
    public void gotoLatLon(double lat, double lon) {
        this.gotoLatLon(lat, lon, Double.NaN, 0, 0);
    }

    /**
     * Move the current view position, zoom, heading and pitch
     * @param lat the target latitude in decimal degrees
     * @param lon the target longitude in decimal degrees
     * @param zoom the target eye distance in meters
     * @param heading the target heading in decimal degrees
     * @param pitch the target pitch in decimal degrees
     */
    public void gotoLatLon(double lat, double lon, double zoom, double heading, double pitch) {
    	if (m_wwCanvas!=null) {
	        OrbitView view = (OrbitView)m_wwCanvas.getView();
	        Globe globe = m_wwCanvas.getModel().getGlobe();
	        if(!Double.isNaN(lat) || !Double.isNaN(lon) || !Double.isNaN(zoom)) {
	            lat = Double.isNaN(lat) ? view.getCenterPosition().getLatitude().degrees : lat;
	            lon = Double.isNaN(lon) ? view.getCenterPosition().getLongitude().degrees : lon;
	            zoom = Double.isNaN(zoom) ? view.getZoom() : zoom;
	            heading = Double.isNaN(heading) ? view.getHeading().degrees : heading;
	            pitch = Double.isNaN(pitch) ? view.getPitch().degrees : pitch;
	            view.applyStateIterator(FlyToOrbitViewStateIterator.createPanToIterator(
	                    view, globe, Position.fromDegrees(lat, lon, 0),
	                    Angle.fromDegrees(heading), Angle.fromDegrees(pitch), zoom, true));
	        }
    	}
    }

    /**
     * Set the current view heading and pitch
     * @param heading the traget heading in decimal degrees
     * @param pitch the target pitch in decimal degrees
     */
    public void setHeadingAndPitch(double heading, double pitch) {
    	if (m_wwCanvas!=null) {
	        OrbitView view = (OrbitView)m_wwCanvas.getView();
	        if(!Double.isNaN(heading) || !Double.isNaN(pitch)) {
	            heading = Double.isNaN(heading) ? view.getHeading().degrees : heading;
	            pitch = Double.isNaN(pitch) ? view.getPitch().degrees : pitch;
	
	            view.applyStateIterator(ScheduledOrbitViewStateIterator.createHeadingPitchIterator(
	                view.getHeading(), Angle.fromDegrees(heading), view.getPitch(), Angle.fromDegrees(pitch)));
	        }
    	}
    }

    /**
     * Set the current view zoom
     * @param zoom the target eye distance in meters
     */
    public void setZoom(double zoom) {
    	if (m_wwCanvas!=null) {
	        OrbitView view = (OrbitView)m_wwCanvas.getView();
	        if(!Double.isNaN(zoom)) {
	            view.applyStateIterator(ScheduledOrbitViewStateIterator.createZoomIterator(
	                view.getZoom(), zoom));
	        }
    	}
    }
    
    /**
     * Returns the current zoom
     * 
     * @return			the zoom (eye distance in meters)
     */
    public double getZoom() {
    	if (m_wwCanvas!=null)
	        return ((OrbitView)m_wwCanvas.getView()).getZoom();
    	
    	return 1d;
    }

	private class ViewControlPanel extends JPanel {
		private static final long serialVersionUID = -9206793272354085841L;
		private WorldWindow wwd;
		private JSlider pitchSlider;
		private JSlider headingSlider;
		private JSlider fovSlider;

		private boolean suspendEvents = false;

		public ViewControlPanel(WorldWindow wwd)
		{
			this.wwd = wwd;
			// Add view property listener
			this.wwd.getView().addPropertyChangeListener(new PropertyChangeListener()
			{
				public void propertyChange(PropertyChangeEvent propertyChangeEvent)
				{
					update();
				}
			});

			// -- Compose panel ---
			this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

			// -- Pitch slider ------------------------------------
			JPanel pitchPanel = new JPanel(new GridLayout(0, 1, 5, 5));
			pitchPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
			pitchPanel.add(new JLabel("Pitch:"));
			pitchSlider = new JSlider(0, 90, 0);
			pitchSlider.addChangeListener(new ChangeListener()
			{
				public void stateChanged(ChangeEvent changeEvent)
				{
					updateView();
				}
			});
			pitchPanel.add(pitchSlider);

			// -- Heading slider ------------------------------------
			JPanel headingPanel = new JPanel(new GridLayout(0, 1, 5, 5));
			headingPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
			headingPanel.add(new JLabel("Heading:"));
			headingSlider = new JSlider(-180, 180, 0);
			headingSlider.addChangeListener(new ChangeListener()
			{
				public void stateChanged(ChangeEvent changeEvent)
				{
					updateView();
				}
			});
			headingPanel.add(headingSlider);

			// -- Field of view slider ------------------------------
			JPanel fovPanel = new JPanel(new GridLayout(0, 1, 5, 5));
			fovPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
			fovPanel.add(new JLabel("Field of view:"));
			fovSlider = new JSlider(10, 120, 45);
			fovSlider.addChangeListener(new ChangeListener()
			{
				public void stateChanged(ChangeEvent changeEvent)
				{
					updateView();
				}
			});
			fovPanel.add(fovSlider);

			// -- Assembly -----------------------------------------
			this.add(pitchPanel);
			this.add(headingPanel);
			this.add(fovPanel);
			this.setBorder(
					new CompoundBorder(BorderFactory.createEmptyBorder(9, 9, 9, 9), new TitledBorder("View")));
			this.setToolTipText("View controls");
		}

		// Update view settings from control panel in a 'first person' perspective
		private void updateView()
		{
			if (!suspendEvents)
			{
				OrbitView view = (OrbitView) this.wwd.getView();

				// Stop iterators first
				view.stopStateIterators();

				// Save current eye position
				final Position pos = view.getEyePosition();

				// Set view heading, pitch and fov
				view.setHeading(Angle.fromDegrees(this.headingSlider.getValue()));
				view.setPitch(Angle.fromDegrees(this.pitchSlider.getValue()));
				view.setFieldOfView(Angle.fromDegrees(this.fovSlider.getValue()));
				view.setZoom(0);

				// Restore eye position
				view.setCenterPosition(pos);

				// Redraw
				this.wwd.redraw();
			}
		}

		// Update control panel from view
		public void update()
		{
			this.suspendEvents = true;
			{
				OrbitView view = (OrbitView) wwd.getView();
				this.pitchSlider.setValue((int)view.getPitch().degrees);
				this.headingSlider.setValue((int)view.getHeading().degrees);
				this.fovSlider.setValue((int)view.getFieldOfView().degrees);
			}
			this.suspendEvents = false;
		}

	}

	@Override
	protected Component getFocusComponent() {
		return m_wwCanvas;
	}
}