/**********************************************************************

  Audacity: A Digital Audio Editor

  RealtimeEffectList.cpp

 *********************************************************************/

#include "RealtimeEffectList.h"
#include "RealtimeEffectState.h"

#include "Project.h"
#include "Channel.h"

RealtimeEffectList::RealtimeEffectList()
{
}

RealtimeEffectList::~RealtimeEffectList()
{
}

// Deep copy of states
std::unique_ptr<ClientData::Cloneable<>> RealtimeEffectList::Clone() const
{
   auto result = std::make_unique<RealtimeEffectList>();
   for (auto &pState : mStates)
      result->mStates.push_back(pState);
   result->SetActive(this->IsActive());
   return result;
}

// Access for per-project effect list
static const AttachedProjectObjects::RegisteredFactory masterEffects
{
   [](AudacityProject &project)
   {
      return std::make_shared<RealtimeEffectList>();
   }
};

RealtimeEffectList &RealtimeEffectList::Get(AudacityProject &project)
{
   return project.AttachedObjects::Get<RealtimeEffectList>(masterEffects);
}

RealtimeEffectList &RealtimeEffectList::Set(
   AudacityProject &project, const std::shared_ptr<RealtimeEffectList> &list)
{
   auto &result = *list;
   project.AttachedObjects::Assign(masterEffects, list);
   return result;
}

const RealtimeEffectList &
RealtimeEffectList::Get(const AudacityProject &project)
{
   return Get(const_cast<AudacityProject &>(project));
}

static const ChannelGroup::Attachments::RegisteredFactory
channelGroupEffects
{
   [](auto&)
   {
      return std::make_unique<RealtimeEffectList>();
   }
};

// Access for per-group effect list
RealtimeEffectList &RealtimeEffectList::Get(ChannelGroup &group)
{
   return group.Attachments::Get<RealtimeEffectList>(channelGroupEffects);
}

const RealtimeEffectList &RealtimeEffectList::Get(
   const ChannelGroup &group)
{
   return Get(const_cast<ChannelGroup &>(group));
}

bool
RealtimeEffectList::AddState(std::shared_ptr<RealtimeEffectState> pState)
{
   const auto &id = pState->GetID();
   if (pState->GetEffect() != nullptr) {
      auto shallowCopy = mStates;
      shallowCopy.emplace_back(pState);
      // Lock for only a short time
      (LockGuard{ mLock }, swap(shallowCopy, mStates));

      Publisher<RealtimeEffectListMessage>::Publish({
         RealtimeEffectListMessage::Type::Insert,
         mStates.size() - 1,
         { },
         pState
      });

      return true;
   }
   else
      // Effect initialization failed for the id
      return false;
}

bool
RealtimeEffectList::ReplaceState(size_t index,
   std::shared_ptr<RealtimeEffectState> pState)
{
   if (index >= mStates.size())
      return false;
   const auto &id = pState->GetID();
   if (pState->GetEffect() != nullptr) {
      auto shallowCopy = mStates;

      Publisher<RealtimeEffectListMessage>::Publish({
         RealtimeEffectListMessage::Type::WillReplace,
         index,
         { },
         shallowCopy[index]
      });

      swap(pState, shallowCopy[index]);
      // Lock for only a short time
      (LockGuard{ mLock }, swap(shallowCopy, mStates));

      Publisher<RealtimeEffectListMessage>::Publish({
         RealtimeEffectListMessage::Type::DidReplace,
         index,
         { },
         pState
      });

      return true;
   }
   else
      // Effect initialization failed for the id
      return false;
}

void RealtimeEffectList::RemoveState(
   const std::shared_ptr<RealtimeEffectState> pState)
{
   auto shallowCopy = mStates;
   auto end = shallowCopy.end(),
      found = std::find(shallowCopy.begin(), end, pState);
   if (found != end)
   {
      const auto index = std::distance(shallowCopy.begin(), found);
      shallowCopy.erase(found);

      // Lock for only a short time
      (LockGuard{ mLock }, swap(shallowCopy, mStates));

      Publisher<RealtimeEffectListMessage>::Publish({
         RealtimeEffectListMessage::Type::Remove,
         static_cast<size_t>(index),
         { },
         pState
      });
   }
}

void RealtimeEffectList::Clear()
{
   decltype(mStates) temp;

   // Swap an empty list in as a whole, not removing one at a time
   // Lock for only a short time
   (LockGuard{ mLock }, swap(temp, mStates));

   for (auto index = temp.size(); index--;)
      Publisher<RealtimeEffectListMessage>::Publish(
         { RealtimeEffectListMessage::Type::Remove, index, {}, temp[index] });
}

std::optional<size_t> RealtimeEffectList::FindState(
   const std::shared_ptr<RealtimeEffectState> &pState) const
{
   const auto begin = mStates.begin()
      , end = mStates.end()
      , iter = std::find(begin, end, pState);
   if (iter == end)
      return {};
   return std::distance(begin, iter);
}

size_t RealtimeEffectList::GetStatesCount() const noexcept
{
   return mStates.size();
}

std::shared_ptr<RealtimeEffectState>
RealtimeEffectList::GetStateAt(size_t index) noexcept
{
   if (index < mStates.size())
      return mStates[index];
   return nullptr;
}

std::shared_ptr<const RealtimeEffectState>
RealtimeEffectList::GetStateAt(size_t index) const noexcept
{
   return const_cast<RealtimeEffectList*>(this)->GetStateAt(index);
}

void RealtimeEffectList::MoveEffect(size_t fromIndex, size_t toIndex)
{
   assert(fromIndex < mStates.size());
   assert(toIndex < mStates.size());

   auto shallowCopy = mStates;
   if(fromIndex == toIndex)
      return;
   if(fromIndex < toIndex)
   {
      const auto first = shallowCopy.begin() + fromIndex;
      const auto last = shallowCopy.begin() + toIndex + 1;
      std::rotate(first, first + 1, last);
   }
   else
   {
      const auto first =
         shallowCopy.rbegin() + (shallowCopy.size() - (fromIndex + 1));
      const auto last = shallowCopy.rbegin() + (shallowCopy.size() - toIndex);
      std::rotate(first, first + 1, last);
   }
   // Lock for only a short time
   (LockGuard{ mLock }, swap(shallowCopy, mStates));

   Publisher<RealtimeEffectListMessage>::Publish({
      RealtimeEffectListMessage::Type::Move,
      fromIndex,
      toIndex,
      mStates[toIndex]
   });
}

const std::string &RealtimeEffectList::XMLTag()
{
   static const std::string result{"effects"};
   return result;
}

static constexpr auto activeAttribute = "active";

bool RealtimeEffectList::HandleXMLTag(
   const std::string_view &tag, const AttributesList &attrs)
{
   if (tag == XMLTag()) {
      for (auto &[attr, value] : attrs) {
         if (attr == activeAttribute)
            SetActive(value.Get<bool>());
      }
      return true;
   }
   return false;
}

XMLTagHandler *RealtimeEffectList::HandleXMLChild(const std::string_view &tag)
{
   if (tag == RealtimeEffectState::XMLTag()) {
      mStates.push_back(RealtimeEffectState::make_shared(PluginID{}));
      return mStates.back().get();
   }
   return nullptr;
}

void RealtimeEffectList::WriteXML(XMLWriter &xmlFile) const
{
   xmlFile.StartTag(XMLTag());
   xmlFile.WriteAttr(activeAttribute, IsActive());
   for (const auto & state : mStates)
      state->WriteXML(xmlFile);
   xmlFile.EndTag(XMLTag());
}

void RealtimeEffectList::RestoreUndoRedoState(AudacityProject &project) noexcept
{
   // Restore per-project states
   Set(project, shared_from_this());
}

bool RealtimeEffectList::IsActive() const
{
   return mActive.load(std::memory_order_relaxed);
}

void RealtimeEffectList::SetActive(bool value)
{
   (LockGuard{ mLock }, mActive.store(value, std::memory_order_relaxed));
}

static UndoRedoExtensionRegistry::Entry sEntry {
   [](AudacityProject &project) -> std::shared_ptr<UndoStateExtension> {
      return RealtimeEffectList::Get(project).shared_from_this();
   }
};
