package brn.sim.builder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.swans.Node;
import jist.swans.misc.Util;
import brn.sim.DataManager;
import brn.sim.builder.Builder.Params;

/**
 * A simple registry for builders. This class is not intended to be subclassed.
 * Add your custom builders instead.
 *
 * @author kurth
 */
public final class Provider {

  protected Map mapParamsToBuilder;

  protected DataManager dataManager;

  protected static class HookupEntry {
    public Builder builder;
    public Builder.Params params;
    public Node node;
    public Object entity;

    public HookupEntry(Builder builder, Params params, Node node, Object entity) {
      super();
      this.builder = builder;
      this.params = params;
      this.node = node;
      this.entity = entity;
    }
  }

  protected List /* HookupEntry */ hookUps;

  public Provider(DataManager dataManager) {
    this.mapParamsToBuilder = new HashMap();
    this.hookUps = new ArrayList();
    this.dataManager = dataManager;
  }

  /**
   * Looks up a builder for the specified type.
   *
   * @param classParams class of parameters to look for a builder
   * @return the builder for the specified type
   * @throws BuilderException if no builder was found
   */
//  public Builder getBuilder(Class classParams) throws BuilderException {
//    Builder builder = (Builder) mapParamsToBuilder.get(classParams);
//    return builder;
//  }

  public Builder getBuilder(Object objParams) throws BuilderException {
    Builder builder = (Builder) mapParamsToBuilder.get(objParams.getClass());
    if (null == builder) {
      if (objParams instanceof Builder.ParamsWithBuilder) {
        Class builderClass = ((Builder.ParamsWithBuilder)objParams).builderClass();
        try {
          builder = (Builder) builderClass.newInstance();
        } catch (Exception e) {
          throw new BuilderException("Could not create builder '"+
              builderClass.getSimpleName()+"'", e);
        }
        this.addBuilder(builder);
        builder = (Builder) mapParamsToBuilder.get(objParams.getClass());
      }
      if (null == builder)
        throw new BuilderException("Builder for paramerts " +
            objParams.getClass().getSimpleName() + " not found");
    }
    return builder;
  }

  /**
   * Adds the specified builder and makes it available for entity creation.
   *
   * @param builder builder to add.
   */
  public void addBuilder(Builder builder) {
    mapParamsToBuilder.put(builder.getParamClass(), builder);
    builder.setProvider(this);
  }

  /**
   * Schedules a hookup call for second build stage.
   *
   * @param builder
   * @param params
   * @param node
   * @param entity
   */
  public void addHookUp(Builder builder, Builder.Params params, Node node, Object entity) {
    if (Main.ASSERT)
      Util.assertion(builder.getParamClass().isInstance(params));
    hookUps.add(new HookupEntry(builder, params, node, entity));
  }

  /**
   * Hook up all entities.
   *
   * @throws BuilderException
   */
  public void hookUp() throws BuilderException {
    List currHookUps = hookUps;
    hookUps = new ArrayList();;

    for (int i = 0; i < currHookUps.size(); i++) {
      HookupEntry entry = (HookupEntry) currHookUps.get(i);
      entry.builder.hookUp(entry.params, entry.node, entry.entity);

      if (Main.ASSERT)
        Util.assertion(JistAPI.getTime() <= 100);
    }

    for (int i = 0; i < currHookUps.size(); i++) {
      HookupEntry entry = (HookupEntry) currHookUps.get(i);
      entry.builder.postHookUp(entry.params, entry.node, entry.entity);

      if (Main.ASSERT)
        Util.assertion(JistAPI.getTime() <= 100);
    }

    // go in recursion if someone added something in the meantime
    if (0 != hookUps.size())
      hookUp();
  }

  /**
   * @return the dataManager
   */
  public DataManager getDataManager() {
    return dataManager;
  }
}
