package unileipzig.isys.neuronviz.kernel;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Vector;

import javax.swing.JComponent;

/**
 * Class NeuronComponent
 * 
 */
public class NeuronComponent extends JComponent implements MouseMotionListener, MouseListener
{
	private Point startPoint, endPoint;
	private int rasterSize = 20;
//	private int neuronSize = 20;
//	private Point neuronCirclePos;
	private int activationSize = 10;
	private String neuronName, biasName;
	private double outputValue, biasValue;
	private Vector inputValueList, inputNeuronList;
	private boolean activationVisibility = false, biasVisibility = false;

	public NeuronComponent()
	{
		inputValueList = new Vector();
		inputNeuronList = new Vector();
		
		setLayout(null);
		addMouseMotionListener(this);
		addMouseListener(this);
		
		setPreferredSize(new Dimension(50,50));

		setBounds(getLocation().x, getLocation().y, 50, 50);
		
//		neuronCirclePos = new Point((getBounds().width-neuronSize)/2, (getBounds().height-neuronSize)/2);
		
		endPoint = startPoint = new Point(0, 0);
	}

	public void paintComponent(Graphics g)
	{
		// use the extended possibilities of Graphics2D
		Graphics2D g2 = (Graphics2D) g;

		// switch on antialising for pretty rendering results
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		
		// resize the component if necessary
		int width = inputValueList.size() * (activationSize+1) + 1;
		if (width > getBounds().width) setBounds(getLocation().x, getLocation().y, width, getBounds().height);
		
		// fill the background of the component
		g2.setColor(new Color(250,250,100));
		g2.fillRect(0, 0, getBounds().width, getBounds().height);
		
		// draw input activation region
		g2.setColor(new Color(0,0,0));
		g2.fillRect(0, getBounds().height - activationSize-2, getBounds().width, activationSize*2);
		
		// draw a frame around the whole component
		g2.setColor(new Color(0,0,0));
		g2.drawRect(0, 0, getBounds().width-1, getBounds().height-1);
		
		// print out the neuron name
		g2.drawString(neuronName, 5, 25);
		
		// draw the output, use a color corresponding to the neuron's activation
		if(isActivationVisibility())
			g2.setColor(getNeuronColor());
		else
			g2.setColor(Color.black);
		
		g2.fillRect(1, 1, getBounds().width-2, 10);
		g2.setColor(Color.black);
		g2.drawRect(0, 0, getBounds().width-1, 10);
		
		// draw the bias
		if(isBiasVisibility())
		{
			g2.setColor(getNeuronColor(getBiasValue()));
			g2.fillRect(1,1, 13, 13);
			g2.setColor(Color.black);
			g2.drawRect(0, 0, 13, 13);
		}

//		g2.fillOval(neuronCirclePos.x, neuronCirclePos.y, neuronSize, neuronSize);
//		g2.setColor(Color.black);
//		g2.drawOval(neuronCirclePos.x, neuronCirclePos.y, neuronSize, neuronSize);

		// draw a visualization of the inputs
		// draw the activation fields centered
		int offset = (getBounds().width - width) / 2;

		Object [] obj = inputNeuronList.toArray();
		for(int i=0; i<obj.length; i++)
		{
			// find minimum (smallest x position) in the neuron list
			int min = Integer.MAX_VALUE;
			int min_index = -1;
			for(int j=0; j<obj.length; j++)
			{
				if (obj[j] == null) continue;
				int x = ((NeuronComponent)(obj[j])).getX();
				if (x < min) 
				{
					min = x;
					min_index = j;
				}
			}
			obj[min_index] = null;
			double value = ((Double)(inputValueList.get(min_index))).doubleValue();

			// use a color that indicates the activation level
			g2.setColor(getNeuronColor(value));
			g2.fillRect(i*(activationSize+1) + offset + 1, getBounds().height-activationSize - 1, activationSize, activationSize);
		}
		
		
		
//		for (int x=0; x<inputValueList.size(); x++)
//		{
//			double value = ((Double)(inputValueList.get(x))).doubleValue();
//			
//			// use a color that indicates the activation level
//			g2.setColor(getNeuronColor(value));
//			g2.fillRect(x*(activationSize+1) + offset + 1, getBounds().height-activationSize - 2, activationSize, activationSize);
//		}
	}
	
	
	/**
	 * Get a color indicating the output value of the neuron
	 * @return
	 */
	private Color getNeuronColor()
	{		
		return getNeuronColor(outputValue);
	}
	
	/**
	 * Get a color representation of parameter value
	 * @param value
	 * @return
	 */
	private Color getNeuronColor(double value)
	{
		Color col;
		
		int c = (int)(255 * value);
		if (c>255) c = 255;
		if (c<-255) c = -255;
		
		if (c<0) col = new Color(255, 255+c, 255+c);
		else col = new Color(255-c, 255-c, 255);
		
		return col;
	}

	/**
	 * Deletes the input lists. This has to be done before a new channel data line is read. After deleting the old inputs
	 * the new inputs of the new data line can be inserted.
	 *
	 */
	public void clearInputList()
	{
		inputValueList.clear();
		inputNeuronList.clear();
	}
	
	/**
	 * Adds a new input to the neuron. The input can now be visualized after repainting the component.
	 * @param value
	 */
	public void addInputValue(double value, NeuronComponent fromNeuron)
	{
		inputValueList.add(new Double(value));
		inputNeuronList.add(fromNeuron);
	}

	
	
	/*
	 * Listen to the mouse events and mouse motion events
	 */
	
	public void mouseDragged(MouseEvent e)
	{
		e.consume();
		
		endPoint = e.getPoint();
		endPoint.x -= startPoint.x;
		endPoint.y -= startPoint.y;
		endPoint.x = Math.round(endPoint.x / rasterSize) * rasterSize;
		endPoint.y = Math.round(endPoint.y / rasterSize) * rasterSize;

		setLocation(getX() + endPoint.x, getY() + endPoint.y);
		
		getParent().repaint();
	}

	public void mouseMoved(MouseEvent e)
	{
	}

	public void mouseClicked(MouseEvent e)
	{
	}

	public void mousePressed(MouseEvent e)
	{
		e.consume();
		startPoint = e.getPoint();
	}

	public void mouseReleased(MouseEvent e)
	{
		e.consume();
		endPoint = startPoint = new Point(0, 0);
	}

	public void mouseEntered(MouseEvent e)
	{
	}

	public void mouseExited(MouseEvent e)
	{
	}

	/////////////////////////////////
	// {{{ Getter- and Setter-Methods

	public String getNeuronName()
	{
		return neuronName;
	}

	public void setNeuronName(String neuronName)
	{
		this.neuronName = neuronName;
	}

	public int getRasterSize()
	{
		return rasterSize;
	}

	public void setRasterSize(int rasterSize)
	{
		this.rasterSize = rasterSize;
	}

	public double getOutputValue()
	{
		return outputValue;
	}

	public void setOutputValue(double outputValue)
	{
		this.outputValue = outputValue;
	}

	public boolean isActivationVisibility()
	{
		return activationVisibility;
	}

	public void setActivationVisibility(boolean activationVisibility)
	{
		this.activationVisibility = activationVisibility;
	}

	public String getBiasName()
	{
		return biasName;
	}

	public void setBiasName(String biasName)
	{
		this.biasName = biasName;
	}

	public double getBiasValue()
	{
		return biasValue;
	}

	public void setBiasValue(double biasValue)
	{
		this.biasValue = biasValue;
	}

	public boolean isBiasVisibility()
	{
		return biasVisibility;
	}

	public void setBiasVisibility(boolean biasVisibility)
	{
		this.biasVisibility = biasVisibility;
	}

	// Getter- and Setter-Methods }}}
	/////////////////////////////////
}
