package mars.tools;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

import javax.swing.*;
import javax.swing.Timer;

import mars.Globals;
import mars.mips.hardware.AddressErrorException;
import mars.mips.hardware.Coprocessor0;
import mars.mips.hardware.Memory;
import mars.mips.hardware.MemoryAccessNotice;
import mars.simulator.Exceptions;
@SuppressWarnings("serial")
/* Add these two lines in exceptions.java file
 * public static final int EXTERNAL_INTERRUPT_TIMER = 0x00000100; //Add for digital Lab Sim
 * public static final int EXTERNAL_INTERRUPT_HEXA_KEYBOARD = 0x00000200;// Add for digital Lab Sim
*/

/*
 * Didier Teifreto LIFC Université de franche-Comté www.lifc.univ-fcomte.fr/~teifreto
 * didier.teifreto@univ-fcomte.fr
 */
public class DigitalLabSim extends AbstractMarsToolAndApplication {
	private static String heading =  "Digital Lab Sim";
	private static String version = " Version 1.0 (Didier Teifreto)";
	private static final int IN_ADRESS_DISPLAY_1=Memory.memoryMapBaseAddress+0x10;
	private static final int IN_ADRESS_DISPLAY_2=Memory.memoryMapBaseAddress+0x11;
	private static final int IN_ADRESS_HEXA_KEYBOARD=Memory.memoryMapBaseAddress+0x12;
	private static final int IN_ADRESS_COUNTER=Memory.memoryMapBaseAddress+0x13;
	private static final int OUT_ADRESS_HEXA_KEYBOARD=Memory.memoryMapBaseAddress+0x14;

  public static final int EXTERNAL_INTERRUPT_TIMER = 0x00000100; //Add for digital Lab Sim
  public static final int EXTERNAL_INTERRUPT_HEXA_KEYBOARD = 0x00000200;// Add for digital Lab Sim

	// GUI Interface. 
   	private static JPanel panelTools;
   	// Seven Segment display
	private SevenSegmentPanel sevenSegPanel;
	// Keyboard
	private static int KeyBoardValueButtonClick=-1; // -1 no button click
	private HexaKeyboard hexaKeyPanel;
	private static boolean KeyboardInterruptOnOff=false;
	// Counter
	private static int CounterValueMax=30; 
	private static int CounterValue=CounterValueMax;
	private static boolean CounterInterruptOnOff=false;
	private static OneSecondCounter SecondCounter;

	public DigitalLabSim(String title, String heading) {
		super(title,heading);
	}
	public DigitalLabSim() {
		super (heading+", "+version, heading);
	}
	public static void main(String[] args) {
		new DigitalLabSim(heading+", "+version,heading).go();
	}
	public String getName() {
		return "Digital Lab Sim";
	}
	protected void addAsObserver(){
    	addAsObserver(IN_ADRESS_DISPLAY_1, IN_ADRESS_DISPLAY_1);
    	addAsObserver(Memory.textBaseAddress, Memory.textLimitAddress);
    }
	public void update(Observable ressource, Object accessNotice){
    	MemoryAccessNotice notice = (MemoryAccessNotice) accessNotice;
    	int address=notice.getAddress();
    	char value=(char)notice.getValue();
    	if(address == IN_ADRESS_DISPLAY_1)
    			updateSevenSegment(1, value);
    	else
    		if  (address == IN_ADRESS_DISPLAY_2)
    			updateSevenSegment(0, value);
    		else
    			if (address == IN_ADRESS_HEXA_KEYBOARD)
    				updateHexaKeyboard(value);
    			else
    				if (address == IN_ADRESS_COUNTER)
    					updateOneSecondCounter(value);
    	if  (CounterInterruptOnOff)
    		if (CounterValue >0){
    			CounterValue--;
    		}
    		else{
    			CounterValue=CounterValueMax;
    			if((Coprocessor0.getValue(Coprocessor0.STATUS) & 2)==0){
    				mars.simulator.Simulator.externalInterruptingDevice = /*Exceptions.*/EXTERNAL_INTERRUPT_TIMER;
    			}
    		}
	}
	protected void reset(){
    	sevenSegPanel.resetSevenSegment();
    	hexaKeyPanel.resetHexaKeyboard();
    	SecondCounter.resetOneSecondCounter();
    }
    protected JComponent buildMainDisplayArea() {
    	panelTools=new JPanel(new GridLayout(1,2));
    	sevenSegPanel=new SevenSegmentPanel();
	 	panelTools.add(sevenSegPanel);
	 	hexaKeyPanel = new HexaKeyboard();
	 	panelTools.add(hexaKeyPanel);
	 	SecondCounter= new OneSecondCounter();
	   	return panelTools;
    }
    private synchronized void updateMMIOControlAndData(int dataAddr, int dataValue) {
        if (!this.isBeingUsedAsAMarsTool || (this.isBeingUsedAsAMarsTool && connectButton.isConnected())) {
           synchronized (Globals.memoryAndRegistersLock) {
              try {
              		Globals.memory.setByte(dataAddr, dataValue);
              } 
                  catch (AddressErrorException aee) {
                    System.out.println("Tool author specified incorrect MMIO address!"+aee);
                    System.exit(0);
                 }
           }
           if (Globals.getGui() != null && Globals.getGui().getMainPane().getExecutePane().getTextSegmentWindow().getCodeHighlighting() ) {
              Globals.getGui().getMainPane().getExecutePane().getDataSegmentWindow().updateValues();
           }
        }
     }
    protected JComponent getHelpComponent() {
        final String helpContent = 
        	" This tool is composed of 3 parts : two seven-segment displays, an hexadecimal keyboard and counter \n"+
        	"Seven segment display\n"+
        	" Byte value at address 0xFFFF0010 : command right seven segment display \n "+
        	" Byte value at address 0xFFFF0011 : command left seven segment display \n "+
        	" Each bit of these two bytes are connected to segments (bit 0 for a segment, 1 for b segment and 7 for point \n \n"+
        	"Hexadecimal keyboard\n"+
        	" Byte value at address 0xFFFF0012 : command row number of hexadecimal keyboard (bit 0 to 3) and enable keyboard interrupt (bit 7) \n" +
        	" Byte value at address 0xFFFF0014 : receive row and column of the key pressed, 0 if not key pressed \n"+
        	" The mips program have to scan, one by one, each row (send 1,2,4,8...)"+
        	" and then observe if a key is pressed (that mean byte value at adresse 0xFFFF0014 is different from zero). "+
        	" This byte value is composed of row number (4 left bits) and column number (4 right bits)"+
        	" Here you'll find the code for each key : 0x11,0x21,0x41,0x81,0x12,0x22,0x42,0x82,0x14,0x24,0x44,0x84,0x18,0x28,0x48,0x88. \n"+
        	" For exemple key number 2 return 0x41, that mean the key is on column 3 and row 1. \n"+
        	" If keyboard interruption is enable, an exception is started, with cause register bit number 11 set.\n \n"+
        	"Counter\n"+
        	" Byte value at address 0xFFFF0013 : If one bit of this byte is set, the counter interruption is enable.\n"+
        	" If counter interruption is enable, every 30 instructions, an exception is started with cause register bit number 10.\n" +
			"   (contributed by Didier Teifreto, dteifreto@lifc.univ-fcomte.fr)"
        	;
        JButton help = new JButton("Help");
        help.addActionListener(
        		new ActionListener() {
        			public void actionPerformed(ActionEvent e) {
        				JTextArea ja = new JTextArea(helpContent);
        				ja.setRows(20);
        				ja.setColumns(60);
        				ja.setLineWrap(true);
        				ja.setWrapStyleWord(true);
        				JOptionPane.showMessageDialog(theWindow, new JScrollPane(ja),
                        "Simulating the Hexa Keyboard and Seven segment display", JOptionPane.INFORMATION_MESSAGE);
        			}
        		});		
        return help;  
    }/* ....................Seven Segment display start here................................... */
   /* ...........................Seven segment display start here ..............................*/
    public void updateSevenSegment(int number,char value) {
    	sevenSegPanel.display[number].modifyDisplay(value);
    }
    public class SevenSegmentDisplay extends JComponent {
    	public char aff;
    	public SevenSegmentDisplay(char aff){
    		this.aff=aff;
    		this.setPreferredSize(new Dimension(60,80));
    	}
    	public void modifyDisplay(char val){
    		aff=val;
    		this.repaint();
    	}
    	public void SwitchSegment(Graphics g,char segment){
    		switch(segment){
    		case 'a': //a segment
    			int[]pxa1={12,9,12};
    			int[]pxa2={36,39,36};
    			int[]pya={5,8,11};
    			g.fillPolygon(pxa1,pya, 3);
    			g.fillPolygon(pxa2,pya, 3);
    			g.fillRect(12,5,24,6);
    			break;
    		case 'b': //b segment	
    			int[]pxb={37,40,43};
    			int[]pyb1={12,9,12};
    			int[]pyb2={36,39,36};
    			g.fillPolygon(pxb,pyb1, 3);
    			g.fillPolygon(pxb,pyb2, 3);
    			g.fillRect(37,12,6,24);
    			break;
    		case 'c': // c segment
    			int[]pxc={37,40,43};
    			int[]pyc1={44,41,44};
    			int[]pyc2={68,71,68};
    			g.fillPolygon(pxc,pyc1, 3);
    			g.fillPolygon(pxc,pyc2, 3);
    			g.fillRect(37,44,6,24);
    			break;
    		case 'd': // d segment
    			int[]pxd1={12,9,12};
    			int[]pxd2={36,39,36};
    			int[]pyd={69,72,75};
    			g.fillPolygon(pxd1,pyd, 3);
    			g.fillPolygon(pxd2,pyd, 3);
    			g.fillRect(12,69,24,6);
    			break;
    		case 'e': // e segment
    			int[]pxe={5,8,11};
    			int[]pye1={44,41,44};
    			int[]pye2={68,71,68};
    			g.fillPolygon(pxe,pye1, 3);
    			g.fillPolygon(pxe,pye2, 3);
    			g.fillRect(5,44,6,24);
    			break;
    		case 'f': // f segment
    			int[]pxf={5,8,11};
    			int[]pyf1={12,9,12};
    			int[]pyf2={36,39,36};
    			g.fillPolygon(pxf,pyf1, 3);
    			g.fillPolygon(pxf,pyf2, 3);
    			g.fillRect(5,12,6,24);
    			break;
    		case 'g': // g segment 
    			int[]pxg1={12,9,12};
    			int[]pxg2={36,39,36};
    			int[]pyg={37,40,43};
    			g.fillPolygon(pxg1,pyg, 3);
    			g.fillPolygon(pxg2,pyg, 3);
    			g.fillRect(12,37,24,6);
    			break;
    		case 'h': // decimal point
    			g.fillOval(49,68,8,8);
    			break;
    		}	
    	}
    	public void paint(Graphics g) {
    		char c='a';	
    		while(c<='h'){			
    			if ((aff & 0x1) == 1)
    				g.setColor(Color.RED);
    			else
    				g.setColor(Color.LIGHT_GRAY);
    			SwitchSegment(g,c);
				aff = (char)(aff  >>>1);
				c++;
    		}
    	}
    }
    public class SevenSegmentPanel extends JPanel {
    	public SevenSegmentDisplay[] display;
    	public SevenSegmentPanel(){
    		int i;
    		FlowLayout fl= new FlowLayout();
    		this.setLayout(fl);
    		display= new SevenSegmentDisplay[2];
    		for (i=0;i<2;i++){
    			display[i]= new SevenSegmentDisplay((char)(0)); 
    			this.add(display[i]);
    		}
    	}	
    	public void modifyDisplay(int num,char val){
    		display[num].modifyDisplay(val); 
    		display[num].repaint();
    	}
    	public void resetSevenSegment() {
    		int i;
    		for (i=0;i<2;i++)   		
    	  	  modifyDisplay(i, (char)0);
    	   }
    }
 /* ...........................Seven segment display end here ..............................*/
/* ....................Hexa Keyboard start here................................... */
    public void updateHexaKeyboard(char row) {
    	int key = KeyBoardValueButtonClick;
    	if ((key !=-1)&& ((1 << (key / 4))==(row & 0xF))){	
    		updateMMIOControlAndData(OUT_ADRESS_HEXA_KEYBOARD, 
    				(char)(1 << (key / 4)) | (1 << (4+(key%4))));
    	}
    	else{
    		updateMMIOControlAndData(OUT_ADRESS_HEXA_KEYBOARD, 0);
    	}
    	if ((row & 0xF0) != 0) 
    		KeyboardInterruptOnOff = true;
    	else
    		KeyboardInterruptOnOff = false;
    }
    public class HexaKeyboard extends JPanel{
		public JButton[] button;	
		public HexaKeyboard(){
			int i;
			GridLayout layout = new GridLayout(4,4);
			this.setLayout(layout);
			button = new JButton[16];
			for (i=0;i<16;i++){
				button[i]= new JButton(Integer.toHexString(i)); 
				button[i].setBackground(Color.WHITE);
				button[i].setMargin(new Insets(10,10,10,10));
				button[i].addMouseListener(new EcouteurClick(i));
				this.add(button[i]);
			}	
		}
	    public void resetHexaKeyboard(){
	    	int i;
	 	   	KeyBoardValueButtonClick=-1;
	 	   	for(i=0;i<16;i++){
	 	   		button[i].setBackground(Color.WHITE);
	 	   	}
	    }
		public class EcouteurClick implements MouseListener{
			private int buttonValue;
			public EcouteurClick(int val){ 
				buttonValue=val;
			}
			public void mouseEntered(MouseEvent arg0) { }
			public void mouseExited(MouseEvent arg0) {}			
			public void mousePressed(MouseEvent arg0) {}
			public void mouseReleased(MouseEvent arg0) {}
			public void mouseClicked(MouseEvent arg0) {
				int i;
				if(KeyBoardValueButtonClick!=-1){//Button already pressed -> now realease
					KeyBoardValueButtonClick = -1;
					updateMMIOControlAndData(OUT_ADRESS_HEXA_KEYBOARD,0);
					for(i=0;i<16;i++) 
						button[i].setBackground(Color.WHITE);
				}
				else{ // new button pressed
					KeyBoardValueButtonClick = buttonValue;
					button[KeyBoardValueButtonClick].setBackground(Color.GREEN);
					if( KeyboardInterruptOnOff &&  (Coprocessor0.getValue(Coprocessor0.STATUS) & 2)==0){
						mars.simulator.Simulator.externalInterruptingDevice = /*Exceptions.*/EXTERNAL_INTERRUPT_HEXA_KEYBOARD;
					}
				}
			}
		}
    }
/* ....................Hexa Keyboard end here................................... */
/* ....................Timer start here................................... */
    public void updateOneSecondCounter(char value) {
    	if (value !=0){
    		CounterInterruptOnOff=true;
    		CounterValue=CounterValueMax;
    	}
    	else{
    		CounterInterruptOnOff=false;
    	}
    }
    public class OneSecondCounter{
    	public OneSecondCounter(){
    		CounterInterruptOnOff=false;
    	}
 	    public void resetOneSecondCounter(){
	    	CounterInterruptOnOff=false;
	    	CounterValue=CounterValueMax;
	    }
    }
}