/***************************************************************************
                          socket.cpp  -  description
                             -------------------
    begin                : Sun Mar 10 2002
    copyright            : (C) 2002 by Vladimir Shutoff
    email                : vovan@shutoff.ru
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <qmutex.h>
#include <qtimer.h>

#include "socket.h"
#include "misc.h"
#include "log.h"

namespace SIM
{

using namespace std;

#ifndef INADDR_NONE
#define INADDR_NONE	0xFFFFFFFF
#endif

const unsigned RECONNECT_TIME		= 5;
const unsigned RECONNECT_IFINACTIVE = 60;
const unsigned LOGIN_TIMEOUT		= 120;

struct SocketFactoryPrivate
{
    bool m_bActive;

    QValueList<ClientSocket*> errSockets;
    QValueList<ClientSocket*> errSocketsCopy;
    QValueList<Socket*> removedSockets;
    QValueList<ServerSocket*> removedServerSockets;

    SocketFactoryPrivate() : m_bActive(true) {}
};

void Socket::error(const QString &err_text, unsigned code)
{
    if (notify)
        notify->error_state(err_text, code);
}

ServerSocket::ServerSocket()
{
    notify = NULL;
}

ClientSocket::ClientSocket(ClientSocketNotify *notify, Socket *sock)
{
    m_notify = notify;
    bRawMode = false;
    bClosed  = false;
    m_sock   = sock;
    if (m_sock == NULL)
        m_sock = getSocketFactory()->createSocket();
    m_sock->setNotify(this);
}

ClientSocket::~ClientSocket()
{
    getSocketFactory()->erase(this);
    delete m_sock;
}

void ClientSocket::close()
{
    m_sock->close();
    bClosed = true;
}

const QString &ClientSocket::errorString() const
{
    return errString;
}

void ClientSocket::connect(const QString &host, unsigned short port, TCPClient *client)
{
    if (client){
        EventSocketConnect e(this, client, host, port);
        e.process();
    }
    m_sock->connect(host, port);
}

void ClientSocket::write()
{
    if (writeBuffer().size() == 0)
        return;
    m_sock->write(writeBuffer().data(), writeBuffer().size());
    writeBuffer().init(0);
}

bool ClientSocket::created()
{
    return (m_sock != NULL);
}

void ClientSocket::resolve_ready(unsigned long ip)
{
    m_notify->resolve_ready(ip);
}

void ClientSocket::connect_ready()
{
    m_notify->connect_ready();
    bClosed = false;
}

void ClientSocket::setRaw(bool mode)
{
    bRawMode = mode;
    read_ready();
}

void ClientSocket::read_ready()
{
    if (bRawMode){
        for (;;){
            char b[2048];
            int readn = m_sock->read(b, sizeof(b));
            if (readn < 0){
                error_state(I18N_NOOP("Read socket error"));
                return;
            }
            if (readn == 0)
                break;
            unsigned pos = readBuffer().writePos();
            readBuffer().setWritePos(readBuffer().writePos() + readn);
            memcpy(readBuffer().data(pos), b, readn);
        }
        if (m_notify)
            m_notify->packet_ready();
        return;
    }
    for (;;){
        if (bClosed || errString.length())
          break;
        int readn = m_sock->read(readBuffer().data(readBuffer().writePos()),
                                 readBuffer().size() - readBuffer().writePos());
        if (readn < 0){
            error_state(I18N_NOOP("Read socket error"));
            return;
        }
        if (readn == 0)
          break;
        readBuffer().setWritePos(readBuffer().writePos() + readn);
        if (readBuffer().writePos() < readBuffer().size())
          break;
        if (m_notify)
            m_notify->packet_ready();
    }
}

void ClientSocket::write_ready()
{
    if (m_notify)
        m_notify->write_ready();
}

unsigned long ClientSocket::localHost()
{
    return m_sock->localHost();
}

void ClientSocket::pause(unsigned n)
{
    m_sock->pause(n);
}

void ClientSocket::setSocket(Socket *s, bool bClearError)
{
    if (m_sock){
        if (m_sock->getNotify() == this)
            m_sock->setNotify(NULL);
        if (bClearError){
            getSocketFactory()->erase(this);
        }
    }
    m_sock = s;
    if (s)
        s->setNotify(this);
}

void ClientSocket::error_state(const QString &err, unsigned code)
{
    // -> false -> already there
    if(!getSocketFactory()->add(this))
      return;

    errString = err;
    errCode = code;
    QTimer::singleShot(0, getSocketFactory(), SLOT(idle()));
}

SocketFactory::SocketFactory(QObject *parent)
  : QObject(parent)
{
  d = new SocketFactoryPrivate;
}

SocketFactory::~SocketFactory()
{
    idle();
    delete d;
}

bool SocketFactory::isActive() const
{
    return d->m_bActive;
}

void SocketFactory::setActive(bool isActive)
{
    if (isActive == d->m_bActive)
        return;
    d->m_bActive = isActive;
    EventSocketActive(d->m_bActive).process();
}

void SocketFactory::remove(Socket *s)
{
    s->setNotify(NULL);
    s->close();

    if(d->removedSockets.contains(s))
      return;

    d->removedSockets.push_back(s);

    QTimer::singleShot(0, this, SLOT(idle()));
}

void SocketFactory::remove(ServerSocket *s)
{
    s->setNotify(NULL);
    s->close();

    if(d->removedServerSockets.contains(s))
      return;

    d->removedServerSockets.push_back(s);
    QTimer::singleShot(0, this, SLOT(idle()));
}

bool SocketFactory::add(ClientSocket *s)
{
  if(!d->errSockets.contains(s)) {
    d->errSockets += s;
    return true;
  }
  return false;
}

bool SocketFactory::erase(ClientSocket *s)
{
  QValueList<ClientSocket*>::iterator it = d->errSocketsCopy.find(s);
  if(it != d->errSocketsCopy.end())
    *it = NULL;
  return(d->errSockets.remove(s) > 0);
}

void SocketFactory::idle()
{
    d->errSocketsCopy = d->errSockets;  // important! error_state() modifes d->errSockets
    d->errSockets.clear();

    QValueList<ClientSocket*>::iterator it = d->errSocketsCopy.begin();
    for ( ; it != d->errSocketsCopy.end(); ++it){
        ClientSocket *s = *it;
        // can be removed in SocketFactory::erase();
        if(!s)
          continue;
        ClientSocketNotify *n = s->m_notify;
        if (n){
            QString errString = s->errorString();
            s->errString = QString::null;
            if (n->error_state(errString, s->errCode))
                delete n;
        }
    }

    QValueList<Socket*>::iterator its = d->removedSockets.begin();
    for ( ; its != d->removedSockets.end(); ++its)
        delete *its;
    d->removedSockets.clear();

    QValueList<ServerSocket*>::iterator itss = d->removedServerSockets.begin();
    for ( ; itss != d->removedServerSockets.end(); ++itss)
        delete *itss;
    d->removedServerSockets.clear();
}

TCPClient::TCPClient(Protocol *protocol, Buffer *cfg, unsigned priority)
        : Client(protocol, cfg), EventReceiver(priority)
{
    m_clientSocket = NULL;
    m_ip     = 0;
    m_timer  = new QTimer(this);
    m_loginTimer = new QTimer(this);
    m_reconnect = RECONNECT_TIME;
    m_bWaitReconnect = false;
    connect(m_timer, SIGNAL(timeout()), this, SLOT(reconnect()));
    connect(m_loginTimer, SIGNAL(timeout()), this, SLOT(loginTimeout()));
}

bool TCPClient::processEvent(Event *e)
{
    if (e->type() == eEventSocketActive){
		EventSocketActive *s = static_cast<EventSocketActive*>(e);
        if (m_bWaitReconnect && s->active())
            reconnect();
    }
    return false;
}

void TCPClient::resolve_ready(unsigned long ip)
{
    m_ip = ip;
}

bool TCPClient::error_state(const QString &err, unsigned code)
{
    log(L_DEBUG, "Socket error %s (%u)", err.local8Bit().data(), code);
    m_loginTimer->stop();
    if (m_reconnect == NO_RECONNECT){
        m_timer->stop();
        setStatus(STATUS_OFFLINE, getCommonStatus());
        setState(Error, err, code);
        return false;
    }
    if (!m_timer->isActive()){
        unsigned reconnectTime = m_reconnect;
        if (!getSocketFactory()->isActive()){
            if (reconnectTime < RECONNECT_IFINACTIVE)
                reconnectTime = RECONNECT_IFINACTIVE;
        }
        setClientStatus(STATUS_OFFLINE);
        setState((m_reconnect == NO_RECONNECT) ? Error : Connecting, err, code);
        m_bWaitReconnect = true;
        log(L_DEBUG, "Wait reconnect %u sec", reconnectTime);
        m_timer->start(reconnectTime * 1000);
    } else {
        /*
          slot reconnect() neeeds this flag 
          to be true to make actual reconnect,
          but it was somehow false. serzh.
        */
        m_bWaitReconnect = true;
    }
    return false;
}

void TCPClient::reconnect()
{
    m_timer->stop();
    if (m_bWaitReconnect)
        setClientStatus(getManualStatus());
}

void TCPClient::setStatus(unsigned status, bool bCommon)
{
    setClientStatus(status);
    Client::setStatus(status, bCommon);
}

void TCPClient::connect_ready()
{
    m_timer->stop();
    m_bWaitReconnect = false;
    m_loginTimer->stop();
    m_loginTimer->start(LOGIN_TIMEOUT * 1000, true);
}

void TCPClient::loginTimeout()
{
    m_loginTimer->stop();
    if ((m_state != Connected) && socket())
        socket()->error_state(I18N_NOOP("Login timeout"));
}

Socket *TCPClient::createSocket()
{
    return NULL;
}

void TCPClient::socketConnect()
{
    if (socket())
        socket()->close();
    if (socket() == NULL)
        m_clientSocket = createClientSocket();
    log(L_DEBUG, "Start connect %s:%u", static_cast<const char *>(getServer().local8Bit()), getPort());
    socket()->connect(getServer(), getPort(), this);
}

ClientSocket *TCPClient::createClientSocket()
{
    return new ClientSocket(this, createSocket());
}

void TCPClient::setClientStatus(unsigned status)
{
    if (status != STATUS_OFFLINE){
        if (getState() == Connected){
            setStatus(status);
            return;
        }
        m_logonStatus = status;
        if ((getState() != Connecting) || m_bWaitReconnect){
            setState(Connecting, NULL);
            m_reconnect = RECONNECT_TIME;
            m_bWaitReconnect = false;
            setState(Connecting);
            socketConnect();
        }
        return;
    }
    m_bWaitReconnect = false;
    m_timer->stop();
    m_loginTimer->stop();
    if (socket())
        setStatus(STATUS_OFFLINE);
    m_status = STATUS_OFFLINE;
    setState(Offline);
    disconnected();
    if (socket()){
        socket()->close();
        delete socket();
        m_clientSocket = NULL;
    }
}

ServerSocketNotify::ServerSocketNotify()
{
    m_listener = NULL;
}

ServerSocketNotify::~ServerSocketNotify()
{
    if (m_listener)
        getSocketFactory()->remove(m_listener);
}

void ServerSocketNotify::setListener(ServerSocket *listener)
{
    if (m_listener)
        getSocketFactory()->remove(m_listener);
    m_listener = listener;
    if (m_listener)
        m_listener->setNotify(this);
}

void ServerSocketNotify::bind(unsigned short minPort, unsigned short maxPort, TCPClient *client)
{
    if (m_listener)
        getSocketFactory()->remove(m_listener);
    m_listener = getSocketFactory()->createServerSocket();
    m_listener->setNotify(this);
    m_listener->bind(minPort, maxPort, client);
}

#ifndef WIN32

void ServerSocketNotify::bind(const char *path)
{
    if (m_listener)
        getSocketFactory()->remove(m_listener);
    m_listener = getSocketFactory()->createServerSocket();
    m_listener->setNotify(this);
    m_listener->bind(path);
}

#endif

}

#ifndef NO_MOC_INCLUDES
#include "socket.moc"
#endif


