package yi.iu.processor.color.seg.mb;

import yi.util.io.*;
import java.util.*;
import yi.util.config.*;
import yi.util.io.input.*;
import java.awt.*;
import yi.iu.processor.resample.*;
import yi.iu.processor.morph.*;
import yi.iu.processor.color.reg.*;
import yi.util.io.output.*;
import yi.util.log.*;
import yi.iu.processor.color.*;
import yi.iu.imgdb.learn.FeatureRepresentable;
import yi.iu.imgdb.learn.RepresentableFeatureGenerator;

/**
 * The references can be found at:
 * D. Comanicu, P. Meer: "Mean shift: A robust approach toward feature space analysis."  IEEE Trans. Pattern Anal. Machine Intell., 24, 603-619, May 2002.
 * C. Christoudias, B. Georgescu, P. Meer: "Synergism in low-level vision." 16th International Conference on Pattern Recognition, Quebec City, Canada, August 2002, vol. IV, 150-155
 *
 * class MeanShiftBasedImageSegmenter doesn't implement the algorithm itself,
 * although it is possible that the implementation can be added later.  This
 * class is cooked to take the advantage of the free source code of "Mean Shift
 * Based Image Segmenter" written in C++:
 * http://www.caip.rutgers.edu/riul/research/code/EDISON/doc/help.html
 *
 * The running procedure is:
 * * resize the original image if necessary
 * * use this class (GenerateScript) to generate the running script for C++
 *   implementation
 * * use C++ implementation to segment which outputs the segmentation results
 *   in ppm
 * * use this class to read the C++ implementation output and generate
 *   segmentation results in java
 */

public class MeanShiftBasedImageSegmenter implements Configurable, RepresentableFeatureGenerator{
  static String scaled_suffix = ".s128.ppm";
//  public int REUSE = DIU.INPUT_NONE;
//  public int OUTPUT = DOU.OUTPUT_NONE;
  public int LOG_LEVEL = 3;


  public MeanShiftBasedImageSegmenter() {
    Configuration.configParameters(this);
  }

  public String[] getParameterNames()
  {
   return new String[] {
//       "REUSE",
//       "OUTPUT",
       "LOG_LEVEL",
   };
  }

  /**
   * Edison can't handle long path, so those long path should be changed
   * @param image_name
   * @return
   */
  protected static String fixLogImagePath(String image_name) {
    image_name = image_name.replaceFirst("sumimages/Cliffords_addition/buses/Single_Deck", "bus/SDeck");
    image_name = image_name.replaceFirst("sumimages/Cliffords_addition/buses/Double_Deck", "bus/DDeck");
    return image_name;
  }

  /**
   * Generate the output file name for Edison.  It also fix long image file name
   * @param image_name
   * @return
   */
  protected static String getEdisonOutputName(String image_name) {
    return (image_name + ".ms.ppm").replaceFirst("ppm.ms.ppm", "ms.ppm").replaceFirst(".jpg.s128.ms.ppm", ".s128.ms.ppm");
  }

  public ColorLabeledRgnFts getColorLabeledRgnFts(int[] edison, int width, int height, int old_w, int old_h) {
    // classify the data with the edison result
    HashMap color2label = new HashMap();
    int label_num = 0;
    int[] labels = new int[edison.length];
    for (int i=0; i<edison.length; i++) {
      Integer c = new Integer(edison[i]);
      if (!color2label.containsKey(c)) {
        color2label.put(c, new Integer(label_num++));
      }
      labels[i] = ((Integer)color2label.get(c)).intValue();
    }

    double[][] means = new double[color2label.size()][3];
    for (Iterator i = color2label.keySet().iterator(); i.hasNext(); ) {
      Integer c = (Integer)i.next();
      int label = ((Integer)color2label.get(c)).intValue();
      int color = c.intValue();
      // convert rgb values of each pixel to hsb
      int r = (color >> 16) & 0xff;
      int g = (color >> 8)  & 0xff;
      int b =  color        & 0xff;
      float[] hsbvals = new float[3];
      java.awt.Color.RGBtoHSB(r, g, b, hsbvals);
      means[label][0] = hsbvals[0];
      means[label][1] = hsbvals[1];
      means[label][2] = hsbvals[2];
    }

    ColorLabeledRgnFts ds = new ColorLabeledRgnFts(means, labels, width, height,
        old_w, old_h, false);
    return ds;
  }

//  public ColorLabeledRgnFts getReusableColorLabeledRgnFts(int[] pixels, int width, int height, int old_w, int old_h)
//  {
//
//     ColorLabeledRgnFts rst = (ColorLabeledRgnFts)reuseColorLabeledRgnFts(REUSE);
//
//     if (rst == null) {
//       rst = getColorLabeledRgnFts(pixels, width, height, old_w, old_h);
//     }
//
//     outputColorLabeledRgnFts(OUTPUT, rst);
//
//     return rst;
//  }
//
//  public Object reuseColorLabeledRgnFts(int reuse_level)
//  {
//   if (reuse_level != DIU.INPUT_NONE) {
//     try {
//       ColorLabeledRgnFts rst = (ColorLabeledRgnFts)DIU.in(
//           reuse_level,
//           DOU.shortName(this.getClass().getName()),
//           "ColorLabeledRgnFts",
//           Class.forName("yi.iu.processor.color.reg.ColorLabeledRgnFts"));
//       return rst;
//     }
//     catch (Exception e) {
//       LogHandler.log(this, LOG_LEVEL, 1, "Can't Reuse ColorLabeledRgnFts:\n" + e);
//     }
//   }
//   return null;
//  }
//
//  void outputColorLabeledRgnFts(int output_level, ColorLabeledRgnFts ds)
//  {
//   if (output_level != DOU.OUTPUT_NONE) {
//     try {
//       DOU.out(
//           output_level,
//           DOU.shortName(this.getClass().getName()),
//           "ColorLabeledRgnFts",
//           ds);
//     } catch (DataOutputException e) {
//       LogHandler.log(this, LOG_LEVEL, 1, e.toString());
//     }
//   }
//  }

  public CIELabLabeledRgnFts getCIELabLabeledRgnFts(int[] edison, int width, int height, int old_w, int old_h) {
    // classify the data with the edison result
    HashMap color2label = new HashMap();
    int label_num = 0;
    int[] labels = new int[edison.length];
    for (int i=0; i<edison.length; i++) {
      Integer c = new Integer(edison[i]);
      if (!color2label.containsKey(c)) {
        color2label.put(c, new Integer(label_num++));
      }
      labels[i] = ((Integer)color2label.get(c)).intValue();
    }

    double[][] means = new double[color2label.size()][3];
    for (Iterator i = color2label.keySet().iterator(); i.hasNext(); ) {
      Integer c = (Integer)i.next();
      int label = ((Integer)color2label.get(c)).intValue();
      int color = c.intValue();
      // convert rgb values of each pixel to lab
      CIELab.rgb2lab(color, means[label]);
    }

    CIELabLabeledRgnFts ds = new CIELabLabeledRgnFts(means, labels, width, height,
        old_w, old_h, false);
    return ds;
  }

//  public CIELabLabeledRgnFts getReusableCIELabLabeledRgnFts(int[] pixels, int width, int height, int old_w, int old_h)
//  {
//
//     CIELabLabeledRgnFts rst = (CIELabLabeledRgnFts)reuseCIELabLabeledRgnFts(REUSE);
//
//     if (rst == null) {
//       rst = getCIELabLabeledRgnFts(pixels, width, height, old_w, old_h);
//     }
//
//     outputCIELabLabeledRgnFts(OUTPUT, rst);
//
//     return rst;
//  }
//
//  public Object reuseCIELabLabeledRgnFts(int reuse_level)
//  {
//   if (reuse_level != DIU.INPUT_NONE) {
//     try {
//       CIELabLabeledRgnFts rst = (CIELabLabeledRgnFts)DIU.in(
//           reuse_level,
//           DOU.shortName(this.getClass().getName()),
//           "CIELabLabeledRgnFts",
//           Class.forName("yi.iu.processor.color.reg.CIELabLabeledRgnFts"));
//       return rst;
//     }
//     catch (Exception e) {
//       LogHandler.log(this, LOG_LEVEL, 1, "Can't Reuse CIELabLabeledRgnFts:\n" + e);
//     }
//   }
//   return null;
//  }
//
//  void outputCIELabLabeledRgnFts(int output_level, CIELabLabeledRgnFts ds)
//  {
//   if (output_level != DOU.OUTPUT_NONE) {
//     try {
//       DOU.out(
//           output_level,
//           DOU.shortName(this.getClass().getName()),
//           "CIELabLabeledRgnFts",
//           ds);
//     } catch (DataOutputException e) {
//       LogHandler.log(this, LOG_LEVEL, 1, e.toString());
//     }
//   }
//  }

  /**
   * generate the running script for C++ implementation.
   */
  public static class GenerateScript {
    public final static String USAGE = "Usage: -i image_list -s spatial_bandwidth -r range_bandwidth -m minimum_region_area -p speed_up";

    public static void main(String[] args) {
      // get the image list file name
      String img_list_file = ReadArgu.getOption('i', args);

      // image list
      String[] images = null;
      if (img_list_file != null) {
        try {
          images = ReadArgu.readStrings(img_list_file);
        } catch (Exception e) {
          e.printStackTrace();
          System.exit(1);
        }
      } else {
        System.err.println(USAGE);
        System.exit(1);
      }

      // path
      String from_path = Configuration.getParameter("FEATURE_PATH", "");
      String to_path = Configuration.getParameter("FEATURE_PATH", "");

      String spatial_bandwidth = ReadArgu.getOption(
          's', args, "16");
      String range_bandwidth = ReadArgu.getOption(
          'r', args, "6.5");
      String minimum_region_area = ReadArgu.getOption(
          'm', args, "64");
      String speed_up = ReadArgu.getOption(
          'p', args, "MEDIUM");


      //Specify mean shift parameters
      System.out.println("SpatialBandwidth = " + spatial_bandwidth + ";");
      System.out.println("RangeBandwidth = " + range_bandwidth + ";");
      System.out.println("MinimumRegionArea = " + minimum_region_area + ";");
      System.out.println("Speedup = " + speed_up + ";");

      //Display progress
      System.out.println("DisplayProgress ON;");

      for (int i= 0; i<images.length; i++) {
        String image_name = images[i] + scaled_suffix;
        image_name = fixLogImagePath(image_name);

        //Load an image to be segmented
        System.out.println("Load('" + from_path + image_name + "', IMAGE);");

        //Segment the image
        System.out.println("Segment;");

        //Save the result:

        //The segmented image...
        System.out.println("Save('" + to_path + getEdisonOutputName(image_name) + "', PPM, SEGM_IMAGE);");
      }

    }
  }

//  /**
//   * Generate the ColorLabeledRgnFts (region features with labels)
//   */
//  public static class GenColorLabeledRgnFts {
//
//    public static void main(String[] args)
//    {
//      // init configuration
//      Configuration.init(args);
//
//      // get the image name
//      String img_name = ReadArgu.getOption('i', args);
//      if (img_name == null) {
//        System.err.println("Usage: -i image_name");
//        System.exit(1);
//      }
//
//      // original image path
//      String from_path = ReadArgu.getOption('f', args, "");
//      // path of the edison output
//      String data_path = ReadArgu.getOption('d', args, "");
//      // path to output the result
//      String to_path = ReadArgu.getOption('t', args, "");
//
//      // get the scale
//      String scale_str = ReadArgu.getOption('s', args);
//      int scale = 1;
//      if (scale_str != null) {
//        scale = Integer.parseInt(scale_str);
//      }
//
//      try {
//        // read in original image
//        Dimension old_d = new Dimension();
//        System.out.println("original image : " + from_path + img_name);
//        int[] pixels = yi.util.io.ImageIOUtil.read(from_path + img_name, old_d);
//
//        // read in the edison result
//        Dimension d = new Dimension();
//        if (scale != 1) img_name = img_name + ".s" + scale_str;
//        String edison_file = getEdisonOutputName(img_name);
//        System.out.println("edison result : " + data_path + edison_file);
//        int[] edison = yi.util.ImageUtil.read(data_path + edison_file, d);
//
//        // process
//        Configuration.setParameter("SOURCE_NAME", img_name);
//        Configuration.setParameter("DEST_PATH", to_path);
//        yi.util.io.File.makeDirectory(to_path, false);
//
//        yi.util.ImageUtil.setPixels(pixels);
//        yi.util.ImageUtil.setDimension(d);
//
//        MeanShiftBasedImageSegmenter processor = new MeanShiftBasedImageSegmenter();
//        ColorLabeledRgnFts ds = processor.getReusableColorLabeledRgnFts(
//            edison, d.width, d.height, old_d.width, old_d.height);
//
//        int[] demo = ds.colorDemo();
//        System.out.println("output : " + to_path + img_name + ".ms.rgnc.gif");
//        yi.util.io.OutputResult.outputGIFImage(to_path + img_name + ".ms.rgnc.gif", demo, d.width,
//                                               d.height, false);
//
//        demo = yi.util.ImageUtil.makeNewImageFromLabel(ds.m_labels, false);
//        System.out.println("output : " + to_path + img_name + ".ms.rgn.gif");
//        yi.util.io.OutputResult.outputGIFImage(to_path + img_name + ".ms.rgn.gif", demo, d.width,
//                                               d.height, false);
//      } catch (Exception e) {
//        e.printStackTrace();
//      }
//
//      System.exit(0);
//     }
//
//  }
//
//  /**
//   * Generate the ColorLabeledRgnFts (region features with labels)
//   */
//  public static class GenCIELabLabeledRgnFts {
//
//    public static void main(String[] args)
//    {
//      // init configuration
//      Configuration.init(args);
//
//      // get the image name
//      String img_name = ReadArgu.getOption('i', args);
//      if (img_name == null) {
//        System.err.println("Usage: -i image_name");
//        System.exit(1);
//      }
//
//      // original image path
//      String from_path = ReadArgu.getOption('f', args, "");
//      // path of the edison output
//      String data_path = ReadArgu.getOption('d', args, "");
//      // path to output the result
//      String to_path = ReadArgu.getOption('t', args, "");
//
//      // get the scale
//      String scale_str = ReadArgu.getOption('s', args);
//      int scale = 1;
//      if (scale_str != null) {
//        scale = Integer.parseInt(scale_str);
//      }
//
//      try {
//        // read in original image
//        Dimension old_d = new Dimension();
//        System.out.println("original image : " + from_path + img_name);
//        int[] pixels = yi.util.io.ImageIOUtil.read(from_path + img_name, old_d);
//
//        // read in the edison result
//        Dimension d = new Dimension();
//        if (scale != 1) img_name = img_name + ".s" + scale_str;
//        String edison_file = getEdisonOutputName(img_name);
//        System.out.println("edison result : " + data_path + edison_file);
//        int[] edison = yi.util.ImageUtil.read(data_path + edison_file, d);
//
//        // process
//        Configuration.setParameter("SOURCE_NAME", img_name);
//        Configuration.setParameter("DEST_PATH", to_path);
//        yi.util.io.File.makeDirectory(to_path, false);
//
//        yi.util.ImageUtil.setPixels(pixels);
//        yi.util.ImageUtil.setDimension(d);
//
//        MeanShiftBasedImageSegmenter processor = new MeanShiftBasedImageSegmenter();
//        CIELabLabeledRgnFts ds = processor.getReusableCIELabLabeledRgnFts(
//            edison, d.width, d.height, old_d.width, old_d.height);
//
//        int[] demo = ds.colorDemo(null);
//        System.out.println("output : " + to_path + img_name + ".ms.rgnc.gif");
//        yi.util.io.OutputResult.outputGIFImage(to_path + img_name + ".ms.rgnc.gif", demo, d.width,
//                                               d.height, false);
//
//        demo = yi.util.ImageUtil.makeNewImageFromLabel(ds.m_labels, false);
//        System.out.println("output : " + to_path + img_name + ".ms.rgn.gif");
//        yi.util.io.OutputResult.outputGIFImage(to_path + img_name + ".ms.rgn.gif", demo, d.width,
//                                               d.height, false);
//      } catch (Exception e) {
//        e.printStackTrace();
//      }
//
//      System.exit(0);
//     }
//
//  }

  public FeatureRepresentable generateFeatureRepresentable(String feature_type, int[] pixels, int width, int height) {
    String img_name = Configuration.getParameter("SOURCE_NAME");
    String ft_path = Configuration.getParameter("FEATURE_PATH");

//    if (img_name.indexOf("crystal") >=0) img_name = img_name.replaceFirst("pgm.png", "pgm");

    // read in the edison result
    String edison_file = getEdisonOutputName(ft_path + img_name + scaled_suffix);
    Dimension d = new Dimension();
    int[] edison = yi.util.ImageUtil.read(edison_file, d);

    FeatureRepresentable rst = null;

    if (feature_type.indexOf("CIELabLabeledRgnFts") >= 0) {
      rst = getCIELabLabeledRgnFts(edison, width, height, width, height);
    }
//    else if (feature_type.indexOf("ColorLabeledRgnFts") >= 0) {
//      rst = getColorLabeledRgnFts(edison, width, height, width, height);
//    }
    return rst;

  }

}