package ch.uzh.ifi.ce.cabne.integration;

import java.util.Iterator;
import java.util.List;

import ch.uzh.ifi.ce.cabne.algorithm.BNESolverContext;
import ch.uzh.ifi.ce.cabne.domains.BidSampler;
import ch.uzh.ifi.ce.cabne.strategy.Strategy;

// Integrator that does Monte-Carlo sampling.
// It takes samples from context.sampler, so it implicitly uses e.g. common random numbers if context.rngs contains common
// random generators.

// a subclass of this could compute E[u] by directly sampling values and applying strategies, but this would almost always 
// be very inefficient (e.g. LLG knows that the global player is truthful, and the corresponding sampler does some
// importance sampling)

public class MCIntegrator<Value, Bid> extends Integrator<Value, Bid> {

	public MCIntegrator(BNESolverContext<Value, Bid> context) {
		super(context);
	}

	@Override
	public double computeExpectedUtility(int i, Value v, Bid b, List<Strategy<Value, Bid>> strats) {
		int nsamples = context.getIntParameter("mcsamples");
		double result = 0.0;
		
		Iterator<BidSampler<Value, Bid>.Sample> biditer = context.sampler.conditionalBidIterator(i, v, b, strats);
		BidSampler<Value, Bid>.Sample sample;
		
		for (int MCsample=0; MCsample<nsamples; MCsample++) {
			sample = biditer.next();
			
			// add mechanism output to total, weighted by density
			result += sample.density * context.mechanism.computeUtility(i, v, sample.bids);
			if (Double.isNaN(result)) {
				throw new RuntimeException();
			}
		}
		
		// multiply result by integration volume
		// TODO: this would be cleaner if it was handled by separate classes explicitly 
		// extending Integrator<Value, Double> and Integrator<Value, Double[]>
		if (b instanceof Double) {
			for (int j=0; j<strats.size(); j++) {
				if (j==i) continue;
				Double maxval = (Double) strats.get(j).getMaxValue();
				result *= maxval;
			}
			
		} else if (b instanceof Double[]) {
			for (int j=0; j<strats.size(); j++) {
				if (j==i) continue;
				Double[] maxval = (Double[]) strats.get(j).getMaxValue();
				for (int k=0; k<maxval.length; k++) {
					result *= maxval[k];
				}
			}
			
		} else {
			throw new RuntimeException("MCIntegrator does not support this type of Bid.");
		}

		return result / nsamples;
	}
}
