/*
 *    Converter.java
 *    Copyright (C) 2012-2013 Aristotle University of Thessaloniki, Greece
 */
package dbn.convert;

import mulan.classifier.MultiLabelOutput;
import weka.core.Instance;
import weka.core.Instances;

/**
 * An abstract class that has some helpful methods to convert {@link mulan.classifier.MultiLabelOutput},
 * {@link Instance} or {@link Instances} to double or bipartition vectors or
 * matrixes. It uses the field <code>threshold</code> to build the bipartition vectors 
 * from a double one. User can change the <code>threshold</code> using the appropriate
 * method.
 * 
 * @author Evripidis Gkanias
 */
public abstract class Converter {
    /**
     * This field is used by the static methods to convert double vectors in 
     * bipartition vectors. <br />
     * Default value is 0.45
     */
    protected static double threshold = 0.45;
    
    /**
     * Converts a set of {@link MultiLabelOutput} instances to a boolean matrix
     * using the <code>threshold</code> field.
     * 
     * @param Z a set of {@link MultiLabelOutput} instances to be converted
     * @return the boolean matrix of the parameters data.
     * @throws NullPointerException
     */
    public static boolean[][] MultiLabelOutputArrayToBipartitionArray(MultiLabelOutput[] Z) {
        if (Z == null) throw new NullPointerException("The Z parameter is not set!");
        
        boolean[][] bipartition = new boolean[Z.length][];
        for (int i = 0; i < Z.length; i++) {
            bipartition[i] = MultiLabelOutputToBipartition(Z[i]);
        }
        return bipartition;
    }
    
    /**
     * Converts a set of {@link MultiLabelOutput} instances to a double matrix.
     * 
     * @param Z a set of {@link MultiLabelOutput} instances to be converted
     * @return the double matrix of the parameters data.
     * @throws NullPointerException
     */
    public static double[][] MultiLabelOutputArrayToDoubleMatrix(MultiLabelOutput[] Z) {
        if (Z == null) throw new NullPointerException("The Z parameter is not set!");
        
        double[][] Z_double = new double[Z.length][];
        for (int i = 0; i < Z.length; i++) {
            Z_double[i] = MultiLabelOutputToDoubleArray(Z[i]);
        }
        return Z_double;
    }
    
    /**
     * Converts a {@link MultiLabelOutput} instance to a bipartition using the
     * <code>threshold</code> field.
     * 
     * @param z a {@link MultiLabelOutput} instance to be converted
     * @return the bipartition of the parameters data
     * @throws NullPointerException
     */
    public static boolean[] MultiLabelOutputToBipartition(MultiLabelOutput z) {
        boolean[] result;
        if (!z.hasBipartition()) {
            if (z.hasConfidences()) {
                int i = 0;
                result = new boolean[z.getBipartition().length];
                for (double iter : z.getConfidences()) {
                    result[i] = iter >= threshold;
                    i++;
                }
            } else if (z.hasRanking()) {
                int i = 0;
                result = new boolean[z.getRanking().length];
                for (int iter : z.getRanking()) {
                    result[i] = (double) iter / (double) z.getRanking().length >= threshold;
                    i++;
                }
            } else {
                throw new NullPointerException("The Z parameter is not set!");
            }
        } else {
            result = z.getBipartition();
        }
        return result;
    }
    
    /**
     * Converts a {@link MultiLabelOutput} instance to a double array.
     * 
     * @param z a {@link MultiLabelOutput} instance to be converted
     * @return the double array of the parameters data
     * @throws NullPointerException
     */
    public static double[] MultiLabelOutputToDoubleArray(MultiLabelOutput z) {
        double[] result;
        if (!z.hasConfidences()) {
            if (z.hasRanking()) {
                int i = 0;
                result = new double[z.getRanking().length];
                for (int iter : z.getRanking()) {
                    result[i] = (double) iter / (double) z.getRanking().length;
                    i++;
                }
            } else if (z.hasBipartition()) {
                int i = 0;
                result = new double[z.getBipartition().length];
                for (boolean iter : z.getBipartition()) {
                    result[i] = iter ? 1. : 0.00001;
                    i++;
                }
            } else {
                throw new NullPointerException("The Z parameter is not set!");
            }
        } else {
            result = z.getConfidences();
        }
        return result;
    }
    
    /**
     * Converts a subset of an {@link Instances} dataset to a boolean matrix
     * using the <code>threshold</code> field.
     * 
     * @param instances the {@link Instances} dataset to be converted
     * @param indices the indices of the subset in each instance
     * @return the boolean matrix of the parameters data.
     * @throws NullPointerException
     */
    public static boolean[][] InstancesToBipartionArray(Instances instances, int[] indices) {
        if (instances == null)
            throw new NullPointerException("Parameter \"instances\" is not set!");
        if (indices == null)
            throw new NullPointerException("parameter \"indices\" is not set!");
        
        boolean[][] dataset = new boolean[instances.numInstances()][];
        for (int i = 0; i < dataset.length; i++) {
            dataset[i] = InstanceToBipartition(instances.instance(i), indices);
        }
        return dataset;
    }
    
    /**
     * Converts a subset of an {@link Instances} dataset to a double matrix.
     * 
     * @param instances the {@link Instances} dataset to be converted
     * @param indices the indices of the subset in each instance
     * @return the double matrix of the parameters data.
     * @throws NullPointerException
     */
    public static double[][] InstancesToDoubleMatrix(Instances instances, int[] indices) {
        if (instances == null)
            throw new NullPointerException("Parameter \"instances\" is not set!");
        if (indices == null)
            throw new NullPointerException("parameter \"indices\" is not set!");
        
        double[][] dataset = new double[instances.numInstances()][];
        for (int i = 0; i < dataset.length; i++) {
            dataset[i] = InstanceToDoubleArray(instances.instance(i), indices);
        }
        return dataset;
    }
    
    /**
     * Converts a subset of an {@link Instance} to a bipartition
     * using the <code>threshold</code> field.
     * 
     * @param instance the {@link Instance} to be converted
     * @param indices the indices of the subset in the {@link Instance}
     * @return the bipartition of the parameters data
     * @throws NullPointerException
     */
    public static boolean[] InstanceToBipartition(Instance instance, int[] indices) {
        if (instance == null)
            throw new NullPointerException("Parameter \"instance\" is not set!");
        if (indices == null)
            throw new NullPointerException("parameter \"indices\" is not set!");
        
        boolean[] data = new boolean[indices.length];
        int i = 0;
        for (int index : indices) {
            data[i] = instance.toDoubleArray()[index] >= threshold;
            i++;
        }
        return data;
    }
    
    /**
     * Converts a subset of an {@link Instance} to a double array.
     * 
     * @param instance the {@link Instance} to be converted
     * @param indices the indices of the subset in the {@link Instance}
     * @return the double array of the parameters data
     * @throws NullPointerException
     */
    public static double[] InstanceToDoubleArray(Instance instance, int[] indices) {
        if (instance == null)
            throw new NullPointerException("Parameter \"instance\" is not set!");
        if (indices == null)
            throw new NullPointerException("parameter \"indices\" is not set!");
        
        double[] data = new double[indices.length];
        int i = 0;
        for (int index : indices) {
            data[i] = instance.toDoubleArray()[index];
            i++;
        }
        return data;
    }
    
    /**
     * Sets the threshold value.
     * 
     * @param threshold 
     */
    public static void SetThreshold(double threshold) {
        Converter.threshold = threshold;
    }
}
