#include "rive/data_bind/data_bind_container.hpp"
#include "rive/data_bind/data_bind_context.hpp"
#include "rive/data_bind/data_bind.hpp"
#include "rive/data_bind/data_context.hpp"

using namespace rive;

void DataBindContainer::deleteDataBinds()
{
    for (auto& dataBind : m_dataBinds)
    {
        delete dataBind;
    }
}

void DataBindContainer::unbindDataBinds()
{
    for (auto& dataBind : m_dataBinds)
    {
        dataBind->unbind();
    }
}

void DataBindContainer::bindDataBindsFromContext(DataContext* dataContext)
{

    for (auto& dataBind : m_dataBinds)
    {
        if (dataBind->is<DataBindContext>())
        {
            dataBind->as<DataBindContext>()->bindFromContext(dataContext);
        }
    }
}

bool DataBindContainer::advanceDataBinds(float elapsedSeconds)
{
    bool didUpdate = false;
    for (auto& dataBind : m_dataBinds)
    {
        if (dataBind->advance(elapsedSeconds))
        {
            didUpdate = true;
        }
    }
    return didUpdate;
}

void DataBindContainer::addDataBind(DataBind* dataBind)
{
    m_dataBinds.push_back(dataBind);
    // Any data bind that is applied to source needs to be updated regardless of
    // it having dirt or not. The reason is that they depend on changes of the
    // value of the target, which is not propagated as dirt to the data binding
    // object. That's why there is a separate list for these that is constantly
    // updated.
    if (dataBind->toSource())
    {
        m_persistingDataBinds.push_back(dataBind);
    }
    else
    {
        m_dirtyDataBinds.push_back(dataBind);
    }
    dataBind->container(this);
}

void DataBindContainer::updateDataBind(DataBind* dataBind,
                                       bool applyTargetToSource)
{
    auto d = dataBind->dirt();

    // Update dependents before applying both target to source and source to
    // target
    if ((d & ComponentDirt::Dependents) == ComponentDirt::Dependents)
    {
        dataBind->updateDependents();
    }

    if (applyTargetToSource && !dataBind->sourceToTargetRunsFirst())
    {

        dataBind->updateSourceBinding();
    }
    if (d != ComponentDirt::None)
    {
        dataBind->dirt(ComponentDirt::None);
        dataBind->update(d);
    }
    if (applyTargetToSource && dataBind->sourceToTargetRunsFirst())
    {

        dataBind->updateSourceBinding();
    }
}

void DataBindContainer::updateDataBinds(bool applyTargetToSource)
{
    m_isProcessing = true;
    for (auto& dataBind : m_persistingDataBinds)
    {
        if (!dataBind->canSkip())
        {
            updateDataBind(dataBind, applyTargetToSource);
        }
    }
    for (auto& dataBind : m_dirtyDataBinds)
    {
        // data binds on this list don't need to apply target to source because
        // any data bind that applies to source is collected on the
        // m_persistingDataBinds list
        updateDataBind(dataBind, false);
    }
    m_dirtyDataBinds.clear();
    if (m_pendingDirtyDataBinds.size() > 0)
    {

        m_dirtyDataBinds.assign(m_pendingDirtyDataBinds.begin(),
                                m_pendingDirtyDataBinds.end());
        m_pendingDirtyDataBinds.clear();
    }
    m_isProcessing = false;
}

void DataBindContainer::sortDataBinds()
{
    size_t currentToSourceIndex = 0;
    for (size_t i = 0; i < m_dataBinds.size(); i++)
    {
        if (m_dataBinds[i]->toSource())
        {
            if (i != currentToSourceIndex)
            {

                std::iter_swap(m_dataBinds.begin() + currentToSourceIndex,
                               m_dataBinds.begin() + i);
            }
            currentToSourceIndex += 1;
        }
    }
}

void DataBindContainer::addDirtyDataBind(DataBind* dataBind)
{
    if (dataBind->toSource())
    {
        return;
    }
    auto& insertingList =
        m_isProcessing ? m_pendingDirtyDataBinds : m_dirtyDataBinds;
    auto itr = std::find(insertingList.begin(), insertingList.end(), dataBind);
    if (itr == insertingList.end())
    {
        insertingList.push_back(dataBind);
    }
}