
/* VNetwork  */
VAddress = function(id, port)
{
    this.id = id;
    this.port = port;
};

var VNetwork = function() {
    this.table = new Object;
    this.id_index = 0;
};

VNetwork.Queue = {
  SERVER: 0,
  CLIENT: 1
};

VNetwork.prototype = 
{
    newId: function()
    {
	var id = this.id_index++;
	this.table[id] = new Object();
	return id;
    },
    
    deleteId: function(id)
    {
	delete this.table[id];
    },
    
    ready: function(address)
    {
	if (this.table[address.id] 
	    && this.table[address.id][address.port])
	{
	    return true;
	}
	return false;
    },
    readyQueue: function(queue_index, address)
    {
	if (this.table[address.id] 
	    && this.table[address.id][address.port]
	    && this.table[address.id][address.port][queue_index])
	{
	    return true;
	}
	return false;
    },
    
    openRandomPort: function(id)
    {
	if (!this.table[id]) {
	    throw "id not found";
	}
	while (true) {
	    var port = Math.floor(Math.random() * 0xffff);
	    if (!this.table[id][port]) {
		var local_address = new VAddress(id, port);
		this.open(local_address);
		return { status: true, address: local_address };
	    }
	}
    },
    
    open: function(address)
    {
	if (this.ready(address)) {
	    throw "double open";
	}
	this.table[address.id][address.port] = new Array();
	this.table[address.id][address.port][VNetwork.Queue.SERVER] = new Array();
	this.table[address.id][address.port][VNetwork.Queue.CLIENT] = new Array();
	
	return true;
    },
    
    close: function(address)
    {
	if (this.ready(address)) {
	    delete this.table[address.id][address.port];
	}
    },
    shutdown: function(queue_index, address)
    {
	if (this.readyQueue(queue_index, address)) {
	    delete this.table[address.id][address.port][queue_index];
	}
    },
    
    send: function(queue_index, address, packet)
    {
	if (!this.readyQueue(queue_index, address)) {
	    return false;
	}
	this.table[address.id][address.port][queue_index].push(packet);
	
	return true;
    },
    
    poll: function(queue_index, address)
    {
	if (!this.readyQueue(queue_index, address)) {
	    return false;
	}
	if (this.table[address.id][address.port][queue_index].length > 0) {
	    return true;
	}
	return false;
    },
    
    receive: function(queue_index, address)
    {
	if (!this.poll(queue_index, address)) {
	    return null;
	}
	return this.table[address.id][address.port][queue_index].shift();
    }
};

//debug
function vnet_debug()
{
    var net = new VNetwork();
    var id = net.newId();
    var addr = new VAddress(id, 80);
    var ret, i;
    ret = net.open(addr);
    for (i = 0; i < 10; ++i) {
	ret = net.send(VNetwork.Queue.SERVER, addr, "to server" + i);
	ret = net.send(VNetwork.Queue.CLIENT, addr, "to client" + i);
    }
    for (i = 0; i < 11; ++i) {
	ret = net.receive(VNetwork.Queue.SERVER, addr);
	ret = net.receive(VNetwork.Queue.CLIENT, addr);
    }
    net.shutdown(VNetwork.Queue.CLIENT, addr);
    net.shutdown(VNetwork.Queue.SERVER, addr);
    net.close(addr);
    
    for (i = 0; i < 10; ++i) {
	ret = net.openRandomPort(id);
    }
    net.deleteId(addr.id);
};

var NET = new VNetwork();

/* VPacket */
VPacket = function(command, argv)
{
    this.command = command;
    this.argv = argv;
};

VPacket.Command = {
  CLOSE: 0,
  CONNECT: 1,
  DATA: 2
};

/* VSocket */
VSocket = function(id, bind_address)
{
    this.id = id;
    this.bind_address = bind_address ? bind_address : null;
    this.is_server = false;
};

VSocket.prototype = 
{
    connect: function(server_address)
    {
	var result = NET.openRandomPort(this.id);
	if (!result.status) {
	    return false;
	}
	var request = new VPacket(VPacket.Command.CONNECT, result.address);
	if (!NET.send(VNetwork.Queue.SERVER, server_address, request)) {
	    NET.close(result.address);
	    return false;
	}
	this.is_server = false;
	this.bind_address = result.address;
	
	return true;
    },
    
    listen: function(port)
    {
	var address = new VAddress(this.id, port);
	if (!NET.open(address)) {
	    return false;
	}
	this.bind_address = address;
	this.is_server = true;
	
	return true;
    },
    
    accept: function()
    {
	if (!this.accept) {
	    throw "illegal socket operation";
	}
	var packet = NET.receive(VNetwork.Queue.SERVER, this.bind_address);
	if (!packet) {
	    return null;
	}
	
	if (packet.command != VPacket.Command.CONNECT) {
	    return null;
	}
	
	var client_socket = new VSocket(this.id, packet.argv);
	client_socket.is_server = true;
	
	return client_socket;
    },
    
    close: function()
    {
	var request = new VPacket(VPacket.Command.CLOSE, null);
	if (this.is_server) {
	    NET.send(VNetwork.Queue.CLIENT, this.bind_address, request);
	    NET.shutdown(VNetwork.Queue.SERVER, this.bind_address);
	} else {
	    NET.send(VNetwork.Queue.SERVER, this.bind_address, request);
	    NET.shutdown(VNetwork.Queue.CLIENT, this.bind_address);
	}
	// keep alive
    },
    
    send: function(data)
    {
	var packet = new VPacket(VPacket.Command.DATA, data);
	var queue_index = this.is_server ? VNetwork.Queue.CLIENT : VNetwork.Queue.SERVER;
	if (!NET.send(queue_index, this.bind_address, packet)) {
	    // close peer
	    NET.close(this.bind_address);
	    return false;
	}
	return true;
    },
    
    poll: function()
    {
	if (this.is_server) {
	    return NET.poll(VNetwork.Queue.SERVER, this.bind_address);
	}
	return NET.poll(VNetwork.Queue.CLIENT, this.bind_address);
    },
    
    receive: function(data)
    {
	var packet;
	if (this.is_server) {
	    packet = NET.receive(VNetwork.Queue.SERVER, this.bind_address);
	} else {
	    packet = NET.receive(VNetwork.Queue.CLIENT, this.bind_address);
	}
	if (!packet) {
	    return null;
	}
	switch (packet.command) {
	  case VPacket.Command.CLOSE:
	    NET.close(this.bind_address);
	    return null;
	  case VPacket.Command.DATA:
	    break;
	  default:
	    throw "illegal packet";
	}
	return packet.argv;
    }
}

function vsocket_debug()
{
    var server = new VSocket(NET.newId());
    var client = new VSocket(NET.newId());
    
    server.listen(80);
    
    client.connect(new VAddress(server.id, 80));
    
    child = server.accept();
    
    client.send('hello server');
    var data = child.receive();
    child.send("hello client");
    data = client.receive();
    
    child.close();
    client.close();
}

