package phonesystem;

import org.apache.log4j.*;
import junit.framework.TestCase;

import java.net.*;
import java.io.*;
import java.util.*;

import phonesystem.communication.PhoneExchangeReceiver;
import phonesystem.exchange.ActivityBroker;
import phonesystem.initialize.PopulateDatabase;
import phonesystem.server.ServerLauncher;

// Note: must set system param "signalling.port" to other than 5000, since this
// framework builds a signalling server on that port.

public abstract class SystemTestCase extends TestCase
{
    /**
     * Decides whether to allow or deny an acquire-resource attempt for the given
     * phone.  The result is allowed to change over time.  If this test case has
     * the system launch with phones in the database, then acquireResourceAttempt
     * calls will result from the setUp routine, i.e. before any textXyz() code
     * gets called.
     */
    protected abstract boolean acquireResourceAttempt(InetAddress hwip);

    /**
     * Used to talk to the call processing component.  Only needed to simulate
     * UI actions that talk to a manager and the A.B.  Gets initialized before
     * any testXyz routines are called but is not guaranteed to be present
     * during an allowAcquireResource invocation.
     */
    protected ActivityBroker _activityBroker = null;

    SignallingServerSimulator sss  = null;
	
    protected void setUp() throws Exception
    {
    PopulateDatabase.main(new String[]{"data/DatabaseSchema.sql", "data/InitialData.sql"});
	sss = new SignallingServerSimulator();
	sss.start();
	_activityBroker = ServerLauncher.initializeSystem();
    }

    protected void tearDown() throws Exception
    {
    	_activityBroker.dischargeAll();
		sss.stop();
		// HACK: pause so the thread can stop using the socket
		Thread.sleep(1000);
		sss.end();
		PhoneExchangeReceiver.shutdownForTesting();
		// HACK: pause so the socket can close
		Thread.sleep(3000);
		System.gc();
    }

    private class SignallingServerSimulator extends Thread
    {
    	/** Socket for the signalling server */
    	ServerSocket _serverSocket;
    	
	public SignallingServerSimulator() throws IOException
	{
	    int signallingListenPort = Integer.parseInt(System.getProperty("SignallingPort"));
	    _serverSocket = new ServerSocket(signallingListenPort);
	    //setDaemon(true);
	}

	public void run()
	{
	    try {
		while (true) {
		    Socket phoneInterface = _serverSocket.accept();
		    String command = readCommand(phoneInterface);
		    if (command.indexOf("<AcquireResource>") != -1) {
			int start = command.indexOf("<Resource>") + ("<Resource>").length();
			int end = command.indexOf("</Resource>");
			String ip = command.substring(start, end).trim();
			InetAddress address = InetAddress.getByName(ip);
			OutputStream os = phoneInterface.getOutputStream();
			if (acquireResourceAttempt(address)) {
			    os.write(("<ResourceAcquired><Resource>phoneIP</Resource>"
				     +"</ResourceAcquired>\r\n\r\n").getBytes());
			    socketsToLivePhones.put(address, phoneInterface);
			} else {
			    os.write(("<Error><ErrorDescription>Test case refused connect"
				     + "</ErrorDescription></Error>\r\n\r\n").getBytes());
			    phoneInterface.close();
			}
		    }
		}
	    } catch (IOException ex) {
		System.err.println("Unexpected error in signalling server; "
				   + "no new phones will be able to connect");
		ex.printStackTrace();
	    }
	}
	
	public void end() throws IOException
	{
		_serverSocket.close();
	}
    }

    
    
    private String readCommand(Socket phone) throws IOException
    {
	StringBuffer sb = new StringBuffer();
	InputStream is = phone.getInputStream();
	int ch;
	while ( (ch = is.read())  !=  -1 ) {
	    sb.append((char)ch);
	    if (ch == '\n') {
		break;
	    }
	}
	return sb.toString();
    }

    protected void assertCommandReceivedFrom(String expectedCommand, InetAddress phoneAddr) throws IOException
    {
	expectedCommand = stripWs(expectedCommand);
	Socket phone = (Socket) socketsToLivePhones.get(phoneAddr);
	String actualCommand;
	do {
	    actualCommand = stripWs(readCommand(phone));
	} while (actualCommand.equals(""));
	assertTrue( "Received wrong command; expected: " + expectedCommand
		    + " received " + actualCommand,
		    expectedCommand.equals(actualCommand) );
    }

    protected void sendEventTo(String event, InetAddress phoneAddr) throws IOException
    {
	Socket phone = (Socket) socketsToLivePhones.get(phoneAddr);
	phone.getOutputStream().write((event + "\r\n\r\n").getBytes());
    }

    /**
     * (InetAddress) hwip  -->  (Socket) toPhoneInterface
     */
    private Map socketsToLivePhones = Collections.synchronizedMap(new HashMap());

    private static String stripWs(String arg)
    {
	return arg.replaceAll("[\\s]", "");
    }

    public static String buildHandsetOnCommand()
    {
	return "<HandsetOn/>"; 
    }

    public static String buildHandsetOffCommand()
    { 
	return "<HandsetOff/>"; 
    }

    public static String buildStartRingingCommand()
    {
	return "<StartRinging/>";
    }

    public static String buildStopRingingCommand() 
    {
	return "<StopRinging/>"; 
    }
    
    public static String buildPlayToneCommand(int tone, int cadence)
    {
	return "<PlayTone><Tone>" + tone + "</Tone><Cadence>"
	    + cadence + "</Cadence></PlayTone>"; 
    }
    
    public static String buildStartAudioSendCommand(InetAddress destDevice)
    {
	return "<StartAudioSend><DestDevice>" + destDevice.getHostAddress() +
	    "</DestDevice></StartAudioSend>";
    }
    
    public static String buildStartAudioReceiveCommand(InetAddress  destDevice)
    {
	return "<StartAudioReceive><DestDevice>" + destDevice.getHostAddress() +
	    "</DestDevice></StartAudioReceive>";
    }
    
    public static String buildStopAudioSendCommand(InetAddress destDevice) 
    {
	return "<StopAudioSend><DestDevice>" + destDevice.getHostAddress() + 
	    "</DestDevice></StopAudioSend>";
    }

    public static String buildStopAudioReceiveCommand(InetAddress destDevice)
    { 
	return "<StopAudioReceive><DestDevice>" + destDevice.getHostAddress() + 
	    "</DestDevice></StopAudioReceive>";
    }
    
    public static String buildOnHookEvent()
    { 
	return "<OnHook/>";
    }
    
    public static String buildOffHookEvent()
    {
	return "<OffHook/>";
    }
    
    public static String buildDigitPressedEvent(int value) 
    {
	return "<DigitPressed><Value>" + value + "</Value></DigitPressed>";
    }
    
    public static String buildBusyTone()
    {
    	return buildPlayToneCommand(7,17);
    }
    
    public void pickUpIdlePhoneCheckGotDialTone(InetAddress actor)
	throws Exception
    {
	sendEventTo(buildOffHookEvent(), actor);
	assertCommandReceivedFrom(buildHandsetOnCommand(), actor);
	assertCommandReceivedFrom(buildPlayToneCommand(1, 0), actor);
    }

    public void pickUpIdlePhoneCheckGotBusyTone(InetAddress actor)
	throws Exception
    {
	sendEventTo(buildOffHookEvent(), actor);
	assertCommandReceivedFrom(buildHandsetOnCommand(), actor);
	assertCommandReceivedFrom(buildBusyTone(), actor);
    }

    public void dialCheckRingtone(InetAddress actor, int[] dialString)
	throws Exception
    {
	for (int i = 0;  i < dialString.length;  i++) {
	    sendEventTo( buildDigitPressedEvent(dialString[i]), actor);
	}
	assertCommandReceivedFrom( buildPlayToneCommand(5,0), actor );
    }

    public void dialCheckBusyTone(InetAddress actor, int[] dialString)
	throws Exception
    {
	for (int i = 0;  i < dialString.length;  i++) {
	    sendEventTo( buildDigitPressedEvent(dialString[i]), actor);
	}
	assertCommandReceivedFrom( buildBusyTone(), actor );
    }

    
    public void checkRinging(InetAddress actor)
	throws Exception
    {
	assertCommandReceivedFrom(buildStartRingingCommand(), actor);
    }

    public void checkBusyTone(InetAddress actor)
	throws Exception
    {
	assertCommandReceivedFrom(buildBusyTone(), actor);
    }
    
    public void pickUpRingingPhoneCheckConverstionBegun(
	InetAddress actor, InetAddress peer)
	throws Exception
    {
	sendEventTo(buildOffHookEvent(), actor);
	assertCommandReceivedFrom(buildHandsetOnCommand(), actor);
	assertCommandReceivedFrom(buildStopRingingCommand(), actor);
	assertCommandReceivedFrom(buildPlayToneCommand(255, 0), actor);
	assertCommandReceivedFrom(buildStartAudioSendCommand(peer), actor);
	assertCommandReceivedFrom(buildStartAudioReceiveCommand(peer), actor);
    }
	
    public void checkRingtoneStoppedAndConversationBegun(InetAddress actor,
							 InetAddress peer)
	throws Exception
    {
	assertCommandReceivedFrom(buildPlayToneCommand(255, 0), actor);
	assertCommandReceivedFrom(buildStartAudioSendCommand(peer), actor);
	assertCommandReceivedFrom(buildStartAudioReceiveCommand(peer), actor);
    }

    public void hangupOnActiveConversationCheckAllShutDown(InetAddress actor,
							   InetAddress peer)
	throws Exception
    {
	sendEventTo(buildOnHookEvent(), actor);
	assertCommandReceivedFrom(buildStopAudioSendCommand(peer), actor);
	assertCommandReceivedFrom(buildStopAudioReceiveCommand(peer), actor);
	assertCommandReceivedFrom(buildPlayToneCommand(7, 0), actor);
	assertCommandReceivedFrom(buildPlayToneCommand(255, 0), actor);
	assertCommandReceivedFrom(buildHandsetOffCommand(), actor);
    }

    public void checkConversationCancelled(InetAddress actor, InetAddress peer)
	throws Exception
    {
	assertCommandReceivedFrom(buildStopAudioSendCommand(peer), actor);
	assertCommandReceivedFrom(buildStopAudioReceiveCommand(peer), actor);
	assertCommandReceivedFrom(buildPlayToneCommand(7, 0), actor);
    }

    public void hangupCancelledPhoneCheckAllShutDown(InetAddress actor)
	throws Exception
    {
	sendEventTo(buildOnHookEvent(), actor);
	assertCommandReceivedFrom(buildPlayToneCommand(255, 0), actor);
	assertCommandReceivedFrom(buildHandsetOffCommand(), actor);
    }

    public void doCompleteCall(InetAddress originator,
			       InetAddress receiver,
			       int[] originatorsDialString)
	throws Exception
    {
	// originator picks up
	pickUpIdlePhoneCheckGotDialTone(originator);
	// originator dials
	dialCheckRingtone(originator, originatorsDialString);
	checkRinging(receiver);
	// receiver picks up
	pickUpRingingPhoneCheckConverstionBegun(receiver, originator);
	checkRingtoneStoppedAndConversationBegun(originator, receiver);
	// originator hangs up
	hangupOnActiveConversationCheckAllShutDown(originator, receiver);
	checkConversationCancelled(receiver, originator);
	// receiver hangs up
	hangupCancelledPhoneCheckAllShutDown(receiver);
    }
    
    /**
     * Simulate a call from a can-originate to a cannot-receive.
     * 
     * @param originator IP of caller.
     * @param originatorsDialString Extension of cannot-receive callee.
     * @throws Exception
     */
    public void doCallFromCanOriginateToCannotReceive(InetAddress originator, int[] originatorsDialString)
	throws Exception
	{
		// originator picks up
		pickUpIdlePhoneCheckGotDialTone(originator);
		// originator dials and gets busy
		dialCheckBusyTone(originator, originatorsDialString);
		// caller hangs up
		hangupCancelledPhoneCheckAllShutDown(originator);
	}
    
    /**
     * Simulate a call from a can-originate to a non-existent phone
     * 
     * @param originator IP of caller.
     * @param originatorsDialString Extension of cannot-receive callee.
     * @throws Exception
     */
    public void doCallFromCanOriginateToNonexistent(InetAddress originator, int[] originatorsDialString)
	throws Exception
	{
		// originator picks up
		pickUpIdlePhoneCheckGotDialTone(originator);
		// originator dials and gets busy
		dialCheckBusyTone(originator, originatorsDialString);
		// receiver hangs up
		hangupCancelledPhoneCheckAllShutDown(originator);
	}
	
	    /**
	     * Simulate a call from a cannot-originate phone.
     * 
     * @param originator IP of caller.
     * @param originatorsDialString Extension of cannot-receive callee.
     * @throws Exception
     */
    public void doCallFromCannotOriginate(InetAddress originator, int[] originatorsDialString)
	throws Exception
	{
		// originator picks up and gets busy
		pickUpIdlePhoneCheckGotBusyTone(originator);
		// receiver hangs up
		hangupCancelledPhoneCheckAllShutDown(originator);
	}


}

