#include "wvunixdgsocket.h"

WvUnixDGSocket::WvUnixDGSocket(WvStringParm filename, bool _server, int perms)
    : socketfile(filename)
{
//    log(WvLog::Debug2, "Starting up %s!\n", filename);
    server = _server;
    backoff = 10;

    bufsize = 0;

    // open a datagram unix domain socket
    setfd(socket(PF_UNIX, SOCK_DGRAM, 0));

    // if we don't have a file desciptor, something is wrong.
    if (getfd() < 0)
    {
        seterr("No Socket available.");
        return;
    }

    // set non-blocking mode
    fcntl(getfd(), F_SETFL, O_RDWR|O_NONBLOCK);

    WvUnixAddr uaddr(socketfile);

    // Let this file be reusable, since we're going to own this anyway
    // The business with the int x is just Unix stupidities.. *sigh*
    int x = 1;
    setsockopt(getfd(), SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));

    if (server)
    {
        // Fix it so that there can't be another process on this file
        unlink(socketfile);

        // Actually bind to the address we set up above.
        sockaddr *addr = uaddr.sockaddr();
        if (bind(getfd(), (sockaddr *)addr, uaddr.sockaddr_len()))
        {
            seterr("Bind to %s failed: %s", socketfile, strerror(errno));
            close();
        }
        delete addr;

        chmod(socketfile, perms);
    }
    else
    {
        // we're the client, so we connect to someone else's socket
        sockaddr *addr = uaddr.sockaddr();
        if (connect(getfd(), (sockaddr *)addr, uaddr.sockaddr_len()))
        {
            seterr("Connect to %s failed: %s",
                   socketfile, strerror(errno));
            close();
        }
        delete addr;
    }

    drain();
}

WvUnixDGSocket::~WvUnixDGSocket()
{
//    log(WvLog::Debug2, "Destroying: %s\n", socketfile);
    close();
    if (server)
        unlink(socketfile);
}

size_t WvUnixDGSocket::uwrite(const void *buf, size_t count)
{
    size_t ret = bufs.isempty() ? WvFDStream::uwrite(buf, count) : 0;

    if (ret < count)
    {
        WvDynBuf *b = new WvDynBuf;
        b->put(buf, count);
        bufs.append(b, true);
	bufsize += count;
    }

    return count;
}

void WvUnixDGSocket::pre_select(SelectInfo &si)
{
    SelectRequest oldwant = si.wants;
    if (!bufs.isempty())
    {
        // stupid unix domain sockets seem to return true when selecting
        // for write EVEN IF write() RETURNS -EAGAIN!  Just shoot me.
        // 
        // To deal with this, we set an alarm() in post_select() if we
        // couldn't write everything we wanted.  While the alarm is set,
        // we don't try to flush our output buffer.
        if (alarm_remaining() <= 0)
            si.wants.writable = true;
        else if (si.msec_timeout < 0
                 || si.msec_timeout > alarm_remaining())
            si.msec_timeout = alarm_remaining();
    }

    WvFDStream::pre_select(si);

    si.wants = oldwant;
}

bool WvUnixDGSocket::post_select(SelectInfo &si)
{
    SelectRequest oldwant = si.wants;
    if (!bufs.isempty())
        si.wants.writable = true;

    bool sure = WvFDStream::post_select(si);

    si.wants = oldwant;

    if (sure)
    {
        // try flushing previous bufs
        WvBufList::Iter i(bufs);
        for (i.rewind(); i.next(); )
        {
            int used = i->used();
            int retval = WvFDStream::uwrite(i->get(used), used);
            if (retval < used)
            {
                i->unget(used);
                alarm(backoff *= 2);
                if (backoff > 1000)
                    backoff = 1000;
                break; // can't continue
            }
            else
            {
		bufsize -= used;
                i.xunlink(); // done with that one
                backoff = 10;
            }
        }
    }

    return sure;
}


