/*
 * Copyright (c) 2011. All rights reserved.
 */
package ro.isdc.wro.manager.callback;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.config.Context;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.util.Function;
import ro.isdc.wro.util.ObjectFactory;


/**
 * Register all available callbacks. The registry acts as a {@link LifecycleCallback} itself whose implementation
 * delegate the call to registered callbacks. The registry will handle any runtime exceptions thrown by callbacks, in
 * order to allow successful lifecycle execution.
 *
 * @author Alex Objelean
 * @created Created on 8 Dec 2011
 * @since 1.4.3
 */
public class LifecycleCallbackRegistry
  implements LifecycleCallback {
  private static final Logger LOG = LoggerFactory.getLogger(LifecycleCallbackRegistry.class);

  /**
   * The list of registered callbacks.
   */
  private final List<ObjectFactory<LifecycleCallback>> callbackFactoryList = new ArrayList<ObjectFactory<LifecycleCallback>>();
  private final Map<String, List<LifecycleCallback>> map = new ConcurrentHashMap<String, List<LifecycleCallback>>();

  /**
   * Register a callback using a factory responsible for callback instantiation.
   *
   * @param callbackFactory
   *          the factory used to instantiate callbacks.
   */
  public void registerCallback(final ObjectFactory<LifecycleCallback> callbackFactory) {
    callbackFactoryList.add(callbackFactory);
  }

  private List<LifecycleCallback> getCallbacks() {
    final String key = Context.getCorrelationId();
    List<LifecycleCallback> callbacks = map.get(key);
    if (callbacks == null) {
      callbacks = initCallbacks();
      map.put(key, callbacks);
    }
    return callbacks;
  }

  protected List<LifecycleCallback> initCallbacks() {
    final List<LifecycleCallback> callbacks = new ArrayList<LifecycleCallback>();
    for (final ObjectFactory<LifecycleCallback> callbackFactory : callbackFactoryList) {
      callbacks.add(callbackFactory.create());
    }
    return callbacks;
  }

  /**
   * {@inheritDoc}
   */
  public void onBeforeModelCreated() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onBeforeModelCreated();
        return null;
      }
    });
  }


  /**
   * {@inheritDoc}
   */
  public void onAfterModelCreated() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onAfterModelCreated();
        return null;
      }
    });
  }


  /**
   * {@inheritDoc}
   */
  public void onBeforePreProcess() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onBeforePreProcess();
        return null;
      }
    });
  }


  /**
   * {@inheritDoc}
   */
  public void onAfterPreProcess() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onAfterPreProcess();
        return null;
      }
    });
  }


  /**
   * {@inheritDoc}
   */
  public void onBeforePostProcess() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onBeforePostProcess();
        return null;
      }
    });
  }


  /**
   * {@inheritDoc}
   */
  public void onAfterPostProcess() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onAfterPostProcess();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onBeforeMerge() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onBeforeMerge();
        return null;
      }
    });
  }

  public void onAfterMerge() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onAfterMerge();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onProcessingComplete() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onProcessingComplete();
        return null;
      }
    });
  }


  public void onResourceChanged(final Resource resource) {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onResourceChanged(resource);
        return null;
      }
    });
  }

  private void forEachCallbackDo(final Function<LifecycleCallback, Void> func) {
    for (final LifecycleCallback callback : getCallbacks()) {
      try {
        func.apply(callback);
      } catch (final Exception e) {
        LOG.error("Problem invoking callback", e);
        onException(e);
      } finally {
        map.remove(Context.getCorrelationId());
      }
    }
  }

  /**
   * Invoked when a callback fails. By default exception is ignored.
   *
   * @param e
   *          {@link Exception} thrown by the fallback.
   */
  protected void onException(final Exception e) {
  }
}
