/*
* Copyright 2003-2010 Tufts University  Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 * 
 * http://www.osedu.org/licenses/ECL-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package edu.tufts.vue.compare;

import tufts.vue.DEBUG;
import java.util.*;

/**
 * @author akumar03
 * @author Scott Fraize re-write 2012
 */
public class VoteAggregate extends WeightAggregate
{
    private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(VoteAggregate.class);

    public static final int POSITIVE_VOTE = 1;
    public static final int NEGATIVE_VOTE = 0;
    
    /** The percentage of maps that must contain this node (by merge-key) before the vote is yes */
    private final double nodePercentMaps;
    /** The percentage of maps that must contain this link (by endpoint merge-keys) before the vote is yes */
    private final double linkPercentMaps;
    
    private final int aggregateLinkThreshold;
    private final int aggregateNodeThreshold;

    protected final int[][] linkCounts;
    
    public static VoteAggregate create(List<ConnectivityMatrix> matrices, double nodeThresh, double linkThresh) {
        final IndexedCountingSet preComputed = new IndexedCountingSet();
        for (ConnectivityMatrix matrix : matrices)
            preComputed.addAll(matrix.keys);
        // We pre-compute the index so the matrix aggregate will know how big
        // of a matrix will be required to hold it all.
        return new VoteAggregate(preComputed, matrices, nodeThresh, linkThresh);
    }
    
    private VoteAggregate(IndexedCountingSet preComputed, List<ConnectivityMatrix> matrices, double nodePercent, double linkPercent) {
        super(preComputed, matrices);
        // WeightAggregate superclass will have merged in all the matrices to produce counts: we save them here
        this.linkCounts = super.cx.clone();  // computeLinkVotes is about to write all over super.cx
        this.nodePercentMaps = nodePercent;
        this.linkPercentMaps = linkPercent;
        final double nodeThresh = nodePercent * (double) matrices.size();
        final double linkThresh = linkPercent * (double) matrices.size();
        this.aggregateNodeThreshold = (int) nodeThresh;
        this.aggregateLinkThreshold = (int) linkThresh;
        if (DEBUG.Enabled) {
            Log.debug(this + " nodePercent " + nodePercent + "*count(" + getCount() + ") = vote threshold: " + nodeThresh + "=" + aggregateNodeThreshold);
            Log.debug(this + " linkPercent " + linkPercent + "*count(" + getCount() + ") = vote threshold: " + linkThresh + "=" + aggregateLinkThreshold);
        }
        computeLinkVotes();
    }
    
    private void computeLinkVotes()
    {
        final int size = super.size();

        // This will collapse the connection count values (in super.cx) from their counts down to a
        // 0 or 1, depending on if the vote passed (the count was at or over the threshold).

        // This is nice, and was in the original code, but probably not required: At best it only
        // gives us a slightly faster truth test. E.g., how much faster can (x == 1) be than a full
        // "vote", which is: x > 0 && x >= (final int this.aggregateLinkThreshold) ?  In both
        // cases, we still need to hash two strings to indicies first, then do the 2d-array access.

        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                if (super.cx[i][j] > 0 && super.cx[i][j] >= aggregateLinkThreshold)
                    super.cx[i][j] = POSITIVE_VOTE;
                else
                    super.cx[i][j] = NEGATIVE_VOTE;
            }
        }
    }
    
    public boolean isNodeVotedIn(Object mergeKey) {
        return getNodeCount(mergeKey) >= aggregateNodeThreshold;
    }
    
    public boolean isLinkVotedIn(Object headKey, Object tailKey) {
        return super.getConnection(headKey, tailKey) == POSITIVE_VOTE;
    }
    
    /** if the alpha [head,tail] relationship was voted yes over threshold, zero out the omega
     * [tail,head] so we will only see this head/tail connection once */
    public boolean testAndConsumeOppositeLinkVote(Object headKey, Object tailKey) {
        final int ihead = super.keys.findIndex(headKey);
        final int itail = super.keys.findIndex(tailKey);
        if (ihead < 0 || itail < 0)
            return false;
        
        if (super.cx[ihead][itail] > 0) {
            // reverse the index order and consume the opposite direction link vote
            super.cx[itail][ihead] = 0;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param headKey label/merge property of head endpoint node
     * @param tailKey label/merge property of tail endpoint node
     * @return number of links between head and tail (based merge-keys)
     */
    public int getLinkCount(Object headKey, Object tailKey) {
        final int headIndex = keys.findIndex(headKey);
        final int tailIndex = keys.findIndex(tailKey);
        if (headIndex >= 0 && tailIndex >= 0)
            return linkCounts[headIndex][tailIndex];
        else
            return 0;
    }

    
    // /*
    //  * This method is similar to isNodeVoteAboveThreshold except it works for links
    //  * @param label1 label/merge property of node 1
    //  * @param label2 label/merge property of node 2
    //  * @return true when number of links is above threshold
    //  */
    // public boolean isLinkVoteAboveThreshold(Object headKey, Object tailKey) {
    //     return super.getConnection(headKey, tailKey) > 0;
    //     // This is exactly what was already computed in computeVotes!
    //     // final int linkCount = getLinkCount(headKey, tailKey);
    //     // return linkCount > 0 && linkCount >= aggregateLinkThreshold;
    // }
    // Public void setLinkThreshold(double percentage) {
    //     linkThreshold = percentage;
    //     if (DEBUG.Enabled) Log.debug("linkThresh set to " + percentage, new Throwable("HERE"));
    // }
    // public void setNodeThreshold(double percentage) {
    //     nodeThreshold = percentage;
    //     if (DEBUG.Enabled) Log.debug("nodeThresh set to " + percentage, new Throwable("HERE"));
    // }
    // public VoteAggregate(List<ConnectivityMatrix> matrices,boolean nodeMerge) { super(matrices); }    
}
