import java.awt.Color;
import java.awt.Container;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.TimeZone;
import java.util.Vector;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

// Map data from http://www.mccurley.org/svg/data/states.svg

public class Map extends JFrame implements ActionListener {
    private static final String fileName = "state_data";
    private Vector<State> states = new Vector<State>(51);
    private Vector<Poll> polls = new Vector<Poll>(51);
    final StatePanel statePanel = new StatePanel();
    final JPanel scorePanel = new JPanel();
    JScrollPane pollPanel = null;
    final JPanel choicePanel = new JPanel();
    final JPanel rightPanel = new JPanel();
    static final Color LIGHTBLUE = new Color(173, 216, 230);
    static final Color LIGHTRED = new Color(240, 128, 128);
    private Status currentStatus = Status.CLEAR;
    JLabel winnerLabel = new JLabel();
    JLabel score = new JLabel("Biden 0  Trump 0");
    JList pollList = null;
    int UTCOffset = TimeZone.getDefault().getRawOffset();
    /*
      year - the year minus 1900.
      month - the month between 0-11.
      date - the day of the month between 1-31.
    */
    // 11/6/12
    /*
    final static int YEAR = 112;
    final static int MONTH = 10;
    final static int DAY = 7;
    */
    // 11/8/16
    /*
    final static int YEAR = 116;
    final static int MONTH = 10;
    final static int DAY = 9;
    */
    // 11/3/20
    final static int YEAR = 120;
    final static int MONTH = 10;
    final static int DAY = 4;

    public static void main(String[] paramArrayOfString) {
	final Map map = new Map();
	
	SwingUtilities.invokeLater(new Runnable() {
		public void run() {
		    map.init();
		}
	    });
    }
    
    public void init() {
	BorderLayout borderLayout = new BorderLayout();
	GridLayout gridLayout = new GridLayout(3, 0);
	Container rootPanel = getContentPane();
	
	setDefaultCloseOperation(EXIT_ON_CLOSE);
	setSize(1350, 520);
	readStateData();
	createScorePanel();
	createChoicePanel();
	readPollTimes();

	rightPanel.setLayout(gridLayout);
	rightPanel.add(scorePanel);
	rightPanel.add(pollPanel);
	rightPanel.add(choicePanel);

	rootPanel.setLayout(borderLayout);
	rootPanel.add(statePanel, BorderLayout.CENTER);
	rootPanel.add(rightPanel, BorderLayout.EAST);

	setVisible(true);

	new PollThread().start();
    }
    
    private void readStateData() {
	String line = null;
	String name = null;
	Vector<String> coords = null;
	String centroid = null;
	int i = 0;
	char y2000 = ' ';
	char y2004 = ' ';
	char y2008 = ' ';
	char y2012 = ' ';
	char y2016 = ' ';

	try {
	    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("state_data")));
	    while ((line = bufferedReader.readLine()) != null) {
		if (line.startsWith("id")) {
		    if (name != null)
			states.add(new State(name, coords, centroid, i, y2000, y2004, y2008, y2012, y2016));
		    name = line.split("=")[1];
		    coords = new Vector<String>();
		}
		else if (line.startsWith("polygon")) {
		    coords.add(line.split("=")[1]);
		} else if (line.startsWith("centroid")) {
		    centroid = line.split("=")[1];
		} else if (line.startsWith("votes")) {
		    i = Integer.valueOf(line.split("=")[1]).intValue();
		} else if (line.startsWith("2000")) {
		    y2000 = line.split("=")[1].charAt(0);
		} else if (line.startsWith("2004")) {
		    y2004 = line.split("=")[1].charAt(0);
		} else if (line.startsWith("2008")) {
		    y2008 = line.split("=")[1].charAt(0);
		} else if (line.startsWith("2012")) {
		    y2012 = line.split("=")[1].charAt(0);
		} else if (line.startsWith("2016")) {
		    y2016 = line.split("=")[1].charAt(0);
		}
	    }
	    if (name != null)
		states.add(new State(name, coords, centroid, i, y2000, y2004, y2008, y2012, y2016)); 
	} catch (Exception ex) { System.out.println(ex); }
    }
    
    public void mouseClicked(MouseEvent mouseEvent) {
	/*
	for (State state : states) {
	    for (Polygon polygon : state.polygons) {
		if (polygon.contains(mouseEvent.getX(), mouseEvent.getY())) { 
		    state.status = currentStatus;
		    recalculate();
		    return;
		}
	    }
	}
	*/
    }
    
    public void mouseEntered(MouseEvent mouseEvent) {}
    public void mouseExited(MouseEvent mouseEvent) {}
    public void mousePressed(MouseEvent mouseEvent) {}
    public void mouseReleased(MouseEvent mouseEvent) {}

    private void recalculate()
    {
	int dVotes = 0;
	int rVotes = 0;
	for (State state : states) {
	    switch (state.status) {
	    case PROJECTED_D:
		dVotes += state.votes;
		break;
	    case PROJECTED_R:
		rVotes += state.votes;
		break;
	    case PROPOSED_D:
		dVotes += state.votes;
		break;
	    case PROPOSED_R:
		rVotes += state.votes;
	    }
	    
	}

	score.setText("Biden " + dVotes + "  Trump " + rVotes);
	String winner = dVotes > 269 ? "BIDEN" : rVotes >= 269 ? "TRUMP" : null;
	if (winner != null)
	    winnerLabel.setText(winner + " WINS");
	else
	    winnerLabel.setText("");

	/*
	 * moved to dedicated thread
	 *
	DefaultListModel model = (DefaultListModel)pollList.getModel();
	Date now = new Date();
	if (((Poll)model.firstElement()).time.before(now)) {
	    for (int i = 0; i < model.getSize(); i++) {
		if (((Poll)model.getElementAt(i)).time.after(now)) {
		    model.removeRange(0, i - 1);
		    break;
		}
	    }
	}
	*/

	repaint();
    }

    private void createScorePanel() {
	GridLayout gridLayout = new GridLayout(2, 0);
	scorePanel.setLayout(gridLayout);
	scorePanel.add(winnerLabel);
	Font font = new Font("Monospaced", Font.PLAIN, 20);
	winnerLabel.setFont(font);
	scorePanel.add(score);
    }

    private void createChoicePanel() {
	JPanel row1 = new JPanel();
	JPanel row2 = new JPanel();
	JPanel row3 = new JPanel();
	JPanel row4 = new JPanel();
	JPanel row5 = new JPanel();
	JButton button1 = new JButton("BIDEN Projected");
	JButton button2 = new JButton("BIDEN Proposed");
	JButton button3 = new JButton("TRUMP Projected");
	JButton button4 = new JButton("TRUMP Proposed");
	JButton button5 = new JButton("2000 Results");
	JButton button6 = new JButton("2004 Results");
	JButton button7 = new JButton("2008 Results");
	JButton button8 = new JButton("2012 Results");
	JButton button9 = new JButton("2016 Results");
	JButton button10 = new JButton("Clear State");
	JButton button11 = new JButton("Clear Country");
	GridLayout gridLayout = new GridLayout(5, 2);
	choicePanel.setLayout(gridLayout);
	
	button1.setBackground(Color.BLUE);
	button2.setBackground(LIGHTBLUE);
	button1.addActionListener(this);
	button2.addActionListener(this);
	row1.add(button1);
	row1.add(button2);
	
	button3.setBackground(Color.RED);
	button4.setBackground(LIGHTRED);
	button3.addActionListener(this);
	button4.addActionListener(this);
	row2.add(button3);
	row2.add(button4);
	
	button5.addActionListener(this);
	button6.addActionListener(this);
	row3.add(button5);
	row3.add(button6);
	
	button7.addActionListener(this);
	button8.addActionListener(this);
	button9.addActionListener(this);
	row4.add(button7);
	row4.add(button8);
	row4.add(button9);
	
	button10.addActionListener(this);
	button11.addActionListener(this);
	row5.add(button10);
	row5.add(button11);
	
	choicePanel.add(row1, "Center");
	choicePanel.add(row2, "Center");
	choicePanel.add(row3, "Center");
	choicePanel.add(row4, "Center");
	choicePanel.add(row5, "Center");
    }
    
    public void actionPerformed(ActionEvent actionEvent) {
	if (actionEvent.getActionCommand().equals("BIDEN Projected")) {
	    currentStatus = Status.PROJECTED_D;
	}
	if (actionEvent.getActionCommand().equals("BIDEN Proposed")) {
	    currentStatus = Status.PROPOSED_D;
	}
	if (actionEvent.getActionCommand().equals("TRUMP Projected")) {
	    currentStatus = Status.PROJECTED_R;
	}
	if (actionEvent.getActionCommand().equals("TRUMP Proposed")) {
	    currentStatus = Status.PROPOSED_R;
	}
	if (actionEvent.getActionCommand().equals("2000 Results")) {
	    for (State state : states)
		if ((state.status != Status.PROJECTED_D) && (state.status != Status.PROJECTED_R))
		    state.status = getYear(state, 2000);
	}
	if (actionEvent.getActionCommand().equals("2004 Results")) {
	    for (State state : states)
		if ((state.status != Status.PROJECTED_D) && (state.status != Status.PROJECTED_R))
		    state.status = getYear(state, 2004);
	}
	if (actionEvent.getActionCommand().equals("2008 Results")) {
	    for (State state : states)
		if ((state.status != Status.PROJECTED_D) && (state.status != Status.PROJECTED_R))
		    state.status = getYear(state, 2008);
	}
	if (actionEvent.getActionCommand().equals("2012 Results")) {
	    for (State state : states)
		if ((state.status != Status.PROJECTED_D) && (state.status != Status.PROJECTED_R))
		    state.status = getYear(state, 2012);
	}
	if (actionEvent.getActionCommand().equals("2016 Results")) {
	    for (State state : states)
		if ((state.status != Status.PROJECTED_D) && (state.status != Status.PROJECTED_R))
		    state.status = getYear(state, 2016);
	}
	if (actionEvent.getActionCommand().equals("Clear State")) {
	    currentStatus = Status.CLEAR;
	}
	if (actionEvent.getActionCommand().equals("Clear Country")) {
	    for (State state : states)
		state.status = Status.CLEAR;
	}
	recalculate();
    }

    private Status getYear(State state, int year) {
	Status status = null;
	switch (year) {
	case 2000:
	    status = state.y2000;
	    break;
	case 2004:
	    status = state.y2004;
	    break;
	case 2008:
	    status = state.y2008;
	    break;
	case 2012:
	    status = state.y2012;
	    break;
	case 2016:
	    status = state.y2016;
	    break;
	}
	return status;
    }

    private void readPollTimes() {
	String line = null;
	String name = null;
	Integer hour = null;
	Integer minute = null;
	Date time = null;
	DefaultListModel model = new DefaultListModel();

	try {
	    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("poll_closing")));
	    while ((line = bufferedReader.readLine()) != null) {
		name = line.split("=")[0];
		hour = Integer.valueOf(line.split("=")[1]);
		minute = Integer.valueOf(line.split("=")[2]);
		time = new Date(new Date(YEAR, MONTH, DAY, hour, minute).getTime() + UTCOffset);
		model.addElement(new Poll(name, time));
	    }
	} catch (Exception ex) { System.out.println(ex); }
	pollList = new JList(model);
	pollPanel = new JScrollPane(pollList);
    }

    public class PollThread extends Thread {

	public void run() {
	    while (true) {
		DefaultListModel model = (DefaultListModel)pollList.getModel();
		Date now = new Date();
		if (model.size() > 0 && ((Poll)model.firstElement()).time.before(now)) {
		    boolean allDone = true;
		    for (int i = 0; i < model.getSize(); i++) {
			if (((Poll)model.getElementAt(i)).time.after(now)) {
			    model.removeRange(0, i - 1);
			    allDone = false;
			    break;
			}
		    }
		    if (allDone) {
			model.removeRange(0, model.getSize() -1);
		    }
		}

		repaint();

		try {
		    sleep(60000);
		} catch (Exception ex) {}
	    }
	}

    }

    public class StatePanel extends JPanel implements MouseListener {

	public StatePanel() {
	    super();
	    addMouseListener(this);
	}

	public void mouseClicked(MouseEvent mouseEvent) {
	    for (State state : states) {
		for (Polygon polygon : state.polygons) {
		    if (polygon.contains(mouseEvent.getX(), mouseEvent.getY())) { 
			state.status = currentStatus;
			recalculate();
			return;
		    }
		}
	    }
	}
    
	public void mouseEntered(MouseEvent mouseEvent) {}
	public void mouseExited(MouseEvent mouseEvent) {}
	public void mousePressed(MouseEvent mouseEvent) {}
	public void mouseReleased(MouseEvent mouseEvent) {}

	public void paint(Graphics g) {
	    for (State state : states)
		state.draw(g);
	}
    }

    public class Poll {
	String name = null;
	Date time = null;

	public Poll(String name, Date time) {
	    this.name = name;
	    this.time = time;
	}

	public String toString() {
	    return " " + time.getHours() + ":" + (time.getMinutes() == 30 ? "30" : "00") + "  " + name;
	}
    }

    public class State {
	String name = null;
	Status status = Status.CLEAR;
	Vector<Polygon> polygons = new Vector<Polygon>();
	Point centroid = null;
	int votes = 0;
	Status y2000 = Status.CLEAR;
	Status y2004 = Status.CLEAR;
	Status y2008 = Status.CLEAR;
	Status y2012 = Status.CLEAR;
	Status y2016 = Status.CLEAR;
	
	public State(String name, Vector<String> coords, String centroid, int votes, char y2000, char y2004, char y2008, char y2012, char y2016) {
	    int x, y;
	    Float rawX, rawY;
	    float shiftX, shiftY;

	    this.name = name;
	    for (String coord : coords) {
		Polygon polygon = new Polygon();
		String[] xy = coord.split(" ");
		for (int i = 0; i < xy.length; i++) {
		    rawX = Float.valueOf(xy[i].split(",")[0]) * 15.0F;
		    rawY = Float.valueOf(xy[i].split(",")[1]) * 15.0F;
		    if (name.equals("HI")) {
			rawX += 50 * 15.0F;
			rawY += 6 * 15.0F;
		    }
		    if (name.equals("AK")) {
			shiftX = 0.75F * (rawX + (178 * 15.0F));
			shiftY = 0.75F * (rawY - (71 * 15.0F));
			rawX += 55 * 15.0F - shiftX;
			rawY += -42 * 15.0F - shiftY;
		    }
		    x = rawX.intValue() + 1900;
		    y = 800 - rawY.intValue();
		    polygon.addPoint(x, y);
		}
		polygons.add(polygon);
	    }
	    rawX = Float.valueOf(centroid.split(",")[0]) * 15.0F;
	    rawY = Float.valueOf(centroid.split(",")[1]) * 15.0F;
	    if (name.equals("HI")) {
		rawX += 50 * 15.0F;
		rawY += 6 * 15.0F;
	    }
	    if (name.equals("AK")) {
		shiftX = 0.75F * (rawX + (178 * 15.0F));
		shiftY = 0.75F * (rawY - (71 * 15.0F));
		rawX += 55 * 15.0F - shiftX;
		rawY += -42 * 15.0F - shiftY;
	    }
	    x = rawX.intValue() + 1900;
	    y = 800 - rawY.intValue();
	    this.centroid = new Point(x, y);
	    this.votes = votes;
	    this.y2000 = getColor(y2000);
	    this.y2004 = getColor(y2004);
	    this.y2008 = getColor(y2008);
	    this.y2012 = getColor(y2012);
	    this.y2016 = getColor(y2016);
	}

	private Status getColor(char party) {
	    if (party == 'D') {
		return Status.PROPOSED_D;
	    }
	    return Status.PROPOSED_R;
	}
	
	public void draw(Graphics g) {
	    Color color = Color.GRAY;
	    switch (status) {
	    case CLEAR:
		color = Color.GRAY;
		break;
	    case PROJECTED_D:
		color = Color.BLUE;
		break;
	    case PROJECTED_R:
		color = Color.RED;
		break;
	    case PROPOSED_D:
		color = LIGHTBLUE;
		break;
	    case PROPOSED_R:
		color = LIGHTRED;
	    }
	    
	    for (Polygon polygon : polygons) {
		g.setColor(color);
		g.fillPolygon(polygon);
		g.setColor(Color.BLACK);
		g.drawPolygon(polygon);
	    }
	    String str = name + " " + String.valueOf(votes);
	    g.drawString(str, (int)centroid.getX(), (int)centroid.getY());
	}
    }
    
    public static enum Status
    {
	CLEAR, PROJECTED_D, PROJECTED_R, PROPOSED_D, PROPOSED_R;
    }
}
