package gate.plugin.learningframework.export;

import cc.mallet.types.FeatureVector;
import cc.mallet.types.Instance;
import cc.mallet.types.InstanceList;
import cc.mallet.types.Label;
import gate.plugin.learningframework.data.CorpusRepresentationMallet;
import gate.plugin.learningframework.engines.Info;
import gate.plugin.learningframework.features.FeatureExtraction;
import gate.util.GateRuntimeException;
import java.io.File;
import java.io.PrintStream;
import java.text.DecimalFormat;

/**
 * Export data as two files in MatrixMarket format.
 * This exports the attributes as indep.mtx and the targets as dep.mtx.
 * 
 * @author Johann Petrak
 */
public class CorpusExporterMatrixMarket2 extends CorpusExporter {

  @Override
  public Info getInfo() {
    Info info = new Info();
    // TODO: we should check for regression vs classification HERE
    info.algorithmClass = "gate.plugin.learningframework.engines.AlgorithmClassification";
    info.algorithmName = "DUMMY";
    info.engineClass = "DUMMY";
    info.modelClass =  "DUMMY";    
    return info;
  }

  @Override
  public void export(File directory, CorpusRepresentationMallet crm, String instanceType, String parms) {
    PrintStream outDep = null;
    PrintStream outIndep = null;
    File outFileIndep = new File(directory, "indep.mtx");
    File outFileDep = new File(directory, "dep.mtx");
    try {
      outDep = new PrintStream(outFileDep);
      outIndep = new PrintStream(outFileIndep);
    } catch (Exception ex) {
      throw new GateRuntimeException("Could not open output file ",ex);
    }
    // NOTE: the following code is based on simple reverse engineering of the format
    // as generated by python scipy.io.mmwrite for float vectors.
    
    InstanceList instances = crm.getRepresentationMallet();
    // for the MatrixMarket format we need the following information 
    // beforehand: the total number of non-zero values and the shape of 
    // the matrix (rows/columns).
    // The only way to find the total number of non-zero values is to actually
    // add them up over all the instances.
    int nrRows = instances.size();
    int nrCols = crm.getPipe().getDataAlphabet().size();
    int nrVals = 0;
    for(Instance instance : instances) {
      Object fvObj = instance.getData();
      if(fvObj instanceof FeatureVector) {
        FeatureVector fv = (FeatureVector)fvObj;
        nrVals += fv.numLocations();
      } else {
        throw new GateRuntimeException("Instance is not a feature vector but "+fvObj.getClass());
      }
    }
    System.err.println("DEBUG: rows="+nrRows+", cols="+nrCols+", vals="+nrVals);
    // TODO: check if these formats are correct!
    DecimalFormat DFf = new DecimalFormat("0.0#########");
    DecimalFormat DFi = new DecimalFormat("0");
    // write the headers of the two files
    // indep
    outIndep.println("%%MatrixMarket matrix coordinate real general\n%");
    outIndep.print(DFi.format(nrVals));
    outIndep.print(" ");
    outIndep.print(DFi.format(nrCols));
    outIndep.print(" ");
    outIndep.print(DFi.format(nrRows));
    outIndep.println();
    // dep
    outDep.println("%%MatrixMarket matrix coordinate real general\n%");
    // TODO: we could actually also count the non-zero targets above and
    // not include the zeros!
    outDep.print(DFi.format(nrRows)); // Each row has one value, non-sparse
    outDep.print(" ");
    outDep.print(DFi.format(1));
    outDep.print(" ");
    outDep.print(DFi.format(nrRows));
    outDep.println();
    
    // NOTE: MatrixMarket numbers are base-1!!
    int rowNr = 0;
    for(Instance instance : instances) {
      rowNr++;
      Boolean ignoreInstance = (Boolean)instance.getProperty(FeatureExtraction.PROP_IGNORE_HAS_MV);
      if(ignoreInstance) continue;
      Boolean haveMV = (Boolean)instance.getProperty(FeatureExtraction.PROP_HAVE_MV);      
      Object targetObj = instance.getTarget();
      double target = 0.0;
      if(targetObj == null) {
        // TODO: NOTE: we export instances with missing targets in the ARFF exporter, so 
        // we do it here too. However, other than there we always have to output a target
        // because the format requires it. We therefore always output target 0.0 
        // which is the default value, so nothing to do        
      } else if (targetObj instanceof Double) {
        target = (Double)targetObj;
      } else if(targetObj instanceof Label) {
        Label label = (Label)targetObj;
        target = label.getIndex();
      }
      outDep.print(rowNr);
      outDep.print(" ");
      outDep.print("1 ");
      outDep.println(DFf.format(target));
      // Now print all the feature values, but no MVs which will be ignored (as this is a sparse 
      // representation and we want to replace them with zero here).
      // NOTE: if the values were transformed, zero may not be the correct value to use,
      // this problem is ignored for now!!!
      Object fvObj = instance.getData();
      if(fvObj instanceof FeatureVector) {
        FeatureVector fv = (FeatureVector)fvObj;
        for(int idx : fv.getIndices()) {
          double val = fv.valueAtLocation(idx);
          if(val!=Double.NaN) {
            outIndep.print(rowNr); outIndep.print(" ");
            outIndep.print(idx+1); outIndep.print(" ");
            outIndep.println(DFf.format(val));
          }
        } // for
      } else {
        throw new GateRuntimeException("Instance is not a FeatureVector but "+fvObj.getClass());
      }
    }
  }
  
}

@@ -1715,16 +1715,93 @@
 ectors.%0A
+    // Format description see http://math.nist.gov/MatrixMarket/formats.html%0A
     %0A   
@@ -3004,19 +3004,19 @@
 ormat(nr
-Val
+Row
 s));%0A   
@@ -3126,35 +3126,35 @@
 nt(DFi.format(nr
-Row
+Val
 s));%0A    outInde

Correct mistake in format.