package usda.weru.weps.location.mode;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.util.List;
import java.util.logging.Logger;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import org.jscience.geography.coordinates.LatLong;

import usda.weru.gis.GISData;
import usda.weru.gis.GISLookup;
import usda.weru.gis.GISUtil;
import usda.weru.gis.data.SelectArea;
import usda.weru.gis.data.WindgenSelectArea;
import usda.weru.util.PropertyVetoRunnableException;
import usda.weru.weps.RunFileBean.StationType;
import usda.weru.weps.location.InterpolatedStation;
import usda.weru.weps.location.Site.Level2;
import usda.weru.weps.location.Station;
import usda.weru.weps.location.chooser.StationChooser;

/**
 *
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public class NRCSHandler extends GISHandler {

    private static final Logger LOGGER = Logger.getLogger(NRCSHandler.class.getName());

    /**
     *
     * @param state
     * @return the stations from the data model
     */
    protected Station[] getValidStationChoices(GISBeanState state) {
        return state.getDataModel().getNearestStations(state.getBean().getLatLong(),
                state.getDistanceLimit(), state.getCountLimit());
    }

    @Override
    protected void handleValidate(final GISBeanState state, Station oldStation,
            final Station newStation, boolean loading, PropertyChangeEvent event)
            throws PropertyVetoException {
        if (!loading) {
            return;
        }

        if (newStation == null) {
            //TODO: might need to alert the user here?
            LOGGER.warning("newStation should not be null");
            return;
        }

        if (state.getBean().getLatLong() == null) {
            return;
        }

        SelectArea area = getSelectArea(state);
        // if the area is null then cligen goes to NULL and windgen goes to OUT
        final SelectArea.Type type = area != null ? area.getType()
                : (state.getType() == StationType.Cligen ? SelectArea.Type.Null : SelectArea.Type.Out);

        Station[] choices = new Station[0];
        Station validStation = null;
        TypeSwitch:
        switch (type) {
            case Station:
                validStation = area != null ? area.getStation() : null;
                break;
            case Interpolated:
                if (newStation instanceof InterpolatedStation) {
                    // we ignore the latlong
                    setView(state, StationChooser.View.Label);
                    return;
                }

                //default to the county centroid                
                validStation = createInterpolatedStation(area, state.getBean().getLatLong());

                break;
            case Out:

                choices = getValidStationChoices(state);
                if (choices != null && choices.length > 0) {

                    //loop over the choices, if it's still in here, we're okay
                    for (Station temp : choices) {
                        if (newStation.equals(temp)) {
                            validStation = newStation;
                            break TypeSwitch;
                        }
                    }
                    //was not in the list, so we default to the nearest station
                    validStation = choices[0];
                    ChoiceHandler.proxy(state, choices);
                    break;
                } else {
                    validStation = null;
                    break;
                }
            case Null:  //cligen in the eastern half
                Station[] nearest = getValidStationChoices(state);
                if (nearest != null && nearest.length > 0) {
                    //nearest is the first
                    validStation = nearest[0];
                    break;
                } else {
                    validStation = null;
                    break;
                }
        }
        if (newStation.equals(validStation)) {
            //we're valid
            switch (type) {
                case Station:
                    //use the station as defined in the select area
                    setView(state, StationChooser.View.Label);
                    break;
                case Interpolated:
                    //make an interpolated station
                    setView(state, StationChooser.View.Label);
                    break;
                case Out:
                    setView(state, StationChooser.View.Choice);
                    ChoiceHandler.proxy(state, choices);
                    break;
                case Null:
                    setView(state, StationChooser.View.Label);
                    break;
            }
        } else {
            //send the exception off
            final Station validStation2 = validStation;
            throw new PropertyVetoRunnableException("Station choice is no longer valid.", event, new Runnable() {

                @Override
                public void run() {

                    String validName = validStation2 != null ? validStation2.getDisplayName() : "no selection";

                    JOptionPane pane = new JOptionPane("The simulation you are attempting to open uses a "
                            + state.getType().getDisplayName() + " station that is no longer valid. "
                            + newStation.getDisplayName() + " will be substituted with " + validName
                            + ".  Simulation results may be different.", JOptionPane.WARNING_MESSAGE) {
                                private static final long serialVersionUID = 1L;

                                @Override
                                public int getMaxCharactersPerLineCount() {
                                    return 80;
                                }
                            };

                    JDialog dialog = pane.createDialog(state.getType().getDisplayName() + " Station Selection");
                    dialog.setModal(true);
                    dialog.setVisible(true);

                    //set to a valid station view
                    switch (type) {
                        case Station:
                            //use the station as defined in the select area
                            setView(state, StationChooser.View.Label);
                            break;
                        case Interpolated:
                            //make an interpolated station
                            setView(state, StationChooser.View.Label);
                            break;
                        case Out:
                            setView(state, StationChooser.View.Choice);
                            break;
                    }
                    //set the valid choice
                    state.setSelectedStation(validStation2);
                }
            });
        }

    }

    /**
     *
     * @param state
     * @param area
     */
    @Override
    protected void handleSelectArea(GISBeanState state, SelectArea area) {
        //if the area is null then cligen goes to NULL and windgen goes to OUT
        final SelectArea.Type type = area != null ? area.getType()
                : (state.getType() == StationType.Cligen ? SelectArea.Type.Null : SelectArea.Type.Out);
        switch (type) {
            case Station:
                //use the station as defined in the select area
                setView(state, StationChooser.View.Label);

                Station station = area.getStation();
                if (station != state.getSelectedStation()) {
                    state.setSelectedStation(station);
                }
                return;
            case Interpolated:
                //make an interpolated station
                setView(state, StationChooser.View.Label);

                InterpolatedStation intStation = createInterpolatedStation(area, state.getBean().getLatLong());

                if (!intStation.equals(state.getSelectedStation())) {
                    state.setSelectedStation(intStation);
                }
                return;
            case Out:
                setView(state, StationChooser.View.Choice);
                Station[] choices = getValidStationChoices(state);

                //pass the work off to the choice handler
                ChoiceHandler.proxy(state, choices);
                return;
            case Null:     //Used by cligen
                //Go to nearest station if there is no select area
                setView(state, StationChooser.View.Label);
                Station[] nearest = getValidStationChoices(state);

                //only care about the one nearest station
                if (nearest != null && nearest.length > 1) {
                    nearest = new Station[]{nearest[0]};
                }

                //pass the work off to the choice handler
                ChoiceHandler.proxy(state, nearest);
        }
    }

    private InterpolatedStation createInterpolatedStation(SelectArea area, LatLong latlong) {
        GISLookup<Level2> countyLookup = GISData.getInstance().getLookup(Level2.class);

        List<Level2> counties = countyLookup.lookup(latlong);

        if (counties != null && counties.size() > 0) {
            Level2 county = counties.get(0);

            LatLong centroid = county.getLatLong();

            Geometry interpolationShape = null;
            Geometry countyShape = null;

            //test that the centroid is actually in the interpolated region.
            GISLookup<WindgenSelectArea> selectAreaLookup
                    = GISData.getInstance().getLookup(WindgenSelectArea.class);
            List<WindgenSelectArea> areas = selectAreaLookup.lookup(centroid);
            WindgenSelectArea centroidArea = areas != null && areas.size() > 0 ? areas.get(0) : null;

            if (centroidArea != null && centroidArea.getType() == SelectArea.Type.Interpolated) {
                /*
                 * Approach A
                 * Use the centroid of the county if the centroid is actually within the interpolated region
                 */

                //yes, the centroid is inside the interpolated region, we're good to go!
                return new InterpolatedStation(centroid);
            } else if (centroidArea != null) {
                /*
                 * Approach B
                 * Calculate the overlap between the county and interpolation region.  
                 * Use the centroid of this overlap if the overlap centroid is in 
                 * the interpolation region
                 */
                interpolationShape = GISData.getGeometry(area);
                countyShape = GISData.getGeometry(county);

                if (interpolationShape != null && countyShape != null) {

                    Geometry intersection = interpolationShape.intersection(countyShape);

                    //Point intersectionPoint =  intersection.getCentroid();
                    Point intersectionPoint = GISUtil.representativePoint(intersection);

                    //verify the new centroid is in the interpolation area                
                    if (interpolationShape.contains(intersectionPoint)) {
                        LatLong intersectionLatLong = GISUtil.toLatLong(intersectionPoint.getCoordinate());
                        LOGGER.info(String.format("Calculated interpolation latlong from county "
                                + "and interpolation region overlap: %s", intersectionLatLong.toString()));
                        return new InterpolatedStation(intersectionLatLong);
                    }
                }
            }

            LOGGER.severe(String.format(
                    "Unable to calculate static interpolation location for lat/long: %s", latlong.toString()));
            return new InterpolatedStation(latlong);

        } else {
            //fall back onto the latlong
            LOGGER.severe(String.format("No county equivalent found for lat/long: %s", latlong.toString()));
            return new InterpolatedStation(latlong);
        }

    }

    @Override
    @Deprecated
    public int getId() {
        return 5;
    }

    /**
     *
     * @return
     */
    @Override
    public String getDisplayName() {
        return "NRCS";
    }

    @Override
    public String getName() {
        return "nrcs";
    }
}
