/*
 *  Copyright (c) 2014-present, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

#include "ComponentViewManager.h"

#import <objc/runtime.h>
#import <unordered_map>

#import <RenderCore/RCAssert.h>
#import <RenderCore/RCAssociatedObject.h>
#import <RenderCore/CKGlobalConfig.h>
#import <RenderCore/CKMutex.h>
#import <RenderCore/ComponentViewReuseUtilities.h>

#import "CKMountedObjectForView.h"

using namespace CK::Component;

namespace CK {
  namespace Component {
    struct PersistentAttributeShapeKey {
      /** Identifiers in sorted order. For the small sizes we use, this is faster than set or unordered_set. */
      std::vector<std::string> identifiers;
      /** Cumulative hash of all identifiers */
      size_t hash;

      /** Initialize the hash field to 0; remember that C++ doesn't initialize POD fields by default. */
      PersistentAttributeShapeKey() : hash(0) {};

      void addString(const std::string &s)
      {
        identifiers.insert(std::lower_bound(identifiers.begin(), identifiers.end(), s), s);
        // XOR'ing hashes is often a bad idea. Two of the reasons why are that two identical values will cancel each
        // other out, and that ordering information is lost (a ^ b == b ^ a). Since we don't expect identical values and
        // ordering doesn't matter (indeed *must not* matter), go ahead and use XOR here.
        hash ^= std::hash<std::string>()(s);
      }

      bool operator==(const CK::Component::PersistentAttributeShapeKey &k) const
      {
        return identifiers == k.identifiers;
      }
    };

    struct ActionDisabler {
      ActionDisabler() : _originalValue([CATransaction disableActions]) { [CATransaction setDisableActions:YES]; }
      ~ActionDisabler() { [CATransaction setDisableActions:_originalValue]; }
    private:
      BOOL _originalValue;
    };
  }
}

namespace std {
  template <> struct hash<const CK::Component::PersistentAttributeShapeKey>
  {
    size_t operator()(const CK::Component::PersistentAttributeShapeKey &k) const { return k.hash; }
  };
}

int32_t PersistentAttributeShape::computeIdentifier(const CKViewComponentAttributeValueMap &attributes) noexcept
{
  CK::Component::PersistentAttributeShapeKey key;
  for (const auto &it : attributes) {
    if (it.first.unapplicator == nil) {
      key.addString(it.first.identifier);
    }
  }

  static CK::StaticMutex lock = CK_MUTEX_INITIALIZER; // protects identifierMap
  CK::StaticMutexLocker l(lock);
  static auto *identifierMap = new std::unordered_map<const CK::Component::PersistentAttributeShapeKey, const int32_t>();

  static int32_t nextIdentifier = 0;
  const auto it = identifierMap->find(key);
  if (it == identifierMap->end()) {
    // We don't need fancy atomic here because we're already under the StaticMutex (for identifierMap).
    int32_t identifier = nextIdentifier++;
    identifierMap->emplace(std::move(key), identifier);
    return identifier;
  } else {
    return it->second;
  }
}

@interface CKOptimisticViewMutationTokenWrapper : NSObject
{
@public
  int _serial;
  __weak id _view;
}
@end

@interface CKComponentAttributeSetWrapper : NSObject
{
@public
  int _tokenSerial;
  bool _suppressApply;
  int _tokenSerialBaseline;
  std::shared_ptr<const CKViewComponentAttributeValueMap> _attributes;
  std::vector<CK::Component::OptimisticViewMutationInfo> _optimisticViewMutations;
  std::vector<CKOptimisticViewMutationTeardown> _optimisticViewMutationTeardowns_Old;
}
@end

@interface CKComponentViewReusePoolMapWrapper : NSObject {
@public
  ViewReusePoolMap _viewReusePoolMap;
}
@end

UIView *ViewReusePool::viewForClass(const CKComponentViewClass &viewClass, UIView *container, CK::Component::MountAnalyticsContext *mountAnalyticsContext) noexcept
{
  if (position == pool.end()) {
    UIView *v = viewClass.createView();
    RCCAssertNotNil(v, @"Expected non-nil view to be created for view class %s", viewClass.getIdentifier().description().c_str());
    [container addSubview:v];
    pool.push_back(v);
    position = pool.end();
    ViewReuseUtilities::createdView(v, viewClass, container);
    if (auto mac = mountAnalyticsContext) {
      mac->viewAllocations++;
    }
    return v;
  } else {
    if (auto mac = mountAnalyticsContext) {
      mac->viewReuses++;
    }
    return *position++;
  }
}

void ViewReusePool::reset(CK::Component::MountAnalyticsContext *mountAnalyticsContext) noexcept
{
  for (auto it = pool.begin(); it != position; ++it) {
    ViewReuseUtilities::willUnhide(*it, mountAnalyticsContext);
    [*it setHidden:NO];
  }
  for (auto it = position; it != pool.end(); ++it) {
    [*it setHidden:YES];
    CK::Component::AttributeApplicator::resetOptimisticViewMutations(*it);
    ViewReuseUtilities::didHide(*it, mountAnalyticsContext);
  }
  position = pool.begin();
}

const char kComponentViewReusePoolMapAssociatedObjectKey = ' ';

void ViewReusePool::hideAll(UIView *view, MountAnalyticsContext *mountAnalyticsContext) noexcept
{
  CKComponentViewReusePoolMapWrapper *wrapper = RCGetAssociatedObject_MainThreadAffined(view, &kComponentViewReusePoolMapAssociatedObjectKey);
  if (!wrapper) {
    return;
  }
  const auto hide = [&](ViewReusePool &viewReusePool) {
    viewReusePool.position = viewReusePool.pool.begin();
    viewReusePool.reset(mountAnalyticsContext);
  };
  auto &viewReusePoolMap = wrapper->_viewReusePoolMap;

  for (auto &it : viewReusePoolMap.dictionary) {
    hide(it.second);
  }
}

ViewReusePoolMap::ViewReusePoolMap() {}

ViewReusePoolMap &ViewReusePoolMap::viewReusePoolMapForView(UIView *v) noexcept
{
  CKComponentViewReusePoolMapWrapper *wrapper = RCGetAssociatedObject_MainThreadAffined(v, &kComponentViewReusePoolMapAssociatedObjectKey);
  if (!wrapper) {
    wrapper = [[CKComponentViewReusePoolMapWrapper alloc] init];
    RCSetAssociatedObject_MainThreadAffined(v, &kComponentViewReusePoolMapAssociatedObjectKey, wrapper);
  }
  return wrapper->_viewReusePoolMap;
}

void ViewReusePoolMap::reset(UIView *container, CK::Component::MountAnalyticsContext *mountAnalyticsContext) noexcept
{
  for (auto &it : dictionary) {
    it.second.reset(mountAnalyticsContext);
  }

  // Now we need to ensure that the ordering of container.subviews matches vendedViews.
  NSMutableArray *subviews = [[container subviews] mutableCopy];
  std::vector<UIView *>::const_iterator nextVendedViewIt = vendedViews.cbegin();

  // Can't use NSFastEnumeration since we mutate subviews during enumeration.
  for (NSUInteger i = 0; i < [subviews count]; i++) {
    UIView *subview = subviews[i];

    // We use linear search here. We could create a std::unordered_set of vended views, but given the typical size of
    // the list of vended views, I guessed a linear search would probably be faster considering constant factors.
    const auto &vendedViewIt = std::find(nextVendedViewIt, vendedViews.cend(), subview);

    if (vendedViewIt == vendedViews.cend()) {
      // Ignore subviews not created by components infra, or that were not vended during this pass (they are hidden).
      continue;
    }

    if (vendedViewIt != nextVendedViewIt) {
      NSUInteger swapIndex = [subviews indexOfObjectIdenticalTo:*nextVendedViewIt];
      // This check can cause some z-ordering issue if views vended by the framework are manipulated outside of the framework,
      if (swapIndex != NSNotFound) {
        // This naive algorithm does not do the minimal number of swaps. But it's simple, and swaps should be relatively
        // rare in any case, so let's go with it.
        [subviews exchangeObjectAtIndex:i withObjectAtIndex:swapIndex];
        [container exchangeSubviewAtIndex:i withSubviewAtIndex:swapIndex];
      }
      RCCAssertWithCategory(swapIndex != NSNotFound,
                            [CKMountedObjectForView(*nextVendedViewIt) class],
                            @"Expected to find subview %@ (mounted object: %@) in %@ (mounted object: %@)",
                            [*nextVendedViewIt class],
                            [CKMountedObjectForView(*nextVendedViewIt) class],
                            [container class],
                            [CKMountedObjectForView(container) class]);
    }

    ++nextVendedViewIt;
  }

  vendedViews.clear();
}

static char kPersistentAttributesViewKey = ' ';

static CKComponentAttributeSetWrapper *attributeSetWrapperForView(UIView *view)
{
  CKComponentAttributeSetWrapper *wrapper = RCGetAssociatedObject_MainThreadAffined(view, &kPersistentAttributesViewKey);
  if (wrapper == nil) {
    wrapper = [[CKComponentAttributeSetWrapper alloc] init];
    RCSetAssociatedObject_MainThreadAffined(view,
                                            &kPersistentAttributesViewKey,
                                            wrapper);
  }
  return wrapper;
}

void AttributeApplicator::applyAttributes(UIView *view, std::shared_ptr<const CKViewComponentAttributeValueMap> attributes) noexcept
{
  CK::Component::ActionDisabler actionDisabler; // We never want implicit animations when applying attributes

  // Avoid the static destructor fiasco, use a pointer:
  static const auto *empty = new CKViewComponentAttributeValueMap();

  CKComponentAttributeSetWrapper *const wrapper = attributeSetWrapperForView(view);

  const bool useNewStyleOptimisticMutations = CKReadGlobalConfig().useNewStyleOptimisticMutations;
  const bool hasNewStyleOptimisticViewMutations = useNewStyleOptimisticMutations && !wrapper->_optimisticViewMutations.empty();

  if (!useNewStyleOptimisticMutations) {
    // Reset optimistic mutations so that applicators see they see the state they expect.
    if (!wrapper->_optimisticViewMutationTeardowns_Old.empty()) {
      const auto copiedTeardowns = wrapper->_optimisticViewMutationTeardowns_Old;
      wrapper->_optimisticViewMutationTeardowns_Old.clear();
      for (CKOptimisticViewMutationTeardown teardown : copiedTeardowns) {
        if (teardown) {
          teardown(view);
        }
      }
    }
  } else {
    for (auto it = wrapper->_optimisticViewMutations.rbegin(); it != wrapper->_optimisticViewMutations.rend(); ++it) {
      if (it->undo) {
        it->undo(view);
      }
    }
  }

  const CKViewComponentAttributeValueMap &oldAttributes = wrapper->_attributes ? *wrapper->_attributes : *empty;
  const CKViewComponentAttributeValueMap &newAttributes = *attributes;

  // First, tear down any attributes that appear in the *old* set but not the new set, and *do* have an unapplicator.
  for (const auto &oldAttr : oldAttributes) {
    if (oldAttr.first.unapplicator) {
      const auto &newAttr = newAttributes.find(oldAttr.first);
      if (newAttr == newAttributes.end()) {
        // There is no new attribute, so we always must call "unapplicator".
        oldAttr.first.unapplicator(view, oldAttr.second);
      } else if (!RCObjectIsEqual(newAttr->second, oldAttr.second)) {
        // If the attribute has an updater, don't call the unapplicator; instead, the updater will be called below.
        if (newAttr->first.updater == nil) {
          oldAttr.first.unapplicator(view, oldAttr.second);
        }
      }
    }
  }

  // Now apply the applicators for all attributes in the *new* set, except those that haven't changed in value.
  for (const auto &newAttr : newAttributes) {
    const auto &oldAttr = oldAttributes.find(newAttr.first);
    if (oldAttr == oldAttributes.end()) {
      // There is no old attribute, so we always must call "applicator".
      newAttr.first.applicator(view, newAttr.second);
    } else if (!RCObjectIsEqual(oldAttr->second, newAttr.second)) {
      // If the attribute has an "updater", call that. Otherwise, call the applicator.
      if (newAttr.first.updater) {
        newAttr.first.updater(view, oldAttr->second, newAttr.second);
      } else {
        newAttr.first.applicator(view, newAttr.second);
      }
    }
  }

  if (hasNewStyleOptimisticViewMutations) {
    auto copiedOptimisticMutations = wrapper->_optimisticViewMutations;

    wrapper->_suppressApply = true;

    for (auto optimisticMutation : copiedOptimisticMutations) {
      optimisticMutation.load(view);
    }

    wrapper->_suppressApply = false;

    if (copiedOptimisticMutations.size() != wrapper->_optimisticViewMutations.size()) {
      copiedOptimisticMutations = wrapper->_optimisticViewMutations;
    }

    for (auto optimisticMutation : copiedOptimisticMutations) {
      optimisticMutation.apply(view);
    }
  }

  // Update the wrapper to reference the new attributes. Don't do this before now since it changes oldAttributes.
  wrapper->_attributes = std::move(attributes);
}

void AttributeApplicator::addOptimisticViewMutationTeardown_Old(UIView *view, CKOptimisticViewMutationTeardown teardown) noexcept
{
  // We must tear down the mutations in the *reverse* order in which they were applied,
  // or we could end up restoring the wrong value.
  CKComponentAttributeSetWrapper *const wrapper = attributeSetWrapperForView(view);
  wrapper->_optimisticViewMutationTeardowns_Old.insert(wrapper->_optimisticViewMutationTeardowns_Old.begin(), teardown);
}

CKOptimisticMutationToken AttributeApplicator::addOptimisticViewMutation(UIView *view, CKOptimisticViewMutationOperation undo, CKOptimisticViewMutationOperation apply, CKOptimisticViewMutationOperation load) noexcept
{
  CKComponentAttributeSetWrapper *const wrapper = attributeSetWrapperForView(view);

  for (auto it = wrapper->_optimisticViewMutations.rbegin(); it != wrapper->_optimisticViewMutations.rend(); ++it) {
    if (it->undo) {
      it->undo(view);
    }
  }

  load(view);

  auto token = [[CKOptimisticViewMutationTokenWrapper alloc] init];
  token->_serial = wrapper->_tokenSerial++;
  token->_view = view;

  wrapper->_optimisticViewMutations.push_back({ .serial = token->_serial, .undo = undo, .apply = apply, .load = load });

  for (auto optimisticMutation : wrapper->_optimisticViewMutations) {
    optimisticMutation.apply(view);
  }

  return token;
}

void AttributeApplicator::removeOptimisticViewMutation(CKOptimisticMutationToken token) noexcept
{
  if (token == CKOptimisticMutationTokenNull) {
    return;
  }

  UIView *view = ((CKOptimisticViewMutationTokenWrapper *)token)->_view;

  if (view == nullptr) {
    return;
  }

  CKComponentAttributeSetWrapper *const wrapper = attributeSetWrapperForView(view);

  if (wrapper->_tokenSerialBaseline > ((CKOptimisticViewMutationTokenWrapper *)token)->_serial) {
    // Token no longer valid or relevant because the associated view recycled
    return;
  }

  for (auto it = wrapper->_optimisticViewMutations.begin(); it != wrapper->_optimisticViewMutations.end(); ++it) {
    if (it->serial == ((CKOptimisticViewMutationTokenWrapper *)token)->_serial) {
      if (wrapper->_optimisticViewMutations.size() == 1) {
        if (it->undo) {
          it->undo(view);
        }
      }
      wrapper->_optimisticViewMutations.erase(it);
      break;
    }
  }

  if (!wrapper->_suppressApply) {
    for (auto optimisticMutation : wrapper->_optimisticViewMutations) {
      optimisticMutation.apply(view);
    }
  }
}

void AttributeApplicator::resetOptimisticViewMutations(UIView *view) noexcept
{
  CKComponentAttributeSetWrapper *const wrapper = attributeSetWrapperForView(view);

  if (wrapper->_tokenSerial > 0) {
    for (auto it = wrapper->_optimisticViewMutations .rbegin(); it != wrapper->_optimisticViewMutations.rend(); ++it) {
      if (it->undo) {
        it->undo(view);
      }
    }
    wrapper->_optimisticViewMutations.clear();
    wrapper->_tokenSerial++;
    wrapper->_tokenSerialBaseline = wrapper->_tokenSerial;
  }
}

@implementation CKComponentAttributeSetWrapper
@end

@implementation CKComponentViewReusePoolMapWrapper
@end


@implementation CKOptimisticViewMutationTokenWrapper
@end
