package de.roboshock.gui.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;


@SuppressWarnings("serial")
public class PcGraph extends JPanel {

	public Double MIN = 0.0;
	public Double MAX = 100.0;
	public boolean autoRange = true;
	public int stepSize = 4;
	public boolean MAKE_DOTS = true;
	
	private boolean freeRunOn = false;
	private Timer timer;

	private Map<Color, List<Double>> reihen = new LinkedHashMap<Color, List<Double>>();
	private Map<Color, Double>       nextValue = new HashMap<Color, Double>();
	private Map<Color, String>       legendMap = new HashMap<Color, String>();
	private List<Double>         supportlines = new ArrayList<Double>();
	private List<Integer>        supportlinesX = new ArrayList<>();
	private Map<Integer, String> supportStringsX = new HashMap<>();
	private int stepCount = 0;


	public PcGraph() {
	    
	}
	
	public void freeRun(boolean on, int ticksInMillis) {
		if (on) {
			freeRunOn = true;
		    ActionListener stepper = new ActionListener() {
		    	public void actionPerformed(ActionEvent evt) {
		    		step();
		    	}
		    };
		    timer = new Timer( ticksInMillis, stepper );  // Fires every 0.5 sec
		    timer.start();
		}
		else {
			timer.stop();
			freeRunOn = false;
		}
	}
	
	/**
	 * There is no need to call this from the swing thread.
	 */
	public void setNextValue(Color c, double d) {
		if (! reihen.containsKey(c)) {
			defineRow(c);
		}
		nextValue.put(c, d);
	}
	
	/**
	 * This can be called from any thread, not only from the swing thread.
	 */
	public void step() {
		stepCount++;
		if (freeRunOn) {
			System.err.println("step() is not possible - freeRun is active!");
			return;
		}
		for (Color c: reihen.keySet()) {
    		addValue(reihen.get(c), nextValue.get(c));
		}
		repaint();
	}
	
	private boolean defineRow(Color color) {
		reihen.put(color, new ArrayList<Double>());
		return true;
	}

	public void addSupportline(double d) {
		supportlines.add(d);
	}
	
	int maxGraphWidth = 2000;

	public void addSupportlineX() {
		supportlinesX.add(stepCount);
		if (supportlinesX.size() > maxGraphWidth) {
			supportlinesX.remove(0);
		}
	}
	
	public void addSupportStringX(String s) {
		supportStringsX.put(stepCount, s);
		Set<Integer> all = new HashSet<>(supportStringsX.keySet());
		for (int step: all) {
			if (step < stepCount - maxGraphWidth) {
				supportStringsX.remove(step);
			}
		}
	}
	
	public void addLegend(Color color, String legend) {
		legendMap.put(color, legend);
	}
	
	private void drawLineX(Graphics g, int step) {
		int xPos = this.getWidth() - (stepCount - step) * stepSize;
		g.drawLine(xPos, 20, xPos, this.getHeight());
	}
	
	private void drawStringX(Graphics g, int step,String s) {
		int xPos = this.getWidth() - (stepCount - step) * stepSize;
		g.drawLine(xPos, 20, xPos, this.getHeight());
		g.drawString(s, xPos, this.getHeight() - 20);
	}
	
	private void drawLine(Graphics g, double y, int xOffset) {
		int yScaled = (int) scale(y); 
		g.drawLine(20, yScaled, this.getWidth(), yScaled);
		g.drawString(Double.toString(y), 10 + xOffset, yScaled-5);
	}
	
	private void drawLineFat(Graphics g, double y, int xOffset) {
		int yScaled = (int) scale(y); 
		g.drawLine(20, yScaled, this.getWidth(), yScaled);
		g.drawLine(20, yScaled+1, this.getWidth(), yScaled+1);
		g.drawLine(20, yScaled-1, this.getWidth(), yScaled-1);
		g.drawString(Double.toString(y), 10 + xOffset, yScaled-5);
	}
	
	public void paint(Graphics g) {
		try {
			// background
			g.setColor(Color.LIGHT_GRAY);
			g.fillRect(0, 0, this.getWidth(), this.getHeight());
			
			// adjust MIN MAX values
			if (autoRange) {
				MAX = null;
				MIN = null;
				for (List<Double> reihe: reihen.values()) {
					for (Double val: reihe) {
						if (MAX == null || val > MAX) MAX = val ;
						if (MIN == null || val < MIN) MIN = val ;
					}
				}
				for (Double val: supportlines) {
					if (MAX == null || val > MAX) MAX = val ;
					if (MIN == null || val < MIN) MIN = val ;
				}
				if (MAX == null) MAX = 0.0;
				if (MIN == null) MIN = 0.0;
				MAX +=10;
				MIN -=10;
			}
			
			// draw Y support lines
			g.setColor(Color.BLACK);
			for (double d: supportlines) {
				if (d == 0.0) {
					// draw the zero line fat
					drawLineFat(g, d, 1);
				}
				else {
					drawLine(g, d, 1);
				}
			}
	
			// draw X support lines
			g.setColor(Color.BLACK);
			for (int d: supportlinesX) {
				drawLineX(g, d);
			}
	
			for (int d: supportStringsX.keySet()) {
				drawStringX(g, d, supportStringsX.get(d));
			}
	
			// draw the graph lines
			for (Color c: reihen.keySet()) {
				g.setColor(c);
				List<Double> reihe = reihen.get(c);
				int i = 0;
				for (int x = this.getWidth()-4; x >= 0; x = x - stepSize) {
					if (i >= reihe.size()) break;
					if (MAKE_DOTS) {
						g.fillOval(x, (int) scale(reihe.get(i))-2, 4, 4);
					}
					if (i < reihe.size()-2) {
						g.drawLine(x+stepSize, (int) scale(reihe.get(i)), x, (int) scale(reihe.get(i+1)));
					}
					i++;
				}
			}
		
			// write the current values as text
			g.setColor(Color.BLACK);
			g.setFont(new Font("Corier", Font.BOLD, 18));
			int entryNum = 0;
			for (Color c: reihen.keySet()) {
				g.setColor(c);
				// value
				int v = reihen.get(c).get(0).intValue();
				g.drawString(""+v, 20,  25+25*entryNum);
				// legend
				String legend = legendMap.get(c);
				if (legend != null) {
					g.drawString(legend, 70,  25+25*entryNum);
				}
				entryNum++;
			}
			
		} catch (Exception e) {	
			// ignore, may get NPE during shutdown
			System.err.println(e +  " " + e.getMessage());
		}
	}
	
	private double scale(double y) {
		y = (y - MIN) / (MAX - MIN);  // scale to 0...1
		y = this.getHeight() - y * this.getHeight();
		return y;
	}
	
	private void addValue(List<Double> reihe, double value) {
		reihe.add(0, value);
		int valuesToKeep = getWidth() / stepSize;
		if (valuesToKeep < 200) valuesToKeep = 200; 
		if (reihe.size() > valuesToKeep) {
			for (int i=reihe.size() - 1; i > valuesToKeep; i--) {
				reihe.remove(i);
			}
		}
	}
	

	// for testing and as example code
	public static void main(String arg[]) throws Exception {
		PcGraph graph = new PcGraph();
		graph.addSupportline(0);
		graph.addSupportline(40.0);
		graph.stepSize = 2;

		JFrame frame = new JFrame("Java Graph");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(500, 400);
		frame.setContentPane(graph);
		frame.setLocationRelativeTo(null); // center
		frame.setVisible(true);
		
		double x = 0;
		while (true) {
			x += 0.1;
			graph.setNextValue(Color.RED, Math.sin(x)*20.0);
			graph.setNextValue(Color.BLUE,Math.sin(x)*20.0 + x*5);
			graph.step();
			Thread.sleep(50);
		}
	}

 
}
