/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package it.cnr.iasi.giant.algorithm.clustering;

/**
 * Spectral Clustering : Normalized
 * --------------------------------
 * Implementatie van het "Normalized Spectral Clustering"-algoritme.
 *
 * @author Uyttersprot Bram - modded by Fabio Cumbo
 */

import Jama.EigenvalueDecomposition;
import Jama.Matrix;
import it.cnr.iasi.giant.algorithm.distance.GraphUtil;
import it.cnr.iasi.giant.frame.SpectralInitFrame;
import it.cnr.iasi.giant.thread.NetworkLoaderThread;
import it.cnr.iasi.giant.util.Helper;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import net.sf.javaml.clustering.Clusterer;
import net.sf.javaml.clustering.KMeans;
import net.sf.javaml.core.Dataset;
import net.sf.javaml.tools.data.FileHandler;


public class NormalizedSpectralClustering extends ClusteringAlgorithm implements Clusterer{

    /* Veld om het aantal clusters bij te houden.*/
    private int numberOfClusters = 4;
    /* Variabele die tijdens het cluseren zijn nut zal bewijzen. */
    private double maximum = 0;
    /* Index bijhouden van de te gebruiken afstandsmaat. */
    private int indexOfDistance = 1;
    
    private GraphUtil graph;
    private String dist;

    /* Constructor verwacht 2 argumenten: het aantal clusters en de index van de te gebruiken afstandsmaat.*/
    public NormalizedSpectralClustering(int number, int dist){
        numberOfClusters = number;
        indexOfDistance = dist;
    }
    
    public NormalizedSpectralClustering(int number, GraphUtil g, String d){
        numberOfClusters = number;
        graph = g;
        dist = d;
    }

    
    @Override
    public Dataset[] cluster(Dataset data){
        /* Benodigde matrices aanmaken */
        Matrix m = new Matrix(data.size(),data.size());
        Matrix d = new Matrix(data.size(),data.size());
        /* Afstandsmaat tussen datapunten*/
        /*DistanceMeasure distance;
        switch(indexOfDistance){
        case 0: distance = new CosineDistance(); break;
        case 1: distance = new EuclideanDistance(); break;
        case 2: distance = new ManhattanDistance(); break;
        case 3: distance = new NormDistance(); break;
        default:distance = new EuclideanDistance();
        }*/
        
        int[][] distMatrix = null;
        try {
            SpectralInitFrame.appendInConsole("COMPUTING DISTANCE MATRIX: please wait....");
            if (dist.equals("SP"))
                distMatrix = SpectralClustering.getDistanceMatrix("SP", graph, null);
            else if (dist.equals("HAMMING"))
                distMatrix = SpectralClustering.getDistanceMatrix("HAMMING", null, NetworkLoaderThread.getAdjMatrix());
            SpectralInitFrame.appendInConsole("COMPUTING DISTANCE MATRIX: completed!");
            SpectralInitFrame.appendInConsole("---------------------------------------------------------");
        } catch (IOException e) {}
        
        SpectralInitFrame.appendInConsole("SPECTRAL CLUSTERING: starting spectral clustering algorithm at " + Helper.getCurrentTime());
        SpectralInitFrame.appendInConsole("SPECTRAL CLUSTERING: k = " + numberOfClusters + " - normalized = " + true);
        SpectralInitFrame.appendInConsole("---------------------------------------------------------");

        /* Gewichtsmatrix aanmaken */
        for(int i=0; i < m.getRowDimension(); i++){
            for(int j=i; j < m.getColumnDimension(); j++){
                m.set(i, j, distMatrix[i][j]);
                m.set(j, i, distMatrix[i][j]);
                if(m.get(i, j) > maximum) maximum = m.get(i, j);
            }
        }
        /* Similariteitsmatrix en degree matrix hieruit berekenen. */
        for(int i=0; i < m.getRowDimension(); i++){
            for(int j=0; j < m.getColumnDimension(); j++){
                double temp = Math.abs(maximum - m.get(i, j));
                m.set(i, j, temp);
                d.set(i, i, d.get(i, i) + temp);
            }
        }

        /* Laplace matrix berekenen L = D - W */
        Matrix l = d.minus(m);
        /* Symmetrische Laplace matrix hieruit berekenen */
        d = d.inverse();
        for(int i=0; i < d.getRowDimension(); i++)
            d.set(i, i, Math.sqrt(d.get(i, i)));
        l = l.times(d); d = d.times(l);

        /* Gewenste eigenvectoren berekenen. */
        EigenvalueDecomposition e = d.eig();
        Matrix V = e.getV();
        V = V.getMatrix(0, V.getRowDimension() - 1, 0, numberOfClusters-1);

        Matrix T = new Matrix(V.getRowDimension(),V.getColumnDimension());
        for(int i=0; i < V.getRowDimension(); i++){
            Matrix P = V.getMatrix(i, i, 0, V.getColumnDimension() - 1);
            double norm = P.normF();
            for(int j=0; j < V.getColumnDimension(); j++){
                T.set(i, j, V.get(i, j)/norm);
            }
        }
        
        /* Tijdelijk wegschrijven van de eigenvectoren naar een bestand temp.data */
        File eigenvectors = null;
        try{
            eigenvectors = File.createTempFile(String.valueOf(Math.random()), ".eigenvectors");
            FileWriter w = new FileWriter(eigenvectors);
            /* Eigenvectoren één voor één wegschrijven */
            for(int i=0; i < T.getRowDimension(); i++){
                for(int j=0; j < T.getColumnDimension(); j++){
                    w.write("" + T.get(i, j) + "");
                    if(j != (T.getColumnDimension() - 1))
                        w.write(", ");
                }
                w.write("\n");
            }
            w.flush();
            /* FileWriter sluiten*/
            w.close();

        }catch(Exception ex){
            // leeg
        }

        /* Net aangemaakt bestand inlezen om te laten verwerken door het KMeans-algoritme */
        try{
            Dataset dataset = FileHandler.loadDataset(eigenvectors,",");
            Clusterer km = new KMeans(numberOfClusters);
            Dataset[] clusters = km.cluster(dataset);            
            return clusters;
        }catch(Exception exception){
            return null;
        }
    }

    @Override
    public boolean execute() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}