package signals;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;

import main.ChronoPanel;


public abstract class Signals implements Serializable{

	private static final long serialVersionUID = 1L;
	
	public static int leftSpace=80;
	public static int Yspace=20;
	public static int heightUnit=25;
	private static int idBank=0;
	
	public int id = -1;
	public int widthUnit=20;
	public String name;
	public int setUpTime = 0;
	public boolean bus=false;
	public boolean clock=false;
	public boolean dependsOnClk=false;
	public float ratio=1;
	public int clockDomainId;
	public boolean selected_for_edit=false;
	public boolean clkToQdelay=false;
	public boolean displayRisingEdges=false;
	public boolean bold=false;
	public Color color=Color.black;
	
	public transient boolean selected=false;
	
	public int[] wave = new int[500];
	protected String[] busNames = new String[500];
	protected boolean[] busFill = new boolean[500];
	
	public abstract void generate(boolean beginOnRisingEdge);
	
	public static void resetIdBank(){
		idBank=0;
	}
	
	public static void setIdBank(int id){
		idBank=id;
	}
	
	protected void setId(){
		id=idBank;
		idBank++;
	}
	
	public void setWave(int x, boolean toggle){
		int value,newValue = 0,i;
		if (clkToQdelay) x=x+widthUnit/2;
		if (!clock){
			value=wave[(x-leftSpace)/widthUnit];
			if (value==0) newValue=1;
			if (value==1) newValue=0;
			i=0;
			
			do{
				wave[(x-leftSpace)/widthUnit+i]=newValue;
				i++;
			}while((x-leftSpace)/widthUnit+i<500 && wave[(x-leftSpace)/widthUnit+i]==value && toggle);
			
			if (bus){
				if (busNames[(x-leftSpace)/widthUnit]!=null) setBusName(x,busNames[(x-leftSpace)/widthUnit]);
				if (busFill[(x-leftSpace)/widthUnit]==true) setBusFill(x,false);
				while((x-leftSpace)/widthUnit+i<500){
					if (wave[(x-leftSpace)/widthUnit+i]==0){
						wave[(x-leftSpace)/widthUnit+i]=1;
					}
					else if (wave[(x-leftSpace)/widthUnit+i]==1){
						wave[(x-leftSpace)/widthUnit+i]=0;
					}
					i++;
				}
			}
		}
	}
	
	public void setBusName(int x, String s){
		int indice = (x-leftSpace)/widthUnit;
		for (int i=getFirstChange(x); i<getNextChange(indice); i++) busNames[i]=null;
		busNames[getFirstChange(x)]=s;
	}
	
	public void setBusFill(int x, boolean clean){
		int indice = (x-leftSpace)/widthUnit;
		if (!clean){
			boolean mem = busFill[getFirstChange(x)];
			for (int i=getFirstChange(x); i<getNextChange(indice); i++) busFill[i]=false;
			busFill[getFirstChange(x)]=!mem;
		}
		else{
			for (int i=getFirstChange(x); i<getNextChange(indice); i++) busFill[i]=false;
		}
	}
	
	public int getFirstChange(int x){
		int indice=(x-leftSpace)/widthUnit;
		
		while(indice>0 && wave[indice-1]==wave[indice]){
			indice--;
		}
		
		return indice;
	}
	
	public int getIndex(int x){
		return (x-leftSpace)/widthUnit;
	}
	
	public int getNextChange(int offset){
		int indice=offset;
		
		while(indice<199 && wave[indice+1]==wave[indice]){
			indice++;
		}
		
		return indice;
	}
	
	public int getLastDisplayed(int maxWidth){
		int res=0;
		for (int i=0; i<200; i++){
			if (leftSpace+(i+1)*widthUnit<maxWidth-10){
				res++;
			}
		}
		return leftSpace+res*widthUnit;
	}
	
	public void draw(Graphics g, ChronoPanel chrono, int nbr, int maxWidth){
		// Graphics init
		Graphics2D g2 = (Graphics2D)g;
		float dash1[] = {5.0f};
		float thickness=1f;
		g.setFont(ChronoPanel.myFont);
		
		if (bold){
			g.setFont(ChronoPanel.myBoldFont);
			thickness=3f;
		}
		
		Stroke dashed = new BasicStroke(thickness,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER,10.0f,dash1, 0.0f);
		
		
		// Selection in Rect
		if (selected){
			g.setColor(new Color(215,215,235));
			g.fillRect(0, nbr*(heightUnit+Yspace)+Yspace/2, maxWidth-15, (heightUnit+Yspace));
		}
		
		g.setColor(color);
		g.translate(0, nbr*(heightUnit+Yspace)+Yspace);
		g.drawString(name, leftSpace-(ChronoPanel.metrics.stringWidth(name)+8), heightUnit-1);
		
		// Signal x Translation
		g.translate(leftSpace, 0);
		
		// Draw settings
		g2.setStroke(new BasicStroke(thickness));
		
		// Draw Bus Fill Options
		if (bus) drawBusFillOptions(g, 0, maxWidth-15-leftSpace);
		
		// Draw signals
		if (chrono!=null && chrono.nbrTimeLapse>0){
			drawSignalsInInterval(g, 0, chrono.timeLapse[0][0]);
			drawSignalsInInterval(g, chrono.timeLapse[chrono.nbrTimeLapse-1][3], maxWidth-15-leftSpace);
		}
		else{
			drawSignalsInInterval(g, 0, maxWidth-15-leftSpace);
		}
		if (chrono!=null){
			for (int i=0; i<chrono.nbrTimeLapse; i++){
				for (int j=0; j<3; j++){
					if (j==0 || j==2){
						g2.setStroke(dashed);
						drawSignalsInInterval(g, chrono.timeLapse[i][j], chrono.timeLapse[i][j+1]);
						g2.setStroke(new BasicStroke(thickness));
					}
				}
				if (i<chrono.nbrTimeLapse-1) drawSignalsInInterval(g, chrono.timeLapse[i][3], chrono.timeLapse[i+1][0]);
			}
		}
		
		g.translate(-leftSpace, 0);
		g2.setStroke(new BasicStroke());
		g.setFont(ChronoPanel.myFont);
		g.translate(0, -(nbr*(heightUnit+Yspace)+Yspace));
	}
	
	
	
	public void drawSignalsInInterval(Graphics g, int min, int max){
		Rectangle2D drawArea = new Rectangle2D.Float(min, -1, max-min, heightUnit+3);
		int limit = (int)(max/widthUnit)+2;
		g.setClip(drawArea);
		
		
		// Decal bus on clock edge
		if (bus) g.translate(-setUpTime/2, 0);
		if (clkToQdelay) g.translate(-widthUnit/2, 0);
		
		for (int i=0; i<limit; i++){
			// Transition Part
			if (i>0 && wave[i-1]!=wave[i]){
				if (wave[i]==0 || bus) g.drawLine(i*widthUnit, 0, i*widthUnit+setUpTime, heightUnit); // Falling edge
				if (wave[i]==1 || bus) g.drawLine(i*widthUnit, heightUnit, i*widthUnit+setUpTime, 0); // Rising edge
			}
			else{
				// Continue Part For Bus and setUpTime
				if (setUpTime>0 || bus){
					if (wave[i]==0 || bus) g.drawLine(i*widthUnit, heightUnit, i*widthUnit+setUpTime-1, heightUnit); // Continue
					if (wave[i]==1 || bus) g.drawLine(i*widthUnit, 0, i*widthUnit+setUpTime-1, 0); // Continue
				}
			}
			// Continue Part
			if (wave[i]==0 || bus) g.drawLine(i*widthUnit+setUpTime, heightUnit, (i+1)*widthUnit-1, heightUnit); // Continue
			if (wave[i]==1 || bus) g.drawLine(i*widthUnit+setUpTime, 0, (i+1)*widthUnit-1, 0); // Continue
		}
		
		if (bus) g.translate(setUpTime/2, 0);
		if (clkToQdelay) g.translate(widthUnit/2, 0);
		
		g.setClip(null);
	}
	
	public void drawBusFillOptions(Graphics g, int min, int max){
		Rectangle2D drawArea = new Rectangle2D.Float(min, 0, max-min, heightUnit+1);
		g.setClip(drawArea);
		
		// Decal bus on clock edge
		if (bus) g.translate(-setUpTime/2, 0);
		if (clkToQdelay) g.translate(-widthUnit/2, 0);
				
		for (int i=0; i<200; i++){
			int left=i*widthUnit+setUpTime;
			if (i==0) left=0;
			int endData = (getNextChange(i)+1)*widthUnit;
			// Fill
			if (busFill[i]){
				//g.setColor(new Color(210,210,210));
				g.setColor(new Color(Math.min(color.getRed()+150,255),Math.min(color.getGreen()+150,255),Math.min(color.getBlue()+150,255)));
				g.fillRect(left, 0, endData-left, heightUnit);
				
				// Fill setUpTime parts
				int start= setUpTime/2;
				// Begin
				if (left!=0 && setUpTime!=0){
					for (int j=0; j<start; j++){
						g.drawLine(left-start+j,heightUnit/2-j*heightUnit/(2*start),left-start+j,heightUnit/2+j*heightUnit/(2*start));
					}
				}
				// End
				if (endData!=getLastDisplayed(max) && setUpTime!=0){
					for (int j=start; j>=0; j--){
						g.drawLine(endData+start-j,heightUnit/2-j*heightUnit/(2*start),endData+start-j,heightUnit/2+j*heightUnit/(2*start));
					}
				}
				g.setColor(color);
			}
			// Name
			if (busNames[i]!=null){
				endData=Math.min((getNextChange(i)+1)*widthUnit,getLastDisplayed(max));
				g.drawString(busNames[i], (left+endData)/2 - ChronoPanel.metrics.stringWidth(busNames[i])/2, heightUnit-8);
			}
		}
		
		if (bus) g.translate(setUpTime/2, 0);
		if (clkToQdelay) g.translate(widthUnit/2, 0);
		
		g.setClip(null);
	}
}
