package tutorial.geotoolsquery01;

import java.io.File;
import java.io.IOException;

import java.util.HashSet;
import java.util.Set;

import javax.swing.JButton;
import javax.swing.JToolBar;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JLabel;
import javax.swing.JTextField;

import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;

import org.geotools.data.FeatureSource;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.Mark;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.Symbolizer;
import org.geotools.swing.JMapFrame;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.swing.event.MapMouseEvent;
import org.geotools.swing.tool.CursorTool;

import org.opengis.style.ContrastMethod;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.identity.FeatureId;

import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;

import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.styling.SLD;
import org.geotools.styling.ChannelSelection;
import org.geotools.styling.ContrastEnhancement;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.SelectedChannelType;
import org.geotools.swing.action.SafeAction;

public class SelectionLab {

    private StyleFactory mStyleFactory = CommonFactoryFinder.getStyleFactory(null);
    private FilterFactory2 mFilterFactory = CommonFactoryFinder.getFilterFactory2(null);

    private enum GEOM_TYPE { Point, Line, Polygon };

    private static final Color mColorLinea = Color.BLUE;
    private static final Color mColorRelleno = Color.MAGENTA;
    private static final Color mColorSeleccion = Color.YELLOW;
    private static final float mOpacidad = 1.0f;
    private static final float mGrosorLinea = 1.0f;
    private static final float mGrosorPunto = 10.0f;
    private JMapFrame mJmapFrame;
    private MapContext mMapContext;
    JTextField mJTextField;
    private FeatureSource<SimpleFeatureType, SimpleFeature> mFeatureSource;
    private String mGeomName;
    private GEOM_TYPE mTipoGeom;
    private AbstractGridCoverage2DReader mReader;
    JToolBar mToolBar;

    public SelectionLab(){
        mMapContext = new DefaultMapContext();
        mMapContext.setTitle("Lab de selección");

        mJmapFrame = new JMapFrame(mMapContext);
        mJmapFrame.enableToolBar(true);
        mJmapFrame.enableStatusBar(true);
        mJmapFrame.enableLayerTable(true);
        mJmapFrame.setSize(800, 600);
        mJmapFrame.setVisible(true);
        mToolBar = mJmapFrame.getToolBar();
        JLabel mJlabel = new JLabel("Seleccionar capa:");
        mJTextField = new JTextField(50);
        JButton mJbutton = new JButton("Seleccionar");
        mToolBar.addSeparator();
        mToolBar.add(mJbutton);
        mToolBar.add(mJlabel);
        mToolBar.add(mJTextField);

        mJbutton.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent ae) {
                mJmapFrame.getMapPane().setCursorTool(new CursorTool(){
                    @Override
                    public void onMouseClicked(MapMouseEvent evt) {
                        seleccionarFigura(evt);
                    }
                });
            }
        });

        JMenuBar mMenuBar = new JMenuBar();
        mJmapFrame.setJMenuBar(mMenuBar);
        JMenu mMenu = new JMenu("Referencias");
        mMenuBar.add(mMenu);

        mMenu.add(new SafeAction("Agregar Imagen") {
            @Override
            public void action(ActionEvent e) throws Throwable {
                File mFile = JFileDataStoreChooser.showOpenFile("jpg", null);
                if (mFile == null) {
                    return;
                }
                AbstractGridFormat mFormat = GridFormatFinder.findFormat(mFile);
                mReader = mFormat.getReader(mFile);
                Style rasterStyle = crearEstiloRGB();

                mMapContext.addLayer(mReader, rasterStyle);
            }
        });
        mMenu.add(new SafeAction("Agregar ShapeFile") {
            @Override
            public void action(ActionEvent e) throws Throwable {
                File f = JFileDataStoreChooser.showOpenFile("shp", null);
                if ( f == null) return;
                mostrarShapeFile(f);
            }
        });

        JMenu mMenuOpciones = new JMenu("Opciones");
        mMenuBar.add(mMenuOpciones);
        mMenuOpciones.add(new SafeAction("Barra de herramientas") {
            @Override
            public void action(ActionEvent e) throws Throwable {
                mToolBar.setVisible(!mToolBar.isVisible());
                mJmapFrame.getToolBar();
            }
        });
        mMenuOpciones.add(new SafeAction("Barra de estado") {
            @Override
            public void action(ActionEvent e) throws Throwable {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        });
    }

    public static void main(String[] args) throws Exception {
        SelectionLab me = new SelectionLab();
    }

    private void mostrarShapeFile(File mFile) throws Exception {
        FileDataStore mDataStore = FileDataStoreFinder.getDataStore(mFile);
        mFeatureSource = mDataStore.getFeatureSource();

        establecerFigura();
        Style mStyle = crearStyle();
        mMapContext.addLayer(mFeatureSource, mStyle);
    }

    private void seleccionarFigura(MapMouseEvent evt) {
        System.out.println("Click en: " + evt.getMapPosition());

        Point screenPoint = evt.getPoint();
        Rectangle screenRect = new Rectangle(
                screenPoint.x - 2,
                screenPoint.y - 2,
                1,
                1);
        AffineTransform pixel2crs = mJmapFrame.getMapPane().getScreenToWorldTransform();
        Rectangle2D creRect = pixel2crs.createTransformedShape(screenRect).getBounds2D();
        ReferencedEnvelope mBbox = new ReferencedEnvelope(
                creRect,
                mJmapFrame.getMapContext().getCoordinateReferenceSystem());

        Filter mFilter = mFilterFactory.intersects(
                mFilterFactory.property(mGeomName),
                mFilterFactory.literal(mBbox));

        try {
            FeatureCollection<SimpleFeatureType, SimpleFeature> mSelectedFeature =
                    mFeatureSource.getFeatures(mFilter);
            FeatureIterator<SimpleFeature> mIterator = mSelectedFeature.features();
            Set<FeatureId> mId = new HashSet<FeatureId>();

            try {
                while (mIterator.hasNext()) {
                    SimpleFeature mFeature = mIterator.next();
                    mId.add(mFeature.getIdentifier());
                    
                    System.out.println(mFeature.getIdentifier());
                }
            } finally {
                mIterator.close();
            }

            if (mId.isEmpty()) System.out.println("No se selecciono ninguna figura.");

            mostrarFiguraSeleccionada(mId);
        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
    }

    private void mostrarFiguraSeleccionada(Set<FeatureId> mId) {
        Style mStyle;

        if (mId.isEmpty()){
            mStyle = crearStyle();
        } else {
            mStyle = crearStyleSeleccion(mId);
        }
        mJmapFrame.getMapContext().getLayer(1).setStyle(mStyle);
        mJmapFrame.getMapPane().repaint();
    }

    private Style crearStyle() {   
        Rule mRule = crearRule(mColorLinea, null);

        FeatureTypeStyle mTypeStyle = mStyleFactory.createFeatureTypeStyle();
        mTypeStyle.rules().add(mRule);

        Style mStyle = mStyleFactory.createStyle();
        mStyle.featureTypeStyles().add(mTypeStyle);
        return mStyle;
    }

    private Style crearStyleSeleccion(Set<FeatureId> mId) {
        Rule mRule = crearRule(mColorSeleccion, mColorSeleccion);
        mRule.setFilter(mFilterFactory.id(mId));

        Rule mRule2 = crearRule(mColorLinea, null);
        mRule2.setElseFilter(true);

        FeatureTypeStyle mTypeStyle = mStyleFactory.createFeatureTypeStyle();
        mTypeStyle.rules().add(mRule);
        mTypeStyle.rules().add(mRule2);

        Style mStyle = mStyleFactory.createStyle();
        mStyle.featureTypeStyles().add(mTypeStyle);
        return mStyle;
    }

    private Rule crearRule(Color mColorBorde, Color mColorRelleno){
        Symbolizer mSymbolizer = null;
        Fill mFill = null;
        Stroke mStroke = mStyleFactory.createStroke(
                mFilterFactory.literal(mColorBorde),
                mFilterFactory.literal(mGrosorLinea));
        switch(mTipoGeom) {
            case Polygon:
                mFill = mStyleFactory.createFill(
                        mFilterFactory.literal(mColorRelleno),
                        mFilterFactory.literal(mOpacidad));
                mSymbolizer = mStyleFactory.createPolygonSymbolizer(mStroke, mFill, mGeomName);
                break;
            case Point:
                mFill = mStyleFactory.createFill(
                        mFilterFactory.literal(mColorRelleno),
                        mFilterFactory.literal(mOpacidad));
                Mark mMark = mStyleFactory.getCircleMark();
                mMark.setFill(mFill);
                mMark.setStroke(mStroke);
                Graphic mGraphic = mStyleFactory.createDefaultGraphic();
                mGraphic.graphicalSymbols().clear();
                mGraphic.graphicalSymbols().add(mMark);
                mGraphic.setSize(mFilterFactory.literal(mGrosorPunto));
                mSymbolizer = mStyleFactory.createPointSymbolizer(mGraphic, mGeomName);
                break;
            case Line:
                mSymbolizer = mStyleFactory.createLineSymbolizer(mStroke, mGeomName);
                break;
        }

        Rule mRule = mStyleFactory.createRule();
        mRule.symbolizers().add(mSymbolizer);
        return mRule;
    }

    private void establecerFigura() {
        GeometryDescriptor mGeometryDesc = mFeatureSource.getSchema().getGeometryDescriptor();
        mGeomName = mGeometryDesc.getLocalName();

        Class<?> mClass = mGeometryDesc.getType().getBinding();

        if (Polygon.class.isAssignableFrom(mClass) || MultiPolygon.class.isAssignableFrom(mClass)){
            mTipoGeom = GEOM_TYPE.Polygon;
        } else if(LineString.class.isAssignableFrom(mClass) || MultiLineString.class.isAssignableFrom(mClass)){
            mTipoGeom = GEOM_TYPE.Line;
        } else {
            mTipoGeom = GEOM_TYPE.Point;
        }
    }

    private Style crearEstiloRGB() {
        GridCoverage2D cov = null;
        try {
            cov = mReader.read(null);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        int numBandas = cov.getNumSampleDimensions();
        if (numBandas < 3) {
            return null;
        }
        String[] bandas = new String[numBandas];

        for (int i = 0; i < numBandas; i++) {
            GridSampleDimension dim = cov.getSampleDimension(i);
            bandas[i] = dim.getDescription().toString();
        }
        final int ROJO = 0, VERDE = 1, AZUL = 2;
        int canales[] = {-1, -1, -1};

        for (int i = 0; i < numBandas; i++) {
            String nombre = bandas[i].toLowerCase();
            if (nombre != null) {
                if (nombre.matches("red.*")) {
                    canales[ROJO] = i + 1;
                } else if (nombre.matches("green.*")) {
                    canales[VERDE] = i + 1;
                } else if (nombre.matches("blue.*")) {
                    canales[AZUL] = i + 1;
                }
            }
        }

        if (canales[ROJO] < 0 || canales[VERDE] < 0 || canales[AZUL] < 0) {
            canales[ROJO] = 1;
            canales[VERDE] = 2;
            canales[AZUL] = 3;
        }

        SelectedChannelType[] mSelectedChannel = new SelectedChannelType[cov.getNumSampleDimensions()];
        ContrastEnhancement mContrast = mStyleFactory.contrastEnhancement(
                mFilterFactory.literal(1.0),
                ContrastMethod.NORMALIZE);
        for (int i = 0; i < 3; i++) {
            mSelectedChannel[i] = mStyleFactory.createSelectedChannelType(String.valueOf(canales[i]), mContrast);
        }
        RasterSymbolizer mSymbolizer = mStyleFactory.getDefaultRasterSymbolizer();
        ChannelSelection mChannelSelection = mStyleFactory.channelSelection(
                mSelectedChannel[ROJO],
                mSelectedChannel[VERDE],
                mSelectedChannel[AZUL]);
        mSymbolizer.setChannelSelection(mChannelSelection);
        return SLD.wrapSymbolizers(mSymbolizer);
    }
}