#include "datatable/AbstractDataTableBindingProvider.h"
#include "expression/Reference.h"
#include "binding/ReferenceWriter.h"
#include "binding/ReferenceListener.h"
#include "binding/Binding.h"
#include "binding/EvaluationOptions.h"
#include "datatable/DataTableBindingProvider.h"
#include "util/Util.h"
#include "util/Log.h"
#include "IllegalStateException.h"
#include <boost/thread/lock_guard.hpp>

AbstractDataTableBindingProvider::AbstractDataTableBindingProvider(): AbstractBindingProvider()
{
}

AbstractDataTableBindingProvider::AbstractDataTableBindingProvider(ErrorCollectorPtr errorCollector) : AbstractBindingProvider(errorCollector)
{
}

void AbstractDataTableBindingProvider::writeReference(ReferencePtr ref, AgObjectPtr value)
{
    if (isLocalReference(ref) && ref->getServer().length() == 0)
    {
        int row = ref->getRow();
        AgString field = ref->getField();
        AgString property = ref->getProperty();

        if (field.length() == 0)
        {
            writeToEditor(property, value);
        }
        else
        {
            writeToField(row, field, property, value);
        }
    }
    else
    {
        ReferenceWriterPtr externalReferenceWriter = getExternalReferenceWriter();
        if (externalReferenceWriter.get() != NULL)
        {
            externalReferenceWriter->writeReference(ref, value);
        }
        else
        {
           LOG_BINDINGS_DEBUG("Unable to write value referenced by '" + ref->toString().toUtf8() + "': no external reference writer is defined");
        }
     }
}

void AbstractDataTableBindingProvider::writeToEditor(const AgString &property, AgObjectPtr value)
{
    if (DataTableBindingProvider::PROPERTY_ENABLED() == property)
    {
        setEditorEnabled(Util::convertToBoolean(value, true, false));
    }
}

void AbstractDataTableBindingProvider::writeToField(int row, const AgString &field, const AgString &property, AgObjectPtr value)
{
    if (DataTableBindingProvider::PROPERTY_ENABLED() == property)
    {
        setEnabled(value, row, field);
    }
    else if (DataTableBindingProvider::PROPERTY_HIDDEN() == property)
    {
        setHidden(value, row, field);
    }
    else if (DataTableBindingProvider::PROPERTY_CHOICES() == property)
    {
        setSelectionValues(value, row, field);
    }
    else if (DataTableBindingProvider::PROPERTY_OPTIONS() == property)
    {
        setOptions(value, row, field);
    }
    else if (property.length() == 0)
    {
        setCellValue(value, row, field);
    }
    else
    {
        throw IllegalStateException("Unknown property: \'" + property + "\'");
    }
}

int AbstractDataTableBindingProvider::getListenerCount()
{
    return listeners.size();
}

std::map<ReferenceListenerPtr, ReferencePtr> AbstractDataTableBindingProvider::getListeners()
{
    boost::lock_guard<boost::mutex> guard(listenersLock);
    return listeners;
}

void AbstractDataTableBindingProvider::addReferenceListener(ReferencePtr ref, ReferenceListenerPtr listener)
{
    boost::lock_guard<boost::mutex> guard(listenersLock);
    listeners[listener] = ref;
}

void AbstractDataTableBindingProvider::removeReferenceListener(ReferenceListenerPtr listener)
{
    boost::lock_guard<boost::mutex> guard(listenersLock);
    listeners.erase(listener);
}

bool AbstractDataTableBindingProvider::isLocalReference(ReferencePtr ref)
{
    return ref->getSchema().length() == 0 && ref->getContext().length() == 0 && ref->getEntity().length() == 0;
}

void AbstractDataTableBindingProvider::processBindings(const AgString &field, int record, bool startup)
{
    boost::lock_guard<boost::mutex> guard(listenersLock);

    for (std::map<ReferenceListenerPtr, ReferencePtr>::iterator entry = listeners.begin(); entry != listeners.end(); ++entry)
    {
        ReferencePtr ref = entry->second;
        ReferenceListenerPtr listener = entry->first;

        if (startup && listener->getBinding()->getReference()->getSchema() == Reference::SCHEMA_TABLE())
        {
            continue;
        }

        AgString rfield = ref->getField();

        bool nonLocal = !isLocalReference(ref);

        AgString listenerField = listener->getBinding()->getReference()->getField();
        bool targetPointsToCurrentField = listenerField.length() != 0 && listenerField == field;

        if ((startup && nonLocal && targetPointsToCurrentField) || (rfield.length() && rfield == field))
        {
            if (ref->getRow() == record)
            {
                callReferenceChanged(ref, startup ? EvaluationOptions::STARTUP : EvaluationOptions::EVENT, listener);
            }


            //todo why ref->getRow() can be NULL?
            assert(0);
            /*if (ref->getRow().get() == NULL)
            {
                ReferencePtr clone = ReferencePtr(ref->clone());
                clone->setRow(ref->getSchema().length() == NULL ? record : 0); // Substituting row to current one if reference uses default schema
                callReferenceChanged(clone, startup ? EvaluationOptions::STARTUP : EvaluationOptions::EVENT, listener);
            }*/
        }
    }
}
