package org.kne.cloud.network.klalb.ui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.GeneralPath;
import java.io.IOException;
import java.net.Inet6Address;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBUtils;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;

public class GraphPanel extends JPanel {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public GraphPanel() {
		super();
		initgp();
	}

	public GraphPanel(boolean isDoubleBuffered) {
		super(isDoubleBuffered);
		initgp();
	}

	public GraphPanel(LayoutManager layout, boolean isDoubleBuffered) {
		super(layout, isDoubleBuffered);
		initgp();
	}

	public GraphPanel(LayoutManager layout) {
		super(layout);
		initgp();
	}
	
	private void initgp() {
		addMouseListener(new MouseListener() {
			
			@Override
			public void mouseReleased(MouseEvent e) {
				GraphNode node= getHitboxMatched(e);
				if(node!=null)
				node.runMouseReleased(e);
			}
			
			@Override
			public void mousePressed(MouseEvent e) {
				GraphNode node= getHitboxMatched(e);
				if(node!=null)
				node.runMousePressed(e);
			}
			
			@Override
			public void mouseExited(MouseEvent e) {
				GraphNode node= getHitboxMatched(e);
				if(node!=null)
				node.runMouseExited(e);
			}
			
			@Override
			public void mouseEntered(MouseEvent e) {
				GraphNode node= getHitboxMatched(e);
				if(node!=null)
				node.runMouseEntered(e);
			}
			
			@Override
			public void mouseClicked(MouseEvent e) {
				GraphNode node= getHitboxMatched(e);
				if(node!=null)
				node.runMouseClicked(e);
			}
		});
		
	}





	private GraphNode getHitboxMatched(MouseEvent e) {
		for (Iterator<GraphNode> iterator = nodes.values().iterator(); iterator.hasNext();) {
			GraphNode graphEdgeGroup = (GraphNode) iterator.next();
			if(graphEdgeGroup.checkPosHit(e.getX()/scale-translateX,e.getY()/scale-translateY)) {
				return graphEdgeGroup;
			}
		}
		return null;
	}





	private Map<IPv6Address,GraphNode> nodes=new ConcurrentHashMap<IPv6Address,GraphNode>();
	private List<GraphEdgeGroup> edgeGroups=new ArrayList<GraphEdgeGroup>();
	
	public Map<IPv6Address, GraphNode> getNodes() {
		return nodes;
	}

	public List<GraphEdgeGroup> getEdgeGroups() {
		return edgeGroups;
	}

	
	
	
	
	
	
	
	private double borderGap=250;
	
	private double translateX,translateY;
	
	private double scale=1;
	public Image defaultimage;
	public Image defaultimage2;
	{
	
		try {
			defaultimage=ImageIO.read(GraphPanel.class.getResourceAsStream("/assets/ipv6router1.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			defaultimage2=ImageIO.read(GraphPanel.class.getResourceAsStream("/assets/ipv6router2.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public double getTranslateX() {
		return translateX;
	}

	public double getTranslateY() {
		return translateY;
	}

	
	
	public double getScale() {
		return scale;
	}

	public void setScale(double scale) {
		this.scale = scale;
	}

	
	
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		
		Graphics2D g2d=(Graphics2D)g;

		recalcTranslate();
		g2d.scale(scale, scale);
		g2d.translate(translateX,translateY);
		double w=xmax-xmin;
		double h=ymax-ymin;
		if(w>=0&&h>=0) {
		g2d.drawRect((int)(xmin-borderGap), (int)(ymin-borderGap), (int)(w+borderGap*2), (int)(h+borderGap*2));
		}
		synchronized (edgeGroups) {
		
		for(	GraphEdgeGroup graphNode :edgeGroups) {
			graphNode.paint(g2d);
		}
			
		}
		for(GraphNode graphNode :nodes.values()) {
			graphNode.paint(g2d);
		}
		
		
	}
	public class GraphNode{
		private String text;
		private Color color;
		private double nodesize=110; 
		private double x;
		private double y;
		private boolean ismarked;
		private Vector2 delta=new Vector2(0, 0);
		private Image image=defaultimage;
		private Image image2=defaultimage2;
		public GraphNode(String text, Color color, double x, double y,boolean ismarked) {
			super();
			this.text = text;
			this.color = color;
			this.x = x;
			this.y = y;
			this.ismarked=ismarked;
		}
		
		private List<MouseListener>mouselisteners=new ArrayList<MouseListener>();
		
		public void addMouseListener(MouseListener listeners) {
			mouselisteners.add(listeners);
		}
		
		protected void runMouseClicked(MouseEvent e) {
			mouselisteners.forEach((v)->{v.mouseClicked(e);});
		}

		protected void runMouseEntered(MouseEvent e) {
			mouselisteners.forEach((v)->{v.mouseEntered(e);});
		}

		protected void runMouseExited(MouseEvent e) {
			mouselisteners.forEach((v)->{v.mouseExited(e);});
			
		}

		protected void runMousePressed(MouseEvent e) {
			mouselisteners.forEach((v)->{v.mousePressed(e);});
		}

		protected void runMouseReleased(MouseEvent e) {
			mouselisteners.forEach((v)->{v.mouseReleased(e);});
			
		}

		public boolean checkPosHit(double x2, double y2) {
			double dx=x2-x;
			double dy=y2-y;
			return Math.sqrt(dx*dx+dy*dy)<=nodesize/2;
		}

		public double getNodesize() {
			return nodesize;
		}

		public void setNodesize(double nodesize) {
			this.nodesize = nodesize;
		}

		public boolean isIsmarked() {
			return ismarked;
		}

		public void setIsmarked(boolean ismarked) {
			this.ismarked = ismarked;
		}

		public String getText() {
			return text;
		}

		public void setText(String text) {
			this.text = text;
		}
		
		
		
		public Image getImage() {
			return image;
		}

		public void setImage(Image image) {
			this.image = image;
		}
		
		public Image getImage2() {
			return image2;
		}

		public void setImage2(Image image2) {
			this.image2 = image2;
		}

		public Color getColor() {
			return color;
		}

		public void setColor(Color color) {
			this.color = color;
		}

		public double getX() {
			return x;
		}

		public void setX(int x) {
			this.x = x;
		}

		public GraphNode() {
			super();
		}

		public double getY() {
			return y;
		}

		public void setY(int y) {
			this.y = y;
		}

		protected Image getCurrentImage() {
			return ismarked?image2:image;
		}
		
		public void paint(Graphics2D g) {
			g.setColor(Color.BLACK);
			g.setStroke(new BasicStroke(2.0f));
			if(image!=null) {
				Image imgtmp=getCurrentImage();
					g.drawImage(imgtmp,(int)(x-image.getWidth(null)/2),(int)(y-image.getHeight(null)/2) ,null);
			}else {
			g.drawOval((int)(x-nodesize/2), (int)(y-nodesize/2), (int)(nodesize), (int)(nodesize));
			if(ismarked) {
				Vector2 pos=new Vector2(x, y);
				Vector2 v2a=new Vector2(0, -1).multi(nodesize/4);
				Vector2 v2b=new Vector2(Math.sqrt(3)/2.0, 0.5).multi(nodesize/4);
				Vector2 v2c=new Vector2(-Math.sqrt(3)/2.0, 0.5).multi(nodesize/4);
				v2a=v2a.add(pos);
				v2b=v2b.add(pos);
				v2c=v2c.add(pos);
				g.drawPolygon(new int[] {(int) v2a.x,(int) v2b.x,(int) v2c.x}, new int[] {(int) v2a.y,(int) v2b.y,(int) v2c.y}, 3);
				}
			}
			g.setFont(UIEnv.getFont().deriveFont(10.0f));
			
			//g.drawString(text, (int)(x+nodesize/2), (int)(y-nodesize/3)); 
			g.drawString(text, (int)(x-image.getWidth(null)/2-10), (int)(y+image.getHeight(null)/2+10)); 
		}

		public Vector2 getPositionVec2() {
			return new Vector2(x, y);
		}

		public void commitDelta() {
			x+=delta.x;
			y+=delta.y;
			delta=new Vector2(0, 0);
		}

		public void clearDelta() {
			delta=new Vector2(0, 0);
			
		}
	}
	public class GraphEdge{
		private GraphNode from;
		private GraphNode to;
		LinkDirection linkPath;
		
		public GraphEdge(GraphNode nfrom, GraphNode nto,LinkDirection lp) {
			this.from=nfrom;
			this.to=nto;
			this.linkPath=lp;
		}
		
	}
	public class GraphEdgeGroup{
		private double length=100;
		private double gap=12;
		private boolean isShortest=false;
	private	GraphNode nodeA,nodeB;
	private List<GraphEdge> edges=new ArrayList<>();
	
	
	
	public double getLength() {
		return length;
	}

	public void setLength(double length) {
		this.length = length;
	}

	public double getGap() {
		return gap;
	}

	public void setGap(double gap) {
		this.gap = gap;
	}

	public boolean match(GraphNode a,GraphNode b) {
		if(a.equals( nodeA)&&b.equals( nodeB))
			return true;
		if(b.equals( nodeA)&&a.equals( nodeB))
			return true;
		return false;
	}
	
	public GraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
		super();
		this.nodeA = nodeA;
		this.nodeB = nodeB;
	}
	
	public boolean isShortest() {
		return isShortest;
	}

	public void setShortest(boolean isShortest) {
		this.isShortest = isShortest;
	}

	public List<GraphEdge> getPaths() {
		return getEdges();
	}
	public void paint(Graphics2D g) {
		
		Vector2 nodeAv=new Vector2(nodeA.getX(), nodeA.getY());
		Vector2 nodeBv=new Vector2(nodeB.getX(), nodeB.getY());
		Vector2 vecDir=nodeBv.subtract(nodeAv);
		Vector2 vecDirnor=vecDir.normalize();
		Vector2 vecRA=vecDirnor.multi(nodeA.getNodesize()/2);
		Vector2 vecRB=vecDirnor.multi(nodeB.getNodesize()/2);
		
		Vector2 vec90nor=new Vector2( -vecDir.y, vecDir.x).normalize();
		
		//所有线路完整显示
		/*
		double width=gap*(edges.size()-1);
		//System.out.println(width);
		
		Vector2 startPoint=nodeAv.add(vecR).add(vec90nor.multi(width/2));
		Vector2 endPoint=nodeBv.subtract(vecR).add(vec90nor.multi(width/2));
		
		for (Iterator<GraphEdge> iterator = edges.iterator(); iterator.hasNext();) {
			GraphEdge graphEdge = (GraphEdge) iterator.next();

			//System.out.println(vec90nor);

			g.setColor(KLALBUtils.getColorByLoadPercentage(graphEdge.linkPath.getSpeed()*100f/graphEdge.linkPath.getBandwidth()));
			
			if(graphEdge.from.equals( nodeA)&&graphEdge.to.equals(nodeB) )
			drawAL((int)startPoint.x, (int)startPoint.y, (int)endPoint.x, (int)endPoint.y,g);
			

			if(graphEdge.to.equals( nodeA)&&graphEdge.from.equals(nodeB) )
			drawAL((int)endPoint.x, (int)endPoint.y, (int)startPoint.x, (int)startPoint.y,g);
			
			 startPoint=startPoint.subtract(vec90nor.multi(gap));
			 endPoint=endPoint.subtract(vec90nor.multi(gap));
		}
		*/
		long totalupspeed=0;
		long usedupspeed=0;
		long totaldownspeed=0;
		long useddownspeed=0;
		for(GraphEdge graphEdge :getEdges()) {
			if(graphEdge.from.equals( nodeA)&&graphEdge.to.equals(nodeB) ) {
				totalupspeed+=graphEdge.linkPath.getBandwidth();
				usedupspeed+=graphEdge.linkPath.getSpeed();
			}
				
			if(graphEdge.to.equals( nodeA)&&graphEdge.from.equals(nodeB) ) {
				totaldownspeed+=graphEdge.linkPath.getBandwidth();
				useddownspeed+=graphEdge.linkPath.getSpeed();
			}
		}
		
		

		g.setStroke(new BasicStroke((float) Math.max(2.0, Math.log10(totalupspeed/10.0/1024.0))));
		
		double width=gap*1;
		Vector2 startPoint=nodeAv.add(vecRA).add(vec90nor.multi(width/2));
		Vector2 endPoint=nodeBv.subtract(vecRB).add(vec90nor.multi(width/2));

		g.setColor(KLALBUtils.getColorByLoadPercentagex(usedupspeed*100f/totalupspeed));
		
		drawAL((int)startPoint.x, (int)startPoint.y, (int)endPoint.x, (int)endPoint.y,g);
		

		
		 startPoint=startPoint.subtract(vec90nor.multi(gap));
		 endPoint=endPoint.subtract(vec90nor.multi(gap));
		
		 

		 g.setStroke(new BasicStroke((float) Math.max(2.0, Math.log10(totaldownspeed/10.0/1024.0))));

			g.setColor(KLALBUtils.getColorByLoadPercentagex(useddownspeed*100f/totaldownspeed));
			
			

			drawAL((int)endPoint.x, (int)endPoint.y, (int)startPoint.x, (int)startPoint.y,g);
			
			 startPoint=startPoint.subtract(vec90nor.multi(gap));
			 endPoint=endPoint.subtract(vec90nor.multi(gap));
		 
			if(isShortest) {
				BasicStroke dotted = new BasicStroke(2, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND, 0, new float[]{0,3,0,3}, 0);
				 g.setStroke(dotted);
				 g.setColor(Color.BLUE);
		g.drawLine((int)nodeA.getX(), (int)nodeA.getY(), (int)nodeB.getX(), (int)nodeB.getY());
			}
		
			 
	}

	public List<GraphEdge> getEdges() {
		return edges;
	}

	
	}
	// 计算
		public static double[] rotateVec(int px, int py, double ang,
				boolean isChLen, double newLen) {
	 
			double mathstr[] = new double[2];
			// 矢量旋转函数，参数含义分别是x分量、y分量、旋转角、是否改变长度、新长度
			double vx = px * Math.cos(ang) - py * Math.sin(ang);
			double vy = px * Math.sin(ang) + py * Math.cos(ang);
			if (isChLen) {
				double d = Math.sqrt(vx * vx + vy * vy);
				vx = vx / d * newLen;
				vy = vy / d * newLen;
				mathstr[0] = vx;
				mathstr[1] = vy;
			}
			return mathstr;
		}
		public static void drawAL(int sx, int sy, int ex, int ey, Graphics2D g2)
		{
			 
			double H = 13; // 箭头高度
			double L = 8; // 底边的一半
			int x3 = 0;
			int y3 = 0;
			int x4 = 0;
			int y4 = 0;
			double awrad = Math.atan(L / H); // 箭头角度
			double arraow_len = Math.sqrt(L * L + H * H); // 箭头的长度
			double[] arrXY_1 = rotateVec(ex - sx, ey - sy, awrad, true, arraow_len);
			double[] arrXY_2 = rotateVec(ex - sx, ey - sy, -awrad, true, arraow_len);
			double x_3 = ex - arrXY_1[0]; // (x3,y3)是第一端点
			double y_3 = ey - arrXY_1[1];
			double x_4 = ex - arrXY_2[0]; // (x4,y4)是第二端点
			double y_4 = ey - arrXY_2[1];
	 
			Double X3 = new Double(x_3);
			x3 = X3.intValue();
			Double Y3 = new Double(y_3);
			y3 = Y3.intValue();
			Double X4 = new Double(x_4);
			x4 = X4.intValue();
			Double Y4 = new Double(y_4);
			y4 = Y4.intValue();
			// 画线
			g2.drawLine(sx, sy, ex, ey);
			//
			GeneralPath triangle = new GeneralPath();
			triangle.moveTo(ex, ey);
			triangle.lineTo(x3, y3);
			triangle.lineTo(x4, y4);
			triangle.closePath();
			//实心箭头
			g2.fill(triangle);
			//非实心箭头
			//g2.draw(triangle);
	 
		}
	
	protected void loadNodes() {
		updateBounds();
	}

	private void recalcTranslate() {
		double xmin=Double.MAX_VALUE,xmax=Double.MIN_VALUE;
		double ymin=Double.MAX_VALUE,ymax=Double.MIN_VALUE;
			for(GraphNode graphNode:nodes.values()) {
			xmin=Math.min(xmin, graphNode.x);
			xmax=Math.max(xmax, graphNode.x);
			ymin=Math.min(ymin, graphNode.y);
			ymax=Math.max(ymax, graphNode.y);
		}
		this.xmin=xmin;
		this.xmax=xmax;
		this.ymin=ymin;
		this.ymax=ymax;
		if(xmin!=Double.MAX_VALUE) {
			translateX=-xmin+borderGap;
			translateY= -ymin+borderGap;
		}
	}
	
	private double xmin=Double.MAX_VALUE,xmax=Double.MIN_VALUE;
	private double ymin=Double.MAX_VALUE,ymax=Double.MIN_VALUE;
	public void updateBounds() {
		recalcTranslate();
	
		if(xmin!=Double.MAX_VALUE) { 
			Dimension d=new Dimension((int)(((xmax-xmin)+borderGap*2)*scale),(int)(((ymax-ymin)+borderGap*2)*scale) );
		setPreferredSize(d); 
		setSize(d);
		}
	}
	
	public void runPhy() {
		for(	GraphNode graphEdgeGroup :nodes.values()) {
			Vector2 vecpos=graphEdgeGroup.getPositionVec2();
			Vector2 vec=new Vector2(0, 0);
			for (Iterator<GraphNode> iteratorx =nodes.values().iterator(); iteratorx.hasNext();) {
				GraphNode graphEdgeGroup2 = (GraphNode) iteratorx.next();
				if(graphEdgeGroup2!=graphEdgeGroup) {
					Vector2 vecpos2=graphEdgeGroup2.getPositionVec2();
					Vector2 sub=vecpos.subtract(vecpos2);
					double distance=sub.Length();
					Vector2 dic=sub.normalize();
					vec=vec.add(dic.multi(Math.min(10.0/distance,10.0)));
				}
			}
			graphEdgeGroup.delta=graphEdgeGroup.delta.add(vec);
		}
		synchronized (edgeGroups) {
			
			for(GraphEdgeGroup graphEdgeGroup :edgeGroups) {
			GraphNode na=graphEdgeGroup.nodeA;
			GraphNode nb=graphEdgeGroup.nodeB;
			Vector2 veca=na.getPositionVec2();
			Vector2 vecb=nb.getPositionVec2();
			Vector2 sub=veca.subtract(vecb);
			double distance=sub.Length();
			double length=graphEdgeGroup.getLength()+na.getNodesize()/2+nb.getNodesize()/2;

			//System.out.println(length);
			if(distance>length) {
			Vector2 dicb=sub.normalize().multi((distance-length)/4);
			Vector2 dica=dicb.opposite();
			na.delta=na.delta.add(dica);
			nb.delta=nb.delta.add(dicb);
			}
		}
		
		}
		
		for(	GraphNode graphEdgeGroup:nodes.values()) {
			
			graphEdgeGroup.commitDelta();
		}
		
	}

	private Random r=new Random();
	public Vector2 getRandomPos() {
		double x,y;
		if(xmin<xmax) {
			x=r.nextDouble(xmin, xmax);
		}else {
			x=r.nextDouble();
		}
		if(ymin<ymax) {
			y=r.nextDouble(ymin, ymax);
		}else {
			y=r.nextDouble();
		}
		return new Vector2(x, y);
	}
}
