#include "BerkeleySocket.h"

#include "FileLogger.h"

#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

BerkeleySocket::BerkeleySocket(const std::string& address, unsigned port)
    : Socket(address, port)
{

}

BerkeleySocket::BerkeleySocket(int handle)
    : Socket("", 0),
        mHandle(handle)
{
    mState.mConnectStatus = State::ConnectStatus::OK;
}

BerkeleySocket::~BerkeleySocket()
{
    MLOG_INFO("Socket being destroyed");
}

void BerkeleySocket::initialize()
{
    mHandle = ::socket(AF_INET, SOCK_STREAM, 0);
}

void BerkeleySocket::initializeForBind()
{

}

void BerkeleySocket::doConnect()
{
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;

    ::inet_aton(mAddress.c_str(), &serv_addr.sin_addr);
    serv_addr.sin_port = ::htons(mPort);

    int result = ::connect(mHandle, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    if( result < 0)
    {
        mState.mConnectStatus = Socket::State::ConnectStatus::FAILED;
        mState.mErrorCode = result;
        mState.mErrorMessage = "Socket: Unable to connect to server.";
        return;
    }
    mState.mConnectStatus = Socket::State::ConnectStatus::OK;
}

void BerkeleySocket::doBind()
{
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(mPort);

    int result = ::bind(mHandle, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    if (result< 0)
    {
        mState.mBindStatus = Socket::State::BindStatus::FAILED;
        mState.mErrorCode = result;
        mState.mErrorMessage = "Socket: Unable to bind socket";
        return;
    }
    mState.mBindStatus = Socket::State::BindStatus::OK;
}

void BerkeleySocket::doListen(onIncomingConnectionFunc connectionFunc)
{
    initializeForBind();
    if (mState.mBindStatus == Socket::State::BindStatus::FAILED)
    {
        return;
    }

    doBind();
    if (mState.mBindStatus == Socket::State::BindStatus::FAILED)
    {
        return;
    }

    ::listen(mHandle, 5);

    struct sockaddr_in cli_addr;
    socklen_t clilen = sizeof(cli_addr);
    while(true)
    {
        const auto new_socket_handle = ::accept(mHandle, (struct sockaddr *) &cli_addr, &clilen);
        if (new_socket_handle < 0)
        {
            mState.mBindStatus = Socket::State::BindStatus::FAILED;
            mState.mErrorCode = new_socket_handle;
            mState.mErrorMessage = "Socket: Accept failed";
            ::close(mHandle);
            return;
        }
        connectionFunc(new_socket_handle);
    }
}

std::string BerkeleySocket::send(const std::string& message)
{
    if (mState.mConnectStatus != Socket::State::ConnectStatus::OK)
    {
        initialize();
        if (mState.mConnectStatus == Socket::State::ConnectStatus::FAILED)
        {
            return {};
        }

        doConnect();
        if (mState.mConnectStatus == Socket::State::ConnectStatus::FAILED)
        {
            return {};
        }
    }

    auto n = ::write(mHandle, message.c_str(), message.length());
    if (n < 0)
    {
        onSockerError("Socket: Send failed.");
        return{};
    }

    std::string response;
    while (mState.mConnectStatus == Socket::State::ConnectStatus::OK)
    {
        response += recieve();
    }
    ::close(mHandle);

    return response;
}

void BerkeleySocket::respond(const std::string& message)
{
    auto result = ::write(mHandle, message.c_str(), static_cast<int>(message.size()));
    (void)result;
}

void BerkeleySocket::onSockerError(const std::string& message)
{
    mState.mErrorCode = -1;
    mState.mErrorMessage = message;

    if (mState.mConnectStatus == Socket::State::ConnectStatus::OK)
    {
        ::close(mHandle);
    }
    mState.mConnectStatus = Socket::State::ConnectStatus::FAILED;
}

std::string BerkeleySocket::recieve()
{
    const int BUFFER_SIZE = 512;
    char buffer[BUFFER_SIZE];
    auto result = ::read(mHandle, buffer, BUFFER_SIZE);
    if (result > 0)
    {
        return std::string(buffer);
    }
    else if (result == 0)
    {
        mState.mConnectStatus = Socket::State::ConnectStatus::UNSET;
    }
    else
    {
        mState.mConnectStatus = Socket::State::ConnectStatus::FAILED;
        mState.mErrorCode = result;
        mState.mErrorMessage = "Socket: read failed.";
    }
    return {};
}
