#include <SSLSession.h>

#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>

namespace DataExporter_ns
{

//==============================================================================
//      SSLSession::SSLSession()
//==============================================================================
SSLSession::SSLSession(Tango::DeviceImpl* deviceImpl_p,
    Configuration::SP configuration_sp, DBManager::SP dBManager_sp,
    boost::shared_ptr<boost::asio::io_service> ioService_sp,
    boost::shared_ptr<boost::asio::ssl::context> context_sp) :
    Session::Session(deviceImpl_p, configuration_sp, dBManager_sp, ioService_sp),
    m_sslSocket(*ioService_sp, *context_sp)
{
    DEBUG_STREAM << "SSLSession::SSLSession()" << endl;
}

//==============================================================================
//      SSLSession::~SSLSession()
//==============================================================================
SSLSession::~SSLSession()
{
    DEBUG_STREAM << "SSLSession::~SSLSession()" << endl;

    INFO_STREAM << "SSLSession::~SSLSession() Disconnection from "
        << m_remoteEndpoint << endl;

    boost::system::error_code errorCode;

    m_sslSocket.shutdown(errorCode);

    m_sslSocket.lowest_layer().shutdown(
        boost::asio::ip::tcp::socket::shutdown_both, errorCode);

    m_sslSocket.lowest_layer().close(errorCode);
}

//==============================================================================
//      SSLSession::create()
//==============================================================================
Session::SP SSLSession::create(Tango::DeviceImpl* deviceImpl_p,
    Configuration::SP configuration_sp, DBManager::SP dBManager_sp,
    boost::shared_ptr<boost::asio::io_service> ioService_sp,
    boost::shared_ptr<boost::asio::ssl::context> context_sp)
{
    Session::SP s_sp(new SSLSession(deviceImpl_p, configuration_sp,
        dBManager_sp, ioService_sp, context_sp), SSLSession::Deleter());

    return s_sp;
}

//==============================================================================
//      SSLSession::getSocket()
//==============================================================================
boost::asio::ip::tcp::socket& SSLSession::getSocket()
{
    DEBUG_STREAM << "SSLSession::getSocket()" << endl;

    return m_sslSocket.next_layer();
}

//==============================================================================
//      SSLSession::start()
//==============================================================================
void SSLSession::start()
{
    DEBUG_STREAM << "SSLSession::start()" << endl;

    m_remoteEndpoint = boost::lexical_cast<std::string>(
        m_sslSocket.lowest_layer().remote_endpoint());

    INFO_STREAM << "SSLSession::start() Connection from "
        << m_remoteEndpoint << endl;

    m_protocolManager_sp->setRemoteEndpoint(m_remoteEndpoint);

    startHandShake();
}

//==============================================================================
//      SSLSession::startHandShake()
//==============================================================================
void SSLSession::startHandShake()
{
    DEBUG_STREAM << "SSLSession::startHandShake()" << endl;

    m_sslSocket.async_handshake(boost::asio::ssl::stream_base::server,
        boost::bind(&SSLSession::handleHandShake, shared_from_this(),
        boost::asio::placeholders::error));
}

//==============================================================================
//      SSLSession::handleRequest()
//==============================================================================
void SSLSession::handleHandShake(const boost::system::error_code& errorCode)
{
        DEBUG_STREAM << "SSLSession::handleHandShake()" << endl;

        if(!errorCode)
        {
            startReadRequestHeader();
        }
        else
        {
            ERROR_STREAM << "SSLSession::handleHandShake() error "
                << errorCode.message() << " from " << m_remoteEndpoint << endl;
        }
}

//==============================================================================
//      SSLSession::startReadRequestHeader()
//==============================================================================
void SSLSession::startReadRequestHeader()
{
        DEBUG_STREAM << "SSLSession::startReadRequestHeader()" << endl;

        m_readBuff.resize(HEADER_SIZE);

        boost::asio::async_read(m_sslSocket, boost::asio::buffer(m_readBuff),
            m_strand.wrap(boost::bind(&SSLSession::handleReadRequestHeader,
            shared_from_this(), boost::asio::placeholders::error)));
}

//==============================================================================
//      SSLSession::startReadRequestBody()
//==============================================================================
void SSLSession::startReadRequestBody(boost::uint32_t bodySize)
{
    DEBUG_STREAM << "SSLSession::startReadRequestBody()" << endl;

    m_readBuff.resize(HEADER_SIZE + bodySize);

    boost::asio::mutable_buffers_1 mutableBuffer =
        boost::asio::buffer(&m_readBuff[HEADER_SIZE], bodySize);

    #ifdef VERBOSE_DEBUG
        INFO_STREAM << "SSLSession::startReadRequestBody() "
            << m_remoteEndpoint << " >>>> " << bodySize << " byte" << endl;
    #endif

    boost::asio::async_read(m_sslSocket, mutableBuffer,
        m_strand.wrap(boost::bind(&SSLSession::handleReadRequestBody,
        shared_from_this(), boost::asio::placeholders::error)));
}

//==============================================================================
//      SSLSession::startWriteResponse()
//==============================================================================
void SSLSession::startWriteResponse()
{
    DEBUG_STREAM << "SSLSession::startWriteResponse()" << endl;

    try
    {
        RequestSP request_sp(new Request);

        request_sp->ParseFromArray(&m_readBuff[HEADER_SIZE],
            m_readBuff.size() - HEADER_SIZE);

        ResponseSP response_sp = m_protocolManager_sp->prepareResponse(request_sp);

        boost::uint32_t bodySize = response_sp->ByteSizeLong();

        std::vector<boost::uint8_t> writeBuff;
        writeBuff.resize(HEADER_SIZE + bodySize);

        encodeHeader(writeBuff, bodySize);

        response_sp->SerializeToArray(&writeBuff[HEADER_SIZE], bodySize);

        #ifdef VERBOSE_DEBUG
            INFO_STREAM << "SSLSession::startWriteResponse() "
                << m_remoteEndpoint << " <<<< " << bodySize << " byte" << endl;
        #endif

        boost::asio::async_write(m_sslSocket, boost::asio::buffer(writeBuff),
            m_strand.wrap(boost::bind(&SSLSession::handleWriteResponse,
            shared_from_this(), boost::asio::placeholders::error)));
    }
    catch(std::exception& ec)
    {
        ERROR_STREAM << "SSLSession::startWriteResponse() "
            << ec.what() << " from " << m_remoteEndpoint << endl;
    }
    catch(...)
    {
        ERROR_STREAM << "SSLSession::startWriteResponse() unknown error from "
            << m_remoteEndpoint <<  endl;
    }
}

//==============================================================================
//      SSLSession::startWriteData()
//==============================================================================
void SSLSession::startWriteData(FileWrapper::SP fileWrapper_sp)
{
    try
    {
        if(!fileWrapper_sp->isBad())
        {
            if(!fileWrapper_sp->isCompleted())
            {
                std::vector<char> writeBuff;

                fileWrapper_sp->read(writeBuff);

                boost::asio::async_write(m_sslSocket, boost::asio::buffer(writeBuff),
                    m_strand.wrap(boost::bind(&SSLSession::handleWriteData,
                    shared_from_this(), fileWrapper_sp,
                    boost::asio::placeholders::error)));
            }
            else
            {
                INFO_STREAM << "SSLSession::startWriteData() "
                    << " transfer completed " << endl;

                startReadRequestHeader();
            }
        }
        else
        {
            ERROR_STREAM << "SSLSession::startWriteData() error on file I/O "
                << "from " << m_remoteEndpoint << endl;
        }
    }
    catch(std::exception& ec)
    {
        ERROR_STREAM << "SSLSession::startWriteData() "
            << ec.what() << " from " << m_remoteEndpoint << endl;
    }
    catch(...)
    {
        ERROR_STREAM << "SSLSession::startWriteData() unknown error from "
            << m_remoteEndpoint <<  endl;
    }
}

}   //namespace