
package com.opengamma.financial.marketdatasnapshot;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

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

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.opengamma.core.marketdatasnapshot.SnapshotDataBundle;
import com.opengamma.core.marketdatasnapshot.ValueSnapshot;
import com.opengamma.core.marketdatasnapshot.impl.ManageableUnstructuredMarketDataSnapshot;
import com.opengamma.core.value.MarketDataRequirementNames;
import com.opengamma.engine.depgraph.DependencyGraph;
import com.opengamma.engine.depgraph.DependencyNode;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.engine.view.ViewComputationResultModel;
import com.opengamma.engine.view.cycle.ComputationCacheResponse;
import com.opengamma.engine.view.cycle.ComputationCycleQuery;
import com.opengamma.engine.view.cycle.ViewCycle;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.util.tuple.Pair;

/**
 * Extracts some type of structured object from a cycle
 * 
 * @param <TKey> the key by which these snaps are ided
 * @param <TCalculatedValue> The type of value which comes out of the engine
 * @param <TSnapshot> The type of value which is stored in the snapshots
 */
public abstract class StructuredSnapper<TKey, TCalculatedValue, TSnapshot> {

  private static final Logger s_logger = LoggerFactory.getLogger(StructuredSnapper.class);

  private final String _requirementName;

  public StructuredSnapper(final String requirementName) {
    super();
    _requirementName = ValueRequirement.getInterned(requirementName);
  }

  public String getRequirementName() {
    return _requirementName;
  }

  public Map<TKey, TSnapshot> getValues(final ViewComputationResultModel results, final Map<String, DependencyGraph> graphs,
      final ViewCycle viewCycle) {
    final Map<TKey, TCalculatedValue> calculatedValues = getValues(viewCycle, graphs);

    final Map<TKey, TSnapshot> ret = new HashMap<TKey, TSnapshot>();
    for (final Entry<TKey, TCalculatedValue> entry : calculatedValues.entrySet()) {
      final TSnapshot snapshot = buildSnapshot(results, entry.getKey(), entry.getValue());
      ret.put(entry.getKey(), snapshot);
    }
    return ret;
  }

  protected static String getSingleProperty(final ValueSpecification spec, final String propertyName) {
    final ValueProperties properties = spec.getProperties();
    final Set<String> curves = properties.getValues(propertyName);
    if (curves.size() != 1) {
      throw new IllegalArgumentException("Couldn't find curve property from " + spec);
    }
    final String curve = Iterables.get(curves, 0);
    return curve;
  }

  private Map<TKey, TCalculatedValue> getValues(final ViewCycle viewCycle, final Map<String, DependencyGraph> dependencyGraphs) {
    final Map<String, Collection<ValueSpecification>> values = getMatchingSpecifications(dependencyGraphs, _requirementName);
    final Map<TKey, TCalculatedValue> ts = new HashMap<TKey, TCalculatedValue>();

    for (final Entry<String, Collection<ValueSpecification>> entry : values.entrySet()) {
      final Iterable<ValueSpecification> requiredSpecsIt = Iterables.filter(entry.getValue(),
          new Predicate<ValueSpecification>() {

            @Override
            public boolean apply(final ValueSpecification input) {
              return !ts.containsKey(getKey(input));
            }

          });
      final Collection<ValueSpecification> requiredSpecs = Lists.newArrayList(requiredSpecsIt);
      if (requiredSpecs.isEmpty()) {
        continue;
      }

      final ComputationCycleQuery cacheQuery = new ComputationCycleQuery();
      cacheQuery.setCalculationConfigurationName(entry.getKey());
      cacheQuery.setValueSpecifications(requiredSpecs);
      final ComputationCacheResponse computationCacheResponse = viewCycle.queryComputationCaches(cacheQuery);

      if (computationCacheResponse.getResults().size() != requiredSpecs.size()) {
        s_logger.debug("Failed to get all results from computation cache");
      }

      final Map<TKey, Pair<ValueSpecification, Object>> infos = Maps.uniqueIndex(computationCacheResponse.getResults(),
          new Function<Pair<ValueSpecification, Object>, TKey>() {

            @Override
            public TKey apply(final Pair<ValueSpecification, Object> from) {
              return getKey(from.getFirst());
            }
          });

      for (final Entry<TKey, Pair<ValueSpecification, Object>> result : infos.entrySet()) {
        @SuppressWarnings("unchecked")
        final TCalculatedValue calcValue = (TCalculatedValue) result.getValue().getSecond();
        ts.put(result.getKey(), calcValue);
      }
    }
    return ts;
  }

  private Map<String, Collection<ValueSpecification>> getMatchingSpecifications(final Map<String, DependencyGraph> graphs,
      final String specName) {
    final Map<String, Collection<ValueSpecification>> ret = new HashMap<String, Collection<ValueSpecification>>();
    for (final Entry<String, DependencyGraph> kvp : graphs.entrySet()) {
      final String config = kvp.getKey();
      final DependencyGraph graph = kvp.getValue();
      final Set<ValueSpecification> specsSet = new HashSet<ValueSpecification>();
      final Iterator<DependencyNode> nodes = graph.nodeIterator();
      while (nodes.hasNext()) {
        final DependencyNode node = nodes.next();
        final int count = node.getOutputCount();
        for (int i = 0; i < count; i++) {
          final ValueSpecification output = node.getOutputValue(i);
          // Value names are interned
          if (output.getValueName() == _requirementName) {
            specsSet.add(output);
          }
        }
      }
      ret.put(config, specsSet);
    }
    return ret;
  }

  protected static ManageableUnstructuredMarketDataSnapshot getUnstructured(final SnapshotDataBundle bundle) {
    final Set<Entry<ExternalIdBundle, Double>> bundlePoints = bundle.getDataPointSet();
    final ManageableUnstructuredMarketDataSnapshot snapshot = new ManageableUnstructuredMarketDataSnapshot();
    for (final Map.Entry<ExternalIdBundle, Double> bundlePoint : bundlePoints) {
      snapshot.putValue(bundlePoint.getKey(), MarketDataRequirementNames.MARKET_VALUE, ValueSnapshot.of(bundlePoint.getValue()));
    }
    return snapshot;
  }

  abstract TKey getKey(ValueSpecification spec);

  abstract TSnapshot buildSnapshot(ViewComputationResultModel resultModel, TKey key, TCalculatedValue calculated);
}
