text: // Copyright (c) Microsoft Corporation.
  // Licensed under the MIT License.
  
  using namespace std;
  #include <cxxreact/JsArgumentHelpers.h>
  #include <cxxreact/MessageQueueThread.h>
  #include <folly/json.h>
  #include <glog/logging.h>
  #include <algorithm>
  #include <iostream>
  
  #include <Threading/MessageDispatchQueue.h>
  #include <unicode.h>
  #include "ShadowNode.h"
  #include "ShadowNodeRegistry.h"
  #include "UIManagerModule.h"
  
  #include <IReactRootView.h>
  #include <cdebug.h>
  #include <winrt/base.h>
  
  using namespace facebook::xplat;
  
  namespace facebook {
  namespace react {
  
  UIManager::UIManager(std::vector<std::unique_ptr<IViewManager>> &&viewManagers, INativeUIManager *nativeManager)
      : m_viewManagers(std::move(viewManagers)), m_nativeUIManager(nativeManager) {
    m_nativeUIManager->setHost(this);
  }
  
  UIManager::~UIManager() noexcept {
    m_nodeRegistry.removeAllRootViews([this](int64_t rootViewTag) { removeRootView(rootViewTag); });
    m_nodeRegistry.ForAllNodes([this](int64_t tag, shadow_ptr const &shadowNode) noexcept {
      DropView(tag, false, true);
    });
    m_nativeUIManager->setHost(nullptr);
    m_nativeUIManager->destroy();
  }
  
  folly::dynamic UIManager::getConstantsForViewManager(const std::string &className) {
    const IViewManager *vm = GetViewManager(className);
    if (vm != nullptr)
      return vm->GetConstants();
    return nullptr;
  }
  
  void UIManager::populateViewManagerConstants(std::map<std::string, folly::dynamic> &constants) {
    for (auto &&vm : m_viewManagers)
      constants.emplace(vm->GetName(), vm->GetConstants());
  }
  
  IViewManager *UIManager::GetViewManager(const std::string &className) const {
    for (auto &&vm : m_viewManagers) {
      if (!strcmp(vm->GetName(), className.c_str()))
        return vm.get();
    }
    return nullptr;
  }
  
  void UIManager::RegisterRootView(IReactRootView *rootView, int64_t rootViewTag, int64_t width, int64_t height) {
    static std::string rootClassName = "ROOT";
    auto viewManager = GetViewManager(rootClassName);
  
    auto root = m_nativeUIManager->createRootShadowNode(rootView);
    root->m_className = rootClassName;
    root->m_viewManager = viewManager;
    root->m_tag = rootViewTag;
    m_nodeRegistry.addRootView(shadow_ptr(root), rootViewTag);
  
    m_nativeUIManager->AddRootView(*root, rootView);
  }
  
  void UIManager::removeRootView(int64_t rootViewTag) {
    m_nativeUIManager->ensureInBatch();
    auto &node = m_nodeRegistry.getRoot(rootViewTag);
    m_nativeUIManager->removeRootView(node);
    DropView(rootViewTag, true);
    m_nodeRegistry.removeRootView(rootViewTag);
    m_nativeUIManager->destroyRootShadowNode(&node);
  }
  
  void UIManager::zombieView(int64_t tag) {
    DropView(tag, false, true);
  }
  
  void UIManager::DropView(int64_t tag, bool removeChildren /*= true*/, bool zombieView /* = false */) {
    auto &node = m_nodeRegistry.getNode(tag);
  
    node.onDropViewInstance();
  
    m_nativeUIManager->RemoveView(node, removeChildren);
  
    if (zombieView)
      node.m_zombie = true;
  
    for (auto childTag : node.m_children)
      DropView(childTag, removeChildren, zombieView);
  
    if (removeChildren)
      node.removeAllChildren();
  
    if (!zombieView)
      m_nodeRegistry.removeNode(tag);
  }
  
  void UIManager::removeSubviewsFromContainerWithID(int64_t containerTag) {
    m_nativeUIManager->ensureInBatch();
    auto &containerNode = m_nodeRegistry.getNode(containerTag);
  
    std::vector<int64_t> indicesToRemove(containerNode.m_children.size());
    for (size_t i = 0; i < containerNode.m_children.size(); i++)
      indicesToRemove[static_cast<size_t>(i)] = static_cast<int64_t>(i);
    std::vector<int64_t> emptyVec;
    manageChildren(containerTag, emptyVec, emptyVec, emptyVec, emptyVec, indicesToRemove);
  }
  
  std::vector<int64_t> PopulateIntVecFromDynamicArray(const folly::dynamic &arr) {
    std::vector<int64_t> vec(arr.empty() ? 0 : arr.size());
  
    if (arr.empty())
      return vec;
  
    auto i = 0;
    for (auto &val : arr) {
      vec[i] = static_cast<int64_t>(val.asDouble());
      ++i;
    }
    return vec;
  }
  
  void UIManager::manageChildren(
      int64_t viewTag,
      folly::dynamic &moveFrom,
      folly::dynamic &moveTo,
      folly::dynamic &addChildTags,
      folly::dynamic &addAtIndices,
      folly::dynamic &removeFrom) {
    auto vecMoveFrom = PopulateIntVecFromDynamicArray(moveFrom);
    auto vecMoveTo = PopulateIntVecFromDynamicArray(moveTo);
    auto vecAddChildTags = PopulateIntVecFromDynamicArray(addChildTags);
    auto vecAddAtIndices = PopulateIntVecFromDynamicArray(addAtIndices);
    auto vecRemoveFrom = PopulateIntVecFromDynamicArray(removeFrom);
    manageChildren(viewTag, vecMoveFrom, vecMoveTo, vecAddChildTags, vecAddAtIndices, vecRemoveFrom);
  }
  
  struct ViewAtIndex {
    int64_t tag;
    int64_t index;
    static bool ViewAtIndexCompare(
        const std::shared_ptr<ViewAtIndex> &x,
        const std::shared_ptr<ViewAtIndex> &y) noexcept {
      return (x->index < y->index);
    }
  };
  
  void UIManager::manageChildren(
      int64_t viewTag,
      std::vector<int64_t> &moveFrom,
      std::vector<int64_t> &moveTo,
      std::vector<int64_t> &addChildTags,
      std::vector<int64_t> &addAtIndices,
      std::vector<int64_t> &removeFrom) {
    m_nativeUIManager->ensureInBatch();
    auto &shadowNodeToManage = m_nodeRegistry.getNode(viewTag);
  
    auto numToMove = moveFrom.size();
    auto numToAdd = addChildTags.size();
    auto numToRemove = removeFrom.size();
    std::vector<shared_ptr<ViewAtIndex>> viewsToAdd(numToMove + numToAdd);
    std::vector<shared_ptr<ViewAtIndex>> viewsToRemove(numToMove + numToRemove);
    std::vector<int64_t> tagsToDelete(numToRemove);
  
    if (numToMove > 0) {
      for (size_t i = 0; i < numToMove; ++i) {
        auto moveFromIndex = moveFrom[i];
        auto tagToMove =
            m_nodeRegistry.getNode(static_cast<size_t>(shadowNodeToManage.m_children[static_cast<size_t>(moveFromIndex)]))
                .m_tag;
        viewsToAdd[i] = make_shared<ViewAtIndex>(ViewAtIndex{tagToMove, moveTo[i]});
        viewsToRemove[i] = make_shared<ViewAtIndex>(ViewAtIndex{tagToMove, moveFromIndex});
      }
    }
  
    if (numToAdd > 0) {
      for (size_t i = 0; i < numToAdd; ++i) {
        viewsToAdd[numToMove + i] = make_shared<ViewAtIndex>(ViewAtIndex{addChildTags[i], addAtIndices[i]});
      }
    }
  
    if (numToRemove > 0) {
      for (size_t i = 0; i < numToRemove; ++i) {
        auto indexToRemove = removeFrom[i];
        auto tagToRemove =
            m_nodeRegistry.getNode(static_cast<size_t>(shadowNodeToManage.m_children[static_cast<size_t>(indexToRemove)]))
                .m_tag;
        viewsToRemove[i] = make_shared<ViewAtIndex>(ViewAtIndex{tagToRemove, indexToRemove});
        tagsToDelete[i] = tagToRemove;
      }
    }
  
    // NB: moveFrom and removeForm are both relative to the starting
    // state of the view's children.
    //
    // 1) Sort the views to add and indices to remove by index
    // 2) Iterate the indices being removed from high to low and remove
    //    them. Going high to low makes sure we remove the correct
    //    index when there are multiple to remove.
    // 3) Iterate the views being added by index low to high and add
    //    them. Like the view removal, iteration direction is important
    //    to preserve the correct index.
  
    std::sort(viewsToAdd.begin(), viewsToAdd.end(), ViewAtIndex::ViewAtIndexCompare);
    std::sort(viewsToRemove.begin(), viewsToRemove.end(), ViewAtIndex::ViewAtIndexCompare);
  
    // Apply changes to the ReactShadowNode hierarchy.
    for (auto i = static_cast<int>(viewsToRemove.size()) - 1; i >= 0; --i) {
      auto viewAtIndex = viewsToRemove[i];
      auto &shadowNodeToRemove = m_nodeRegistry.getNode(viewAtIndex->tag);
  
      shadowNodeToManage.m_children.erase(
          shadowNodeToManage.m_children.begin() + static_cast<size_t>(viewAtIndex->index));
      shadowNodeToManage.RemoveChildAt(viewAtIndex->index);
    }
  
    for (size_t i = 0; i < viewsToAdd.size(); ++i) {
      auto viewAtIndex = viewsToAdd[i];
      auto &shadowNodeToAdd = m_nodeRegistry.getNode(viewAtIndex->tag);
      shadowNodeToAdd.m_parent = shadowNodeToManage.m_tag;
      shadowNodeToManage.m_children.insert(
          shadowNodeToManage.m_children.begin() + static_cast<size_t>(viewAtIndex->index), shadowNodeToAdd.m_tag);
      if (!shadowNodeToManage.m_zombie)
        shadowNodeToManage.AddView(shadowNodeToAdd, viewAtIndex->index);
  
      m_nativeUIManager->AddView(shadowNodeToManage, shadowNodeToAdd, viewAtIndex->index);
    }
  
    for (auto tagToDelete : tagsToDelete)
      DropView(tagToDelete);
  }
  
  void UIManager::configureNextLayoutAnimation(
      folly::dynamic &&config,
      facebook::xplat::module::CxxModule::Callback success,
      facebook::xplat::module::CxxModule::Callback error) {
    m_nativeUIManager->configureNextLayoutAnimation(std::move(config), success, error);
  }
  
  struct hresult_exception : public std::exception {
    hresult_exception(winrt::hresult_error error)
        : std::exception(Microsoft::Common::Unicode::Utf16ToUtf8(error.message()).c_str()), m_error(error) {}
  
    winrt::hresult_error m_error{};
  };
  
  void UIManager::createView(
      int64_t tag,
      std::string &&className,
      int64_t /*rootViewTag*/,
      folly::dynamic && /*ReadableMap*/ props) {
    try {
      m_nativeUIManager->ensureInBatch();
      auto viewManager = GetViewManager(className);
      auto node = viewManager->createShadow();
      node->m_className = std::move(className);
      node->m_tag = tag;
      node->m_viewManager = viewManager;
  
      node->createView();
      m_nativeUIManager->CreateView(*node, props);
  
      m_nodeRegistry.addNode(shadow_ptr(node), tag);
  
      if (!props.isNull())
        node->updateProperties(std::move(props));
    } catch (winrt::hresult_error &hr) {
      throw hresult_exception(hr);
    }
  }
  
  void UIManager::setChildren(int64_t viewTag, folly::dynamic &&childrenTags) {
    m_nativeUIManager->ensureInBatch();
    auto &parent = m_nodeRegistry.getNode(viewTag);
    int64_t index = 0;
    for (auto &&childTag : childrenTags) {
      auto tag = static_cast<int>(childTag.asDouble());
      auto &childNode = m_nodeRegistry.getNode(tag);
      childNode.m_parent = parent.m_tag;
      parent.m_children.push_back(tag);
      try {
        if (!parent.m_zombie)
          parent.AddView(childNode, index);
  
        m_nativeUIManager->AddView(parent, childNode, index);
      } catch (const winrt::hresult_error &e) {
        if (e.code() == E_NOINTERFACE) {
          DEBUG_HRESULT_ERROR(e);
        } else {
          throw;
        }
      }
      ++index;
    }
  }
  
  void UIManager::updateView(int64_t tag, const std::string &className, folly::dynamic &&props) {
    m_nativeUIManager->ensureInBatch();
    ShadowNode *pShadowNode = FindShadowNodeForTag(tag);
    // Guard against setNative calls to removed views
    if (pShadowNode == nullptr)
      return;
  
    if (!pShadowNode->m_zombie)
      pShadowNode->updateProperties(std::move(props));
  
    m_nativeUIManager->UpdateView(*pShadowNode, props);
  }
  
  void UIManager::RemoveShadowNode(ShadowNode &nodeToRemove) {
    nodeToRemove.m_parent = -1;
    m_nodeRegistry.removeNode(nodeToRemove.m_tag);
    for (auto i = static_cast<int>(nodeToRemove.m_children.size()) - 1; i >= 0; --i) {
      RemoveShadowNode(m_nodeRegistry.getNode(nodeToRemove.m_children[i]));
    }
  
    nodeToRemove.m_children.clear();
  }
  
  void UIManager::replaceExistingNonRootView(int64_t oldTag, int64_t newTag) {
    m_nativeUIManager->ensureInBatch();
    std::vector<int64_t> indicesToRemove(1);
    std::vector<int64_t> indicesToAdd(1);
    std::vector<int64_t> tagToAdd(1);
    tagToAdd[0] = newTag;
  
    CHECK(m_nodeRegistry.getNode(oldTag).m_parent != -1) << "oldTag must have a parent";
    auto &parent = m_nodeRegistry.getNode(m_nodeRegistry.getNode(oldTag).m_parent);
    auto it = find(parent.m_children.begin(), parent.m_children.end(), oldTag);
    CHECK(it != parent.m_children.end());
    indicesToAdd[0] = indicesToRemove[0] = it - parent.m_children.begin();
  
    std::vector<int64_t> emptyVec;
    manageChildren(parent.m_tag, emptyVec, emptyVec, tagToAdd, indicesToAdd, indicesToRemove);
  }
  
  void UIManager::dispatchViewManagerCommand(
      int64_t reactTag,
      const std::string &commandId,
      folly::dynamic &&commandArgs) {
    m_nativeUIManager->ensureInBatch();
    auto &node = m_nodeRegistry.getNode(reactTag);
    if (!node.m_zombie)
      node.dispatchCommand(commandId, std::move(commandArgs));
  }
  
  void UIManager::measure(int64_t reactTag, facebook::xplat::module::CxxModule::Callback callback) {
    auto &node = m_nodeRegistry.getNode(reactTag);
    int64_t rootTag = reactTag;
    while (true) {
      auto &currNode = m_nodeRegistry.getNode(rootTag);
      if (currNode.m_parent == -1)
        break;
      rootTag = currNode.m_parent;
    }
    auto &rootNode = m_nodeRegistry.getNode(rootTag);
  
    m_nativeUIManager->measure(node, rootNode, callback);
  }
  
  void UIManager::measureInWindow(int64_t reactTag, facebook::xplat::module::CxxModule::Callback callback) {
    auto &node = m_nodeRegistry.getNode(reactTag);
    m_nativeUIManager->measureInWindow(node, callback);
  }
  
  void UIManager::measureLayout(
      int64_t reactTag,
      int64_t ancestorReactTag,
      facebook::xplat::module::CxxModule::Callback errorCallback,
      facebook::xplat::module::CxxModule::Callback callback) {
    auto &node = m_nodeRegistry.getNode(reactTag);
    auto &ancestorNode = m_nodeRegistry.getNode(ancestorReactTag);
    m_nativeUIManager->measureLayout(node, ancestorNode, errorCallback, callback);
  };
  
  void UIManager::findSubviewIn(
      int64_t reactTag,
      folly::dynamic &&coordinates,
      facebook::xplat::module::CxxModule::Callback callback) {
    auto &node = m_nodeRegistry.getNode(reactTag);
    float x = static_cast<float>(jsArgAsDouble(coordinates, 0));
    float y = static_cast<float>(jsArgAsDouble(coordinates, 1));
    m_nativeUIManager->findSubviewIn(node, x, y, callback);
  }
  
  void UIManager::onBatchComplete() {
    m_nativeUIManager->onBatchComplete();
  }
  
  void UIManager::focus(int64_t reactTag) {
    m_nativeUIManager->focus(reactTag);
  }
  
  void UIManager::blur(int64_t reactTag) {
    m_nativeUIManager->blur(reactTag);
  }
  
  int64_t UIManager::AddMeasuredRootView(IReactRootView *rootView) {
    auto tag = m_nextRootTag;
    m_nextRootTag += RootViewTagIncrement;
  
    int64_t width = rootView->GetActualWidth();
    int64_t height = rootView->GetActualHeight();
  
    RegisterRootView(rootView, tag, width, height);
  
    // TODO: call UpdateRootNodeSize when ReactRootView size changes
    /*var resizeCount = 0;
    rootView.SetOnSizeChangedListener((sender, args) =>
    {
    var currentCount = ++resizeCount;
    var newWidth = args.NewSize.Width;
    var newHeight = args.NewSize.Height;
  
    Context.RunOnNativeModulesQueueThread(() =>
    {
    if (currentCount == resizeCount)
    {
    Context.AssertOnNativeModulesQueueThread();
    _uiImplementation.UpdateRootNodeSize(tag, newWidth, newHeight,
    _eventDispatcher);
    }
    });
    });*/
  
    return tag;
  }
  
  std::unordered_set<int64_t> &UIManager::GetAllRootTags() {
    return m_nodeRegistry.getAllRoots();
  }
  
  ShadowNode *UIManager::FindShadowNodeForTag(int64_t tag) {
    return m_nodeRegistry.findNode(tag);
  }
  
  ShadowNode *UIManager::FindParentRootShadowNode(int64_t tag) {
    return m_nodeRegistry.getParentRootShadowNode(tag);
  }
  
  ShadowNode &UIManager::GetShadowNodeForTag(int64_t tag) {
    return m_nodeRegistry.getNode(tag);
  }
  
  UIManagerModule::UIManagerModule(
      std::shared_ptr<IUIManager> &&manager,
      std::shared_ptr<MessageQueueThread> &&uiQueue) noexcept
      : m_manager{std::move(manager)}, m_uiQueue{std::move(uiQueue)} {}
  
  UIManagerModule::~UIManagerModule() noexcept {
    if (m_uiQueue) {
      // To make sure that we destroy UI components in UI thread.
      // We cannot use the m_uiQueue->runOnQueue directly because
      // the UI MessageQueueThread is already stopped.
      std::static_pointer_cast<Mso::React::MessageDispatchQueue>(m_uiQueue)
          ->DispatchQueue()
          .Post([manager = std::move(m_manager)]() noexcept {});
    }
  }
  
  std::string UIManagerModule::getName() {
    return "UIManager";
  }
  
  std::map<std::string, folly::dynamic> UIManagerModule::getConstants() {
    std::map<std::string, folly::dynamic> constants{};
  
    m_manager->populateViewManagerConstants(constants);
  
    return constants;
  }
  
  std::vector<facebook::xplat::module::CxxModule::Method> UIManagerModule::getMethods() {
    std::shared_ptr<IUIManager> manager(m_manager);
    return {
        Method(
            "getConstantsForViewManager",
            [manager](folly::dynamic argsJson) -> folly::dynamic {
              folly::dynamic args;
              if (argsJson.isString()) {
                args = folly::parseJson(argsJson.asString());
              } else {
                // In JSI mode this is an array
                assert(argsJson.isArray());
                args = argsJson;
              }
              return manager->getConstantsForViewManager(jsArgAsString(args, 0));
            },
            SyncTag),
        Method("removeRootView", [manager](folly::dynamic args) { manager->removeRootView(jsArgAsInt(args, 0)); }),
        Method(
            "createView",
            [manager](folly::dynamic args) {
              manager->createView(
                  jsArgAsInt(args, 0), jsArgAsString(args, 1), jsArgAsInt(args, 2), std::move(jsArgAsDynamic(args, 3)));
            }),
        Method(
            "configureNextLayoutAnimation",
            [manager](folly::dynamic args, Callback cbSuccess, Callback cbError) {
              manager->configureNextLayoutAnimation(std::move(jsArgAsDynamic(args, 0)), cbSuccess, cbError);
            },
            AsyncTag),
        Method(
            "setChildren",
            [manager](folly::dynamic args) {
              manager->setChildren(jsArgAsInt(args, 0), std::move(jsArgAsArray(args, 1)));
            }),
        Method(
            "updateView",
            [manager](folly::dynamic args) {
              manager->updateView(jsArgAsInt(args, 0), jsArgAsString(args, 1), std::move(jsArgAsDynamic(args, 2)));
            }),
        Method(
            "removeSubviewsFromContainerWithID",
            [manager](folly::dynamic args) { manager->removeSubviewsFromContainerWithID(jsArgAsInt(args, 0)); }),
        Method(
            "manageChildren",
            [manager](folly::dynamic args) {
              manager->manageChildren(
                  jsArgAsInt(args, 0),
                  jsArgAsDynamic(args, 1),
                  jsArgAsDynamic(args, 2),
                  jsArgAsDynamic(args, 3),
                  jsArgAsDynamic(args, 4),
                  jsArgAsDynamic(args, 5));
            }),
        Method(
            "replaceExistingNonRootView",
            [manager](folly::dynamic args) {
              manager->replaceExistingNonRootView(jsArgAsInt(args, 0), jsArgAsInt(args, 1));
            }),
        Method(
            "dispatchViewManagerCommand",
            [manager](folly::dynamic args) {
              // 0.61 allows directly dispatching command names instead of querying the ViewManager for the command ID.
              // In stock React Native, integer commands are deprecated but not yet removed. RNW APIs only allow strings,
              // and provide command constants as their literal string.
              manager->dispatchViewManagerCommand(
                  jsArgAsInt(args, 0), jsArgAsString(args, 1), std::move(jsArgAsDynamic(args, 2)));
            }),
        Method("measure", [manager](folly::dynamic args, Callback cb) { manager->measure(jsArgAsInt(args, 0), cb); }),
        Method(
            "measureInWindow",
            [manager](folly::dynamic args, Callback cb) { manager->measureInWindow(jsArgAsInt(args, 0), cb); }),
        Method(
            "measureLayout",
            [manager](folly::dynamic args, Callback cbError, Callback cbSuccess) {
              manager->measureLayout(jsArgAsInt(args, 0), jsArgAsInt(args, 1), cbError, cbSuccess);
            },
            AsyncTag),
        Method(
            "findSubviewIn",
            [manager](folly::dynamic args, Callback cb) {
              manager->findSubviewIn(jsArgAsInt(args, 0), std::move(jsArgAsDynamic(args, 1)), cb);
            }),
        Method("focus", [manager](folly::dynamic args) { manager->focus(jsArgAsInt(args, 0)); }),
        Method("blur", [manager](folly::dynamic args) { manager->blur(jsArgAsInt(args, 0)); }),
        Method(
            "setJSResponder",
            [manager](folly::dynamic args) {
              // TODO: Implement?
              // manager->setJSResponder(jsArgAsInt(args, 0), jsArgAsBool(args,
              // 1));
            }),
        Method(
            "clearJSResponder",
            [this]() {
              // TODO: Implement?
              // manager->clearJSResponder();
            }),
    };
  }
  
  shared_ptr<IUIManager> createIUIManager(
      std::vector<std::unique_ptr<IViewManager>> &&viewManagers,
      INativeUIManager *nativeManager) {
    return std::make_shared<UIManager>(std::move(viewManagers), nativeManager);
  }
  
  std::unique_ptr<facebook::xplat::module::CxxModule> createUIManagerModule(
      std::shared_ptr<IUIManager> &&uimanager,
      std::shared_ptr<MessageQueueThread> &&uiQueue) noexcept {
    return std::make_unique<UIManagerModule>(std::move(uimanager), std::move(uiQueue));
  }
  
  // Deprecated
  std::unique_ptr<facebook::xplat::module::CxxModule> createUIManagerModule(std::shared_ptr<IUIManager> uimanager) {
    return createUIManagerModule(std::move(uimanager), nullptr);
  }
  
  } // namespace react
  } // namespace facebook
  