package csp.smart;

import edu.neu.ccs.demeterf.lib.*;

import java.util.Random;

import scg.Agreement;
import scg.AvatarI;
import scg.Claim;
import scg.Config;
import scg.InstanceI;
import scg.OpposeAction;
import scg.ProtocolI;
import scg.Refuting;
import scg.SolutionI;
import scg.SolveRequest;
import scg.Strengthening;
import scg.Util;
import scg.protocol.*;
import csp.*;


/** Representation of CSPAvatarSmart */
public class CSPAvatarSmart implements AvatarI{

	/** Construct a(n) CSPAvatarSmart Instance */
	public CSPAvatarSmart() {
	}

	/** Is the given object Equal to this CSPAvatarSmart? */
	public boolean equals(Object o) {
		if (!(o instanceof CSPAvatarSmart))
			return false;
		if (o == this)
			return true;
		return true;
	}

	/** Parse an instance of CSPAvatarSmart from the given String */
	public static CSPAvatarSmart parse(String inpt)
			throws csp.avatar.ParseException {
		return new csp.avatar.TheParser(new java.io.StringReader(inpt))
				.parse_CSPAvatarSmart();
	}

	/** Parse an instance of CSPAvatarSmart from the given Stream */
	public static CSPAvatarSmart parse(java.io.InputStream inpt)
			throws csp.avatar.ParseException {
		return new csp.avatar.TheParser(inpt).parse_CSPAvatarSmart();
	}

	/** Parse an instance of CSPAvatarSmart from the given Reader */
	public static CSPAvatarSmart parse(java.io.Reader inpt)
			throws csp.avatar.ParseException {
		return new csp.avatar.TheParser(inpt).parse_CSPAvatarSmart();
	}

	private Config config;

	/** Constructor to be called during registration where you supply config */
	public CSPAvatarSmart(Config cfg) {
		config = cfg;
	}

	/**
	 * proposing claims by using our preferences which are not in the forbidden
	 * list
	 */
	public List<Claim> propose(List<Claim> forbiddenClaims) {
		List<Claim> claims = List.create();
		Claim claim = null;
		ListSet<Integer> base = ListSet.<Integer> create(2);
		int m = 1;
		int n = 2;
		for (int i = 0; i < config.getScgCfg().getMaxProposals(); i++) {
			if (m < 255 && n < 255){
			base = ListSet.<Integer> create(m*2, n*2);
			Random rand = new Random();
			boolean nextBoolean = rand.nextBoolean();
//			while (base.size() <= 2) {
				// Try the preferences...
				for (Entry<Integer, Tools.Triple<Integer, Double, Double>> pr : Tools.PREFS_MAP) {
					CSPInstanceSet instanceSet = new CSPInstanceSet(base.add(pr
							.getKey()));
					double quality = 1;
					double confidence = 1;
					claim = new Claim(instanceSet, nextBoolean ? PositiveSecret
							.getInstance() : NegativeSecret.getInstance(),
							quality, confidence);

					if (!forbiddenClaims.contains(claim)) {

						claim = new Claim(instanceSet,
								nextBoolean ? PositiveSecret.getInstance()
										: NegativeSecret.getInstance(), pr
										.getVal().getZ() - 0.01, pr.getVal()
										.getZ() - 0.01);
					}
//				}
				//base = base.add(2 + 2 * base.size());
			}
			// Otherwise, settle for a random one
			while (forbiddenClaims.contains(claim)) {
				claim = generateRandomClaim();
			}
			m++;
			n += 2;
			}
			else {
				while (forbiddenClaims.contains(claim)) {
					claim = generateRandomClaim();
				}
			}
			claims = claims.append(claim);
		}
		return claims;
	}
	
    /** get claims */
	private static List<Claim> getClaims(List<Claim> claims,
			java.util.List<Claim> l, int i) {
		List<Claim> cs = List.create();
		if (i == l.size()-1) {
			return cs = claims.append(l.get(i));
		} else {
			getClaims(claims.append(l.get(i)), l, ++i);
		}
		return cs;
	}

	  
	private double minStrengthening = 0.05;

	/** Smart oppose method - try to refutes or strengthens, then agree */
	public List<OpposeAction> oppose(List<Claim> claimsToBeOpposed) {
		return claimsToBeOpposed.map(new List.Map<Claim, OpposeAction>() {
			public OpposeAction map(Claim claim) {
				double quality = claim.getQuality();
				ListSet<Integer> relNums = ((CSPInstanceSet) claim
						.getInstanceSet()).getType();
				if (isRefutableRelNums(relNums))
					return new Refuting();
				else if (isStrengthenable(relNums, claim.getProtocol())) {
					// strengthen claim by a little step within [0,1]
					if (claim.getProtocol() instanceof PositiveSecret) {
						return strengthenPosClaim(quality, minStrengthening);
					} else {
						return strengthenNegClaim(quality, minStrengthening);
					}
				} else
					return new Agreement();
			}
		});
	}

	/** is the given instance set refutable? */
	private boolean isRefutableRelNums(ListSet<Integer> relNums) {
		return (isRelNumsOver127(relNums)) || (isRelNumsOdd(relNums));
	}

	private boolean isRelNumsOver127(ListSet<Integer> relNumsSet) {
		List<Integer> relNums = relNumsSet.toList();
		for (int i = 0; i < relNums.length(); i++) {
			if (isRelNumsOver127(((Integer) relNums.lookup(i)).intValue()))
				return true;
		}
		return false;
	}

	private boolean isRelNumsOdd(ListSet<Integer> relNumsSet) {
		List<Integer> relNums = relNumsSet.toList();
		for (int i = 0; i < relNums.length(); i++) {
			if (isRelNumsOdd(((Integer) relNums.lookup(i)).intValue()))
				return true;
		}
		return false;
	}

	private boolean isRelNumsOver127(int relNum) {
		return relNum > 127;
	}

	private boolean isRelNumsOdd(int relNum) {
		return relNum % 2 == 1;
	}

	/** Is the given instance set strengthenable? */
	private boolean isStrengthenable(ListSet<Integer> relNums,
			ProtocolI protocol) {
		if (protocol instanceof PositiveSecret) {

		} else if (protocol instanceof NegativeSecret) {

		}
		return false;
	}
	
	/**
	 * The minimumStrengthen will be halved when the new quality is out of range
	 * [0,1]
	 */
	private OpposeAction strengthenPosClaim(double q, double min) {
		if (q > min) {
			return new Strengthening(q - min);
		} else
			return strengthenPosClaim(q, min / (double) 2);
	}

	private OpposeAction strengthenNegClaim(double q, double min) {
		if ((q + min) < 1) {
			return new Strengthening(q + min);
		} else
			return strengthenNegClaim(q, min / (double) 2);
	}

	/**
	 * providing instance method - provides symmetric Instance of given
	 * instanceSet
	 */
	public InstanceI provide(Claim claimToBeProvided) {
		CSPInstanceSet cspInstanceSet = (CSPInstanceSet) claimToBeProvided
				.getInstanceSet();
		CSPConfig cfg = (CSPConfig) config.getDomainConfig();
		int numVars = cfg.getMaxVariables();
		List<Var> vars = List.<Var> create();

		/** bug found here, fixed by vars = vars.append... and ident("v"... */

		for (int i = 1; i <= numVars; i++) {
			vars = vars.append(new Var(new ident("v" + i)));
		}
		CSPInstance cspInstance = new CSPInstance(vars, Tools.symmetric(
				cspInstanceSet.getType().toList(), numVars, 3));
		return cspInstance;
	}
	
	static final int NUM_ASSIGNS = 20;
	
	/** solve using random assignment of values to all the variables */
	public SolutionI solve(SolveRequest solveRequest) {
		CSPInstance i = (CSPInstance) solveRequest.getInstance();
		Claim claim = solveRequest.getClaim().inner();
		double confidence = claim.getConfidence();
		double quality = claim.getQuality();
		Best best = findBest(i);
		double prof = Tools.payback(confidence, best.quality, quality)
				- confidence;
		if (prof < 0) {
			Tools.updatePreferences(i, best.quality);
		}
		CSPSolution solution = new CSPSolution(ListMap.create(best.assign));
		return solution;
	}
	
	public static class Best {
		public double quality;
		public List<Entry<Var, Boolean>> assign;

		Best() {
			this(-1.0, null);
		}

		Best(double q, List<Entry<Var, Boolean>> a) {
			quality = q;
			assign = a;
		}
	}

	/** Solve the given Problem Instance */
	Best findBest(final CSPInstance i) {
		long t1 = System.currentTimeMillis();
		int turnDuration = config.getScgCfg().getTurnDuration();
		long timeAllowed = turnDuration * 1000;
		return findBest(i, NUM_ASSIGNS, timeAllowed, t1);
	}

	/** Solve the given Problem Instance, using "times" random asssginments */
	public static Best findBest(final CSPInstance p, int times, long timeAllowed, long t1) {
		final List<Var> vars = usedVars(p);
		Best temp = new Best();
		for (int i = 0; i < times; i++) {
			long t2 = System.currentTimeMillis();
			long timeLeft = t2 -t1;
			if (timeLeft < timeAllowed - 20000) {
				Best best = List.buildlist(
						new List.Build<List<Entry<Var, Boolean>>>() {
							public List<Entry<Var, Boolean>> build(int i) {
								return randomAssign(vars, Util.random());// PARTITIONS)/(double)PARTITIONS);
							}
						}, times).fold(
						new List.Fold<List<Entry<Var, Boolean>>, Best>() {
							public Best fold(List<Entry<Var, Boolean>> s, Best b) {
								if (b.quality == 1.0)
									return b;
								double qual = Tools.quality(p, s);
								if (qual < b.quality)
									return b;
								return new Best(qual, s);
							}
						}, new Best());
				if (best.quality > temp.quality)
					temp = best;
			} else {
				return temp;
			}
		}
		return temp;
	}

	/** Used Variables from a Problem Instance */
	static List<Var> usedVars(CSPInstance p) {
		return p.getVars();
	}

	/** Random Assignment to each of the variables */
	public static List<Entry<Var, Boolean>> randomAssign(List<Var> vs,
			final double bias) {
		return vs.map(new List.Map<Var, Entry<Var, Boolean>>() {
			public Entry<Var, Boolean> map(Var v) {
				return Entry.create(v, Util.coinFlip(bias));
			}
		});
	}
	private	Random random = new Random();
	
	private Claim generateRandomClaim() {
		Random rand = new Random();
		boolean nextBoolean = rand.nextBoolean();
		ListSet<Integer> type = ListSet.create();
		CSPConfig cfg = (CSPConfig) config.getDomainConfig();
		if (cfg.getMaxRelNum() >= 127)
			type = type.add(127);
		type = type.add(rand.nextInt(cfg.getMaxRelNum()));
		CSPInstanceSet instanceSet = new CSPInstanceSet(type);
		return new Claim(instanceSet, nextBoolean ? PositiveSecret
				.getInstance() : NegativeSecret.getInstance(), random.nextDouble(), random.nextDouble());
	}

    
    /** N choose K, the dynamic programming way! ;) */
    public static <X> List<List<X>> combs(int n, int k, List.Build<X> b){
        List<List<X>>[][] tbl = new List[n+1][k+1];

        for(int ik = 0; ik <= k; ik++)
            for(int in = 0; in <= n; in++){
                List<List<X>> newlst;
                if(ik == 0) newlst = List.<List<X>>create(List.<X>create());
                else if(in < ik)newlst = List.create();
                else newlst = tbl[in-1][ik].append(tbl[in-1][ik-1].map(new PushN<X>(b.build(in))));   
                tbl[in][ik] = newlst;
            }
        return tbl[n][k];
    }
	
	/** Sort the relations to find the most important one... */
    public static int importantRelation(CSPInstanceSet t){
        int rel = t.getType().toList().sort(new List.Comp<Integer>(){
            public boolean comp(Integer ra, Integer rb){
                return (// Implied
                        ((ra & rb) == ra) ||
                        // A is even, B is odd
                        (ra%2 == 0 && rb%2 == 1) ||
                        // Both even or both odd
                        ((ra%2 == rb%2) && bitsSet(ra) < bitsSet(rb)));
            }
        }).top();
        return rel;
    }
    
    /** Determin the number of bits set in the given relation number */
    public static int bitsSet(int i){
        int c = 0;
        while(i > 0){
            if((i&1) > 0)c++;
            i >>= 1;
        }
        return c;
    }
    
    /** Push the given number onto the front of each list */
    private static class PushN<X> extends List.Map<List<X>,List<X>>{
        X x;
        public PushN(X xx){ x = xx; }
        public List<X> map(List<X> l){ return l.push(x); }
    }

    /** DGP method from Class Display */
    public String display(){ return csp.avatar.Display.DisplayM(this); }
    /** DGP method from Class Print */
    public String print(){ return csp.avatar.Print.PrintM(this); }
    /** DGP method from Class ToStr */
    public String toStr(){ return csp.avatar.ToStr.ToStrM(this); }
    /** DGP method from Class PrintToString */
    public String toString(){ return csp.avatar.PrintToString.PrintToStringM(this); }

}



