// Linjeblock - a java applet showing the functioning of a
// interlocking machine for controlling a railway line
// between two stations. The functioning is documented
// (in swedish) in BMAS-nytt no. 3 2004.
//
// v1.0	2004-08-26 jonas
// v1.1 2004-08-31 Created getImageIcon() for loading icons.
// v1.2 2004-09-01 Red direction arrow led is only lit if the
// 	opposing station has set the direction towards us.
// 	The direction state is kept while the line is blocked.
// v2.0 2004-09-14 Start of new interface. Traffic A/B, no dir
// 	switch. This version implements exit signals.
// v2.1 2004-09-15 Clean up in directory. Frames added.
// 	description (html) updated.
// v2.2 2004-09-18 Fixed Bug that a change of the traffic switch
// 	did not get noticed at once if in any direction state.
// 	Solved by running task_dir() twice. Some more clean up.
// 	Removed "Mode information", as it not used.
// v3.0 2005-01-17 Modified state chart regardning switching
// 	between traffic mode A and B:
// 	1) 	In Traffic mode B: Switching to traffic mode A when
// 		the track was blocked resulted in loosing the direction
// 		information. Direction will now instead be set to Rb->Sl
// 		by jumping to 'rb_block'.
// 	2)	A switch over to traffic mode B would wait until any
// 		trainpaths were resolved (any direction). In the new
// 		version it the transition to traffic mode B will occur
// 		immediately if the trainpath has the direction Rb->Sl
// 		and the track is blocked (i.e. the driver has passed
// 		the exit signal).
// 	3)	It was before possible to switch to traffic mode B if
// 		the track was blocked as long as no direction was set
// 		(note that this is not normal operation). It now is
// 		now required that the track is free before the switch
// 		to traffic mode B will take place.
// 	


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.*;
import javax.swing.border.*;

public class Linjeblock extends JApplet
	implements ActionListener, ItemListener
{
	// How the user interface should work
	int Mode = 0;	/* not used for the moment... */
	
	// States
	
	/* States for line direction & exit signals */

	final int s_nodir =	8;
	final int s_rb_free = 	9;
	final int s_sl_free = 	10;
	final int s_rb_block = 	11;
	final int s_sl_block = 	12;
	final int s_rb_blink =	13;

	/* Directions for the line */
	
	final int dir_none = 0;	// No direction (default)
	final int dir_rb = 1;	// Rb --> Sl
	final int dir_sl = 2;	// Sl --> Rb

	/* Traffic modes */
	final int traffic_a = 0;	// Bidirectional traffic one line1.
	final int traffic_b = 1;	// Unsupervised traffic Rb->Sl.

	// State of interlocking machine
	int LineState = s_nodir;
	int SwitchTraffic = traffic_a;
	boolean SwitchRb = false; 	//Direction switch
	boolean SwitchSl = false;
	boolean SwitchRbGo = false;	// Go button (give train path)
	boolean SwitchSlGo = false;
	boolean TrackBlock = false;	// Is the line occupied (blocked)

	// loading icons that may change
	ImageIcon ico_sl_sig_red = getImageIcon ("icons/sl_sig_red.png");
	ImageIcon ico_sl_sig_green = getImageIcon ("icons/sl_sig_green.png");
	ImageIcon ico_rb_sig_red = getImageIcon ("icons/rb_sig_red.png");
	ImageIcon ico_rb_sig_green = getImageIcon ("icons/rb_sig_green.png");
	ImageIcon ico_rb_sig_dark = getImageIcon ("icons/rb_sig_dark.png");

	ImageIcon ico_track_block = getImageIcon("icons/track_blocked.png");
	ImageIcon ico_track_free = getImageIcon("icons/track_free.png");

	ImageIcon ico_sl_dir_in = getImageIcon ("icons/sl_dir_in.png");
	ImageIcon ico_rb_dir_in = getImageIcon ("icons/rb_dir_in.png");
	ImageIcon ico_sl_dir_out = getImageIcon ("icons/sl_dir_out.png");
	ImageIcon ico_rb_dir_out = getImageIcon ("icons/rb_dir_out.png");
	ImageIcon ico_nodir = getImageIcon ("icons/nodir.png");

	// Timer for blinking lights (80 blinks per minute)
	javax.swing.Timer blinker = new javax.swing.Timer(375, this);
	boolean BlinkOn = false;
	
	// User interface objects that have to be "globally" accessable

	Checkbox cb_line;	// User sets if line is blocked

	JButton bt_sl_go;	// Go-button (gives trainpath)
	JButton bt_rb_go;
	JLabel lb_sl_signal;	// Show signal aspect (Sl)
	JLabel lb_rb_signal;
	JLabel lb_sl_block;	// Led showing if track is blocked
	JLabel lb_rb_block;
	JLabel lb_sl_dirind;	// Triangle leds showing direction
	JLabel lb_rb_dirind;
	Checkbox cb_traffic_a;
	Checkbox cb_traffic_b;

        // getImageIcon() - routine for loading icons.

        public ImageIcon getImageIcon(String path)
        {
        	URL url = null;
                ImageIcon icon = null;
                url = getClass().getResource(path);
                if(url != null) {
		       icon = new ImageIcon(url);
	        } else {
	               	System.err.println("ERROR loading icon: "+path);
                }
	        return icon;
         }


	//
	// init()
	// 	Automatically run when applet is started. Sets up
	// 	the user interface.
	//

	public void init()
	{
		String s;
		s = getParameter("mode");
		if (s == "1") Mode = 1;
		else if (s == "2") Mode = 2;
		else if (s == "3") Mode = 3;
		else Mode = 1;	
		
		// The window is dived into three panels,
		// one for Sl, one for the line and one for Rb.

		JPanel panel_sl = new JPanel(new GridLayout(3,3));
		JPanel panel_line = new JPanel();
		JPanel panel_rb = new JPanel(new GridLayout(3,3));

		// frames around panels
		panel_sl.setBorder(new EtchedBorder());
		panel_rb.setBorder(new EtchedBorder());


		// Sandlid

		lb_sl_signal = new JLabel(ico_sl_sig_red);
		lb_sl_dirind = new JLabel(ico_nodir);
		lb_sl_block = new JLabel(ico_track_free);
		JLabel lb_empty1 = new JLabel("", JLabel.CENTER);
		JLabel lb_empty2 = new JLabel("", JLabel.CENTER);
		JLabel lb_empty3 = new JLabel("", JLabel.CENTER);
		JLabel lb_empty4 = new JLabel("", JLabel.CENTER);
		JLabel lb_empty5 = new JLabel("", JLabel.CENTER);

		bt_sl_go = new JButton("Utfart");

		// The traffic switch is composed by two
		// checkboxes grouped so that the user cannot
		// activate both at the same time.
		//
		// A "subpanel" holds the two checkboxes for
		// proper visual alignment.

		CheckboxGroup group_sl = new CheckboxGroup();
		cb_traffic_a = new Checkbox("A", group_sl, true);
		cb_traffic_b = new Checkbox("B", group_sl, false);
		JPanel p_traffic_sel = new JPanel();
		p_traffic_sel.add(cb_traffic_a);
		p_traffic_sel.add(cb_traffic_b);

		// The gui components at each station
		// are arranged in a 3x3 grid making
		// positioning easier. The empty labels
		// are simply "place holders" where no
		// gui components are present.

		panel_sl.add(lb_sl_signal);
		panel_sl.add(lb_empty1);
		panel_sl.add(lb_sl_dirind);
		panel_sl.add(bt_sl_go);
		panel_sl.add(lb_empty3);
		panel_sl.add(lb_sl_block);
		panel_sl.add(p_traffic_sel);
		panel_sl.add(lb_empty4);
		panel_sl.add(lb_empty5);

		// Line 
		
		cb_line = new Checkbox("Linjen blockerad", false);
		panel_line.add(cb_line);
		
		// Rustansberg (se comments for Sl)

		lb_rb_signal = new JLabel(ico_rb_sig_red);
		lb_rb_dirind = new JLabel(ico_nodir);
		lb_rb_block = new JLabel(ico_track_free);
		JLabel lb_empty6 = new JLabel("", JLabel.CENTER);	
		JLabel lb_empty7 = new JLabel("", JLabel.CENTER);	
		JLabel lb_empty8 = new JLabel("", JLabel.CENTER);	
		JLabel lb_empty9 = new JLabel("", JLabel.CENTER);	
		JLabel lb_empty10 = new JLabel("", JLabel.CENTER);	

		bt_rb_go = new JButton("Utfart");	
		
		JPanel p_dirsel_rb = new JPanel();

		panel_rb.add(lb_rb_dirind);
		panel_rb.add(lb_empty8);
		panel_rb.add(lb_empty7);
		panel_rb.add(lb_rb_block);
		panel_rb.add(lb_empty2);
		panel_rb.add(bt_rb_go);
		panel_rb.add(lb_empty6);
		panel_rb.add(lb_empty9);
		panel_rb.add(lb_rb_signal);

		// Callback registration
		// 	These are the "events" that occur
		// 	when the user presses a button or
		// 	checks a checkbox.
		//
		// 	addActionListener says that the method
		// 	actionPerformed (in 'this' class) should
		// 	be called if something happens with a
		// 	button.
		//
		// 	addItemListener adds a eventhandler for
		// 	changes in checkboxes, calling
		// 	itemStateChanged if they are checked
		// 	or unchecked.
		// 	
		
		bt_sl_go.addActionListener(this);
		bt_rb_go.addActionListener(this);
		cb_line.addItemListener(this);	
		cb_traffic_a.addItemListener(this);
		cb_traffic_b.addItemListener(this);
		
		// All panels in the window go into a main container.
		Container c = getContentPane();
		c.add(panel_sl);
		c.add(panel_line);
		c.add(panel_rb);
		// Main gui: one row, three columns (Sl | line | Rb).
		c.setLayout(new GridLayout(1,3));
		
		blinker.start();
	}

	// redraw()
	// 	Updates the display from the current state
	// 	variables. Calling this redraws the user interface.
	
	public void redraw()
	{
		// Update Direction indicators
		switch(direction())
		{
		case dir_none:
			lb_sl_dirind.setIcon(ico_nodir);
			lb_rb_dirind.setIcon(ico_nodir);
			break;
		case dir_rb:
			lb_sl_dirind.setIcon(ico_sl_dir_in);
			lb_rb_dirind.setIcon(ico_rb_dir_out);
			break;
		case dir_sl:
			lb_sl_dirind.setIcon(ico_sl_dir_out);
			lb_rb_dirind.setIcon(ico_rb_dir_in);
			break;
		}

		// Update Trackblock-leds
		if(TrackBlock) 
		{
			lb_sl_block.setIcon(ico_track_block);
			lb_rb_block.setIcon(ico_track_block);
		}
		else
		{
			lb_sl_block.setIcon(ico_track_free);
			lb_rb_block.setIcon(ico_track_free);
		}
		
		// Update Signal aspects
		if(LineState == s_sl_free)
		{
			lb_sl_signal.setIcon(ico_sl_sig_green);
			lb_rb_signal.setIcon(ico_rb_sig_red);
		}
		else if(LineState == s_rb_free)
		{
			lb_rb_signal.setIcon(ico_rb_sig_green);
			lb_sl_signal.setIcon(ico_sl_sig_red);
		}
		else if(LineState == s_rb_blink)
		{
			set_rb_blink_signal();
			lb_sl_signal.setIcon(ico_sl_sig_red);
		}
		else
		{
			lb_sl_signal.setIcon(ico_sl_sig_red);
			lb_rb_signal.setIcon(ico_rb_sig_red);
		}

	}

	// set_rb_blink_signal()
	// 	Is called to update the icon for the
	// 	blinking signal. This routine is called
	// 	from the timer 'blinker' (if traffic is B)
	// 	and also if user switches to traffic B from A.
	// 	
	
	public void set_rb_blink_signal()
	{
		if(BlinkOn) 
			lb_rb_signal.setIcon(ico_rb_sig_green);
		else
			lb_rb_signal.setIcon(ico_rb_sig_dark);

	}
	

	// Event handler routines (callbacks)
	// Events:
	// 	User Changes traffic switch
	//	User Presses GO-buttons
	//	Train enters/leaves track
	//	Blinktimer times out
	
	// Eventhandler for buttons 
	public void actionPerformed(ActionEvent e)
	{
		if(e.getSource() == bt_sl_go)
		{
			SwitchSlGo = true;
			run_tasks();
			SwitchSlGo = false;	// simulate button release
			run_tasks();
			redraw();
		}
		if(e.getSource() == bt_rb_go)
		{
			SwitchRbGo = true;
			run_tasks();
			SwitchRbGo = false;	// simulate button release
			run_tasks();
			redraw();
		}
		if(e.getSource() == blinker)
		{
			// toggle Blink variable 
			if(BlinkOn) BlinkOn = false;
			else BlinkOn = true;

			// change icon, if in traffic B.
			if(LineState == s_rb_blink)
			{
				set_rb_blink_signal();
			}

		}

	}
	
	// selected()
	// 	Checks if a certain gui component was the cause
	// 	of a ItemEvent. (help function)
	// 	
	public boolean selected(ItemEvent e, Object src)
	{
		if(e.getSource() == src && e.getStateChange() ==
				java.awt.event.ItemEvent.SELECTED)
			return true;
		return false;
	}

	// Eventhandler for checkboxes
	public void itemStateChanged(ItemEvent e)
	{
		if(e.getSource() == cb_line)
		{
			if(e.getStateChange() == 
				java.awt.event.ItemEvent.SELECTED)
			{
				TrackBlock = true;
			}
			if(e.getStateChange() == 
				java.awt.event.ItemEvent.DESELECTED)
			{
				TrackBlock = false;
			}
		}
		if(selected(e, cb_traffic_a)) SwitchTraffic = traffic_a;
		if(selected(e, cb_traffic_b)) SwitchTraffic = traffic_b;
		run_tasks();
		redraw();
	}
		
	//////////////////////////////////////////////////////
	/////// Logic //////

	/// Methods controlling the Direction state

	// direction()
	// Gives the direction (dir_none | dir_rb | dir_sl) for
	// the current LineState.

	public int direction()
	{
		switch(LineState)
		{
		case s_nodir:
			return dir_none; 
		case s_rb_free:
		case s_rb_block:
		case s_rb_blink:
			return dir_rb;
		case s_sl_free:
		case s_sl_block:
			return dir_sl;
		default:
			return dir_none;	// should not happen...
		}
	}
	
	// line_nodir()
	//	Default state when no direction is set and
	//	traffic mode 'A' is in use.. 
	
	public void line_nodir()
	{
		if(SwitchRbGo && !TrackBlock) LineState = s_rb_free;
		else if(SwitchSlGo && !TrackBlock) LineState = s_sl_free;
		//else if(SwitchTraffic == traffic_b) LineState = s_rb_blink;	
/*NEW*/		else if(SwitchTraffic == traffic_b && !TrackBlock)
			LineState = s_rb_blink;	
	}

	// line_rb_free()
	// 	Train path has been given out on the line, but
	// 	the train has not yet entered the line.
	
	public void line_rb_free()
	{
		if(TrackBlock) LineState = s_rb_block;
	}
	
	public void line_sl_free()
	{
		if(TrackBlock) LineState = s_sl_block;
	}

	// line_rb_block()
	// 	The departured train has now entered the line,
	// 	and this state checks if the train has arrived
	// 	at the opposite station.
	
	public void line_rb_block()
	{
		if(!TrackBlock) LineState = s_nodir;
/*NEW*/		else if(SwitchTraffic == traffic_b) LineState = s_rb_blink;
	}

	public void line_sl_block()
	{
		if(!TrackBlock) LineState = s_nodir;
	}

	// line_rb_blink()
	// 	When traffic mode 'B' is in use, the line blocking
	// 	control is disabled, and the only allowed direction
	// 	is Rb --> Sl
	
	public void line_rb_blink()
	{
		//if(SwitchTraffic == traffic_a) LineState = s_nodir;
/*NEW*/		if(SwitchTraffic == traffic_a) LineState = s_rb_block;
	}

	// Run the routine for the current line direction state.

	public void task_dir()
	{
		switch(LineState)
		{
		case s_nodir:	 line_nodir(); break;
		case s_rb_free:  line_rb_free(); break;
		case s_rb_block: line_rb_block(); break;
		case s_rb_blink: line_rb_blink(); break;
		case s_sl_free:  line_sl_free(); break;
		case s_sl_block: line_sl_block(); break;
		}
	}

	//////////////////////////////////////////////////////

	// run_tasks()
	// 	runs the tasks twice. The reason for running them
	// 	twice is so that any change in state caused by one
	// 	task is propagated to the other.
	//
	// 	In a physical implementation, each task will run
	// 	continously, but here we only run them after any
	// 	user interaction.	
	
	public void run_tasks()
	{
		task_dir(); 
		task_dir(); 
	}
	
}

