package usda.weru.weps;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*; // For JPanel, etc.
import java.awt.*;           // For Graphics, etc.
import java.awt.geom.*;      // For Ellipse2D, etc.
import java.beans.*;
import java.util.*;
import java.awt.event.*;

/**
 * Creates graphics panel with representation of field dimensions and
 * orientation.
 *
 * Is property change listener to show field and barriers.
 *
 */
public class DrawMap extends JPanel implements MouseMotionListener {

//	Polygon[] icons;
    Station[] stations;
    /**
     * String that helps to locate the data for a wind or climate station. Here we use latitude and
     * longitude of a station to locate it.
     */
    public static final String StationData = "StationData";
    /**
     * String used to pass as an argument to commit chags made to a component's property in a container.
     */
    public static final String Commit = "Commit";
    private boolean redrawFlg = true;
    //Increased the scaleFactor from 0.00008 to 0.00032(*4) in order to zoom in two
    //levels as default zoom level - neha
    private static double INITAL_ZOOM = .00032;
    private static double scaleFactor = INITAL_ZOOM;
//	private static Color[] colorArry = {Color.RED, Color.CYAN, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.BLUE/*, Color.YELLOW*/};
    private static Color[] stateColors = {Color.RED, Color.CYAN, Color.RED, Color.RED, Color.CYAN, // 0 - 4
        Color.GREEN, Color.MAGENTA, Color.RED, Color.YELLOW, Color.CYAN, // 5 - 9
        Color.YELLOW, Color.RED, Color.GREEN, Color.RED, Color.RED, // 10 - 14
        Color.RED, Color.YELLOW, Color.GREEN, Color.MAGENTA, Color.MAGENTA, // 15 - 19
        Color.RED, Color.RED, Color.RED, Color.GREEN, Color.CYAN, // 20 - 24
        Color.RED, Color.CYAN, Color.GREEN, Color.YELLOW, Color.YELLOW, // 25 - 29
        Color.MAGENTA, Color.CYAN, Color.GREEN, Color.CYAN, Color.GREEN, // 30 - 34
        Color.GREEN, Color.MAGENTA, Color.CYAN, Color.RED, Color.YELLOW, // 35 - 39
        Color.CYAN, Color.CYAN, Color.RED, Color.RED, Color.YELLOW, // 40 - 44
        Color.YELLOW, Color.YELLOW, Color.MAGENTA, Color.YELLOW, Color.RED, // 45 - 49
        Color.GREEN, Color.GREEN, Color.RED, Color.RED, Color.MAGENTA, // 50 - 54
        Color.RED, Color.GREEN, Color.RED, Color.RED, Color.RED}; 		// 55 - 59
    private Graphics2D g2d;
    private Rectangle clipRect = null;

    /**
     * The dimensions of the rectangular area that will be visible for county locations
     * after being clipped from the state marked area as a separate entity within the
     * specified state.
     * @param clipDim The dimensions for the rectangular clipped area.
     */
    public void setClipRect(Dimension clipDim) {
        clipRect = new Rectangle(0, 0, clipDim.width, clipDim.height);
    }

    /**
     * The state and county areas that will be redrawn and re-painted after being
     * scaled in/out.
     * @param g The graphics object that uses all the paint API to avail all the
     * options for repainting.
     */
    public void paintComponent(Graphics g) {

////System.out.println("DM_pC: " + redrawFlg);
//	  if (!redrawFlg) return;
//	  redrawFlg = false;
        clear(g);
//	  Graphics2D g2d = (Graphics2D)g;
        g2d = (Graphics2D) g;
//	Rectangle clipRect = g2d.getClipBounds();
        if (clipRect == null) {
            clipRect = g2d.getClipBounds();
        }

        Rectangle2D.Double border = new Rectangle2D.Double(10, 10,
                clipRect.width - 20, clipRect.height - 20);

        g2d.draw(border);

        // Move the origin to the center of the square.
        drawCompass(g2d);


        g2d.setClip(border);


        int panelHeight = getSize().height;
        int panelWidth = getSize().width;

        g2d.scale(scaleFactor, -scaleFactor);
        g2d.translate(-originX + panelWidth / (2 * scaleFactor)/*panelWidth/2*scaleFactor*/, -originY - panelHeight / (2 * scaleFactor)/*panelHeight/2*scaleFactor*/);

        for (int idx = 0; idx < countyPolygons.length; idx++) {
            g2d.setColor(stateColors[((CountyMap) countyPolygons[idx]).stateNum % stateColors.length]);
            g2d.fill(countyPolygons[idx]);
            g2d.setColor(Color.BLACK);
            g2d.draw(countyPolygons[idx]);
        }

//	if (scaleFactor > .0020) {
//	if (scaleFactor > .0010) {
        drawNames(g2d);

        drawCurrentLocation(g2d);

//	}
        repaint();
    }

    private void drawCompass(Graphics2D g2d) {
        g2d.setPaint(Color.blue);
        g2d.translate(clipRect.width - 30, 20);
        int[] xpoints = {5, 10, 10, 6, 6, 4, 4, 0, 0};
        int[] ypoints = {0, 5, 7, 2, 30, 30, 2, 7, 5};
        Polygon arrow = new Polygon(xpoints, ypoints, xpoints.length);
        g2d.fill(arrow);
        g2d.setPaint(Color.black);
        g2d.drawString("N", 0, 20);
        g2d.translate(-(clipRect.width - 30), -20);
    }

    private void drawNames(Graphics2D g2d) {
        boolean tmp = true;
        for (int idx = 0; idx < stations.length; idx++) {
            if (stations[idx] == null) {
                continue;
            }
            Rectangle l_rect = stations[idx].getPoly().getBounds();
            if (stations[idx].icon_loc.equals("images/cligen.gif")) {
                g2d.setColor(Color.gray);
            } else {
                g2d.setColor(Color.blue);
            }
            g2d.drawImage(stations[idx].getImage(), l_rect.x, l_rect.y, l_rect.width, l_rect.height, null);
        //g2d.fill(stations[idx].getPoly());
        }
    }

    private void drawCurrentLocation(Graphics2D g2d) {
        int radius = (int) (10 / scaleFactor);



        int lineWidth = (int) (1 / scaleFactor);
        g2d.setStroke(new BasicStroke(lineWidth));
        g2d.setPaint(Color.GRAY);
        //draw the vertical line
        g2d.drawLine(currentX, currentY - radius, currentX, currentY + radius);
        //draw the horizantal line
        g2d.drawLine(currentX - radius, currentY, currentX + radius, currentY);


        lineWidth = (int) (2 / scaleFactor);
        g2d.setPaint(Color.BLACK);
        g2d.setStroke(new BasicStroke(lineWidth));
        //draw the circle
        g2d.drawOval(currentX - radius, currentY - radius, radius * 2, radius * 2);
    }

    // .
    /**
     * super.paintComponent clears offscreen pixmap,
     * since we're using double buffering by default
     * @param g The graphics components for the re-paint object.
     */
    protected void clear(Graphics g) {
        super.paintComponent(g);
    }
    Polygon[] countyPolygons = null;
    int originX = Integer.MAX_VALUE;
    int originY = Integer.MAX_VALUE;
    int minX = Integer.MAX_VALUE;
    int minY = Integer.MAX_VALUE;
    int maxX = Integer.MAX_VALUE;
    int maxY = Integer.MAX_VALUE;
    int currentX;
    int currentY;

    /**
     * Two argument constructor that draws the maps using the county veector and places the Wind &
     * Climate generation stations in respective counties as specified by their logitudinal & latitudinal
     * location details.
     * @param V_county Vector that holds all the counties data for a state required for drawing the
     * state map and additional details for drawing the county maps within the state.
     * @param V_mainWinCli Wind and clmate genration station location details within each county of
     * the said state.
     */
    public DrawMap(Vector V_county, Vector V_mainWinCli) {
        super();

        this.V_mainWinCli = V_mainWinCli;
        MouseAdapter adapter = new MyMouseAdapter();
        addMouseListener(adapter);
        addMouseWheelListener(adapter);
        addMouseMotionListener(this);

        setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
//		setPreferredSize(new Dimension(9999, 9999));
        makeIcons();

        countyPolygons = new Polygon[V_county.size()];
        for (int idx = 0; idx < V_county.size(); idx++) {
            countyPolygons[idx] = (CountyMap) V_county.get(idx);
//			if ((idx % 1000) == 0) //System.out.println("DM_m: " + countyPolygons[idx].xpoints[0] + " " + countyPolygons[idx].ypoints[0]);
            Rectangle boundRect = countyPolygons[idx].getBounds();
            minX = (boundRect.x < minX) ? boundRect.x : minX;
            minY = (boundRect.y < minY) ? boundRect.y : minY;
            maxX = ((boundRect.x + boundRect.width) < maxX) ? (boundRect.x + boundRect.width) : maxX;
            maxY = ((boundRect.y + boundRect.height) < maxY) ? (boundRect.y + boundRect.height) : maxY;
        }
        originX = -9865477;
        originY = 3889916;

//		for (int idx = 0; idx < countyPolygons[0].npoints; idx++) {
//			//System.out.println("DM: " + countyPolygons[0].xpoints[idx] + " " + countyPolygons[0].ypoints[idx]);
//		}
        setBackground(Color.white);
        try {
            mouseMover = new Robot();
        } catch (Exception f) {
            //System.err.println("DM_DM: " + f);
        }
        dm = this;

    }

    /**
     * This method notifies when the window or frame is resized and the components need to be
     * re-arranged so they are evenly spaced relative to each other.
     * @param args Command line arguments passed that are needed if it were a stand alone application.
     */
    public static void main(String[] args) {
//		//System.out.println("DM_m: start loading county map");
        Vector V_state = StateMap.loadFile();
        V_state.add(0, new StateMap("none selected", "dy", "X0"));
        Vector V_county = CountyMap.loadFile(StateMap.ht);
//		//System.out.println("DM_m: finished loading county map " + V_county.size());
        Vector V_mainWinCli = WindCliStation.readWinCli(null, null);

        JFrame frame = new JFrame("Draw Field Test");
        frame.setBackground(Color.white);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        DrawMap df = new DrawMap(V_county, V_mainWinCli);
//		df.XLen = 100;
//		df.YLen = 80;
        frame.setSize(540, 560);
        frame.setContentPane(df);
        frame.setVisible(true);
    }
    private boolean winFlg = true;
    private boolean cliFlg = true;

    /**
     * Sets the flag for making the wind stations visible or hidden.
     * @param winFlg True makes wind generation stations visible on the county maps with location
     * information, false makes it hidden.
     */
    public void setWinFlg(boolean winFlg) {
        this.winFlg = winFlg;
        makeIcons();
        this.invalidate();
//		this.repaint();
        paintComponent(g2d);
    }

    /**
     *  Sets the flag for making the climate stations visible or hidden.
     * @param cliFlg True makes climate generations station visible on the county maps with location
     * information, false makes it hidden.
     */
    public void setCliFlg(boolean cliFlg) {
        this.cliFlg = cliFlg;
        makeIcons();
        invalidate();
        repaint();
    }

    public void setLatLong(double lat, double lon) {
//                AffineTransform rat = g2d.getTransform();
//                Point2D real = new Point2D.Double();
//                Point2D display = new Point2D.Double();
//
//                real.setLocation(lon, lat);
//
//                rat.transform(real, display);

        originX = (int) (lon * 100000);
        originY = (int) (lat * 100000);
        currentX = originX;
        currentY = originY;
        scaleFactor = INITAL_ZOOM * 3;
        makeIcons();


    }

    private void makeIcons() {
        int size = 8;
//		icons = new Polygon[V_mainWinCli.size()];
        stations = new Station[V_mainWinCli.size()];
        for (int idx = 0; idx < V_mainWinCli.size(); idx++) {
            WindCliStation cws = (WindCliStation) V_mainWinCli.get(idx);
            int lat = (int) (cws.lat * 100000);
            int lon = (int) (cws.lon * 100000);
            if (cws.type == cws.WinStation && winFlg) {
                Polygon windIcon = new Polygon();
                windIcon.addPoint((int) (size / scaleFactor) + lon, (int) (size / scaleFactor) + lat);
                windIcon.addPoint((int) (size / scaleFactor) + lon, (int) (-size / scaleFactor) + lat);
                windIcon.addPoint((int) (-size / scaleFactor) + lon, (int) (-size / scaleFactor) + lat);
                windIcon.addPoint((int) (-size / scaleFactor) + lon, (int) (size / scaleFactor) + lat);
                // icons[idx] = windIcon;
                stations[idx] = new Station(windIcon, "images/wingen.gif");
            } else if (cws.type == cws.CliStation && cliFlg) {
                Polygon cliIcon = new Polygon();
                cliIcon.addPoint((int) (size / scaleFactor) + lon, (int) (size / scaleFactor) + lat);
                cliIcon.addPoint((int) (size / scaleFactor) + lon, (int) (-size / scaleFactor) + lat);
                cliIcon.addPoint((int) (-size / scaleFactor) + lon, (int) (-size / scaleFactor) + lat);
                cliIcon.addPoint((int) (-size / scaleFactor) + lon, (int) (size / scaleFactor) + lat);
                // icons[idx] = cliIcon;
                stations[idx] = new Station(cliIcon, "images/cligen.gif");
            }
        }
    }
    Vector V_mainWinCli = null;

    /**
     * Does nothing .. don't know why it is here
     * @param e Event generated when mouse movements happen over the mapped area
     * from one point to another
     */
    public void mouseDragged(MouseEvent e) {
    }
    Point2D.Double srcpt = new Point2D.Double();
    Point2D.Double dstpt = new Point2D.Double();

    private String findCounty(int lon, int lat) {
        for (int idx = 0; idx < countyPolygons.length; idx++) {
            if (countyPolygons[idx].contains(lon, lat)) {
                return countyPolygons[idx].toString();
            }
        }
        return "";
    }

    /* Locate the state name.
     * Created on 10-24-05 10:50 AM
     * @param lon The longitude of the point the mouse is over.
     * @param lat The latitude of the point the mouse is over.
     * @return A string containing the state name.
     * @author Matthew Brubaker
     */
    private String findState(int lon, int lat) {
        for (int idx = 0; idx < countyPolygons.length; idx++) {
            if (countyPolygons[idx].contains(lon, lat)) //this is the right county, need to figure out what state we're in
            {
                return ((CountyMap) countyPolygons[idx]).state;
            }
        }
        return "";
    }

    private String findStation(int lon, int lat) {
////System.out.println("DM_fS: " + icons.length + " " + V_mainWinCli.size());
        for (int idx = 0; idx < stations.length; idx++) {
//			if (icons[idx].contains(lon, lat)) return ((WindCliStation) V_mainWinCli.get(idx)).toString();
            try {
                if (stations[idx].contains(lon, lat)) {
////System.out.println("DM_fS: " +				((WindCliStation) V_mainWinCli.get(idx)).getStationInfo());
                    return ((WindCliStation) V_mainWinCli.get(idx)).getStationInfo();
                }
            } catch (Exception e) {
//				//System.out.println("DM_fS: " + idx);
            }
        }
        return "";
    }

    /**
     * Tries to find all available location data for the current mouse pointer position.
     * @param e The mouse move event that fires the property change event to gather the
     * station location data for the current pointer location.
     */
    public void mouseMoved(MouseEvent e) {
        try {
            AffineTransform rat = g2d.getTransform().createInverse();
            srcpt.setLocation(e.getX(), e.getY());
            rat.transform(srcpt, dstpt);
//			//System.out.println("DM_mM: " + dstpt.getX() + " " + dstpt.getY());
//			//System.out.println("DM_mM: " + findCounty((int) dstpt.getX(),(int) dstpt.getY()));
//			//System.out.println("DM_mM: " + findStation((int) dstpt.getX(),(int) dstpt.getY()));
            // 10-24-05 10:50 AM -- Matthew Brubaker
            changes.firePropertyChange(RunFileData.State, null, findState((int) dstpt.getX(), (int) dstpt.getY()));
            changes.firePropertyChange(RunFileData.LatLong, null, ((int) dstpt.getX()) + ";" + ((int) dstpt.getY()));
            changes.firePropertyChange(RunFileData.Site, null, findCounty((int) dstpt.getX(), (int) dstpt.getY()));
            changes.firePropertyChange(StationData, null, findStation((int) dstpt.getX(), (int) dstpt.getY()));
        } catch (NoninvertibleTransformException ex) {
            Logger.getLogger(DrawMap.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
    private Robot mouseMover = null;
    private DrawMap dm = null;
    private int buttonSelected = selectButton;
    /**
     * Tells if the map is centered or not.
     */
    public static final int centerButton = 0;
    /**
     * Indicates the selection of zoom-in option
     */
    public static final int zoomInButton = 1;
    /**
     * Indicates the selection of zoom-out option
     */
    public static final int zoomOutButton = 2;
    public static final int selectButton = 4;

    /**
     * Provides information on whether the button is selected or not.
     * @param button Any +ve integer means the button is selected, else not.
     */
    public void setButton(int button) {
        buttonSelected = button;
    }

    private void zoomIn(MouseEvent e) {
        center(e);
        scaleFactor *= 2;
        makeIcons();
    }

    private void zoomOut(MouseEvent e) {
        center(e);
        scaleFactor *= .5;
        makeIcons();
    }

    private void center(MouseEvent e) {
        if (e == null) {
            return;
        }
        originX -= (getSize().width / 2 - e.getX()) / scaleFactor;
        originY += (getSize().height / 2 - e.getY()) / scaleFactor;
    }

    private void select(MouseEvent e) {
        center(e);
        changes.firePropertyChange(Commit, null, Commit);
    }

    class MyMouseAdapter extends MouseAdapter {

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            if (e.isControlDown()) {
                center(e);
            }
            double zoomFactor = 1.1;
            zoomFactor = zoomFactor * Math.abs(e.getWheelRotation());
            if (e.getWheelRotation() > 0) {
                //Zoom in
                zoomFactor = 1 / zoomFactor;
            }
            scaleFactor *= zoomFactor;
            makeIcons();
        }

        /**
         * Provides information on whether the button is selected or not.
         * @param e Mouse click event recognised for the purpose of location information
         * gatheration and to recognize where the pointer is.
         */
        public void mouseClicked(MouseEvent e) {

            //map modes
            switch (buttonSelected) {
                case selectButton:  //Selecting/Auto
                    if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
                        select(e);
                    } else if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1 && e.isControlDown()) {
                        center(e);
                    } else if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1 && e.isShiftDown()) {
                        zoomIn(e);
                    } else if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() == 1 && e.isShiftDown()) {
                        zoomOut(e);
                    }
                    break;
                case centerButton:  //Centering
                    if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1) {
                        center(e);
                    }
                    break;
                case zoomInButton:  //Zoomin
                    if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1) {
                        zoomIn(e);
                    }
                    break;

                case zoomOutButton: //Zoomout
                    if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1) {
                        zoomOut(e);
                    }
                    break;
            }
        }
    }
    private PropertyChangeSupport changes = new PropertyChangeSupport(this);

    /**
     * Allows the container to add or register some other components to recognize the changes that occur
     * on this component.
     * @param l The listener that listens and reacts towards the the changes to be reflected.
     */
    public void addPropertyChangeListener(PropertyChangeListener l) {
        changes.addPropertyChangeListener(l);
    }

    /**
     * Allows the container to remove or de-register some other components to no longer recognize the
     * changes that occur on this component.
     * @param l The listener that listens and reacts towards the the changes to be reflected.
     */
    public void removePropertyChangeListener(PropertyChangeListener l) {
        changes.removePropertyChangeListener(l);
    }

    /* Wrapper class for the stations.  It contains a Polygon representing the area of the station and a
 * String that holds the name of a file to be used as an icon for the station.
 */

private class Station {

    Polygon station;
    String icon_loc;
    Image image;

    /* Constructor for a Station object.  A Station object is a wrapper that contains an
     * area indicitive of a location as well as a file name for an icon to represent the
     * station.
     * @param x The x value of the top left point where the icon will be displayed.
     * @param y The y value of the top left point where the icon will be displayed.
     * @param width The width of the icon.
     * @param height The height of the icon.
     * @param s The filename of the graphic to be used.
     */
    Station(Polygon p, String s) {
        icon_loc = s;
        station = p;
        try {
            image = new ImageIcon(icon_loc).getImage();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /* Determines whether the specified coordinates are part of this Station
     * @param x The x coordinate to be tested.
     * @param y The y coordinate to be tested.
     * @return true if this Station contains the point, false otherwise
     */
    boolean contains(int x, int y) {
        return station.contains(x, y);
    }

    /* Returns the polygon used to represent this Station.
     * @return The polygon used to represent this Station.
     */
    Polygon getPoly() {
        return station;
    }

    /* Returns the Image to be used to represent this Station.
     * @return The Image used to represent this Station.
     */
    Image getImage() {
        return image;
    }
}
}
