/*******************************************************************************
 * Copyright (c) 2008, 2014 Stuart McCulloch
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Stuart McCulloch - initial API and implementation
 *******************************************************************************/

package org.eclipse.sisu.peaberry.cache;

import static java.util.Collections.binarySearch;
import static java.util.logging.Level.WARNING;
import static org.eclipse.sisu.peaberry.cache.AbstractServiceImport.MODIFIED;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import org.eclipse.sisu.peaberry.AttributeFilter;
import org.eclipse.sisu.peaberry.Export;
import org.eclipse.sisu.peaberry.Import;
import org.eclipse.sisu.peaberry.ServiceWatcher;

/**
 * Keep track of imported, sortable services that provide a specific interface.
 * 
 * @author mcculls@gmail.com (Stuart McCulloch)
 */
public abstract class AbstractServiceListener<T> {

  private static final Logger LOGGER = Logger.getLogger(AbstractServiceListener.class.getName());

  private final List<AbstractServiceImport<T>> imports;
  private final List<ServiceWatcher<T>> watchers;

  protected AbstractServiceListener() {
    imports = new ArrayList<AbstractServiceImport<T>>(4);
    watchers = new ArrayList<ServiceWatcher<T>>(2);
  }

  @SuppressWarnings("unchecked")
  synchronized void addWatcher(final ServiceWatcher watcher) {
    if (!watchers.contains(watcher) && watchers.add(watcher)) {

      // report existing imports to the new watcher
      for (final AbstractServiceImport i : imports) {
        notifyWatcher(watcher, i);
      }
    }
  }

  synchronized void flush(final int targetGeneration) {
    // flush any unused cached service instances
    for (final AbstractServiceImport<T> i : imports) {
      i.flush(targetGeneration);
    }
  }

  protected final void insertService(final AbstractServiceImport<T> i) {
    // find insertion point that maintains ordering
    final int insertIndex = binarySearch(imports, i);

    if (insertIndex < 0) {
      // new object, must flip index
      imports.add(~insertIndex, i);

      // report new import to any watching watchers
      for (final ServiceWatcher<T> w : watchers) {
        notifyWatcher(w, i);
      }
    }
  }

  protected final void updateService(final AbstractServiceImport<T> i) {
    // use linear search in case ranking has changed
    final int index = imports.indexOf(i);

    if (0 <= index) {
      // keep existing instance as it might be in use
      final AbstractServiceImport<T> orig = imports.get(index);

      // need to re-order list?
      if (orig.hasRankingChanged()) {
        imports.remove(index);
        imports.add(~binarySearch(imports, orig), orig);
      }

      // finally update any watchers
      orig.notifyWatchers(MODIFIED);
    } else {
      // not seen before
      insertService(i);
    }
  }

  protected final void removeService(final Import<T> i) {
    // use linear search in case ranking has changed
    final int index = imports.indexOf(i);
    if (0 <= index) {
      // flush cache even if being used
      imports.remove(index).invalidate();
    }
  }

  @SuppressWarnings("unchecked")
  private static void notifyWatcher(final ServiceWatcher watcher, final AbstractServiceImport i) {
    try {
      final Export export = watcher.add(i);
      if (null != export) {
        i.addWatcher(export);
      }
    } catch (final RuntimeException re) {
      LOGGER.log(WARNING, "Exception in service watcher", re);
    }
  }

  synchronized Import<T> findNextImport(final Import<T> prevImport, final AttributeFilter filter) {

    if (imports.isEmpty()) {
      return null;
    }

    if (null == prevImport && null == filter) {
      return imports.get(0);
    }

    // estimate last position based on previous value and current list
    return findNextImport(filter, null == prevImport ? ~0 : binarySearch(imports, prevImport));
  }

  private Import<T> findNextImport(final AttributeFilter filter, final int prevIndex) {

    // may need to flip position if previous import is no longer in the list
    for (int i = prevIndex < 0 ? ~prevIndex : prevIndex + 1; i < imports.size(); i++) {

      // now do a linear search applying the given filter
      final Import<T> nextImport = imports.get(i);
      if (null == filter || filter.matches(nextImport.attributes())) {
        return nextImport;
      }
    }

    return null; // no matching service
  }

  protected abstract void start();
}
