package main;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.File;
import java.util.ArrayList;

import javax.swing.JCheckBox;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.Timer;

import DFT.TraceDFT;

import signals.Signals;
import tools.Arrow;
import tools.Text;
import tools.VertLines;


public class ChronoPanel extends JPanel implements MouseListener,MouseMotionListener {
	
	private static final long serialVersionUID = 1L;
	
	public File openedFile;
	public String fileName="New";
	public boolean saveFileOk=false;
	public boolean modifToSave=false;
	
	public ArrayList<Signals> signals = new ArrayList<Signals>();
	public int selected;
	public boolean elmt_to_edit=false;
	public int selected_bus,selected_signal;
	public int bus_offset;
	
	public int[][] timeLapse = new int[5][4];
	public int nbrTimeLapse=0;
	public boolean setTimeLapse=false;
	public boolean getTimeLapseWidth=false;
	
	public boolean displayBackLines=false;
	public boolean antialiasing=true;
	
	public ArrayList<Arrow> arrows = new ArrayList<Arrow>();
	public boolean placeAssertionFrom = false;
	public boolean placeAssertionTo = false;
	public int assertionIndex=-1;
	
	
	public ArrayList<Text> text = new ArrayList<Text>();
	
	
	private Cursor normalCursor = new Cursor(Cursor.DEFAULT_CURSOR);
	private Cursor handCursor = new Cursor(Cursor.HAND_CURSOR);
	private Cursor moveCursor = new Cursor(Cursor.MOVE_CURSOR);
	
	private JPopupMenu busMenu = new JPopupMenu();
	private JMenuItem bus_name = new JMenuItem("Set Name");
	private JMenuItem bus_fill = new JMenuItem("Fill");
	private JMenuItem bus_clean = new JMenuItem("Clean");
	
	private JPopupMenu signalMenu = new JPopupMenu();
	private JMenu signal_style = new JMenu("Style");
	private JCheckBox signal_style_bold = new JCheckBox("Bold");
	private JMenu signal_style_color = new JMenu("Color");
	private JMenuItem signal_style_color_red = new JMenuItem("Red");
	private JMenuItem signal_style_color_blue = new JMenuItem("Blue");
	private JMenuItem signal_style_color_green = new JMenuItem("Green");
	private JMenuItem signal_style_color_orange = new JMenuItem("Orange");
	private JMenuItem signal_style_color_black = new JMenuItem("Black");
	private JMenuItem signal_properties = new JMenuItem("Properties");
	private JMenuItem signal_set_values = new JMenuItem("Set wave values");
	private JMenuItem signal_traceDFT = new JMenuItem("Trace DFT");
	private JMenuItem signal_delete = new JMenuItem("Delete");
	
	
	private boolean doubleClic=false;
	private boolean moveSelection=false;
	
	public static Font myFont = new Font("Arial", Font.PLAIN,12);
	public static Font myBoldFont = new Font("Arial", Font.BOLD,12);
	public static FontMetrics metrics;
	
	public boolean text_action = false;
	
	
	public ChronoPanel(){
		addMouseListener(this);
		addMouseMotionListener(this);
		
		busMenu.add(bus_name);
		busMenu.add(bus_fill);
		busMenu.add(bus_clean);
		
		signal_style.add(signal_style_bold);
		
		signal_style_color.add(signal_style_color_black);
		signal_style_color.addSeparator();
		signal_style_color.add(signal_style_color_blue);
		signal_style_color.add(signal_style_color_green);
		signal_style_color.add(signal_style_color_orange);
		signal_style_color.add(signal_style_color_red);
		signal_style.add(signal_style_color);
		
		signalMenu.add(signal_style);
		signalMenu.add(signal_properties);
		signalMenu.add(signal_set_values);
		//signalMenu.addSeparator();
		//signalMenu.add(signal_traceDFT);
		//signalMenu.addSeparator();
		signalMenu.add(signal_delete);
		
		popUpMenuInit();
	}
	
	public void reinit(){
		signals.clear();
		Signals.resetIdBank();
		elmt_to_edit=false;
		timeLapse = new int[5][4];
		nbrTimeLapse=0;
		setTimeLapse=false;
		getTimeLapseWidth=false;
	}
	
    public void paintComponent(Graphics g){
    	ArrayList<VertLines> verts = new ArrayList<VertLines>();
    	metrics = g.getFontMetrics(myFont);
    	updateLeftSpace();
    	
    	// AntiAliasing
    	if (antialiasing){
    		Graphics2D g2 = (Graphics2D)g;
    		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    	}
		
        g.setColor(Color.white);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        
        if (displayBackLines){
        	 g.setColor(new Color(230,230,230));
        	 for (int i=1; i<signals.size(); i+=2){
        		 g.fillRect(0, i*(Signals.heightUnit+Signals.Yspace)+Signals.Yspace/2, ChronoWindow.WIDTH, Signals.heightUnit+Signals.Yspace);
        	 }
        }
        
        g.setColor(new Color(100,100,220));
        if (setTimeLapse){
        	g.fillRect(timeLapse[nbrTimeLapse][0], 0, timeLapse[nbrTimeLapse][3]-timeLapse[nbrTimeLapse][0], ChronoWindow.HEIGHT);
        }
       
        g.setColor(Color.black);
        for (int i=0; i<signals.size(); i++){
        	signals.get(i).draw(g, this, i, ChronoWindow.WIDTH);
        	if (signals.get(i).displayRisingEdges) verts.add(new VertLines(signals.get(i), signals.get(i).widthUnit+Signals.leftSpace,ChronoWindow.WIDTH-15,i,signals.size()));
        }
        
        // Draw vertical lines
        for (int i=0; i<verts.size(); i++){
        	verts.get(i).draw(g, this);
        }
        
        // Draw assertion (arrows)
        for (int i=0; i<arrows.size(); i++){
        	arrows.get(i).draw(g);
	   	}
        
        
        for (int i=0; i<text.size(); i++)
        {
        	text.get(i).draw(g);
        	System.out.println("RUN");
	   	}
        
        
        // Change Tab Name if modif to save
        Main.window.checkModifToSave();
    }
    
    public void add(Signals s){
    	changeClockDomain(s);
    	signals.add(s);
    	updateLeftSpace();
    	modifToSave=true;
    	repaint();
    }
    
    public void changeSelClockDomain(int id){
    	if (elmt_to_edit && signals.get(selected).dependsOnClk){
    		signals.get(selected).clockDomainId=id;
    		changeClockDomain(signals.get(selected));
    	}
    }
    
    public void changeClockDomain(Signals s){
    	int period=20;
    	if (s.id!=s.clockDomainId && s.dependsOnClk){
    		for (int i=0; i<signals.size(); i++){
    			if (signals.get(i).clock && signals.get(i).id==s.clockDomainId) period=signals.get(i).widthUnit;
    		}
    		s.widthUnit=(int)(period*s.ratio);
    	}
    }
    
    public void updateClockDomain(){
    	for (int i=0; i<signals.size(); i++){
    		changeClockDomain(signals.get(i));
		}
    }
    
    public void changeClockDomainName(int prevId, int newId){
		for (int i=0; i<signals.size(); i++){
			if (signals.get(i).clockDomainId==prevId){
				signals.get(i).clockDomainId=newId;
				changeClockDomain(signals.get(i));
			}
		}
    }
    
    public void updateLeftSpace(){
    	int lengthMax=0;
    	for (int i=0; i<signals.size(); i++){
			if (metrics.stringWidth(signals.get(i).name)>lengthMax) lengthMax=metrics.stringWidth(signals.get(i).name);
		}
    	Signals.leftSpace=Math.max(lengthMax+13,30);
    }
    
    public void moveTo(int offset){
    	Signals temp;
    	if (elmt_to_edit && selected+offset>=0 && selected+offset<signals.size()){
    		temp=signals.get(selected);
    		signals.remove(selected);
    		signals.add(selected+offset,temp);
    	}
    }
    
    public void removeSelected(){
    	String warning="";
    	String toErase="";
    	if (assertionIndex>-1)toErase="arrow assertion";
    	else if (elmt_to_edit) toErase=signals.get(selected).name+" signal";
    	int n = JOptionPane.showConfirmDialog(null, "Erase " + toErase + "?",  "Confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
    	
    	if (n==JOptionPane.YES_OPTION){
	    	// If assertion (Arrow)
	    	if (assertionIndex>-1){
	    		arrows.remove(assertionIndex);
	    	}
	    	// Else if signal
	    	else if (elmt_to_edit){
	    		for (int i=0; i<signals.size(); i++){
	    			if (i!=selected) if (signals.get(i).clockDomainId==signals.get(selected).id) warning+="- "+signals.get(i).name+"\n";
	    		}
	    		
	    		if (warning=="" || !signals.get(selected).clock){
	        		signals.remove(selected);
	        		selected=0;
	            	elmt_to_edit=false;
	        	}
	        	else{
	        		JOptionPane.showMessageDialog(null,  "These signals/bus are depending on " + signals.get(selected).name + ":\n" + warning, "Warning", JOptionPane.WARNING_MESSAGE);
	        	}
	    	}
    	}
    	
    }
    
    public void changePeriod(int p){
    	if (elmt_to_edit && signals.get(selected).clockDomainId == signals.get(selected).id){
    		int clkId = signals.get(selected).id;
    		for (int i=0; i<signals.size(); i++){
    			if (signals.get(i).clockDomainId == clkId) signals.get(i).widthUnit=(int)(p*signals.get(i).ratio);
    		}
    	}
    }
    
    public boolean checkIfSignalIndependent(int id){
    	boolean res=true;
    	for (int i=0; i<signals.size(); i++){
			if (signals.get(i).id!=id && signals.get(i).clock) if (signals.get(i).clockDomainId == id && signals.get(i).dependsOnClk) res=false;
		}
    	return res;
    }
    
    public void addTimeLapse(){
    	timeLapse[nbrTimeLapse][0]=0;
		timeLapse[nbrTimeLapse][1]=0;
		timeLapse[nbrTimeLapse][2]=0;
		timeLapse[nbrTimeLapse][3]=0;
		getTimeLapseWidth=false;
    	setTimeLapse=true;
    }
    
    public void removeTimeLapse(){
    	timeLapse = new int[5][4];
    	nbrTimeLapse=0;
    }
    
    public void newAssertion(){
    	arrows.add(new Arrow(this));
    	placeAssertionFrom=true;
    }
    
    public int getPosFromId(int id){
    	for (int i=0; i<signals.size(); i++){
    		if (signals.get(i).id==id) return i;
    	}
    	return 0;
    }
    
    public void removeAssertions(){
    	arrows.clear();
    }
    
    public void releaseSelection(){
    	signals.get(selected).selected=false;
    	if (assertionIndex>=0) arrows.get(assertionIndex).selected=false;
		repaint();
    }
    
    /******************** Mouse Listener *************************/

	@Override
	public void mouseClicked(MouseEvent e) {
		
		
		
		if(text_action == true)
		{
			text.get(0).setX(e.getX());
			text.get(0).setY(e.getY());
			System.out.println(e.getX() + " " + e.getY());
			text_action = false;
			repaint();
		}
/*		
		if (e.getX()<Signals.leftSpace)
		{
			if (!doubleClic){
				doubleClic=true;
				ActionListener taskPerformer = new ActionListener() {
					public void actionPerformed(ActionEvent evt) {
						doubleClic=false;
					}
				}; 
				Timer t = new Timer(500, taskPerformer);
				t.setRepeats(false);
				t.start();
			}
			// Double clic
			else{
				doubleClic=false;
				// Edit
				editSignalProperties();
			}
		}
	*/	
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
		if (selected>=0 && selected<signals.size()) signals.get(selected).selected=false;
		moveSelection=false;
		repaint();
	}

	@Override
	public void mousePressed(MouseEvent e) {
		int nbr = (int)(((float)e.getY()-(float)Signals.Yspace/2)/((float)Signals.heightUnit+(float)Signals.Yspace));
		if (nbr>=0 && nbr<signals.size() && !setTimeLapse){
			if (!placeAssertionFrom && !placeAssertionTo){
				if (e.getX()>=Signals.leftSpace){
					modifToSave=true;
					if (e.getButton()==MouseEvent.BUTTON1){
						signals.get(nbr).setWave(e.getX(),signals.get(nbr).bus);
					}
					if (e.getButton()==MouseEvent.BUTTON3){
						if (!signals.get(nbr).bus) signals.get(nbr).setWave(e.getX(),true);
						if (signals.get(nbr).bus){
							selected_bus=nbr;
							bus_offset=e.getX();
							busMenu.show(this, e.getX(), e.getY());
						}
					}
				}
				else if (e.getX()<Signals.leftSpace){
					// Move signal
					if (e.getButton()==MouseEvent.BUTTON1) moveSelection=true;
					// Show signal Menu
					if (e.getButton()==MouseEvent.BUTTON3){
						modifToSave=true;
						selected_signal=nbr;
						signal_style_bold.setSelected(signals.get(nbr).bold);
						signal_set_values.setEnabled(true);
						signal_traceDFT.setEnabled(true);
						if (signals.get(nbr).clock || signals.get(nbr).bus) signal_set_values.setEnabled(false);
						if (signals.get(nbr).bus) signal_traceDFT.setEnabled(false);
						signalMenu.show(this, e.getX(), e.getY());
					}
				}
			}
			else{
				// Assertion tool actions
				if (placeAssertionFrom){
					placeAssertionFrom=false;
					placeAssertionTo=true;
				}
				else if (placeAssertionTo){
					placeAssertionTo=false;
					arrows.get(arrows.size()-1).done();
				}
			}
			repaint();
		}

		if (setTimeLapse){
			getTimeLapseWidth=true;
		}
	}

	@Override
	public void mouseReleased(MouseEvent e){
		// Move selected signal
		if (moveSelection){
			moveSelection=false;
			setCursor(normalCursor);
			int nbr = (int)(((float)e.getY()-(float)Signals.Yspace)/((float)Signals.heightUnit+(float)Signals.Yspace));
			if (nbr>=0 && nbr<signals.size()){
				moveTo(nbr-selected);
				repaint();
			}
		}
		
		// TimeLapse Validation
		if (getTimeLapseWidth){
			getTimeLapseWidth=false;
			setTimeLapse=false;
			
			// 1: 1/4
			timeLapse[nbrTimeLapse][1]=timeLapse[nbrTimeLapse][0]+(timeLapse[nbrTimeLapse][3]-timeLapse[nbrTimeLapse][0])/4;
			// 2: 3/4
			timeLapse[nbrTimeLapse][2]=timeLapse[nbrTimeLapse][0]+3*(timeLapse[nbrTimeLapse][3]-timeLapse[nbrTimeLapse][0])/4;
			
			// For signal simplification:
			timeLapse[nbrTimeLapse][0]-=Signals.leftSpace;
			timeLapse[nbrTimeLapse][1]-=Signals.leftSpace;
			timeLapse[nbrTimeLapse][2]-=Signals.leftSpace;
			timeLapse[nbrTimeLapse][3]-=Signals.leftSpace;
			
			nbrTimeLapse++;
			sortTimeLapseArray();
			
			repaint();
		}
	}

	@Override
	public void mouseDragged(MouseEvent e){

		if (moveSelection){
			modifToSave=true;
			setCursor(moveCursor);
			int nbr = (int)(((float)e.getY()-(float)Signals.Yspace)/((float)Signals.heightUnit+(float)Signals.Yspace));
			if (nbr>=0 && nbr<signals.size()){
				moveTo(nbr-selected);
				selected=nbr;
				repaint();
			}
		}
		
		if (getTimeLapseWidth){
			modifToSave=true;
			timeLapse[nbrTimeLapse][3]=e.getX();
			repaint();
		}
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		
		setCursor(normalCursor);
		
		int nbr = (int)(((float)e.getY()-(float)Signals.Yspace/2)/((float)Signals.heightUnit+(float)Signals.Yspace));
		if (nbr>=0 && nbr<signals.size() && !setTimeLapse){
			signals.get(selected).selected=false;
			signals.get(nbr).selected=true;
			elmt_to_edit=true;
			selected=nbr;
			
			if (e.getX()<Signals.leftSpace){
				setCursor(handCursor);
			}
			
			if (placeAssertionFrom)	arrows.get(arrows.size()-1).setStart(signals.get(nbr), e.getX());
			if (placeAssertionTo)	arrows.get(arrows.size()-1).setEnd(signals.get(nbr), e.getX());
			
			repaint();
		}
		else if(selected>=0 && selected<signals.size()){
			signals.get(selected).selected=false;
			repaint();
		}
		
		if (setTimeLapse && !getTimeLapseWidth){
			timeLapse[nbrTimeLapse][0]=e.getX();
			timeLapse[nbrTimeLapse][3]=e.getX()+1;
		}
		
		// Assertion(Arrow) Selection
		int dMin=8;
		assertionIndex=-1;
        for (int i=0; i<arrows.size(); i++){
        	arrows.get(i).selected=false;
        	if (arrows.get(i).getDistance(e.getX(),e.getY())<dMin){
        		dMin=arrows.get(i).getDistance(e.getX(),e.getY());
        		assertionIndex=i;
        	}
	   	}
        if (assertionIndex!=-1) arrows.get(assertionIndex).selected=true;
	}
	
	/***************** Pop Up Menus Init **************************/
	public void popUpMenuInit(){
		bus_name.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				DialogBox db = new DialogBox(null, "Set bus data name");
				db.showDialog(); 
				if (db.name!=null) signals.get(selected_bus).setBusName(bus_offset,db.name);
				repaint();
			}
		});
		
		bus_fill.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				signals.get(selected_bus).setBusFill(bus_offset,false);
				repaint();
			}
		});
		
		bus_clean.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				signals.get(selected_bus).setBusName(bus_offset,null);
				signals.get(selected_bus).setBusFill(bus_offset,true);
				repaint();
			}
		});
		
		signal_style_bold.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				signals.get(selected_signal).bold=signal_style_bold.isSelected();
				signalMenu.setVisible(false);
				repaint();
			}
		});
		
		signal_style_color_red.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				signals.get(selected_signal).color=new Color(180,0,0);
				repaint();
			}
		});

		signal_style_color_blue.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				signals.get(selected_signal).color=new Color(0,0,180);
				repaint();
			}
		});
		
		signal_style_color_orange.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				signals.get(selected_signal).color=new Color(255,102,50);
				repaint();
			}
		});
		
		signal_style_color_green.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				signals.get(selected_signal).color=new Color(0,180,0);
				repaint();
			}
		});

		signal_style_color_black.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				signals.get(selected_signal).color=Color.black;
				repaint();
			}
		});
		
		signal_properties.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				editSignalProperties();
				repaint();
			}
		});
		
		signal_traceDFT.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				new TraceDFT(signals.get(selected_signal),ChronoWindow.WIDTH);
				repaint();
			}
		});
		
		signal_set_values.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				DialogBox db = new DialogBox(null, "Set Wave Values", signals.get(selected), true);
				db.showDialog();
				if (db.valid) for (int i=0; i<signals.get(selected).wave.length; i++) signals.get(selected).wave[i]=db.waveSet[i];
				repaint();
			}
		});
		
		signal_delete.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				removeSelected();
				repaint();
			}
		});
	}
	
	/****************** Signals Properties/Style.. ***********************/
	
	public void editSignalProperties(){
		if (elmt_to_edit){
			DialogBox db = new DialogBox(null, "Properties", signals.get(selected));
			db.showDialog(); 
			if (db.valid){
				modifToSave=true;
				if (db.name!=null) signals.get(selected).name=db.name;
				repaint();
				if (db.freq!=-1) changePeriod(db.freq);
				repaint();
				if (!signals.get(selected).clock && db.clockDomain!=-1) changeSelClockDomain(db.clockDomain);
				repaint();
				if (signals.get(selected).clock){
					
					signals.get(selected).displayRisingEdges=db.displayRisingEdges;
					signals.get(selected).generate(db.beginOnRisingEdge);
					
					if (db.clockDomain!=-1 && db.clkDependency){
						if (checkIfSignalIndependent(signals.get(selected).id)){
							signals.get(selected).ratio=db.ratio;
							signals.get(selected).dependsOnClk=true;
							changeSelClockDomain(db.clockDomain);
						}
						else{
							// WARNING !
							JOptionPane.showMessageDialog(null, signals.get(selected).name + " can not depend on " + signals.get(db.clockDomain).name + " as other clocks depend on " + signals.get(selected).name, "Warning", JOptionPane.WARNING_MESSAGE);
						}
						
					}
					else{
						signals.get(selected).ratio=1;
						changeSelClockDomain(signals.get(selected).id);
						signals.get(selected).dependsOnClk=false;
						if (db.freq!=-1) changePeriod(db.freq);
					}
					// Update all signals widthunit !
					updateClockDomain();
				}
				signals.get(selected).clkToQdelay=db.clkToQdelay;
				repaint();
				if (!signals.get(selected).bus){
					if (!db.setUp) signals.get(selected).setUpTime=0;
					if (db.setUp) signals.get(selected).setUpTime=8;
					repaint();
				}
			}
		}
	}
	
	/*********************** Useful functions ***************************/
	
	public void sortTimeLapseArray(){
		int[][] temp = new int[9][4];
		int[] rank = new int[nbrTimeLapse];

		//temp = timeLapse;
		for (int i=0; i<nbrTimeLapse; i++){
			temp[i][0] = timeLapse[i][0];
			temp[i][1] = timeLapse[i][1];
			temp[i][2] = timeLapse[i][2];
			temp[i][3] = timeLapse[i][3];
		}
		
		for (int i=0; i<nbrTimeLapse; i++){
			rank[i]=i; 
			for (int j=0; j<i; j++){
				if (timeLapse[i][0]<timeLapse[j][0]){
					rank[i]--;
					rank[j]++;
				}
			}
		}
		
		for (int i=0; i<nbrTimeLapse; i++){
			timeLapse[rank[i]][0] = temp[i][0];
			timeLapse[rank[i]][1] = temp[i][1];
			timeLapse[rank[i]][2] = temp[i][2];
			timeLapse[rank[i]][3] = temp[i][3];
		}
	}
	
	
	 public void Text(String stext)
	 {
	    	text.add(new Text(stext));
	    }

	 public void coco(boolean state)
	 {
		 text_action = state;
	 }
	 
}
