/* sbt -- Simple Build Tool
 * Copyright 2009 Mark Harrah
 */
package xsbt

import java.io.{BufferedReader, BufferedWriter, InputStream, InputStreamReader, OutputStreamWriter, OutputStream}
import java.net.{InetAddress, ServerSocket, Socket}

object IPC
{
	private val portMin = 1025
	private val portMax = 65536
	private val loopback = InetAddress.getByName(null) // loopback
	
	def client[T](port: Int)(f: IPC => T): T =
		ipc(new Socket(loopback, port))(f)
	
	def pullServer[T](f: Server => T): T =
	{
		val server = makeServer
		try { f(new Server(server)) } 
		finally { server.close() }
	}
	def makeServer: ServerSocket =
	{
		val random = new java.util.Random
		def nextPort = random.nextInt(portMax - portMin + 1) + portMin
		def createServer(attempts: Int): ServerSocket =
			if(attempts > 0)
				try { new ServerSocket(nextPort, 1, loopback) }
				catch { case _: Exception => createServer(attempts - 1) }
			else
				error("Could not connect to socket: maximum attempts exceeded")
		createServer(10)
	}
	def server[T](f: IPC => Option[T]): T = serverImpl(makeServer, f)
	def server[T](port: Int)(f: IPC => Option[T]): T =
		serverImpl(new ServerSocket(port, 1, loopback), f)
	private def serverImpl[T](server: ServerSocket, f: IPC => Option[T]): T =
	{
		def listen(): T =
		{
			ipc(server.accept())(f) match
			{
				case Some(done) => done
				case None => listen()
			}
		}
		
		try { listen() }
		finally { server.close() }
	}
	private def ipc[T](s: Socket)(f: IPC => T): T =
		try { f(new IPC(s)) }
		finally { s.close() }
		
	final class Server private[IPC](s: ServerSocket) extends NotNull
	{
		def port = s.getLocalPort
		def close() = s.close()
		def connection[T](f: IPC => T): T = IPC.ipc(s.accept())(f)
	}
}
final class IPC private(s: Socket) extends NotNull
{
	def port = s.getLocalPort
	private val in = new BufferedReader(new InputStreamReader(s.getInputStream))
	private val out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream))
	
	def send(s: String) = { out.write(s); out.newLine(); out.flush() }
	def receive: String = in.readLine()
}
