#include "ProxyContext.h"

#include "action/ActionDefinition.h"
#include "context/ContextException.h"
#include "context/ContextRuntimeException.h"
#include "context/Contexts.h"
#include "context/VariableDefinition.h"
#include "datatable/DataRecord.h"
#include "datatable/DataTable.h"
#include "datatable/encoding/ClassicEncodingSettings.h"
#include "datatable/encoding/FormatCache.h"
#include "protocol/AbstractAggreGateDeviceController.h"
#include "protocol/IncomingAggreGateCommand.h"
#include "protocol/RemoteContextManager.h"
#include "context/ActionConstants.h"
#include "util/Log.h"
#include "util/MessageFormat.h"
#include "AggreGateException.h"
#include "IllegalStateException.h"
#include "IllegalArgumentException.h"
#include "protocol/ProxyContextListeners.h"

#include <algorithm>

using namespace AbstractContextStrings;

std::list< AgString > ProxyContext::initFunc_AUTO_LISTENED_EVENTS()
{
    std::list< AgString > list;
    list.push_back(E_CHILD_ADDED);
    list.push_back(E_CHILD_REMOVED);
    list.push_back(E_VARIABLE_ADDED);
    list.push_back(E_VARIABLE_REMOVED);
    list.push_back(E_FUNCTION_ADDED);
    list.push_back(E_FUNCTION_REMOVED);
    list.push_back(E_EVENT_ADDED);
    list.push_back(E_EVENT_REMOVED);
    list.push_back(E_INFO_CHANGED);
    list.push_back(E_DESTROYED);
    list.push_back(E_ACTION_ADDED);
    list.push_back(E_ACTION_REMOVED);
    list.push_back(E_ACTION_STATE_CHANGED);

    list.push_back(ServerContextConstants::E_VISIBLE_INFO_CHANGED);
    list.push_back(ServerContextConstants::E_VISIBLE_CHILD_ADDED);
    list.push_back(ServerContextConstants::E_VISIBLE_CHILD_REMOVED);

    return list;
}

ProxyContext::ProxyContext(AgString name, AbstractAggreGateDeviceController* controller)
    : AbstractContext(name)
{

    AUTO_LISTENED_EVENTS = initFunc_AUTO_LISTENED_EVENTS();

    notManageRemoteListeners = false;
    localInitComplete = false;
    initializingInfo = false;
    initializedInfo = false;
    initializingChildren = false;
    initializedChildren = false;
    initializingVariables = false;
    initializedVariables = false;
    initializingFunctions = false;
    initializedFunctions = false;
    initializingEvents = false;
    initializedEvents = false;
    initializingActions = false;
    initializedActions = false;
    initializingStatus = false;
    initializedStatus = false;
    initializingVisibleChildren = false;

    visibleChildAddedListener = ContextEventListenerPtr( new VisibleChildAddedListener(*this) );
    visibleChildRemovedListener = ContextEventListenerPtr( new VisibleChildRemovedListener(*this) );
    contextStatusChangedListener = ContextEventListenerPtr( new ContextStatusChangedListener(*this) );

    this->controller = controller;
    this->initializedInfo = false;
    this->initializingInfo = false;

    this->initializedChildren = false;
    this->initializingChildren = false;

    this->initializedVariables = false;
    this->initializingVariables = false;

    this->initializedFunctions = false;
    this->initializingFunctions = false;

    this->initializedEvents = false;
    this->initializingEvents = false;

    this->initializedActions = false;
    this->initializingActions = false;

    this->initializedStatus = false;
    this->initializingStatus = false;

    this->initializingVisibleChildren = false;
    this->visibleChildren.clear();

}

void ProxyContext::setupMyself()
{
    AbstractContext::setupMyself();
    setFireUpdateEvents(false);
    setPermissionCheckingEnabled(false);
    setChildrenSortingEnabled(false);
    addLocalFunctionDefinitions();

    AbstractContext::addEventListener(E_CHILD_ADDED, ContextEventListenerPtr(new ChildAddedListener(*this)));

    AbstractContext::addEventListener(E_CHILD_REMOVED, ContextEventListenerPtr(new ChildRemoveListener(*this)));

    AbstractContext::addEventListener(E_VARIABLE_ADDED, ContextEventListenerPtr(new VariableAddedListener(*this)));

    AbstractContext::addEventListener(E_VARIABLE_REMOVED, ContextEventListenerPtr(new VariableRemovedListener(*this)));

    AbstractContext::addEventListener(E_FUNCTION_ADDED, ContextEventListenerPtr(new FunctionAddedListener(*this)));

    AbstractContext::addEventListener(E_FUNCTION_REMOVED, ContextEventListenerPtr(new FunctionRemovedListener(*this)));

    AbstractContext::addEventListener(E_EVENT_ADDED, ContextEventListenerPtr(new EventAddedListener(*this)));

    AbstractContext::addEventListener(E_EVENT_REMOVED, ContextEventListenerPtr(new EventRemovedListener(*this)));

    AbstractContext::addEventListener(E_ACTION_ADDED, ContextEventListenerPtr(new ActionAddedListener(*this)));

    AbstractContext::addEventListener(E_ACTION_REMOVED, ContextEventListenerPtr(new ActionRemovedListener(*this)));

    AbstractContext::addEventListener(E_ACTION_STATE_CHANGED, ContextEventListenerPtr(new ActionStateChangeListener(*this)));

    AbstractContext::addEventListener(E_INFO_CHANGED, ContextEventListenerPtr(new InfoChangedListener(*this)));

    localInitComplete = true;
}

void ProxyContext::addLocalFunctionDefinitions()
{
    TableFormatPtr EMPTY_FORMAT = TableFormatPtr(new TableFormat(0, 0));

    addFunctionDefinition( FunctionDefinitionPtr(new FunctionDefinition(F_LOCAL_REINITIALIZE(),
                                                                        EMPTY_FORMAT, EMPTY_FORMAT)) );
}

TableFormatPtr ProxyContext::decodeFormat(const AgString & src, CallerControllerPtr caller)
{
    UNUSED(caller);
    AgString source = src;

    if (source.empty()) {
        return TableFormatPtr();
    }


    AgString idSource;

    unsigned int i;
    for (i = 0; i < source.length(); i++)
    {
        AgChar c = source[i];
        //is digit
        if (c >= AgChar('0') && c <= AgChar('9'))
        {
            idSource += c;
        }
        else
        {
            break;
        }
    }

    source = source.substr(i);
    int formatId = idSource.length() > 0 ? AgString(idSource).toInt() : 0;

    TableFormatPtr format = source.length() > 0
            ? TableFormatPtr(new TableFormat(source, ClassicEncodingSettingsPtr(new ClassicEncodingSettings(false)))) : TableFormatPtr();

    if (formatId == 0) {
        return format;
    }else {
        if (format == NULL) {
            TableFormatPtr cached = controller->getFormatCache()->get(formatId);

            if (cached == NULL) {
                throw IllegalArgumentException("Unknown format ID: " + AgString::fromInt(formatId));
            }

            return cached;
        }else {
            controller->getFormatCache()->put(formatId, format);
            return format;
        }
    }
}

void ProxyContext::clear()
{
    try
    {
        accept(ContextVisitorPtr(new AcceptContextVisitor(*this)));
    }catch (ContextException ex) {
        throw ContextRuntimeException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initInfo()
{
    try {
        if(initializedInfo) {
            return;
        }
        if (controller->getContextManager() != NULL) {
            static_cast< RemoteContextManager *>(controller->getContextManager())->initialize();
        }

        {
            boost::lock_guard< boost::recursive_mutex > lock(initializingInfoLock);
            if(!localInitComplete || initializingInfo) {
                return;
            }
            try
            {
                initializingInfo = true;
                initInfoImpl(getRemoteVariable(INFO_DEFINITION_FORMAT, V_INFO, METADATA_READ_TIMEOUT()));
                initializedInfo = true;
            }catch(AggreGateException ex) {
                initializingInfo = false;
                throw ex;
            }
            initializingInfo = false;
        }
    }catch (ContextException ex) {
        throw ex;
    }catch (AggreGateException ex) {
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initChildren()
{
    try
    {
        if (initializedChildren) {
            return;
        }

        if (controller->getContextManager() != 0) {
            static_cast< RemoteContextManager *>(controller->getContextManager())->initialize();
        }

        {
            boost::lock_guard< boost::recursive_mutex > lock(initializingChildrenLock);

            if (!localInitComplete || initializingChildren) {
                return;
            }

            try
            {
                initializingChildren = true;

                initChildrenImpl(getRemoteVariable(VFT_CHILDREN, V_CHILDREN, METADATA_READ_TIMEOUT()));

                initializedChildren = true;
            }catch(AggreGateException ex) {
                initializingChildren = false;
                throw ex;
            }
            initializingChildren = false;
        }
    }catch (ContextException ex) {
        throw ex;
    }catch (AggreGateException ex) {
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initVariables()
{
    try
    {
        if (initializedVariables) {
            return;
        }

        RemoteContextManager* contextManager = static_cast<RemoteContextManager *>(controller->getContextManager());
        if (contextManager != NULL) {
            contextManager->initialize();
        }

        {
            boost::lock_guard< boost::recursive_mutex > lock(initializingVariablesLock);

            if (!localInitComplete || initializingVariables) {
                return;
            }

            try
            {
                initializingVariables = true;

                initVariablesImpl(getRemoteVariable(VARIABLE_DEFINITION_FORMAT, V_VARIABLES, METADATA_READ_TIMEOUT()));

                initializedVariables = true;
            }catch(AggreGateException ex) {
                initializingVariables = false;
                throw ex;
            }
            initializingVariables = false;
        }
    }catch (ContextException ex) {
        throw ex;
    }catch (AggreGateException ex) {
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initFunctions()
{
    try
    {
        if (initializedFunctions){
            return;
        }

        RemoteContextManager* contextManager = static_cast<RemoteContextManager *>(controller->getContextManager());
        if (contextManager != NULL) {
            contextManager->initialize();
        }

        {
            boost::lock_guard< boost::recursive_mutex > lock(initializingFunctionsLock);

            if (!localInitComplete || initializingFunctions) {
                return;
            }

            try
            {
                initializingFunctions = true;

                initFunctionsImpl(getRemoteVariable(FUNCTION_DEFINITION_FORMAT, V_FUNCTIONS, METADATA_READ_TIMEOUT()));

                initializedFunctions = true;
            }catch(AggreGateException ex) {
                initializingFunctions = false;
                throw ex;
            }
            initializingFunctions = false;
        }
    }catch (ContextException ex) {
        throw ex;
    }catch (AggreGateException ex) {
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initEvents()
{
    try
    {
        if (initializedEvents) {
            return;
        }

        RemoteContextManager* contextManager = static_cast<RemoteContextManager *>(controller->getContextManager());
        if (contextManager != NULL) {
            contextManager->initialize();
        }

        {
            boost::lock_guard< boost::recursive_mutex > lock(initializingEventsLock);

            if (!localInitComplete || initializingEvents) {
                return;
            }

            try
            {
                initializingEvents = true;

                initEventsImpl(getRemoteVariable(EVENT_DEFINITION_FORMAT, V_EVENTS, METADATA_READ_TIMEOUT()));

                initializedEvents = true;
            }catch(AggreGateException ex) {
                initializingEvents = false;
                throw ex;
            }
            initializingEvents = false;
        }
    }catch (ContextException ex) {
        throw ex;
    }catch (AggreGateException ex) {
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initActions()
{
    try
    {
        if (initializedActions) {
            return;
        }

        RemoteContextManager* contextManager = static_cast<RemoteContextManager *>(controller->getContextManager());
        if (contextManager != NULL) {
            contextManager->initialize();
        }

        {
            boost::lock_guard< boost::recursive_mutex > lock(initializingActionsLock);

            if (!localInitComplete || initializingActions) {
                return;
            }

            try
            {
                initializingActions = true;

                initActionsImpl(getRemoteVariable(ACTION_DEF_FORMAT, V_ACTIONS, METADATA_READ_TIMEOUT()));

                initializedActions = false;
            }catch(AggreGateException ex)
            {
                initializingActions = false;
                throw ex;
            }
        }
    }catch (ContextException ex) {
        throw ex;
    }catch (AggreGateException ex) {
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initVisibleChildren()
{
    try
    {
        RemoteContextManager* contextManager = static_cast<RemoteContextManager *>(controller->getContextManager());
        if (contextManager != NULL) {
            contextManager->initialize();
        }

        {
            boost::lock_guard< boost::recursive_mutex > lock(initializingVisibleChildrenLock);

            if (!localInitComplete || initializingVisibleChildren) {
                return;
            }

            try
            {
                initializingVisibleChildren = true;

                initVisibleChildrenImpl();

            }catch(AggreGateException ex)
            {
                initializingVisibleChildren = false;
                throw ex;
            }
            initializingVisibleChildren = false;
        }
    }catch (ContextException ex) {
        throw ex;
    }catch (AggreGateException ex) {
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initStatus()
{
    try
    {
        if (initializedStatus) {
            return;
        }

        RemoteContextManager* contextManager = static_cast<RemoteContextManager *>(controller->getContextManager());
        if (contextManager != NULL) {
            contextManager->initialize();
        }

        {
            boost::lock_guard< boost::recursive_mutex > lock(initializingStatusLock);

            if (!localInitComplete || initializingStatus)
            {
                return;
            }

            try
            {
                initializingStatus = true;

                initStatusImpl();

                initializedStatus = true;

            }catch(AggreGateException ex) {
                initializingStatus = false;
                throw ex;
            }
            initializingStatus = false;
        }
    }catch (ContextException ex) {
        throw ex;
    }catch (AggreGateException ex) {
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::initInfoImpl(DataTablePtr info)
{
    setDescription(convertRemoteDescription(info->rec()->getString(VF_INFO_DESCRIPTION)));
    setType(info->rec()->getString(VF_INFO_TYPE));

    if (info->getFormat()->hasField(VF_INFO_GROUP)) {
        setGroup(info->rec()->getString(VF_INFO_GROUP));
    }

    if (info->getFormat()->hasField(VF_INFO_ICON)) {
        setIconId(info->rec()->getString(VF_INFO_ICON));
    }

    if (info->getFormat()->hasField(VF_INFO_LOCAL_ROOT)) {
        localRoot = info->rec()->getString(VF_INFO_LOCAL_ROOT);
    }

    if (info->getFormat()->hasField(VF_INFO_REMOTE_ROOT)) {
        remoteRoot = info->rec()->getString(VF_INFO_REMOTE_ROOT);
    }

    if (info->getFormat()->hasField(VF_INFO_REMOTE_PATH) && remotePath.empty()) {
        remotePath = info->rec()->getString(VF_INFO_REMOTE_PATH);
    }

    if (info->getFormat()->hasField(VF_INFO_REMOTE_PRIMARY_ROOT)) {
        remotePrimaryRoot = info->rec()->getString(VF_INFO_REMOTE_PRIMARY_ROOT);
    }

    if (info->getFormat()->hasField(VF_INFO_MAPPED)) {
        mapped = info->rec()->getBoolean(VF_INFO_MAPPED);
    }
}

AgString ProxyContext::convertRemoteDescription(const AgString & remoteDescription)
{
    return remoteDescription;
}

void ProxyContext::initChildrenImpl(DataTablePtr children)
{
    std::list<Context*> listChildren = getChildren(getContextManager()->getCallerController());
    for (std::list<Context*>::iterator child=listChildren.begin(); child!=listChildren.end(); ++child) {
        if (children->select( VF_CHILDREN_NAME, AgObjectPtr(new AgString((*child)->getName())) ) == NULL)
        {
            removeChild(*child);
        }
    }

    for (std::vector<DataRecordPtr>::iterator rec=children->iteratorBegin(); rec!=children->iteratorEnd(); ++rec) {
        AgString cn = (*rec)->getString(VF_CHILDREN_NAME);
        if (AbstractContext::getChild(cn) == NULL) {
            addChild(createChildContextProxy(cn));
        }
    }
}

void ProxyContext::initVisibleChildrenImpl()
{
    initVariables();

    AbstractContext::addEventListener(ServerContextConstants::E_VISIBLE_CHILD_ADDED, visibleChildAddedListener);
    AbstractContext::addEventListener(ServerContextConstants::E_VISIBLE_CHILD_REMOVED, visibleChildRemovedListener);

    DataTablePtr visibleChildrenData = getRemoteVariable(getVariableDefinition(ServerContextConstants::V_VISIBLE_CHILDREN));
    for (std::vector<DataRecordPtr>::iterator rec=visibleChildrenData->iteratorBegin(); rec!=visibleChildrenData->iteratorEnd(); ++rec) {
        AgString localVisiblePath = getLocalVisiblePath((*rec)->getString(ServerContextConstants::VF_VISIBLE_CHILDREN_PATH));
        if (!localVisiblePath.empty()) {
            visibleChildren.push_back(localVisiblePath);
        }
    }
}

ProxyContext* ProxyContext::createChildContextProxy(const AgString & name)
{
    ProxyContext* proxy = new ProxyContext(name, controller);
    proxy->setNotManageRemoteListeners(isNotManageRemoteListeners());

    return proxy;
}

void ProxyContext::initVariablesImpl(DataTablePtr variables)
{
    std::list<VariableDefinitionPtr> acDef = AbstractContext::getVariableDefinitions();
    for (std::list<VariableDefinitionPtr>::iterator def=acDef.begin(); def!=acDef.end(); ++def) {
        if (variables->select(FIELD_VD_NAME, AgObjectPtr(new AgString((*def)->getName()))) == NULL) {
            removeVariableDefinition((*def)->getName());
        }
    }

    for (std::vector<DataRecordPtr>::iterator rec=variables->iteratorBegin(); rec!=variables->iteratorEnd(); ++rec) {
        VariableDefinitionPtr def = AbstractContext::varDefFromDataRecord(*rec);
        VariableDefinitionPtr existing = getVariableDefinition(def->getName());
        if (existing == NULL || !existing->equals(def.get()))
        {
            if (existing != NULL)
            {
                removeVariableDefinition(existing->getName());
            }
            addVariableDefinition(def);
        }
    }
}

void ProxyContext::initFunctionsImpl(DataTablePtr functions)
{
    std::list<FunctionDefinitionPtr> acDef = AbstractContext::getFunctionDefinitions();
    for (std::list<FunctionDefinitionPtr>::iterator def=acDef.begin(); def!=acDef.end(); ++def) {
        if (functions->select(FIELD_FD_NAME, AgObjectPtr(new AgString((*def)->getName()))) == NULL)
        {
            removeFunctionDefinition((*def)->getName());
        }
    }

    addLocalFunctionDefinitions();

    for (std::vector<DataRecordPtr>::iterator rec=functions->iteratorBegin(); rec!=functions->iteratorEnd(); ++rec) {
        FunctionDefinitionPtr def = AbstractContext::funcDefFromDataRecord(*rec);
        def->setConcurrent(true); // Concurrency is controlled by the server
        FunctionDefinitionPtr existing = getFunctionDefinition(def->getName());
        if (existing == NULL || !existing->equals(def.get()))
        {
            if (existing != NULL)
            {
                removeFunctionDefinition(existing->getName());
            }
            addFunctionDefinition(def);
        }
    }
}

void ProxyContext::initEventsImpl(DataTablePtr events)
{
    std::list<EventDefinitionPtr> acDef = AbstractContext::getEventDefinitions();
    for (std::list<EventDefinitionPtr>::iterator def=acDef.begin(); def!=acDef.end(); ++def) {
        if (events->select(FIELD_ED_NAME, AgObjectPtr(new AgString((*def)->getName()))) == NULL)
        {
            removeEventDefinition((*def)->getName());
        }
    }

    for (std::vector<DataRecordPtr>::iterator rec=events->iteratorBegin(); rec!=events->iteratorEnd(); ++rec) {
        EventDefinitionPtr def = AbstractContext::evtDefFromDataRecord(*rec);
        EventDefinitionPtr existing = getEventDefinition(def->getName());
        if (existing == NULL || !existing->equals(def.get()))
        {
            if (existing != NULL)
            {
                removeEventDefinition(existing->getName());
            }
            addEventDefinition(def);
        }
    }
}

void ProxyContext::initActionsImpl(DataTablePtr actions)
{
    std::list<ActionDefinitionPtr> acDef = AbstractContext::getActionDefinitions();
    for (std::list<ActionDefinitionPtr>::iterator ad=acDef.begin(); ad!=acDef.end(); ++ad) {
        if (actions->select(ActionConstants::FIELD_AD_NAME, AgObjectPtr(new AgString((*ad)->getName()))) == NULL)
        {
            removeActionDefinition((*ad)->getName());
        }
    }

    for (std::vector<DataRecordPtr>::iterator rec=actions->iteratorBegin(); rec!=actions->iteratorEnd(); ++rec) {
        ActionDefinitionPtr def = AbstractContext::actDefFromDataRecord(*rec);
        ActionDefinitionPtr existing = getActionDefinition(def->getName());
        if (existing == NULL || !existing->equals(def.get())) {
            if (existing != NULL) {
                removeActionDefinition(existing->getName());
            }
            addActionDefinition(def);
        }
    }
}

void ProxyContext::initStatusImpl()
{
    initVariables();

    VariableDefinitionPtr statusVariable = getVariableDefinition(ServerContextConstants::V_CONTEXT_STATUS);
    if (statusVariable == NULL){
        return;
    }

    enableStatus();

    AbstractContext::addEventListener(ServerContextConstants::E_CONTEXT_STATUS_CHANGED, contextStatusChangedListener);

    DataTablePtr contextStatus = getRemoteVariable(statusVariable);

    setStatus(contextStatus->rec()->getInt(ServerContextConstants::VF_CONTEXT_STATUS_STATUS),
              contextStatus->rec()->getString(ServerContextConstants::VF_CONTEXT_STATUS_COMMENT));
}

AgString ProxyContext::getDescription()
{
    try{
        initInfo();
    }catch (ContextException ex) {
        throw ContextRuntimeException("Error getting description of remote context: " + ex.getMessage(), ex.getDetails());
    }
    return AbstractContext::getDescription();
}

AgString ProxyContext::getType()
{
    try {
        initInfo();
    }catch (ContextException ex) {
        throw ContextRuntimeException("Error getting type of remote context: " + ex.getMessage(), ex.getDetails());
    }
    return AbstractContext::getType();
}

AgString ProxyContext::getLocalRoot()
{
    try
    {
        initInfo();
    }
    catch (ContextException ex)
    {
        throw ContextRuntimeException(ex.getMessage());
    }

    return localRoot;
}

AgString ProxyContext::getRemoteRoot()
{
    try
    {
        initInfo();
    }
    catch (ContextException ex)
    {
        throw ContextRuntimeException(ex.getMessage(), ex.getDetails());
    }

    return remoteRoot;
}

bool ProxyContext::isMapped()
{
    try
    {
        initInfo();
    }catch (ContextException ex) {
        throw ContextRuntimeException(ex.getMessage(), ex.getDetails());
    }

    return mapped;
}

Context* ProxyContext::get(const AgString & contextPath, CallerControllerPtr caller)
{
    if (contextPath.empty()) {
        //return NULL;
        return AbstractContext::get(contextPath, caller);
    }

    if (ContextUtils::isRelative(contextPath)) {
        return AbstractContext::get(contextPath, caller);
    }

    AgString localPath = getLocalPath(contextPath);

    if (localPath.empty()) {
        return NULL;
    }

    return AbstractContext::get(localPath, caller);
}

AgString ProxyContext::getIconId()
{
    try
    {
        initInfo();
    }
    catch (ContextException ex)
    {
        LOG_CONTEXT_VARIABLES_DEBUG("Error getting icon of remote context: " + ex.getMessage().toUtf8());
    }
    return AbstractContext::getIconId();
}

Context* ProxyContext::getChild(const AgString & name, CallerControllerPtr callerController)
{
    if (AbstractContext::getChild(name, callerController) == NULL)
    {
        try
        {
            initChildren();
        }
        catch (ContextException ex)
        {
            LOG_CONTEXT_VARIABLES_DEBUG("Error initializing children of remote context: " + ex.getMessage().toUtf8());
        }
    }
    return AbstractContext::getChild(name, callerController);
}

VariableDefinitionPtr ProxyContext::getVariableDefinition(const AgString & name)
{
    VariableDefinitionPtr sup = AbstractContext::getVariableDefinition(name);
    if (sup == NULL && isSetupComplete()) {
        initVariablesLoggingErrors();
        return AbstractContext::getVariableDefinition(name);
    }else {
        return sup;
    }
}

FunctionDefinitionPtr ProxyContext::getFunctionDefinition(const AgString & name)
{
    FunctionDefinitionPtr sup = AbstractContext::getFunctionDefinition(name);
    if (sup == NULL && isSetupComplete()) {
        initFunctionsLoggingErrors();
        return AbstractContext::getFunctionDefinition(name);
    }else {
        return sup;
    }
}

EventDataPtr ProxyContext::getEventData(const AgString & name)
{
    EventDataPtr sup = AbstractContext::getEventData(name);
    if (sup == NULL && isSetupComplete())
    {
        initEventsLoggingErrors();
        return AbstractContext::getEventData(name);
    }
    else
    {
        return sup;
    }
}

ActionDefinitionPtr ProxyContext::getActionDefinition(const AgString & name)
{
    initActionsLoggingErrors();
    return AbstractContext::getActionDefinition(name);
}

std::list<VariableDefinitionPtr> ProxyContext::getVariableDefinitions(CallerControllerPtr caller, bool hidden)
{
    initVariablesLoggingErrors();
    return AbstractContext::getVariableDefinitions(caller, hidden);
}

std::list<FunctionDefinitionPtr>  ProxyContext::getFunctionDefinitions(CallerControllerPtr caller, bool hidden)
{
    initFunctionsLoggingErrors();
    return AbstractContext::getFunctionDefinitions(caller, hidden);
}

std::list<EventDefinitionPtr>  ProxyContext::getEventDefinitions(CallerControllerPtr caller, bool hidden)
{
    initEventsLoggingErrors();
    return AbstractContext::getEventDefinitions(caller, hidden);
}

std::list<ActionDefinitionPtr>  ProxyContext::getActionDefinitions(CallerControllerPtr caller, bool hidden)
{
    initActionsLoggingErrors();
    return AbstractContext::getActionDefinitions(caller, hidden);
}

ContextStatusPtr ProxyContext::getStatus()
{
    initStatusLoggingErrors();
    return AbstractContext::getStatus();
}

void ProxyContext::initVariablesLoggingErrors()
{
    try
    {
        initVariables();
    }
    catch (ContextException ex)
    {
        AgString message = "Error initializing variables of remote context '" + getPathDescription() + "': " + ex.getMessage();
        LOG_CONTEXT_VARIABLES_DEBUG(message.toUtf8());
        throw ContextRuntimeException(message, ex.getDetails());
    }
}

void ProxyContext::initFunctionsLoggingErrors()
{
    try
    {
        initFunctions();
    }
    catch (ContextException ex)
    {
        AgString message = "Error initializing functions of remote context '" + getPathDescription() + "': " + ex.getMessage();
        LOG_CONTEXT_FUNCTIONS_DEBUG(message.toUtf8());
        throw ContextRuntimeException(message, ex.getDetails());
    }
}

void ProxyContext::initEventsLoggingErrors()
{
    try
    {
        initEvents();
    }
    catch (ContextException ex)
    {
        AgString message = "Error initializing events of remote context '" + getPathDescription() + "': " + ex.getMessage();
        LOG_CONTEXT_EVENTS_DEBUG(message.toUtf8());
        throw ContextRuntimeException(message, ex.getDetails());
    }
}

void ProxyContext::initActionsLoggingErrors()
{
    try
    {
        initActions();
    }catch (ContextException ex) {
        LOG_CONTEXT_ACTIONS_DEBUG("Error initializing actions of remote context '" + getPathDescription().toUtf8() + "': " + ex.getMessage().toUtf8());
    }
}

void ProxyContext::initStatusLoggingErrors()
{
    try
    {
        initStatus();
    }
    catch (ContextException ex)
    {
        LOG_CONTEXT_DEBUG("Error initializing status of remote context '" + getPathDescription().toUtf8() + "': " + ex.getMessage().toUtf8());
    }
}

IncomingAggreGateCommandPtr ProxyContext::sendGetVariable(const AgString & name, int64_t  timeout)
{
    OutgoingAggreGateCommandPtr cmd = AggreGateCommandUtils::getVariableOperation(getPeerPath(), name);
    cmd->setTimeout(timeout);
    return controller->sendCommandAndCheckReplyCode(cmd);
}

DataTablePtr ProxyContext::getRemoteVariable(TableFormatPtr format, const AgString & name, int64_t  timeout)
{
    AgString encodedReply = sendGetVariable(name, timeout)->getEncodedDataTableFromReply();

    try
    {
        return controller->decodeRemoteDataTable(format, encodedReply);
    }
    catch (AggreGateException ex)
    {
        throw ContextException("Error parsing encoded data table '" + encodedReply + "': " + ex.getMessage());
    }
}

AbstractAggreGateDeviceController* ProxyContext::getController()
{
    return controller;
}

void ProxyContext::setupVariables()
{
    initVariables();
    AbstractContext::setupVariables();
}

DataTablePtr ProxyContext::getVariableImpl(VariableDefinitionPtr def, CallerControllerPtr caller, RequestControllerPtr request)
{
    UNUSED(caller);
    UNUSED(request);
    return getRemoteVariable(def);
}

DataTablePtr ProxyContext::getRemoteVariable(VariableDefinitionPtr def)
{
    try
    {
        bool cleanup = false;

        if (def->getRemoteCacheTime().total_milliseconds() != 0) {
            boost::lock_guard< boost::recursive_mutex > lock(variableCacheLock);//.readLock().lock();

            boost::shared_ptr<CachedVariableValue> ref = variableCache[def->getName()];
            if (ref != NULL) {
                if ((boost::posix_time::microsec_clock::local_time() - ref->getTimestamp()->getValue())
                        < def->getRemoteCacheTime()) {
                    return ref->getValue();
                }else {
                    cleanup = true;
                }
            }else {
                cleanup = true;
            }

            if (cleanup) {
                boost::lock_guard< boost::recursive_mutex > lock(variableCacheLock);//.writeLock().lock();
                variableCache.erase(def->getName());
            }
        }


        IncomingAggreGateCommandPtr ans = sendGetVariable(def->getName(), METADATA_READ_TIMEOUT());

        AgString str = ans->getEncodedDataTableFromReply();
        DataTablePtr value = controller->decodeRemoteDataTable(def->getFormat(), ans->getEncodedDataTableFromReply());

        if (def->getRemoteCacheTime().total_microseconds() != 0) {
            cacheVariableValue(def->getName(), value);
        }

        return value;
    }
    catch (AggreGateException ex)
    {
        LOG_CONTEXT_VARIABLES_DEBUG("Error getting variable '" + def->getName().toUtf8() + "' from context '" + getPathDescription().toUtf8() + "'");
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

bool ProxyContext::setVariableImpl(VariableDefinitionPtr def, CallerControllerPtr caller, RequestControllerPtr request, DataTablePtr value)
{
    UNUSED(caller);
    UNUSED(request);
    try
    {
        AgString encoded = value->encode(controller->createClassicEncodingSettings(true));
        controller->sendCommandAndCheckReplyCode(AggreGateCommandUtils::setVariableOperation(getPeerPath(), def->getName(), encoded));
        return true;
    }
    catch (AggreGateException ex)
    {
        LOG_CONTEXT_VARIABLES_DEBUG("Error setting veriable '" + def->getName().toUtf8() + "' of context '" + getPathDescription().toUtf8() + "'");
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

void ProxyContext::setupFunctions()
{
    initFunctions();
    AbstractContext::setupFunctions();
}

DataTablePtr ProxyContext::callFunctionImpl(FunctionDefinitionPtr def, CallerControllerPtr caller, RequestControllerPtr request, DataTablePtr parameters)
{
    UNUSED(caller);
    UNUSED(request);
    UNUSED(parameters);
    if (def->getName() ==F_LOCAL_REINITIALIZE()) {
        reinitialize();
        return DataTablePtr(new DataTable(def->getOutputFormat(), true));
    }

    return callRemoteFunction(def->getName(), def->getOutputFormat(), parameters);
}

DataTablePtr ProxyContext::callRemoteFunction(const AgString & name, TableFormatPtr outputFormat, DataTablePtr parameters)
{
    try
    {
        return controller->callRemoteFunction(getPeerPath(), name, outputFormat, parameters);
    }
    catch (AggreGateException ex)
    {
        LOG_CONTEXT_FUNCTIONS_DEBUG("Error calling function '" + name.toUtf8() + "' of context '" + getPathDescription().toUtf8() + "'");
        throw ContextException(ex.getMessage(), ex.getDetails());
    }
}

bool ProxyContext::addEventListener(const AgString & name, ContextEventListenerPtr contextEventListener, bool weak)
{
    return addEventListener(name, contextEventListener, weak, true);
}

bool ProxyContext::addEventListener(const AgString & name, ContextEventListenerPtr contextEventListener, bool weak, bool sendRemoteCommand)
{
    try
    {
        initEvents();

        EventDataPtr ed = getEventData(name);

        if (ed == NULL) {
            throw ContextException(Cres::get()->getString("conEvtNotAvail") + name);
        }

        if (sendRemoteCommand) {
            addRemoteListener(ed->getDefinition()->getName(), contextEventListener);
        }

        return AbstractContext::addEventListener(name, contextEventListener, weak);
    }catch (AggreGateException ex) {
        AgString msg = MessageFormat::format(Cres::get()->getString("conErrAddingListener"), name, getPathDescription());
        throw IllegalStateException(msg + ": " + ex.getMessage(), ex.getDetails());
    }
}

bool ProxyContext::removeEventListener(const AgString & name, ContextEventListenerPtr contextEventListener)
{
    return removeEventListener(name, contextEventListener, true);
}

bool ProxyContext::removeEventListener(const AgString & name, ContextEventListenerPtr listener, bool sendRemoteCommand)
{    
    try
    {
        if (!isInitializedEvents())
        {
            return false;
        }

        LOG_CONTEXT_EVENTS_DEBUG("Removing listener for event '" + name.toUtf8() + "' from context '" + getPathDescription().toUtf8() + "'");

        bool res = AbstractContext::removeEventListener(name, listener);

        EventDataPtr ed = getEventData(name);

        if (sendRemoteCommand && (ed != NULL) && ed->getListeners().size() == 0) {
            if (!notManageRemoteListeners) {
                int hashCode = listener->getListenerCode();
                AgString filter = listener->getFilter()!=NULL ? listener->getFilter()->getText() : "";
                OutgoingAggreGateCommandPtr cmd = AggreGateCommandUtils::removeEventListenerOperation(getPeerPath(), name, hashCode, filter);
                cmd->setTimeout(LISTENER_OPERATIONS_TIMEOUT());
                controller->sendCommandAndCheckReplyCode(cmd);
            }
        }

        return res;
    }catch (DisconnectionException ex) {
        LOG_CONTEXT_EVENTS_DEBUG("Disconnection detected when removing listener for event '" + name.toUtf8() + "' from context '" + getPathDescription().toUtf8() + "'");
        return false;
    }catch (AggreGateException ex) {
        AgString msg = MessageFormat::format(Cres::get()->getString("conErrRemovingListener"), name, getPathDescription());
        throw IllegalStateException(msg + ": " + ex.getMessage());
    }

    return 0;
}

void ProxyContext::addRemoteListener(const AgString & ename, ContextEventListenerPtr contextEventListener)
{
    int hashCode = contextEventListener->getListenerCode();

    if (hashCode == 0 && (std::find(AUTO_LISTENED_EVENTS.begin(), AUTO_LISTENED_EVENTS.end(), ename)!=AUTO_LISTENED_EVENTS.end())) {
        return;
    }

    if (!notManageRemoteListeners)
    {
        AgString filterText = contextEventListener->getFilter() != NULL ? contextEventListener->getFilter()->getText() : "";
        OutgoingAggreGateCommandPtr cmd = AggreGateCommandUtils::addEventListenerOperation(getPeerPath(), ename, hashCode, filterText);
        cmd->setTimeout(LISTENER_OPERATIONS_TIMEOUT());
        controller->sendCommandAndCheckReplyCode(cmd);
    }
}

std::list<Context*> ProxyContext::getChildren(CallerControllerPtr caller)
{
    try
    {
        initChildren();
    }
    catch (ContextException ex)
    {
        LOG_CONTEXT_CHILDREN_DEBUG("Error initializing children of remote context");
    }
    return AbstractContext::getChildren(caller);
}

std::list<Context*>  ProxyContext::getVisibleChildren(CallerControllerPtr caller)
{
    std::list<Context*> res;

    try {
        initVisibleChildren();
    }catch (ContextException ex) {
        LOG_CONTEXT_CHILDREN_DEBUG("Error initializing visible children of remote context");
        return res;
    }

    for (std::list<AgString>::iterator path=visibleChildren.begin(); path!=visibleChildren.end(); ++path) {
        Context* con = getRoot()->get(*path, caller);
        if (con != NULL) {
            res.push_back(con);
        }
    }

    return res;
}

void ProxyContext::addVisibleChild(const AgString & localVisiblePath)
{
    visibleChildren.push_back(localVisiblePath);
}

void ProxyContext::removeVisibleChild(const AgString & localVisiblePath)
{
    visibleChildren.remove(localVisiblePath);
}

void ProxyContext::restoreEventListeners()
{
    std::list<EventDefinitionPtr> listEvDef = AbstractContext::getEventDefinitions(); // Calling method of superclass directly to avoid fetching remote events info
    for (std::list<EventDefinitionPtr>::iterator ed=listEvDef.begin(); ed!=listEvDef.end(); ++ed) {
        EventDataPtr edata = getEventData((*ed)->getName());
        {
            boost::lock_guard<boost::mutex> lock(*edata);
            boost::unordered_set<ContextEventListenerPtr> listListener = edata->getListeners();
            for (boost::unordered_set<ContextEventListenerPtr>::iterator listener=listListener.begin(); listener!=listListener.end(); ++listener) {
                try{
                    addRemoteListener((*ed)->getName(), *listener);
                }catch (AggreGateException ex) {
                    LOG_CONTEXT_EVENTS_DEBUG("Error restoring listener for event '" + (*ed)->getName().toUtf8() + "'");
                }
            }
        }
    }
}

void ProxyContext::reinitialize()
{
    clear();
    restoreEventListeners();
}

EventPtr ProxyContext::fireEvent(EventDefinitionPtr ed, DataTablePtr data, int level, int64_t  id,
                                 AgDatePtr creationtime, int  listener, CallerControllerPtr caller,
                                 FireEventRequestControllerPtr request, PermissionsPtr permissions)
{
    EventPtr event = AbstractContext::fireEvent(ed, data, level, id, creationtime, listener, caller, request, permissions);

    if ((ed->getName()==E_UPDATED) && isInitializedVariables()) {
        AgString variable = event->getData()->rec()->getString(EF_UPDATED_VARIABLE);

        DataTablePtr value = event->getData()->rec()->getDataTable(EF_UPDATED_VALUE);

        VariableDefinitionPtr vd = getVariableDefinition(variable);

        if (vd != NULL && vd->getRemoteCacheTime().total_milliseconds() != 0) {
            cacheVariableValue(variable, value);
        }
    }

    return event;
}

EventPtr ProxyContext::fireEvent(const AgString & name, DataTablePtr data, int level, int64_t  id,
                                 AgDatePtr creationtime, int  listener, CallerControllerPtr caller,
                                 FireEventRequestControllerPtr request)
{
    return AbstractContext::fireEvent(name, data, level, id, creationtime, listener, caller, request);
}

void ProxyContext::cacheVariableValue(const AgString & variable, DataTablePtr value)
{
    boost::lock_guard< boost::recursive_mutex > lock(variableCacheLock);
    variableCache.insert( std::pair< AgString, boost::shared_ptr<CachedVariableValue> >
                          (variable, boost::shared_ptr<CachedVariableValue>(new CachedVariableValue(AgDatePtr(new AgDate()), value))) );
}

AgString ProxyContext::getPathDescription()
{
    return getPath();
}

bool ProxyContext::isProxy()
{
    return true;
}

bool ProxyContext::isDistributed()
{
    return !getRemoteRoot().empty();
}

AgString ProxyContext::getRemotePath()
{
    try {
        initInfo();
    } catch (ContextException ex) {
        throw ContextRuntimeException(ex.getMessage(), ex.getDetails());
    }
    return remotePath;
}

AgString ProxyContext::getRemotePrimaryRoot()
{
    return remotePrimaryRoot;
}

AgString ProxyContext::getPeerPath()
{
    return getPath();
}

AgString ProxyContext::getLocalPath(const AgString & remoteFullPath)
{
    AgString remoteRoot = getRemoteRoot();

    if (getRemoteRoot().empty()) {
        return remoteFullPath;
    }

    AgString remoteConverted;
    if (remoteRoot == Contexts::CTX_ROOT) {
        remoteConverted = remoteFullPath;
    }else if (remoteFullPath == remoteRoot) {
        remoteConverted = "";
    }else if (remoteFullPath.startsWith(getRemoteRoot() + ContextUtils::CONTEXT_NAME_SEPARATOR())) {
        remoteConverted = remoteFullPath.substr(getRemoteRoot().length() + 1);
    }else {
        return getRemotePrimaryPath(remoteFullPath);
    }

    std::vector<AgString> param;
    param.push_back(getLocalRoot());
    param.push_back(remoteConverted);
    AgString converted = remoteConverted.length() > 0 ? ContextUtils::createName(param) : getLocalRoot();

    LOG_CONTEXT_DEBUG("Converted remote path '" + remoteFullPath.toUtf8() + "' to: " + converted.toUtf8());

    return converted;
}

AgString ProxyContext::getRemotePrimaryPath(const AgString & remoteFullPath)
{
    AgString primaryMount = getRemotePrimaryRoot();

    if (primaryMount.empty())
    {
        return primaryMount;
    }

    if (Contexts::CTX_ROOT == remoteFullPath) {
        return primaryMount;
    }else
    {
        std::vector<AgString> param;
        param.push_back(primaryMount);
        param.push_back(remoteFullPath);
        return ContextUtils::createName(param);
    }
}

AgString ProxyContext::getLocalVisiblePath(const AgString & remoteFullPath)
{
    return remoteFullPath;
}

void ProxyContext::setRemotePath(const AgString & remotePath)
{
    this->remotePath = remotePath;
}

bool ProxyContext::isInitializedInfo()
{
    return initializedInfo;
}

bool ProxyContext::isInitializedChildren()
{
    return initializedChildren;
}

bool ProxyContext::isInitializedVariables()
{
    return initializedVariables;
}

bool ProxyContext::isInitializedFunctions()
{
    return initializedFunctions;
}

bool ProxyContext::isInitializedEvents()
{
    return initializedEvents;
}

bool ProxyContext::isNotManageRemoteListeners()
{
    return notManageRemoteListeners;
}

void ProxyContext::setNotManageRemoteListeners(bool notManageRemoteListeners)
{
    this->notManageRemoteListeners = notManageRemoteListeners;
}
