package org.geotools.demo;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.geotools.data.ows.CRSEnvelope;
import org.geotools.data.ows.Layer;
import org.geotools.data.ows.OperationType;
import org.geotools.data.ows.WMSCapabilities;
import org.geotools.data.ows.WMSRequest;
import org.geotools.data.wms.WMSUtils;
import org.geotools.data.wms.WebMapServer;
import org.geotools.data.wms.request.GetLegendGraphicRequest;
import org.geotools.data.wms.request.GetMapRequest;
import org.opengis.layer.Style;

/**
 * This lab explores the use of the GeoTools WMS client code.
 * <p>
 * The GeoTools WMS client is a little bit more than simply sending away GetMap
 * request:
 * <ul>
 * <li></li>
 * </ul>
 * 
 * @author Jody Garnett
 */
public class WMSLab extends JFrame {
    private static final long serialVersionUID = -3039255518595806472L;

    /** Original coverage we are working on */
    WebMapServer wms;
    CoveragePanel panel;
    Image image;
    JList layers;
    JButton getMapButton;
    JComboBox styleCombo;
    /**
     * Map<Layer,Style> as controlled by styleCombo box
     */
    Map styles = new HashMap();
    Layer selectedLayer = null;

    DefaultComboBoxModel availableStyles;

    /**
     * Explore the functionality of the provided GridCoverage (think
     * BufferedImage + CRS).
     * <p>
     * A GridCoverage literally a set of features that "covers" an area without
     * gaps; in the case of grid coverage the area is covered by an regular
     * grid.
     * <p>
     * Coverage work by letting you call a "sample" operation in order to
     * retrieve a Record of the data at the location. A grid coverage lets you
     * express the location using row and column.
     * <p>
     * 
     * @param coverage
     */
    public WMSLab(WebMapServer server) {
        this.wms = server;
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        String title = getWMSTitle(wms);
        setTitle(title);

        Set good = WMSUtils.getQueryableLayers(wms.getCapabilities());
        this.layers = new JList(good.toArray());
        layers.setCellRenderer(new LayerCellRenderer());
        layers.getSelectionModel().addListSelectionListener(
                new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent e) {
                        setSelectedLayer((Layer) layers.getSelectedValue());
                    }
                });
        JScrollPane scrollPane = new JScrollPane(layers);
        scrollPane.setPreferredSize(new Dimension(200, 400));
        add(scrollPane, BorderLayout.WEST);

        this.panel = new CoveragePanel();
        add(panel, BorderLayout.CENTER);

        JPanel buttons = new JPanel();
        getMapButton = new JButton("GetMap");
        getMapButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                getMap();
            }
        });
        buttons.add(getMapButton);
        availableStyles = new DefaultComboBoxModel();
        styleCombo = new JComboBox(availableStyles);
        styleCombo.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (selectedLayer != null) {
                    styles.put(selectedLayer, styleCombo.getSelectedItem());
                }
            }
        });
        styleCombo.setRenderer(new StyleCellRenderer());
        buttons.add(styleCombo);

        add(buttons, BorderLayout.NORTH);
        pack();
    }

    private String getWMSTitle(WebMapServer wms) {
        return null;
    }

    public void setSelectedLayer(Layer selected) {
        availableStyles.removeAllElements();
        this.selectedLayer = selected;
        if (selected != null) {
            // availableStyles.addElement("default");
            List styles = selected.getStyles();
            for (Iterator i = styles.iterator(); i.hasNext();) {
                Style style = (Style) i.next();
                availableStyles.addElement(style);
            }
        }
        styleCombo.repaint();
    }

    class StyleCellRenderer extends DefaultListCellRenderer {
        private static final long serialVersionUID = 7421698429520525469L;

        public Component getListCellRendererComponent(JList list, Object value, // value
                // to
                // display
                int index, // cell index
                boolean iss, // is the cell selected
                boolean chf) // the list and the cell have the focus
        {
            super.getListCellRendererComponent(list, value, index, iss, chf);
            Style style = (Style) value;
            if (style == null)
                return this;

            String title = style.getTitle() == null ? style.getName() : style
                    .getTitle().toString();
            setText(title);
            return this;
        }

        Icon getIcon(Style style) {
            List urlList = style.getLegendURLs();
            if (urlList == null || urlList.isEmpty())
                return null;

            URL url = (URL) style.getLegendURLs().get(0);
            ImageIcon icon = new ImageIcon(url);
            return icon;
        }
    }

    class LayerCellRenderer extends DefaultListCellRenderer {
        private static final long serialVersionUID = 1173012107250651733L;
        HashMap icons = new HashMap();

        public Component getListCellRendererComponent(JList list, Object value, // value
                // to
                // display
                int index, // cell index
                boolean iss, // is the cell selected
                boolean chf) // the list and the cell have the focus
        {
            super.getListCellRendererComponent(list, value, index, iss, chf);
            Layer layer = (Layer) value;
            setText(layer.getTitle());
            Icon glyph;
            if (icons.containsKey(layer)) {
                glyph = (Icon) icons.get(layer);
            } else {
                glyph = getLegendGraphics(layer);
                icons.put(layer, glyph);
            }
            setIcon(glyph);
            setToolTipText(layer.getName());
            return this;
        }
    }

    public Icon getLegendGraphics(Layer layer) {
        return null;
    }

    private String getNamedStyle(Layer layer) {
        Object layerStyle = styles.get(layer);
        String namedStyle = null;
        if (layerStyle == null) {
            namedStyle = null;
        } else if (layerStyle instanceof String) {
            namedStyle = (String) layerStyle;

        } else if (layerStyle instanceof Style) {
            Style style = (Style) layerStyle;
            namedStyle = style.getName();
        }
        return namedStyle;
    }

    public void getMap() {
        try {
            Layer selected = (Layer) layers.getSelectedValue();
            getMap(selected);
        } catch (Exception e1) {
            image = null;
        }
    }
    
    private void getMap(Layer layer) throws Exception {
        panel.repaint();
    }

    private void getMap(List layers) throws Exception {
        if (layers == null || layers.isEmpty())
            return;

        GetMapRequest mapRequest = wms.createGetMapRequest();
        CRSEnvelope bounds = null;
        for (Iterator i = layers.iterator(); i.hasNext();) {
            Layer layer = (Layer) i.next();
            String namedStyle = getNamedStyle(layer);
            mapRequest.addLayer(layer, namedStyle);
            CRSEnvelope box = layer.getLatLonBoundingBox();
            box.setEPSGCode("EPSG:4326");
            if (box != null) {
                if (bounds != null) {
                    bounds.setMinX(Math.min(bounds.getMinX(), box.getMinX()));
                    bounds.setMaxX(Math.max(bounds.getMaxX(), box.getMaxX()));
                    bounds.setMinY(Math.min(bounds.getMinY(), box.getMinY()));
                    bounds.setMaxY(Math.max(bounds.getMaxY(), box.getMaxY()));
                } else {
                    bounds = box;
                }
            }
        }
        if (bounds == null) {
            bounds = new CRSEnvelope();
            bounds.setEPSGCode("EPSG:4326");
        }
        mapRequest.setFormat(getImageFormat(wms));

        mapRequest.setSRS(bounds.getEPSGCode());
        mapRequest.setBBox(bounds);
        mapRequest.setDimensions(panel.getWidth(), panel.getHeight());

        URL url = mapRequest.getFinalURL();
        ImageIcon load = new ImageIcon(url);
        image = load.getImage();
        panel.repaint();
    }

    /**
     * Some WMS Servers like GeoServer support strange formats like KML and PDF
     * 
     * @return
     */
    String getImageFormat(WebMapServer wms) {
        OperationType description = wms.getCapabilities().getRequest()
                .getGetMap();

        List formats = description.getFormats();
        for (Iterator i = formats.iterator(); i.hasNext();) {
            String format = (String) i.next();
            if (format.indexOf("pdf") != -1)
                continue;

            return format;
        }
        return "image/jpeg";
    }

    /**
     * Search through the layer data structure for bounds for the provided srs
     * (if null we will return the first bounds).
     * 
     * @param srs
     * @return CRSEnvelope
     */
    CRSEnvelope getCRSEnvelope(Layer layer, String srs) {

        if (layer == null)
            return null;
        Map boundingBoxes = layer.getBoundingBoxes();
        if (boundingBoxes != null && !boundingBoxes.isEmpty()) {
            for (Iterator i = boundingBoxes.entrySet().iterator(); i.hasNext();) {
                Map.Entry entry = (Entry) i.next();
                String layerSrs = (String) entry.getKey();
                CRSEnvelope box = (CRSEnvelope) entry.getValue();
                if (srs == null || srs.equals(layerSrs)) {
                    return box;
                }
            }
        }
        return getCRSEnvelope(layer.getParent(), srs);
    }

    class CoveragePanel extends JPanel {
        private static final long serialVersionUID = -4755270758709990530L;

        CoveragePanel() {
            setBackground(Color.WHITE);
        }

        public Dimension getPreferredSize() {
            return new Dimension(640, 400);
        }

        public void paintComponent(Graphics graphics) {
            super.paintComponents(graphics);
            if (image != null) {
                Graphics2D g = (Graphics2D) graphics;
                g.drawImage(image, 0, 0, null);
            }
        }
    }

    /**
     * Prompt the user for a file and open up ImageLab.
     * 
     * @param args
     *                filename of image
     */
    public static void main(String[] args) throws Exception {
        URL server = getServerURL(args);
        WebMapServer wms;

        System.out.println("Connecting to " + server);
        wms = new WebMapServer(server);

        System.out.println("Welcome");
        WMSLab wmsLab = new WMSLab(wms);
        wmsLab.setVisible(true);
    }

    public static String[] getLayerNames(WebMapServer wms) {
        Layer[] namedLayers = WMSUtils.getNamedLayers(wms.getCapabilities());
        String[] names = new String[namedLayers.length];
        for (int i = 0; i < namedLayers.length; i++) {
            Layer layer = namedLayers[i];
            names[i] = layer.getName();
        }
        return names;
    }

    public static URL getServerURL(String[] args) throws MalformedURLException {
        if (args.length == 0) {
            Object[] servers = new Object[] {
                    "http://localhost:8080/geoserver/wms?service=WMS&request=GetCapabilities",
                    "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap?Service=WMS&VERSION=1.1.0&REQUEST=GetCapabilities",
                    "http://wms.jpl.nasa.gov/wms.cgi?Service=WMS&Version=1.1.1&Request=GetCapabilities",
                    "http://giswebservices.massgis.state.ma.us/geoserver/wms?service=WMS&request=GetCapabilities",
                    "http://wms.cits.rncan.gc.ca/cgi-bin/cubeserv.cgi?VERSION=1.1.0&REQUEST=GetCapabilities",
                    "http://atlas.gc.ca/cgi-bin/atlaswms_en?VERSION=1.1.1&Request=GetCapabilities&Service=WMS", };
            Object selected = JOptionPane.showInputDialog(null,
                    "WMS GetCapabilities URL", "Choose a WMS Server",
                    JOptionPane.QUESTION_MESSAGE, null, servers, null);

            if (selected == null)
                System.exit(0);

            return new URL((String) selected);
        } else {
            return new URL(args[0]);
        }
    }
}