#!/usr/local/bin/php
<?php

/**
 * Based loosely on the NET_Server code in PEAR.
 * This is only an example - considerable additional work is needed
 * Specifically, the code in the handleConnection method should be 
 * handled in a subclass
 *
 * 
 */

class Message
{
  var $properties = array();
  var $content = null;
}

class EventSocketListener
{

  var $host;
  var $port;
  var $sock;
  var $is_parent = true;
  var $clientInfo;
  var $clientFD;

  var $connectionContext = array();


  function &create($port, $host = "localhost")
      {
	$esl = new EventSocketListener;
	$esl->port = $port;
	$esl->host = $host;
        if (!function_exists('socket_create')) {
            return PEAR::raiseError('Sockets extension not available.');
        }
	return $esl;
      }

  function start()
  {
	if (($this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
	  echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
	}

	if (!socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1)) {
	  echo 'Unable to set option on socket: '. socket_strerror(socket_last_error()) . PHP_EOL;
	}

	if (socket_bind($this->sock, $this->host, $this->port) === false) {
	  echo "socket_bind() failed: reason: " . socket_strerror(socket_last_error($this->sock)) . "\n";
	}

	if (socket_listen($this->sock, 5) === false) {
	  echo "socket_listen() failed: reason: " . socket_strerror(socket_last_error($this->sock)) . "\n";
	}

        // Dear children, please do not become zombies
        pcntl_signal(SIGCHLD, SIG_IGN);
        
        // wait for incmoning connections
        while (true)
        {
            // new connection
            if(($fd = socket_accept($this->sock)))
            {
	      $pid = pcntl_fork();
	      if($pid == -1) {
		  return  PEAR::raiseError('Could not fork child process.');
                }
                // This is the child => handle the request
                elseif($pid == 0) {
                    // this is not the parent
                    $this->_isParent = false;
                    // store the new file descriptor
                    $this->clientFD = $fd;

                    $peer_host    =    "";
                    $peer_port    =    "";
                    socket_getpeername($this->clientFD, $peer_host, $peer_port);
                    $this->clientInfo    =    array(
                                                      "host"        =>    $peer_host,
                                                      "port"        =>    $peer_port,
                                                      "connectOn"   =>    time()
                                                   );
		    $this->handleConnection();
		    socket_shutdown($this->clientFD, 2);
		    socket_close($this->clientFD);
		}
		else /* Parent does nothing */
		  {
		  }
	    }
	}
  }

  function handleConnection()
  {
    $fd = $this->clientFD;
    //first, read headers & setup a state for this connection
    $line = "";
    socket_write($fd, "CONNECT\n\n");
    do 
      {
	$line = socket_read($fd, 2048, PHP_NORMAL_READ);
	if (trim($line) == "")
	  break;
	//we got a header, we need to add it to the 
	list($key, $value) = explode(":", $line);
	$key = trim($key);
	$value = trim(urldecode($value));
	$this->connectionContext[$key] = $value;
      }
    while ($line != "");
    
    //    print_r($this->connectionContext);
    $this->callConnected();

    exit();
    
  }

  function processMessages($returnOnReply = false)
  {
    $fd = $this->clientFD;
    $result = new Message();
    $props = array();
    while (true)
      {
	do 
	  {
	    $line = @socket_read($fd, 2048, PHP_NORMAL_READ);
	    if (socket_last_error($fd) == 104)
	      return null;
	    if ($line == null || $line == FALSE || trim($line) == "")
	      break;
	    //we got a header, we need to add it to the message
	    list($key, $value) = explode(":", $line);
	    $key = trim($key);
	    $value = trim(urldecode($value));
	    $props[$key] = $value;
	  }
	while ($line != "");
	$result->properties = $props;

	if (isset($props['Content-Length']))
	  {
	    $length = $props['Content-Length'];
	    print("Reading content - $length\n");
	    $data = socket_read($fd, $length);
	    $result->content = $data;
	  }
	if (isset($props['Content-Type']))
	  {
	    $type = $props['Content-Type'];
	    if ($returnOnReply && 
		($type == "command/reply" || $type == "api/response"))
	      {
		return $result;
	      }
	    else if ($type == "text/event-plain") //only plain events for now
	      {
		$this->handleEvent($result);
	      }
	  }
	else
	  {
	    print("UNKNOWN MESSAGE: \n");
	    print_r($result);
	  }
      }    
  }
    


  function invokeCommand($command)
  {
    //Send the command
    print("Invoking: $command\n");
    $this->sendCommand($command);
    // Wait for the response
    $result = $this->processMessages(true);
    return $result;
  }

  function sendCommand($command)
  {
    $fd = $this->clientFD;
    socket_write($fd, trim($command) . "\n\n"); 
  }

/*-----------------------------------------------------*/
  /* Abstract Methods - should move to subclass*/

  function callConnected()
  {
    print_r($this->connectionContext);
    print("----------------\n");
    $result = $this->invokeCommand("log DEBUG");
    print_r($result);
    $result = $this->invokeCommand("event plain ALL");
    print_r($result);

    $this->processMessages(false);
    print("DONE PROCESSING MESSAGES");
    print_r($this->connectionContext);
  }

  function handleCommandResponse($response)
  {
    print("Recieved Unhandled Response:\n");
    print_r($response);
  }

  function handleEvent($event)
  {
    print("Recieved Unhandled Event:\n");
    print_r($event);
  }
}

  

  
// create a server that forks new processes
$server  = &EventSocketListener::create(9090);

// start the server
$server->start();
?>


