/*
 *  SockUtil.cpp
 *  SpikeGL
 *
 *  Created by calin on 5/18/10.
 *  Copyright 2010 Calin Culianu <calin.culianu@gmail.com>. All rights reserved.
 *
 */
#include "SockUtil.h"
#include "Util.h"
#include <QMutex>
#include <QThreadStorage>

#define LINELEN 65536

namespace SockUtil 
{
    QString errorToString(QAbstractSocket::SocketError e) {
        switch (e) {
            case QAbstractSocket::ConnectionRefusedError: 
                return "The connection was refused by the peer (or timed out).";
            case QAbstractSocket::RemoteHostClosedError:
                return "The remote host closed the connection.";
            case QAbstractSocket::HostNotFoundError:
                return "The host address was not found.";
            case QAbstractSocket::SocketAccessError:
                return "The socket operation failed because the application lacked the required privileges.";
            case QAbstractSocket::SocketResourceError:
                return "The local system ran out of resources (e.g., too many sockets).";
            case QAbstractSocket::SocketTimeoutError:
                return "The socket operation timed out.";
            case QAbstractSocket::DatagramTooLargeError:
                return "The datagram was larger than the operating system's limit.";
            case QAbstractSocket::AddressInUseError:
                return "The address specified to bind() is already in use and was set to be exclusive.";
            case QAbstractSocket::SocketAddressNotAvailableError:
                return "The address specified to bind() does not belong to the host.";
            case QAbstractSocket::UnsupportedSocketOperationError:
                return "The requested socket operation is not supported by the local operating system (e.g., lack of IPv6 support).";
            case QAbstractSocket::ProxyAuthenticationRequiredError:
                return "The socket is using a proxy, and the proxy requires authentication.";
            default:
                return "An unidentified error occurred.";
        }
        return QString::null; // not reached
    }
    
    //QMutex *sockContextMut = 0;
    QThreadStorage<QStringList *> sockContextNames;
    
    void pushContext(const QString & n) { 
        //if (!sockContextMut) sockContextMut = new QMutex;
        //QMutexLocker l(sockContextMut);
        if (!sockContextNames.hasLocalData()) {
            sockContextNames.setLocalData(new QStringList);
        }
        sockContextNames.localData()->push_back(n); 
    }
    void popContext() { 
        //if (!sockContextMut) sockContextMut = new QMutex;
        //QMutexLocker l(sockContextMut);
        if (sockContextNames.hasLocalData() && sockContextNames.localData()->count()) {
            sockContextNames.localData()->pop_back(); 
        }
    }
    QString contextName() {
        //if (!sockContextMut) sockContextMut = new QMutex;
        //QMutexLocker l(sockContextMut);
        return sockContextNames.hasLocalData() && sockContextNames.localData()->count() ? sockContextNames.localData()->back() : "Unknown Context";
    }    
    
    bool send(QTcpSocket & sock, const QString & msg, int timeout_msecs, QString * errStr_out, bool debugPrint)
    {
        if (debugPrint) {
            Debug() << "Sending '" << msg.trimmed() << "'";
        }
        sock.write(msg.toUtf8());
        if (sock.bytesToWrite() && !sock.waitForBytesWritten(timeout_msecs)) {
            QString estr = errorToString(sock.error());
            if (errStr_out) *errStr_out = estr;
            Error() << contextName()  << " failed write with socket error: " << estr;
            return false;
        }        
        //Debug() << "notify_write: " << msg;
        return true;
    }
    
    QString readLine(QTcpSocket & sock, int timeout_msecs, QString * errStr_out) 
    {
        // read greeting from server        
        for (int ct = 0; !sock.canReadLine(); ++ct) {
            if (!sock.isValid() || sock.state() != QAbstractSocket::ConnectedState || !sock.waitForReadyRead(timeout_msecs) || ct >= 3) {
                if (!errStr_out) Error() << contextName() << " timeout or peer shutdown";
                else *errStr_out = "timeout or peer shutdown";
                return QString::null;
            }
        }
        QString line = sock.readLine(LINELEN);
        line = line.trimmed();
        //Debug() << "notify_read: " << line;
        return line;
    }    
}