/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2017 wcm.io
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package io.wcm.sling.commons.caservice.impl;

import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.adapter.Adaptable;
import org.apache.sling.api.resource.Resource;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;

import io.wcm.sling.commons.caservice.ContextAwareService;
import io.wcm.sling.commons.caservice.ContextAwareServiceResolver;

/**
 * {@link ContextAwareServiceResolver} implementation.
 */
@Component(service = ContextAwareServiceResolver.class, immediate = true)
public class ContextAwareServiceResolverImpl implements ContextAwareServiceResolver {

  private BundleContext bundleContext;

  // cache of service trackers for each SPI interface
  private final LoadingCache<String, ContextAwareServiceTracker> serviceTrackerCache = CacheBuilder.newBuilder()
      .removalListener(new RemovalListener<String, ContextAwareServiceTracker>() {
        @Override
        public void onRemoval(RemovalNotification<String, ContextAwareServiceTracker> notification) {
          notification.getValue().dispose();
        }
      })
      .build(new CacheLoader<String, ContextAwareServiceTracker>() {
        @Override
        public ContextAwareServiceTracker load(String className) {
          return new ContextAwareServiceTracker(className, bundleContext);
        }
      });

  @Activate
  private void activate(BundleContext context) {
    this.bundleContext = context;
  }

  @Deactivate
  private void deactivate(BundleContext context) {
    serviceTrackerCache.invalidateAll();
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T extends ContextAwareService> T resolve(Class<T> serviceClass, Adaptable adaptable) {
    Resource resource = getResource(adaptable);
    ContextAwareServiceTracker serviceTracker = getServiceTracker(serviceClass);
    return serviceTracker.resolve(resource)
        .map(serviceInfo -> (T)serviceInfo.getService())
        .findFirst().orElse(null);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T extends ContextAwareService> ResolveAllResult<T> resolveAll(Class<T> serviceClass, Adaptable adaptable) {
    Resource resource = getResource(adaptable);
    ContextAwareServiceTracker serviceTracker = getServiceTracker(serviceClass);
    return new ResolveAllResultImpl(
        serviceTracker.resolve(resource).map(serviceInfo -> (T)serviceInfo.getService()),
        () -> buildCombinedKey(serviceTracker, serviceTracker.resolve(resource)));
  }

  private String buildCombinedKey(ContextAwareServiceTracker serviceTracker, Stream<ServiceInfo> result) {
    return serviceTracker.getLastServiceChangeTimestamp() + "\n"
        + result.map(ServiceInfo::getKey).collect(Collectors.joining("\n~\n"));
  }

  private Resource getResource(Adaptable adaptable) {
    if (adaptable instanceof Resource) {
      return (Resource)adaptable;
    }
    else if (adaptable instanceof SlingHttpServletRequest) {
      return ((SlingHttpServletRequest)adaptable).getResource();
    }
    return null;
  }

  private ContextAwareServiceTracker getServiceTracker(Class<?> serviceClass) {
    try {
      return serviceTrackerCache.get(serviceClass.getName());
    }
    catch (ExecutionException ex) {
      throw new RuntimeException("Error getting service tracker for " + serviceClass.getName() + " from cache.", ex);
    }
  }

  ConcurrentMap<String, ContextAwareServiceTracker> getContextAwareServiceTrackerMap() {
    return serviceTrackerCache.asMap();
  }

}

@@ -1185,16 +1185,73 @@
 ctivate;
+%0Aimport org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;
 %0A%0Aimport
@@ -1870,16 +1870,117 @@
 ntext;%0A%0A
+  private static final Logger log = LoggerFactory.getLogger(ContextAwareServiceResolverImpl.class);%0A%0A
   // cac
@@ -3048,32 +3048,191 @@
 rce(adaptable);%0A
+    if (log.isTraceEnabled()) %7B%0A      log.trace(%22Resolve %7B%7D for resource %7B%7D%22, serviceClass.getName(), (resource != null ? resource.getPath() : %22null%22));%0A    %7D%0A
     ContextAware
@@ -3592,32 +3592,32 @@
 le adaptable) %7B%0A
-
     Resource res
@@ -3644,24 +3644,187 @@
 adaptable);%0A
+    if (log.isTraceEnabled()) %7B%0A      log.trace(%22Resolve all %7B%7D for resource %7B%7D%22, serviceClass.getName(), (resource != null ? resource.getPath() : %22null%22));%0A    %7D%0A
     ContextA

add trace logging