/*
*  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.
*
*/

#import "CKComponentLayout.h"

#import <ComponentKit/CKAnalyticsListener.h>
#import <ComponentKit/CKComponentAnimationPredicates.h>
#import <ComponentKit/CKComponentInternal.h>
#import <ComponentKit/CKComponentSubclass.h>
#import <ComponentKit/CKTreeVerificationHelpers.h>
#import <ComponentKit/ComponentLayoutContext.h>
#import <ComponentKit/CKComponentScopeRoot.h>

#import <pthread.h>

NSSet<id<CKMountable>> *CKMountComponentLayout(const RCLayout &layout,
                                               UIView *view,
                                               NSSet<id<CKMountable>> *previouslyMountedComponents,
                                               id<CKMountable> supercomponent,
                                               id<CKAnalyticsListener> analyticsListener)
{
  ((CKComponent *)layout.component).rootComponentMountedView = view;
  [analyticsListener willMountComponentTreeWithRootComponent:layout.component];

  CK::Component::MountAnalyticsContext mountAnalyticsContext;
  const BOOL collectMountAnalytics =
  [analyticsListener shouldCollectMountInformationForRootComponent:layout.component];

  NSSet<id<CKMountable>> *const mountedComponents =
  CKMountLayout(layout,
                view,
                previouslyMountedComponents,
                supercomponent,
                collectMountAnalytics ? &mountAnalyticsContext : nullptr,
                analyticsListener.systraceListener);
  [analyticsListener
   didMountComponentTreeWithRootComponent:layout.component
   mountAnalyticsContext:
   collectMountAnalytics
   ? CK::Optional<CK::Component::MountAnalyticsContext> {mountAnalyticsContext}
   : CK::none];
  return mountedComponents;
}

static auto buildComponentsByPredicateMap(const RCLayout &layout,
                                          const std::unordered_set<CKMountablePredicate> &predicates)
{
  auto componentsByPredicate = CKComponentRootLayout::ComponentsByPredicateMap {};
  if (predicates.empty()) {
    return componentsByPredicate;
  }
  layout.enumerateLayouts([&](const auto &l){
    if (l.component == nil) { return; }
    for (const auto &p : predicates) {
      if (p(l.component)) {
        componentsByPredicate[p].push_back(l.component);
      }
    }
  });
  return componentsByPredicate;
}

CKComponentRootLayout CKComputeRootComponentLayout(id<CKMountable> rootComponent,
                                                   const CKSizeRange &sizeRange,
                                                   id<CKAnalyticsListener> analyticsListener,
                                                   CK::Optional<CKBuildTrigger> buildTrigger,
                                                   CKComponentScopeRoot *scopeRoot,
                                                   std::shared_ptr<RCLayoutCache> layoutCache)
{
  [analyticsListener willLayoutComponentTreeWithRootComponent:rootComponent buildTrigger:buildTrigger];
  CK::Component::LayoutSystraceContext systraceContext([analyticsListener systraceListener]);

  RCLayoutResult layoutResult;
  if (layoutCache) {
    layoutResult = RCComputeRootLayout(rootComponent, sizeRange, layoutCache);
  } else {
    layoutResult = {CKComputeComponentLayout(rootComponent, sizeRange, sizeRange.max), nil};
  }

  auto layoutLookup = CKComponentRootLayout::ComponentLayoutCache {};
  layoutResult.layout.enumerateLayouts([&](const auto &l){
    if ([l.component isKindOfClass:[CKComponent class]] && ((CKComponent *)l.component).controller) {
      layoutLookup[l.component] = l;
    }
  });
  const auto componentsByPredicate = buildComponentsByPredicateMap(layoutResult.layout, CKComponentAnimationPredicates());
  const auto rootLayout = CKComponentRootLayout {
    layoutResult,
    layoutLookup,
    componentsByPredicate,
  };

  CKDetectDuplicateComponent(rootLayout.layout());
  CKVerifyTreeNodesToParentLinks(scopeRoot, rootLayout.layout());
  [analyticsListener didLayoutComponentTreeWithRootComponent:rootComponent];
  return rootLayout;
}

RCLayout CKComputeComponentLayout(id<CKMountable> component,
                                           const CKSizeRange &sizeRange,
                                           const CGSize parentSize)
{
  return component ? [component layoutThatFits:sizeRange parentSize:parentSize] : (RCLayout){};
}

void RCLayout::enumerateLayouts(const std::function<void(const RCLayout &)> &f) const
{
  f(*this);

  if (children == nil) { return; }
  for (const auto &child : *children) {
    child.layout.enumerateLayouts(f);
  }
}

void CKComponentRootLayout::enumerateCachedLayout(void(^ _Nonnull block)(const RCLayout &layout)) const
{
  for (const auto &it : _layoutCache) {
    block(it.second);
  }
}
