package yi.iu.processor.clc;

import java.awt.*;
import java.util.*;

import yi.iu.processor.color.*;
import yi.iu.processor.ort.*;
import yi.iu.region.*;
import yi.util.config.*;
import yi.util.io.*;
import yi.util.io.input.*;
import yi.util.io.output.*;
import yi.util.log.*;
import yi.iu.processor.clc.CLC_LineStruct;



/**
 * Construct Orientation Consistent Line Cluster.  Class CLC_Clustering is
 * the super class of all the consistent line clustering classes.  Each sub
 * class must implement the method
 *
 * <p><code>
 * public LineClusterSets getClusters(LineClusterSet lines, int[] rgb, int width, int height);
 * </code></p>
 *
 * The "getClusters" method is the kernal of each sub clustering algorithm
 *
 * Class CLC_Clustering also provides a general method to run line clustering
 * algorithms in a chain.  Just specify the parameters "CLC_CLUSTERING_n" in
 * the configuration file and they will be excuted in the order of n.  For
 * example, to cluster the lines using color, orientation and spatially CLC,
 * put these lines in config.properties:
 *
 * <p><code>
 * CLC_CLUSTERING_1 = yi.iu.processor.clc.CLC_Color
 * CLC_CLUSTERING_2 = yi.iu.processor.clc.CLC_Orientation
 * CLC_CLUSTERING_3 = yi.iu.processor.clc.CLC_Spatial
 * </code></p>
 */
public abstract class CLC_Clustering implements Configurable, RstReusable
{

  public int    REUSE  = DIU.INPUT_NONE;
  public int    OUTPUT = DOU.OUTPUT_NONE;
  public boolean SKIP_EXISTING = true;

  public int    LOG_LEVEL = 3;

  /**
   * Demo the resultant line clusters.  Different cluster lines are drawn in
   * different color
   */
  public boolean DEMO_CLC = true;

  /**
   * Demo the resultant line clusters with their bounds.  Different cluster
   * lines are drawn in different color
   */
  public boolean DEMO_CLC_WITH_BOUNDS = false;

  /**
   * Demo the resultant line clusters and each cluster is represented by an
   * ellipse
   */
  public boolean DEMO_CLC_IN_ELLIPSE_REGION = false;

  /**
   * Demo the resultant line clusters and each cluster is bounded by a
   * rectangle
   */
  public boolean DEMO_CLC_IN_RECTANGLE_REGION = false;

  /**
   * Demo the resultant line clusters one by one
   */
  public boolean DEMO_CLC_ONE_BY_ONE = false;

  /**
   * Demo the line clusters before and after clustering
   */
  public boolean DEMO_STEPS = false;

  public boolean SHOW_PROGRESS = false;

  /**
   * For subclass to call.  In the subclasses, getParameterNames() just add
   * more parameters
   *
   * @more_params parameters more parameters
   * @return
   */
  public String[] getParameterNames(String[] more_params) {

    String[] basic_params = new String[] {
      "LOG_LEVEL",
      "REUSE",
      "OUTPUT",
      "SKIP_EXISTING",
      "DEMO_CLC",
      "DEMO_CLC_WITH_BOUNDS",
      "DEMO_CLC_IN_ELLIPSE_REGION",
      "DEMO_CLC_IN_RECTANGLE_REGION",
      "DEMO_CLC_ONE_BY_ONE",
      "DEMO_STEPS",
      "SHOW_PROGRESS",
    };

    String[] params = new String[basic_params.length + (more_params == null ? 0 : more_params.length)];
    for (int i=0; i<basic_params.length; i++) params[i] = basic_params[i];
    if (more_params != null) {
      for (int i=0; i<more_params.length; i++) {
        params[basic_params.length + i] = more_params[i];
      }
    }
    return params;
  }

  /**
   * Reuse the line clusters
   * @param reuse_level
   * @return
   */
  public Object reuse(int reuse_level) {
    if (reuse_level != DIU.INPUT_NONE) {
      try {
        LineClusterSets rst = (LineClusterSets)DIU.in(
            reuse_level,
            DOU.shortName(this.getClass().getName()),
            "LineClusterSets",
            Class.forName("yi.iu.processor.clc.LineClusterSets"));
        return rst;
      }
      catch (Exception e) {
        LogHandler.log(this, LOG_LEVEL, 1, "Can't Reuse LineClusterSets:\n" + e);
      }
    }
    return null;
  }

//  /**
//   * This is a "patch" version of non-static method of reuse().  Since this
//   * class (CLC_Clustering) is an abstract class, (but it does generate reusable
//   * object), so there is possible to new an instance of it and then call
//   * it.reuse().  This static version of "reuse" method allow the call like
//   * CLC_CLustering.reuseFromCLCCluster().
//   * @param reuse_level
//   * @return
//   */
//  public static Object reuseFromCLCClustering(int reuse_level) {
//    if (reuse_level != DIU.INPUT_NONE) {
//      try {
//        LineClusterSets rst = (LineClusterSets)DIU.in(
//            reuse_level,
//            "CLC_Clustering",
//            "LineClusterSets",
//            Class.forName("yi.iu.processor.clc.LineClusterSets"));
//        return rst;
//      }
//      catch (Exception e) {
//        e.printStackTrace();
//      }
//    }
//    return null;
//  }

  /**
   * Output the line clusters for later re-use
   * @param output_level
   * @param ds
   */
  void output(int output_level, LineClusterSets ds){
    if (output_level != DOU.OUTPUT_NONE) {
      try {
        DOU.out(
            output_level,
            DOU.shortName(this.getClass().getName()),
            "LineClusterSets",
            ds);
      } catch (DataOutputException e) {
        LogHandler.log(this, LOG_LEVEL, 1, e.toString());
      }
    }
  }

  /**
   * Return clustered line clusters with "reuse" turned on.  It will read the
   * pass results if "REUSE" is flaged and output the current results if
   * "OUTPUT" is flaged.
   * @param lines
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets getReusableClusteres(LineClusterSet lines, int[] rgb, int width, int height) {
      LineClusterSets rst = (LineClusterSets)reuse(REUSE);

      if (rst == null) {
        rst = getClusters(lines, rgb, width, height);
      }

      output(OUTPUT, rst);

      return rst;

  }

  /**
   * Return clustered line clusters with "reuse" turned on.  It will read the
   * pass results if "REUSE" is flaged and output the current results if
   * "OUTPUT" is flaged.
   * @param lines
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets getReusableClusteres(LineClusterSets lines, int[] rgb, int width, int height) {
      LineClusterSets rst = (LineClusterSets)reuse(REUSE);

      if (rst == null) {
        rst = getClusters(lines, rgb, width, height);
      }

      output(OUTPUT, rst);

      return rst;

  }

  /**
   * Clustering the lines.  This method must be implemented in the subclasses.
   * @param lines
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  abstract public LineClusterSets getClusters(LineClusterSet lines, int[] rgb, int width, int height);

  int output_counter = 0;

  /**
   * Clustering each cluster in the given line clusters.
   * @param lines
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets getClusters(LineClusterSets line_clusters, int[] rgb, int width, int height) {

    // for output demo images
    String img_name = Configuration.getStringParameter("SOURCE_NAME", "");

    // new line cluster sets
    LineClusterSets results = new LineClusterSets(width, height);

    // deal with each LineClusterSet one by one
    Iterator i = line_clusters.values().iterator();
    while (i.hasNext()) {
      if (SHOW_PROGRESS) {
        System.out.println("processing on #" + output_counter);
        if (output_counter == 0) {
          Configuration.setParameter("DEBUG", "true");
        } else {
          Configuration.setParameter("DEBUG", "false");
        }
      }

      LineClusterSet lines = (LineClusterSet)i.next();
      if (Configuration.getStringParameter("DEBUG", "false").compareTo("true") == 0) {
        int it_count = 0;
        for (Iterator it = lines.iterator(); it.hasNext(); it_count++) {
          ORTLine l = (ORTLine)it.next();
          if (l.getX1()<-23 || l.getX1()>-7) continue;
          if (l.getX2()<-23 || l.getX2()>-7) continue;
          if (l.getY1()<-10 || l.getY1()>125) continue;
          if (l.getY2()<-10 || l.getY2()>125) continue;
          System.out.println("" + it_count + " " + l.getX1() + " " + l.getX2());
          System.out.println(" " + l.getY1() + " " + l.getY2());
        }
      }

      // for output demo images
      String prefix = img_name + "." +
          yi.util.data.Conversion.toString(output_counter++, 2);
      // demo the line cluster before clustering
      if (DEMO_STEPS) {
        int[] demo = lines.demo(rgb, new Dimension(width, height), true, false, false, true);
        OutputResult.outputColorImage(prefix + ".a.ppm", demo, width, height);
      }

      LineClusterSets clcs = getClusters(lines, rgb, width, height);
      // add each resultant line clusters to the final results
      results.add(clcs);

      if (DEMO_STEPS) {
        clcs.outputClustersOneByOne(rgb, new Dimension(width, height), prefix + ".b",
                                    true, false, false, true, false);
      }
    }

    results = postProcess(results, rgb, width, height);

    // return the final results
    return results;
  }

  LineClusterSets postProcess(LineClusterSets line_clusters, int[] rgb, int width, int height) {
    return line_clusters;
  }

  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);
    }

    String from_path = ReadArgu.getOption('f', args, "");
    String to_path = ReadArgu.getOption('t', args, "");

    // skip if SKIP_EXISTING
    // first get the last CLC_Clustering processor
    int last = 1;
    String last_method_name = null;
    for (; true; last ++) {
      String param_name = "CLC_CLUSTERING_" + last;
      String tmp = Configuration.getStringParameter(
        param_name, null);
      if (last_method_name != null) last_method_name = tmp;
      else break;
    }

    if (last_method_name != null) {
      try {
        Class clustering_class = Class.forName(last_method_name);
        CLC_Clustering dummy  = (CLC_Clustering)clustering_class.newInstance();
        if (dummy.SKIP_EXISTING) {
          String out_name = DOU.getOutputFileName(
                dummy.OUTPUT,
                img_name,
                DOU.shortName(last_method_name),
                "LineClusterSets");
          java.io.File out = new java.io.File(out_name);
          if (out.exists()) {
            System.out.println(out + " exists.  Skip");
            System.exit(0);
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }


    // process
    try {
      // read in image
      Dimension d = new Dimension();
      // this requires java 1.5
      int[] pixels = yi.util.io.ImageIOUtil.read(from_path + img_name, d);
//      Image image = yi.util.io.ImageReader.readImageFile(from_path + img_name);
//      int[] pixels = yi.util.io.ImageReader.getPixels(image, d);


      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);

      // timing
      long startTime = System.currentTimeMillis();

      // find all the lines in an image
      yi.iu.processor.ort.DPU_Fex dp = new DPU_Fex();
      Vector[] segs= dp.getLinesAndArcs(pixels, d.width, d.height);
      Vector lines= segs[0];
      // put all lines in a line cluster
      LineClusterSet lcs = new LineClusterSet(lines, d.width, d.height);
      // construct a set of line cluster containing only one set
      LineClusterSets lcss = new LineClusterSets(d.width, d.height);
      lcss.put(lcs, lcs);

      // clustering the lines use the all the clustering method specified in the
      // configurations
      String clustering_method_name = null;
      CLC_Clustering clc_clustering = null;
      for (int i = 1; true; i ++) {
        String param_name = "CLC_CLUSTERING_" + i;
        clustering_method_name = Configuration.getStringParameter(
          param_name, null);
        if (clustering_method_name == null) break;
        System.out.println("clusterig step " + i + ": " + clustering_method_name);
        try {
          Class clustering_class = Class.forName(clustering_method_name);
          clc_clustering = (CLC_Clustering)clustering_class.newInstance();
        } catch (Exception e) {
          e.printStackTrace();
        }
        lcss = clc_clustering.getReusableClusteres(lcss, pixels, d.width, d.height);
      }

      if (clc_clustering == null) { // no method was used
        System.err.println("specifiy the clustering methods using CLC_CLUSTERING_n");
        System.exit(0);
      }

      // print out timing
      long endTime = System.currentTimeMillis();
      System.out.println("running time: " + (endTime-startTime));

      // get the shor name of the last clustering method
      clustering_method_name = DOU.shortName(clc_clustering.getClass().getName());

      // demo the line clusters
      if (clc_clustering.DEMO_CLC) {
        int[] lineimg = lcss.demo(d.width, d.height, false, 2);
        OutputResult.outputGIFImage(
          to_path + img_name + '.' + clustering_method_name + ".gif", lineimg, d.width, d.height);
      }

      if (clc_clustering.DEMO_CLC_WITH_BOUNDS) {
        int[] lineimg = lcss.demo(d.width, d.height, true);
        OutputResult.outputGIFImage(
          to_path + img_name + '.' + clustering_method_name + ".polygon" + ".gif", lineimg, d.width, d.height);
      }

      // demo the line cluster ellipse regions (only the biggest)
      if (clc_clustering.DEMO_CLC_IN_ELLIPSE_REGION) {
        int[] lineimg = lcss.demoRegions(d.width, d.height);
        OutputResult.outputGIFImage(
          to_path + img_name + '.' + clustering_method_name + ".ellipse.gif", lineimg, d.width, d.height);
      }

      // demo the line cluster ellipse regions (only the biggest)
      if (clc_clustering.DEMO_CLC_IN_RECTANGLE_REGION) {
        int[] lineimg = lcss.demoRectangles(d.width, d.height);
        OutputResult.outputGIFImage(
          to_path + img_name + '.' + clustering_method_name + ".rct.gif", lineimg, d.width, d.height);
      }

      // illustrate each line cluster
      if (clc_clustering.DEMO_CLC_ONE_BY_ONE) {
        lcss.outputClustersOneByOne(null, d, to_path + img_name + '.' + clustering_method_name,
                                    true, true, false, true, false);
      }

    } catch (Exception e) {
      System.err.println("CLC_Clustering Terminated.");
      e.printStackTrace();
    }

    System.exit(0);
  }

  /**
   * For Marta to run the line clustering on her lines
   */
  public static class ForMarta {
    public static Vector parse(String seg_file, Dimension d) throws java.io.IOException{
      double[][] data = yi.util.io.ReadArgu.readMatrix(seg_file, false);
      d.width = (int)data[0][0];
      d.height = (int)data[0][1];

      Vector lines = new Vector(data.length - 1);
      for (int i=1; i<data.length; i++) {
        ORTLine line = new ORTLine(data[i][1], -data[i][2], data[i][3], -data[i][4]);
        lines.add(line);
      }
      return lines;
    }

    public final static String USAGE = "Usage: -i image_name -s segmentation_file_name";
    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);
        System.exit(1);
      }

      Configuration.setParameter("SOURCE_NAME", img_name);

      // get the segmentation file name
      String seg_name = ReadArgu.getOption('s', args);
      if (seg_name == null) {
        System.err.println(USAGE);
        System.exit(1);
      }

      // process
      try {
        // read in image
        // read in image
        Dimension d = new Dimension();
        int[] rgb = yi.util.io.ImageIOUtil.read(img_name, d);

        // parse segmentation file
        Vector lines = parse(seg_name, d);
        int width = d.width, height = d.height;

        // timing
        long startTime = System.currentTimeMillis();

        // put all lines in a line cluster
        LineClusterSet lcs = new LineClusterSet(lines, d.width, d.height);
        // construct a set of line cluster containing only one set
        LineClusterSets line_clusters = new LineClusterSets(d.width, d.height);
        line_clusters.put(lcs, lcs);

        // clustering the lines use the all the clustering method specified in the
        // configurations
        String clustering_method_name = null;
        CLC_Clustering clc_clustering = null;
        for (int i = 1; true; i ++) {
          String param_name = "CLC_CLUSTERING_" + i;
          clustering_method_name = Configuration.getStringParameter(
            param_name, null);
          if (clustering_method_name == null) break;
          System.out.println("clusterig step " + i + ": " + clustering_method_name);
          try {
            Class clustering_class = Class.forName(clustering_method_name);
            clc_clustering = (CLC_Clustering)clustering_class.newInstance();
          } catch (Exception e) {
            e.printStackTrace();
          }
          line_clusters = clc_clustering.getReusableClusteres(line_clusters, rgb, d.width, d.height);
        }

        if (clc_clustering == null) { // no method was used
          System.err.println("specifiy the clustering methods using CLC_CLUSTERING_n");
          System.exit(0);
        }
//
//        // clustering the lines use the all the clustering method specified in the
//        // first color
//        CLC_Clustering clc_clustering = new CLC_Color();
//        line_clusters = clc_clustering.getReusableClusteres(line_clusters, rgb, width, height);
//
//        // second orientation
//        clc_clustering = new yi.iu.processor.clc.CLC_Orientation();
//        line_clusters = clc_clustering.getReusableClusteres(line_clusters, rgb, width, height);
//
//        // third spatial
//        clc_clustering = new yi.iu.processor.clc.CLC_Spatial();
//        line_clusters = clc_clustering.getReusableClusteres(line_clusters, rgb, width, height);
//
//        // fourth.1 intrarelationship
//        clc_clustering = new CLC_Intrarelationship();
//        LineClusterSets intra_clusters = clc_clustering.getReusableClusteres(line_clusters, rgb, width, height);
//
//        // fourth.2 interrelationship
//        clc_clustering = new CLC_Interrelationship();
//        LineClusterSets inter_clusters = clc_clustering.getReusableClusteres(line_clusters, rgb, width, height);
//
//        // combine the result
//        LineClusterSets combined = new LineClusterSets(width, height);
//        combined.add(inter_clusters);
//        combined.add(intra_clusters);
//
//        double POST_MERGE_THRESHOLD = 0.75;
//        LineClusterSets lcss = LineClusterSets.mergeThoseBeenCovered(combined,
//            POST_MERGE_THRESHOLD);

        // print out timing
        long endTime = System.currentTimeMillis();
        System.out.println("running time: " + (endTime - startTime));

        // demo the line clusters
        if (clc_clustering.DEMO_CLC) {
          int[] lineimg = line_clusters.demo(d.width, d.height, false, 2);
          OutputResult.outputGIFImage(
              img_name + ".clc.gif",
              lineimg, d.width, d.height);
        }

        if (clc_clustering.DEMO_CLC_WITH_BOUNDS) {
          int[] lineimg = line_clusters.demo(d.width, d.height, true);
          OutputResult.outputGIFImage(
              img_name + ".clc.polygon.gif", lineimg, d.width, d.height);
        }

        // demo the line cluster ellipse regions (only the biggest)
        if (clc_clustering.DEMO_CLC_IN_ELLIPSE_REGION) {
          int[] lineimg = line_clusters.demoRegions(d.width, d.height);
          OutputResult.outputGIFImage(
              img_name + ".clc.ellipse.gif",
              lineimg, d.width, d.height);
        }

        // demo the line cluster rectangle
        if (clc_clustering.DEMO_CLC_IN_RECTANGLE_REGION) {
          int[] lineimg = line_clusters.demoRectangles(d.width, d.height);
          OutputResult.outputGIFImage(
              img_name + ".clc.rct.gif",
              lineimg, d.width, d.height);
        }
      }
      catch (Exception e) {
        System.err.println("CLC_Clustering Terminated.");
        e.printStackTrace();
      }

      System.exit(0);
    }
  }

}