/*
 * PossibleSolution.java
 *
 * Created on June 5, 2004, 1:33 PM
 */

package spamfilter.genetic;

import java.text.NumberFormat;

import spamfilter.email.*;
import spamfilter.decisiontree.*;
import spamfilter.Main;

/**
 *
 * @author  AaronStJ
 */
public class PossibleSolution implements Cloneable {
    
    private Tree tree;    
    private Corpus spamCorpus;
    private Corpus nonSpamCorpus;
    
    private double falsePositiveRatio;
    private double falseNegativeRatio;
    private int size;
    
    private boolean statsCalculated = false;
    
    
    /** Creates a new instance of PossibleSolution */
    public PossibleSolution(Corpus s, Corpus ns) {
        spamCorpus = s;
        nonSpamCorpus = ns;
        
        //generate a possible solution randomly by generating a random tree
        tree = new Tree(spamCorpus, nonSpamCorpus);
        statsCalculated = false;
    }
    
    public PossibleSolution(PossibleSolution p1, PossibleSolution p2)  {
        
        spamCorpus      = p1.spamCorpus;
        nonSpamCorpus   = p2.nonSpamCorpus;
        tree = new Tree(p1.tree, p2.tree);
        statsCalculated = false;
    }
    
    public void refreshStats() {
        statsCalculated = false;
        calculateStats();
    }
    
    public double getExactFalseNegativeRatio() {
        Main.verbose(3,"Testing spam email: ");
        int falseNegatives = 0;
        for(int index = 0; index < spamCorpus.getNumberOfEmails(); index++)
            if(!tree.isEmailSpam(spamCorpus.getEmail(index)))
                falseNegatives++;
        
        Main.verboseln(3,falseNegatives + "/" + spamCorpus.getNumberOfEmails() + " false negatives");
        
        falseNegativeRatio = (double)falseNegatives / (double)spamCorpus.getNumberOfEmails();
        return falseNegativeRatio;
                
    }
    
    public double getExactFalsePositiveRatio() {
        Main.verbose(3,"Testing non-spam email: ");
        int falsePositives = 0;
        for(int index = 0; index < nonSpamCorpus.getNumberOfEmails(); index++)
            if(tree.isEmailSpam(nonSpamCorpus.getEmail(index)))
                falsePositives++;
        
        Main.verboseln(3,falsePositives + "/" + nonSpamCorpus.getNumberOfEmails() + " false positives");
        
        falsePositiveRatio = (double)falsePositives / (double)nonSpamCorpus.getNumberOfEmails();
        return falsePositiveRatio;
        
    }
    
    private void calculateStats() {
        if(statsCalculated == true)
            return;
        
        statsCalculated = true;
        
        Main.verbose(3,"Getting fitness for:");
        Main.verbose(3,tree);        
        
        int NUM_EMAILS_TO_TEST = 100;
        
        Main.verbose(3,"Testing spam email: ");
        int falseNegatives = 0;
        for(int index = 0; index < NUM_EMAILS_TO_TEST; index++)
            if(!tree.isEmailSpam(spamCorpus.getRandomEmail()))
                falseNegatives++;
        
        Main.verboseln(3,falseNegatives + "/" + NUM_EMAILS_TO_TEST + " false negatives");
        
        
        Main.verbose(3,"Testing non-spam email: ");
        int falsePositives = 0;
        for(int index = 0; index < NUM_EMAILS_TO_TEST; index++)
            if(tree.isEmailSpam(nonSpamCorpus.getRandomEmail()))
                falsePositives++;
        
        Main.verboseln(3,falsePositives + "/" + NUM_EMAILS_TO_TEST + " false positives");
        
        falseNegativeRatio = (double)falseNegatives / NUM_EMAILS_TO_TEST;
        falsePositiveRatio = (double)falsePositives / NUM_EMAILS_TO_TEST;
         
        size = tree.getSize();
        //trueLeafNodesRatio = tree.trueLeafNodesRatio();
               
    }
    
    public double getFitness() {       
        
        double FALSE_POSITIVE_WEIGHT = 1.6;
        double FALSE_NEGATIVE_WEIGHT = 1.0;
        double SIZE_WEIGHT = 0.2;
        double MAX_SIZE = 500;
        
        if(size > MAX_SIZE)
            return 0.0001;
        else {
            calculateStats();
            
            return Math.pow(((1 - falseNegativeRatio) * FALSE_NEGATIVE_WEIGHT + 
                    (1 - falsePositiveRatio) * FALSE_POSITIVE_WEIGHT +
                    SIZE_WEIGHT - (Math.pow((double)size,2) / Math.pow(MAX_SIZE,2) *SIZE_WEIGHT) + 
                    0.0001)  / 
                    (FALSE_POSITIVE_WEIGHT + FALSE_NEGATIVE_WEIGHT + SIZE_WEIGHT + 0.0001),5);
        }
        
        
            
    }
    
    public void mutate() {
        Main.verboseln(2,"Mutating " + this);
        tree.mutate(); 
        refreshStats();        
    }    
    
    public String toString() {
        calculateStats();
        NumberFormat pf = NumberFormat.getPercentInstance();
        return new String(
            "fp:" + pf.format(falsePositiveRatio) + 
            " fn:" + pf.format(falseNegativeRatio) +
            " size:" + size);
    }
    
    public Tree getTree() { return tree; }
    
    public Object clone() {
        try {
            PossibleSolution ps = (PossibleSolution)super.clone();
            ps.tree = (Tree)tree.clone();
            return ps;
            
        } catch(Exception e) { return null; }
        
    }
    
}
