/* **********************************
 *   FinishAgent.java
 *     Finish a given Raw Material
 * **********************************/
package player.playeragent;

import player.*;
import edu.neu.ccs.demeterf.demfgen.lib.*;
import edu.neu.ccs.demeterf.*;
import gen.*;

/** Class for finishing a list of derivatives */
public class FinishAgent implements PlayerI.FinishAgentI{
    static Sign pos = new Pos(), neg = new Neg();
    static int NUM_ASSIGNS = 60;
    
    /** Calculate the finished product for a given Derivative */
    public FinishedProduct finishDerivative(Derivative d){
        Best best = bestAssign(d);
        double profit = hidden.Tools.payback(d,best.quality) - d.price.val;
        Util.display(Util.color("Finishing","green")+" : "+
                Util.color(Util.tag(((profit > 0)?"Profit":"Loss")+" = "+Util.format(profit),"b"),
                        (profit > 0)?"green":"red"));
            
        return new FinishedProduct(new IntermediateProduct(best.assign), new Quality(best.quality));
    }
    /** Helps to collect the Best assignment */
    public static class Best{
        public double quality;
        public Assignment assign;
        Best(){ this(-1.0, null); }
        Best(double q, Assignment a){ quality = q; assign = a; }
    }
    /** Find the best random assignment for a delivered derivative */
    public static Best bestAssign(Derivative d){
        return bestAssign(d.optraw.inner().instance, NUM_ASSIGNS);
    }
    /** Find the best random assignment for a rawMaterial */
    public static Best bestAssign(final RawMaterialInstance rm, int attempts){
        final Set<ident> ids = usedVars(rm);
        List<Assignment> assigns = List.buildlist(new List.Build<Assignment>(){
            public Assignment build(int i){ return new Assignment(randomAssign(ids, Util.random())); }
        }, attempts);
        return bestAssign(assigns, rm);
    }
    /** Select the best (highest wuality) assignment from the list */
    private static Best bestAssign(List<Assignment> assigns, final RawMaterialInstance rmi){
        return assigns.fold(new List.Fold<Assignment, Best>(){
            public Best fold(Assignment a, Best b){
                double qual = hidden.Tools.quality(rmi, a);
                if(qual < b.quality)return b;
                return new Best(qual,a);
            }
        }, new Best());
    }
    
    /** Create a random assignment using the given set of idents, with a fair coin */
    private static List<Literal> randomAssign(Set<ident> ids){ return randomAssign(ids,0.5); }
    /** Create a random assignment using the given set of idents, with a biased coin */
    private static List<Literal> randomAssign(Set<ident> ids, final double bias){
        return ids.toList().map(new List.Map<ident, Literal>(){
            public Literal map(ident id){
                return new Literal((Util.coinFlip(bias)?pos:neg),new Variable(id));
            }
        });
    }
    /** Find the set of all variables used in the given dirivative's raw material */
    private static Set<ident> usedVars(RawMaterialInstance rmi){
        return new StaticTrav(new TUCombiner<Set<ident>>(){
            public Set<ident> combine(){ return Set.<ident>create(); }
            public Set<ident> fold(Set<ident> a, Set<ident> b){ return a.union(b); }
            Set<ident> combine(ident id){ return Set.<ident>create(List.create(id)); }
        }).traverseRawMaterialInstance(rmi);
    }
}