/*
 * ARX: Powerful Data Anonymization
 * Copyright (C) 2012 - 2014 Florian Kohlmayer, Fabian Prasser
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.deidentifier.arx.metric;

import java.math.BigInteger;
import java.util.Arrays;

/**
 * This class implements an information loss which is represented as a combination
 * of individual utility for each attribute. Instances are compared with repeated
 * conservative estimation.
 * 
 * @author Fabian Prasser
 * @author Florian Kohlmayer
 */
class InformationLossRCE extends InformationLoss<double[]> {

    /** The precision in number of digits per attribute */
    private static final int    PRECISION        = 6;

    /** The precision in number of digits in the string representation */
    private static final int    PRECISION_STRING = 2;

    /** The precision as a multiplier */
    private static final double MULTIPLIER       = Math.pow(10, PRECISION);

    /** The precision as a multiplier */
    private static final double MULTIPLIER_STRING = Math.pow(10, PRECISION_STRING);

    private static final long   serialVersionUID = -602019669402453406L;

    /** The integer representation */
    private BigInteger    ints;

    /** double representation */
    private double        perc;

    /** String representation */
    private String        string;

    /** Value */
    private double[]      value;

    /** Value */
    private double[]      weights;
    
    /**
     * Clone constructor
     * @param value
     * @param ints
     * @param string
     * @param perc
     */
    private InformationLossRCE(final double[] value, final BigInteger ints, final String string, final double perc, final double[] weights) {
        this.value = value;
        this.ints = ints;
        this.string = string;
        this.perc = perc;
        this.weights = weights;
    }

    /**
     * Creates a new instance
     * @param value
     */
    InformationLossRCE(final double[] value) {
        this (value, null);
    } 
    /**
     * Creates a new instance
     * @param value
     */
    InformationLossRCE(final double[] value, final double[] weights) {
        
        // Create weighted representation
        double[] weightedValue = null;
        this.weights = weights;
        if (weights != null) {
            weightedValue = Arrays.copyOf(value, value.length);
            for (int i=0; i<weightedValue.length; i++) {
                weightedValue[i] *= weights[i];
            }
            sortDescending(weightedValue);
        }
        
        // Create sorted array in descending order
        this.value = value;
        sortDescending(this.value);
        
        // Create integer representation
        StringBuilder digits = new StringBuilder();
        double[] source = value;
        if (weightedValue != null) source = weightedValue;
        for (double v : source) {
            long ival = (long)Math.round(v * MULTIPLIER);
            StringBuilder sval = new StringBuilder();
            sval.append(ival);
            while (sval.length() < (PRECISION + 1)) {
                sval.insert(0, "0");
            }
            digits.append(sval);
        }
        this.ints = new BigInteger(digits.toString());
        
        // Create string representation
        digits.setLength(0);
        digits.append("[");
        for (double v : value) {
            long ival = (long)Math.round(v * MULTIPLIER_STRING);
            digits.append(ival);
            digits.append(",");
        }
        digits.setCharAt(digits.length()-1, ']');
        this.string = digits.toString();
        
        // 70% percentile
        double mean = getMean(value);
        this.perc = mean + getStandardDeviation(mean, value);
    }

    /**
     * Sorts the array in descending order
     * @param value
     */
    private void sortDescending(double[] value) {
        Arrays.sort(value);
        for (int i = 0; i < value.length / 2; i++) {
            int other = value.length - (i + 1);
            double temp = value[i];
            value[i] = value[other];
            value[other] = temp;
        }
    }

    @Override
    public InformationLoss<double[]> clone() {
        return new InformationLossRCE(value, ints, string, perc, weights);
    }

    @Override
    public int compareTo(InformationLoss<?> other) {
        InformationLossRCE o = convert(other);
        if (other == null) return +1;
        else return ints.compareTo(o.ints);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        InformationLossRCE other = (InformationLossRCE) obj;
        if (!Arrays.equals(value, other.value)) return false;
        return true;
    }

    @Override
    public double[] getValue() {
        return value;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(value);
        return result;
    }

    @Override
    public void max(final InformationLoss<?> other) {
        InformationLossRCE o = convert(other);
        if (other == null) { return; }
        if (o.compareTo(this)>0) {
            value = o.value;
            ints = o.ints;
            string = o.string;
            perc = o.perc;
            weights = o.weights;
        }
    }

    @Override
    public void min(final InformationLoss<?> other) {
        InformationLossRCE o = convert(other);
        if (other == null) { return; }
        if (o.compareTo(this)<0) {
            value = o.value;
            ints = o.ints;
            string = o.string;
            perc = o.perc;
            weights = o.weights;
        }
    }

    @Override
    public double relativeTo(InformationLoss<?> min, InformationLoss<?> max) {
        if (min == null) {
            throw new IllegalArgumentException("Minimum is null");
        } else if (max == null) {
            throw new IllegalArgumentException("Maximum is null");
        }
        InformationLossRCE _min = convert(min);
        InformationLossRCE _max = convert(max);
        
        // TODO
        return (perc - _min.perc) / (_max.perc - _min.perc);
    }

    @Override
    public String toString() {
        return string;
    }
    

    /**
     * Converter method
     * @param other
     * @return
     */
    private InformationLossRCE convert(InformationLoss<?> other){
        if (other == null) return null;
        if (!(other instanceof InformationLossRCE)) {
            throw new IllegalStateException("Information loss must be of the same type");
        } else {
            return (InformationLossRCE)other;
        }
    }
    
    /**
     * Returns the arithmetic mean of the given values
     * @param values
     * @return
     */
    private double getMean(double... values) {

        double mean = 0d;
        for (int i=0; i<values.length; i++){
            mean += values[i];
        }
        mean /= (double)values.length;
        return mean;
    }

    /**
     * Returns the standard deviation of the given values
     * @param values
     * @return
     */
    private double getStandardDeviation(double mean, double... values) {
        double dev = 0;
        for (int i=0; i<values.length; i++){
            dev += Math.pow(values[i] - mean, 2.0d);
        }
        
        return Math.sqrt(dev / (double)values.length);
    }
}

@@ -6760,32 +6760,83 @@
 (max);%0A        %0A
+        if (_max.perc - _min.perc == 0) return 0d;%0A
         // TODO%0A

Fix computation of ratio