/*-------------------------------------------------------------------------
 * drawElements Quality Program Execution Server
 * ---------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief TCP Server.
 *//*--------------------------------------------------------------------*/

#include "xsTcpServer.hpp"

#include <algorithm>
#include <iterator>
#include <cstdio>

namespace xs
{

TcpServer::TcpServer(deSocketFamily family, int port) : m_socket()
{
    de::SocketAddress address;
    address.setFamily(family);
    address.setPort(port);
    address.setType(DE_SOCKETTYPE_STREAM);
    address.setProtocol(DE_SOCKETPROTOCOL_TCP);

    m_socket.listen(address);
    m_socket.setFlags(DE_SOCKET_CLOSE_ON_EXEC);
}

void TcpServer::runServer(void)
{
    de::Socket *clientSocket = DE_NULL;
    de::SocketAddress clientAddr;

    while ((clientSocket = m_socket.accept(clientAddr)) != DE_NULL)
    {
        ConnectionHandler *handler = DE_NULL;

        try
        {
            handler = createHandler(clientSocket, clientAddr);
        }
        catch (...)
        {
            delete clientSocket;
            throw;
        }

        try
        {
            addLiveConnection(handler);
        }
        catch (...)
        {
            delete handler;
            throw;
        }

        // Start handler.
        handler->start();

        // Perform connection list cleanup.
        deleteDoneConnections();
    }

    // One more cleanup pass.
    deleteDoneConnections();
}

void TcpServer::connectionDone(ConnectionHandler *handler)
{
    de::ScopedLock lock(m_connectionListLock);

    std::vector<ConnectionHandler *>::iterator liveListPos =
        std::find(m_liveConnections.begin(), m_liveConnections.end(), handler);
    DE_ASSERT(liveListPos != m_liveConnections.end());

    m_doneConnections.reserve(m_doneConnections.size() + 1);
    m_liveConnections.erase(liveListPos);
    m_doneConnections.push_back(handler);
}

void TcpServer::addLiveConnection(ConnectionHandler *handler)
{
    de::ScopedLock lock(m_connectionListLock);
    m_liveConnections.push_back(handler);
}

void TcpServer::deleteDoneConnections(void)
{
    de::ScopedLock lock(m_connectionListLock);

    for (std::vector<ConnectionHandler *>::iterator i = m_doneConnections.begin(); i != m_doneConnections.end(); i++)
        delete *i;

    m_doneConnections.clear();
}

void TcpServer::stopServer(void)
{
    // Close socket. This should get accept() to return null.
    m_socket.close();
}

TcpServer::~TcpServer(void)
{
    try
    {
        std::vector<ConnectionHandler *> allConnections;

        if (m_connectionListLock.tryLock())
        {
            // \note [pyry] It is possible that cleanup actually fails.
            try
            {
                std::copy(m_liveConnections.begin(), m_liveConnections.end(),
                          std::inserter(allConnections, allConnections.end()));
                std::copy(m_doneConnections.begin(), m_doneConnections.end(),
                          std::inserter(allConnections, allConnections.end()));
            }
            catch (...)
            {
            }
            m_connectionListLock.unlock();
        }

        for (std::vector<ConnectionHandler *>::const_iterator i = allConnections.begin(); i != allConnections.end();
             i++)
            delete *i;

        if (m_socket.getState() != DE_SOCKETSTATE_CLOSED)
            m_socket.close();
    }
    catch (...)
    {
        // Nada, we're at destructor.
    }
}

ConnectionHandler::~ConnectionHandler(void)
{
    delete m_socket;
}

void ConnectionHandler::run(void)
{
    try
    {
        handle();
    }
    catch (const std::exception &e)
    {
        printf("ConnectionHandler::run(): %s\n", e.what());
    }

    // Notify server that this connection is done.
    m_server->connectionDone(this);
}

} // namespace xs
