using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;

namespace Danga {

    class UsefulStream {
	NetworkStream ns;
	Socket sock;
	int pos = 0;
	int size = 0;
	int BufferSize = 4096;
	byte[] buf;
	AsyncCallback readCallback;
	AsyncCallback onGotLine, onTimeOut;
	bool wantingLine;  // we're fulfilling a BeginGetLine request
	bool eof;
	String gotLine;
	private Encoding lineEnc = Encoding.ASCII;

	public UsefulStream (Socket sock) {
	    this.sock = sock;
	    ns = new NetworkStream(sock);
	    buf = new byte[BufferSize];
	    readCallback = new AsyncCallback(this.OnRead);
	    eof = false;
	}

	public void Close () {
	    ns.Close();
	    ns = null;
	    sock.Close();
	    sock = null;
	}

	public void SetEncoder (Encoding en) {
	    lineEnc = en;
	}

	public void BeginGetLine (AsyncCallback onGotLine, int msTimeOut, AsyncCallback onTimeOut) {
	    this.onGotLine = onGotLine;
	    this.onTimeOut = onTimeOut;
	    checkForLine();
	}

	public String EndGetLine () {
	    wantingLine = false;
	    return gotLine;
        }


	private void ReadMore () {
	    if (eof) return;
	    if (pos > 0 && pos == size) {
		size = 0;
		pos = 0;
	    }
	    ns.BeginRead(buf, size, BufferSize-size, 
			 readCallback, null);
	    
	}

	private void OnRead (IAsyncResult ar)
	{
	    int bytesRead = ns.EndRead(ar);
	    if (bytesRead == 0)
		eof = true;

	    //Console.WriteLine("Read {0}: [{1}]", bytesRead, Encoding.ASCII.GetString(buf, size, bytesRead));
	    
	    size += bytesRead;
	    if (wantingLine) checkForLine();
	}

	private void checkForLine () {
	    wantingLine = true;
	    int i = pos;
	    int npos = -1;
	    while (i < size) {
		if (buf[i] == (byte)'\n') {
		    npos = i;
		    break;
		}
		i++;
	    }
	    //Console.WriteLine("checkForLine:  pos={0}, size={1}, npos={2}", pos, size, npos);
	    if (npos == -1) {
		if (eof) {
		    gotLine = null;  // return nulls to client on eof
		    onGotLine(null);
		    return;
		}
		// FIXME: may be necessary to move the buffer back first, especially
		// if we've tried several times now.
		ReadMore();
		return;
	    }
	    gotLine = lineEnc.GetString(buf, pos, npos-pos+1);
	    pos = npos+1;
	    onGotLine(null);
	}

    }
    
}
