import java.net.*;
import java.io.*;


/**
 * The class SimulatorServer controls a simulator server for the
 * nuclear power plant
 *
 * @see RemoteSimulatorClient
 * @version 1.1
 * @author Henrik Eriksson
 */


abstract class SimulatorServer implements Runnable {

        /** The output stream for the server. Server responses are
            written to this stream. */
        protected DataOutputStream co;

        /** The socket for the server. */
        protected Socket connection;

	/** The socket (port) number for the server. */
	protected int sockNo;

	/** The thread for performing services */
        protected Thread serviceThread;

        /** Start the server */
        abstract synchronized void start();

        /** Calculate the next state */
        abstract synchronized void timeStep();

        /** Blow up a device */
        abstract synchronized void blow(String device);


        /** Set an integer value remotely. Sends the value to the client. */
        protected synchronized void setValue(String var, int val) {
	  try {
	    co.writeUTF(var);
	    co.writeChar('i');
	    co.writeInt(val);
	  }
          catch (IOException e) {
	  }
	}

        /** Set a float value remotely. Sends the value to the client.  */
        protected synchronized void setValue(String var, float val) {
	  try {
                co.writeUTF(var);
                co.writeChar('f');
                co.writeFloat(val);
	  }
          catch (IOException e) {
	  }
        }

        /** Set a String value remotely. Sends the value to the client.  */
        protected synchronized void setValue(String var, String val) {
	  try {
                co.writeUTF(var);
                co.writeChar('s');
                co.writeUTF(val);
	  }
          catch (IOException e) {
	  }
        }

        /** Set a boolean value remotely. Sends the value to the client.  */
        protected synchronized void setValue(String var, boolean val) {
	  try {
                co.writeUTF(var);
                co.writeChar('b');
                co.writeBoolean(val);
	  }
          catch (IOException e) {
	  }
        }

        /** Send a return message to the client */
        protected synchronized void sendReturn() {
	  try {
                co.writeUTF("");
                co.writeChar('r');
                co.flush();
	  }
          catch (IOException e) {
	  }
        }

        /** Send a blow message to the client */
        protected synchronized void sendBlow(String device) {
	  try {
              co.writeUTF("blow");
              co.writeChar('x');
              co.writeUTF(device);
	  }
          catch (IOException e) {
	  }
        }


	/** Run a service request */
	public void run() {
	  try {
	    ServerSocket listener = new ServerSocket(sockNo);
	    while (true) {
	      connection = listener.accept();
	      serviceRequest();
	      connection.close();
	    }
	  }
	  catch (IOException e) {
	  }
	}


        /** Perform a service request. */
        public synchronized void serviceRequest() {
          DataInputStream ci = null;
	  try {
                ci = new DataInputStream(connection.getInputStream());
                co = new DataOutputStream(connection.getOutputStream());

                while (true) {
                  String command = ci.readUTF();
                  if (command.equals("timeStep")) {
                    timeStep();
                    sendReturn();
                  } else if (command.equals("start")) {
                    start();
                    sendReturn();
                  } else if (command.equals("blow")) {
                    String d = ci.readUTF();
                    blow(d);
                    sendReturn();
                  } else if (command.equals("stop")) {
                    System.out.println("This is stop!");
                    break;
                  }
                }
	      }
          catch (IOException e) {
	  }
		  try {
                co.close();
                ci.close();
	      } catch (Throwable t) {
	      }
       }


	/** Simulates the obsolete startServer method */
       public void startServer(int sockNo) {
	 this.sockNo = sockNo;
	 if (serviceThread == null) {
	   serviceThread = new Thread(this);
	   serviceThread.setPriority(Thread.MAX_PRIORITY);
	   serviceThread.start();
	 }
       }


	/** The main method for the server. It is called automatically
	  when the server is invoked. This method simply invokes a test
	  simulator implemented in java. */
    public static void main (String args[]) {
        System.out.println("Server running");
	try {
	  System.out.println(InetAddress.getLocalHost().getHostName());
	}
	catch (UnknownHostException e) {
	}

        new TestSimulatorServer().startServer(1235);
    }


}


/**
 * The class TestSimulatorServer is a test class that simulates the
 * behavior of the Clips-based simulator. It is used for testing the
 * communication between the java side of the server and the client.
 *
 * @see LocalSimulator
 * @see ClipsSimulatorServer
 * @version 1.0
 * @author Henrik Eriksson
 */

class TestSimulatorServer extends SimulatorServer {
        /** The pressure in the reactor tank. (Unit: bar) */
        protected float    reactor_pressure;

        /** The percentage of the reactor moderator used */
        protected int      reactor_moderatorPercent;

        /** The water level in the reactor tank. (Unit: millimeters) */
        protected float    reactor_waterLevel;

        /** Flag for blown reactor. TRUE iff the reactor is blown. */
        protected boolean  reactor_blown;

        /** Flag for overheated reactor. TRUE iff the reactor is overheated. */
        protected boolean  reactor_overheated;

        /** Flag for blown turbine. TRUE iff the turbine is blown. */
        protected boolean  turbine_blown;

        /** The pressure in the condenser tank. (Unit: bar) */
        protected float    condenser_pressure;

        /** The water level in the condenser tank. (Unit: millimeters) */
        protected float    condenser_waterLevel;

        /** Flag for blown reactor. TRUE iff the reactor is blown. */
        protected boolean  condenser_blown;

        /** The output power from the generator. (Unit: MW) */
        protected int      generator_power;

        /** The status of pump 1. (0 = broken, 1-6 = image frame number) */
        protected int      pump_1_status;

        /** The rotation of pump 1. (Unit: rpm) */
        protected int      pump_1_rpm;

        /** The status of pump 2. (0 = broken, 1-6 = image frame number) */
        protected int      pump_2_status;

        /** The rotation of pump 2. (Unit: rpm) */
        protected int      pump_2_rpm;

        /** The status of pump 3. (0 = broken, 1-6 = image frame number) */
        protected int      pump_3_status;

        /** The rotation of pump 3. (Unit: rpm) */
        protected int      pump_3_rpm;

        /** Flag for blown pump. TRUE iff the pump is blown. */
        protected boolean  pump_1_blown;

        /** Flag for blown pump. TRUE iff the pump is blown. */
        protected boolean  pump_2_blown;

        /** Flag for blown pump. TRUE iff the pump is blown. */
        protected boolean  pump_3_blown;

        /** The status of valve 1. FALSE for closed and TRUE for open */
        protected boolean  valve_1_status;

        /** The status of valve 1. FALSE for closed and TRUE for open */
        protected boolean  valve_2_status;

        /** The status of valve 1. FALSE for closed and TRUE for open */
        protected boolean  valve_3_status;

        /** The status of valve 1. FALSE for closed and TRUE for open */
        protected boolean  valve_4_status;


        /** This method processes the start service request. The
            method resets the plant state to the initial (steady)
            state, and sends these values to the client. */
        public void start() {
                // Set the initial values...
                reactor_pressure = 288;
                reactor_moderatorPercent = 50;
                reactor_waterLevel = 1800;
                reactor_blown = false;
                reactor_overheated = false;
                turbine_blown = false;
                condenser_pressure = 40;
                condenser_waterLevel = 6000;
                condenser_blown = false;
                generator_power = 622;
                pump_1_status = 1;
                pump_1_rpm = 1400;
                pump_2_status = 1;
                pump_2_rpm = 0;
                pump_3_status = 1;
                pump_3_rpm = 1285;
                pump_1_blown = false;
                pump_2_blown = false;
                pump_3_blown = false;
                valve_1_status = true;
                valve_2_status = false;
                valve_3_status = true;
                valve_4_status = false;

                // Send the values to the client...
                setValue("reactor.pressure", reactor_pressure);
                setValue("reactor.moderatorPercent", reactor_moderatorPercent);
                setValue("reactor.waterLevel", reactor_waterLevel);
                setValue("reactor.blown", reactor_blown);
                setValue("reactor.overheated", reactor_overheated);
                setValue("turbine.blown", turbine_blown);
                setValue("condenser.pressure", condenser_pressure);
                setValue("condenser.waterLevel", condenser_waterLevel);
                setValue("condenser.blown", condenser_blown);
                setValue("generator.power", generator_power);
                setValue("pump_1.status", pump_1_status);
                setValue("pump_1.rpm", pump_1_rpm);
                setValue("pump_2.status", pump_2_status);
                setValue("pump_2.rpm", pump_2_rpm);
                setValue("pump_3.status", pump_3_status);
                setValue("pump_3.rpm", pump_3_rpm);
                setValue("pump_1.blown", pump_1_blown);
                setValue("pump_2.blown", pump_2_blown);
                setValue("pump_3.blown", pump_3_blown);
                setValue("valve_1.status", valve_1_status);
                setValue("valve_2.status", valve_2_status);
                setValue("valve_3.status", valve_3_status);
                setValue("valve_4.status", valve_4_status);
        }


        /** Calculates the next state of the simulation. This
            calculation is perfomed in java. */
        public void timeStep() {
                float v1, v2, v3, v4;

                  if (!reactor_overheated) {
                    // Compute the flow through valve_1...
                    if (valve_1_status)
                      v1 = (reactor_pressure-condenser_pressure) / 10;
                    else v1 = 0;
                    // Compute the flow through valve_2...
                    if (valve_2_status)
                      v2 = (reactor_pressure-condenser_pressure) / 2.5f;
                    else v2 = 0;

                    // Compute the flow through valve_3 and pump_1...
                    if (valve_3_status)
                      if (pump_1_rpm > 0)
                        if (condenser_waterLevel > 0)
                          v3 = pump_1_rpm * 0.07f;
                        else v3 = 0;
                      else v3 = -30;
                    else v3 = 0;

                    // Compute the flow through valve_4 and pump_2...
                    if (valve_4_status)
                      if (pump_2_rpm > 0)
                        if (condenser_waterLevel > 0)
                          v4 = pump_2_rpm * 0.07f;
                        else v4 = 0;
                      else v4 = -30;
                    else v4 = 0;

                    // Scale the flow levels to allow frequent time steps
                    // (smother animation)
                    float factor = 0.2f;
                    v1 *= factor;
                    v2 *= factor;
                    v3 *= factor;
                    v4 *= factor;

                    // Compute new values for pressure and water levels...
                    float boiledRW = (100 - reactor_moderatorPercent)*2*(900 - reactor_pressure)/620;
                    boiledRW *= factor;

                    float cooledKP = (float)(pump_3_rpm * Math.sqrt(condenser_pressure) * 0.003);
                    cooledKP *= factor;

                    float newRP = reactor_pressure - v1 - v2 + boiledRW/4;

                    // The steam flow to the condenser stops if the
                    // turbine is blown...
                    if (turbine_blown) v1 = 0;

                    // Compute new values for pressure and water levels...
                    float newKP = condenser_pressure + v1 + v2 - cooledKP;
                    float newRW = reactor_waterLevel + v3 + v4 - boiledRW;
                    float newKW = condenser_waterLevel - v3 - v4 + 4*cooledKP;

                    // Make adjustments for blown tanks...
                    if (reactor_blown) newRP = 0.15f * newRP;
                    if (condenser_blown) newKP = 0.2f * newKP;
                    // Check the computed values for illegal values...
                    if (newKW < 0) newKW = 0;
                    if (newKW > 9900) newKW = 9900;
                    if (newRW > 6000) newRW = 6000;
                    if (newKP < 0) newKP = 0;
                    if (newKP > 300) newKP = 300;
                    if (newRP > 800) newRP = 800;

                    // Adjust the generator power...
                    float newEffect;
                    if (valve_1_status && !turbine_blown)
                      newEffect = (newRP - newKP) * 2.5f;
                    else newEffect = 0;

                    // Send the computed values to the server...
                    generator_power = (int)newEffect;
                    setValue("generator.power", generator_power);
                    condenser_pressure = newKP;
                    setValue("condenser.pressure", condenser_pressure);
                    condenser_waterLevel = newKW;
                    setValue("condenser.waterLevel", condenser_waterLevel);
                    reactor_pressure = newRP;
                    setValue("reactor.pressure", reactor_pressure);
                    reactor_waterLevel = newRW;
                    setValue("reactor.waterLevel", reactor_waterLevel);

                    // Rules for the plant...
                    if (pump_1_blown) pump_1_rpm = 0;
                    if (pump_2_blown) pump_2_rpm = 0;
                    if (pump_3_blown) pump_3_rpm = 0;
                    if (reactor_waterLevel < -1500) {
                      reactor_overheated = true;
                      setValue("reactor.overheated", reactor_overheated);
                    }
                    if (reactor_pressure >= 610) {
                      reactor_blown = true;
                      sendBlow("reactor");
                    }
                    if (condenser_pressure >= 225) {
                      condenser_blown = true;
                      sendBlow("condenser");
                    }

                    // R1 (safety rule for blown turbine)...
                    if (turbine_blown) {
                      valve_1_status = false;
                      setValue("valve_1.status", valve_1_status);
                      valve_2_status = true;
                      setValue("valve_2.status", valve_2_status);
                      reactor_moderatorPercent = 100;
                      setValue("reactor.moderatorPercent",
                               reactor_moderatorPercent);
                      pump_1_rpm = 0;
                      setValue("pump_1.rpm", pump_1_rpm);
                      valve_3_status = false;
                      setValue("valve_3.status", valve_3_status);
                    }
                  } // if
        }


        /** Blow up a device. This method responds to blow requests
            from the client, and sets the simulation variables
            accordingly.
            * @param device The device to blow (e.g., "turbine")
            * @see ClipsSimulatorServer#blow */
        public void blow(String device) {
              if (device.equals("turbine")) turbine_blown = true;
              else if (device.equals("reactor")) reactor_blown = true;
              else if (device.equals("condenser")) condenser_blown = true;
              else if (device.equals("pump_1")) pump_1_blown = true;
              else if (device.equals("pump_2")) pump_2_blown = true;
              else if (device.equals("pump_3")) pump_3_blown = true;
        }


}
