#include "AgentController.h"
#include "protocol/AggreGateCommand.h"
#include "protocol/AggreGateCommandParser.h"
#include "protocol/RemoteContextManager.h"
#include "util/BlockingChannel.h"
#include "util/MessageFormat.h"
#include "context/Contexts.h"
#include "context/ContextException.h"

#include <openssl/md5.h>
#include <boost/algorithm/string.hpp>
#include <algorithm>

AgentController::AgentController(AggreGateDevice *device, BlockingChannelPtr channel) :
    AbstractAggreGateDeviceController(device)
{
    this->channel = channel;

    setResetTimeoutsOnData(true);
    setAvoidSendingFormats(true);
}

AgentController::~AgentController()
{
    AggreGateDevice *device = getDevice();
    if (device)
    {
        delete device;
    }
}

void AgentController::send(CommandPtr cmd)
{
    cmd->send(channel);
}

void AgentController::start()
{
    setCommandParser(CommandParserPtr(new AggreGateCommandParser(channel, AggreGateCommand::START_CHAR, AggreGateCommand::END_CHAR)));

    RemoteContextManager *contextManager = new RemoteContextManager(this, getDevice(), false);
    setContextManager(contextManager);

    ProxyContext *root = new ProxyContext(Contexts::CTX_ROOT, this);
    root->setNotManageRemoteListeners(true);

    contextManager->setRoot(root);
    contextManager->start();
}

std::string AgentController::calculatemd5(std::string msg)
{
    unsigned char digest[16];
    const char* string = msg.c_str();

    MD5_CTX ctx;
    MD5_Init(&ctx);
    MD5_Update(&ctx, string, strlen(string));
    MD5_Final(digest, &ctx);

    char mdString[33];
    for (int i = 0; i < 16; i++)
        sprintf(&mdString[i * 2], "%02x", (unsigned int) digest[i]);

    std::string result(mdString);

    std::transform(result.begin(), result.end(), result.begin(), ::toupper);
    return result;
}

bool AgentController::loginImpl()
{
    if (password.empty())
    {
        assert(0);
    }

    if (challenge.empty())
    {
        challenge = createChallenge();
        std::list<AgObjectPtr> pars;
        pars.push_back(AgObjectPtr(new AgString(challenge)));
        DataRecordPtr loginData = getContextManager()->getRoot()->callFunction(RF_LOGIN(), pars)->rec();

        owner = loginData->getString(RFOF_LOGIN_OWNER());
        name = loginData->getString(RFOF_LOGIN_NAME());
        response = loginData->getString(RFOF_LOGIN_RESPONSE());
    }

    std::string strPass = (challenge + password).toUtf8();
    std::string expectedResponse = calculatemd5(strPass);

    std::string needResponce = response.toUtf8();

    if (expectedResponse != needResponce)
    {
        throw ContextException("invalidPassword");
    }

    return true;
}

void AgentController::disconnectImpl()
{
    if (channel.get() != NULL && channel->isOpen())
    {
        try
        {
            channel->close();
        }
        catch (AggreGateException &ex)
        {
            LOG_DEVICE_AGENT_DEBUG("Error closing socket (" + ex.getMessage().toUtf8() + ")");
        }
    }

    AbstractAggreGateDeviceController::disconnectImpl();
}

void AgentController::confirmEvent(Context *con, EventDefinitionPtr def, EventPtr event)
{
    if (event->getId() != 0 && !def->getGroup().empty() && ContextUtils::GROUP_SYSTEM() != def->getGroup() && con->getRoot()->getFunctionDefinition(F_CONFIRM_EVENT()) != NULL)
    {
        try
        {
            std::list<AgObjectPtr> pars;
            pars.push_back(AgObjectPtr(new AgLong(event->getId())));
            con->getRoot()->callFunction(F_CONFIRM_EVENT(), pars);
        }
        catch (ContextException &ex)
        {
            std::string str = boost::lexical_cast<std::string>(event->getId());
            throw ContextException(MessageFormat::format(Cres::get()->getString("errConfirmingEvent"), str));
        }
    }
}

bool AgentController::isConnected()
{
    return AbstractAggreGateDeviceController::isConnected() && channel.get() != NULL && channel->isOpen();
}

void AgentController::getLoginDetails()
{
    challenge = createChallenge();
    std::list<AgObjectPtr> pars;
    pars.push_back(AgObjectPtr(new AgString(challenge)));
    DataRecordPtr loginData = getContextManager()->getRoot()->callFunction(RF_LOGIN(), pars)->rec();

    name = loginData->getString(RFOF_LOGIN_NAME());
    owner = loginData->getString(RFOF_LOGIN_OWNER());
    response = loginData->getString(RFOF_LOGIN_RESPONSE());
}

void AgentController::setPasswordForLogin(const AgString &password)
{
    this->password = password;
}

AgString AgentController::getUsername()
{
    return name;
}

AgString AgentController::getOwnerName()
{
    return owner;
}

AgString AgentController::createChallenge()
{
    AgString chal;

    srand (time(NULL));
    char c;

    for (int i = 0; i < CHALLENGE_LEN; i++)
    {
        int rng = rand() % 3;
        switch (rng)
        {
            case 0:
                c = 'a' + rand() % 26;
                chal += c;
                break;
            case 1:
                c = 'A' + rand() % 26;
                chal += c;
                break;
            case 2:
                c = '0' + rand() % 10;
                chal += c;
                break;
        }
    }

    return chal;
}
