package yi.iu.processor.contour;

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

import yi.iu.processor.canny.*;
import yi.iu.processor.morph.*;
import yi.util.config.*;
import yi.util.io.*;
import yi.util.io.output.*;

/**
 * This code is used to detect the contour of medical images as in cv011.  The
 * idea is: <br/>
 *
 * First edge is detected in the image, then a wheel is put in the region to
 * be detected.  The wheel can be drived wherever it can go, but it can drive
 * out of the boundary through the small leaks.  The whole region reached by the
 * wheel is the region detected.<br/>
 *
 * I tried mimic the wheel driving solution and found it takes too much time, so
 * I changed to use scan instead of to grow from the seed.<br/>
 *
 * I will keep all the code here to save my work, but I will start a new file to
 * use the simple method. (see new AckCountour.java)
 */
public class SeedAndWheel {
  int m_seed_x, m_seed_y;
  int m_filter_struct = Morph.DISK;
  int m_filter_size;
  int m_wheel_struct = Morph.DISK;
  int m_wheel_size;

  public SeedAndWheel(int seed_x, int seed_y, int filter_size, int wheel_size) {
    m_seed_x = seed_x;
    m_seed_y = seed_y;
    m_filter_size = filter_size;
    m_wheel_size = wheel_size;
  }
  public int[] getContour(int[] binary, int width, int height) {
    int[] rst = filter(binary, width, height);
    rst = wheel(rst, width, height);
    return rst;
  }

  public int[] filter(int[] binary, int width, int height) {
    // filter
    if (m_filter_size > 0) {
      boolean[][] filter = Morph.get2DStruct(m_filter_struct, m_filter_size);
      boolean[][] inReg = new boolean[height][width];
      boolean[][] considered = new boolean[height][width];

      LinkedList toSearch = new LinkedList();
      for (int i=0;i<height;i++) {
        for (int j=0;j<width;j++) {
          inReg[i][j]=false;
          considered[i][j]=false;
        }
      }
      toSearch.addLast(new Dimension(m_seed_x, m_seed_y));
      considered[m_seed_y][m_seed_x]=true;
      int[] filtered = (int[])binary.clone();
      filterUnconditionally(filtered, width, height, filter, m_seed_x, m_seed_y, inReg);

      // add a no-problem set to speedup
      Dilation dilation = new Dilation(m_filter_struct, m_filter_size);
      int[] no_problem_set = dilation.getProcessed(binary, new Dimension(width, height));
//      yi.util.ImageUtil.makeImageFromBinary(no_problem_set);
//      OutputResult.outputColorImage("no_p.gif", no_problem_set, width, height);

      for (int h=0;h<height;h++) {
        for (int w=0;w<width;w++) {
          if (no_problem_set[h*width+w]==IN_CONTOUR) continue;
          filter(filtered, width, height, filter, w, h, inReg);
        }
      }

//      int count=0;
//      while (!toSearch.isEmpty()) {
//        System.err.println("count="+count+",size="+toSearch.size());
//        count++;
//        Dimension xy = (Dimension)toSearch.removeFirst();
//        int x=xy.width, y=xy.height;
//        dump(filtered, width, height, x, y, "x");
//        filter(filtered, width, height, filter, x, y, inReg);
//        dump(filtered, width, height, x, y, "x");
//        if (x+1<width&&!considered[y][x+1]&&isFilterable(inReg, width, height, filter, x+1, y)) {
//          toSearch.addLast(new Dimension(x+1, y));
//          considered[y][x+1]=true;
//          dump(filtered, width, height, x+1, y, "+");
//        }
//        if (x-1>0&&!considered[y][x-1]&&isFilterable(inReg, width, height, filter, x-1, y)) {
//          toSearch.addLast(new Dimension(x-1, y));
//          considered[y][x-1]=true;
//          dump(filtered, width, height, x-1, y, "+");
//        }
//        if (y+1<height&&!considered[y+1][x]&&isFilterable(inReg, width, height, filter, x, y+1)) {
//          toSearch.addLast(new Dimension(x, y+1));
//          considered[y+1][x]=true;
//          dump(filtered, width, height, x, y+1, "+");
//        }
//        if (y-1>0&&!considered[y-1][x]&&isFilterable(inReg, width, height, filter, x, y-1)) {
//          toSearch.addLast(new Dimension(x, y-1));
//          considered[y-1][x]=true;
//          dump(filtered, width, height, x, y-1, "+");
//        }
//      }
      return filtered;

    }
    return binary;
  }

  public int[] wheel(int[] binary, int width, int height) {
    // filter
    if (m_wheel_size > 0) {
      boolean[][] filter = Morph.get2DStruct(Morph.DISK, m_wheel_size);
      boolean[][] inReg = new boolean[height][width];
      boolean[][] considered = new boolean[height][width];

      LinkedList toSearch = new LinkedList();
      for (int i=0;i<height;i++) {
        for (int j=0;j<width;j++) {
          inReg[i][j]=false;
          considered[i][j]=false;
        }
      }
      toSearch.addLast(new Dimension(m_seed_x, m_seed_y));
      considered[m_seed_y][m_seed_x]=true;
      int[] filtered = (int[])binary.clone();

      // add a no-problem set to speedup
      Dilation dilation = new Dilation(m_wheel_struct, m_wheel_size);
      int[] no_problem_set = dilation.getProcessed(binary, new Dimension(width, height));
      int[] n = yi.util.ImageUtil.makeNewImageFromBinary(no_problem_set);
      OutputResult.outputGIFImage("no_p.gif", n, width, height);

      for (int h=0;h<height;h++) {
        for (int w=0;w<width;w++) {
          filtered[h*width+w]=(no_problem_set[h*width+w]==IN_CONTOUR)?1:0;
        }
      }
      int[] f = yi.util.ImageUtil.makeNewImageFromBinary(filtered);
      OutputResult.outputGIFImage("filtered.gif", f, width, height);

      Opening opening = new Opening(m_wheel_struct, m_wheel_size);
      filtered = opening.getProcessed(filtered, new Dimension(width, height));

//      int count=0;
//      while (!toSearch.isEmpty()) {
//        System.err.println("count="+count+",size="+toSearch.size());
//        count++;
//        Dimension xy = (Dimension)toSearch.removeFirst();
//        int x=xy.width, y=xy.height;
//        dump(filtered, width, height, x, y, "x");
//        boolean wheelable = wheel(filtered, width, height, filter, x, y, inReg);
//        dump(filtered, width, height, x, y, "x");
//        if (!wheelable) continue;
//        if (x+1<width&&!considered[y][x+1]&&isWheelable(inReg, width, height, filter, x+1, y)) {
//          toSearch.addLast(new Dimension(x+1, y));
//          considered[y][x+1]=true;
//          dump(filtered, width, height, x+1, y, "+");
//        }
//        if (x-1>0&&!considered[y][x-1]&&isWheelable(inReg, width, height, filter, x-1, y)) {
//          toSearch.addLast(new Dimension(x-1, y));
//          considered[y][x-1]=true;
//          dump(filtered, width, height, x-1, y, "+");
//        }
//        if (y+1<height&&!considered[y+1][x]&&isWheelable(inReg, width, height, filter, x, y+1)) {
//          toSearch.addLast(new Dimension(x, y+1));
//          considered[y+1][x]=true;
//          dump(filtered, width, height, x, y+1, "+");
//        }
//        if (y-1>0&&!considered[y-1][x]&&isWheelable(inReg, width, height, filter, x, y-1)) {
//          toSearch.addLast(new Dimension(x, y-1));
//          considered[y-1][x]=true;
//          dump(filtered, width, height, x, y-1, "+");
//        }
//      }
//
//      for (int h=0;h<height;h++) {
//        for (int w=0;w<width;w++) {
//          filtered[h*width+w]=(inReg[h][w])?0:1;
//        }
//      }
      return filtered;

    }
    return binary;
  }
  final static int BOUNDARY = 2;
  final static int IN_CONTOUR = 0;

  /**
   * When nothing on the bounary, filter the inside.
   * @param binary
   * @param width
   * @param height
   * @param filter
   * @param passed
   */
  public void filter(int[] binary, int width, int height, boolean[][] filter,
                     int x, int y, boolean[][] passed) {
    int[][] boundary = getBoundary(filter);
    int bh = boundary.length, bw = boundary[0].length;

    for (int h=0; h<bh; h++) {
      for (int w=0; w<bw; w++) {
        if (boundary[h][w]!=BOUNDARY) continue;
        int tx = x - bw/2 + w;
        int ty = y - bh/2 + h;
        if (tx<0||tx>=width||ty<0||ty>=height) continue;
        if (binary[ty*width+tx]!=IN_CONTOUR) {
          return;
        }
      }
    }

    int fh = filter.length, fw = filter[0].length;

    for (int h=0; h<fh; h++) {
      for (int w=0; w<fw; w++) {
        if (!filter[h][w]) continue;
        int tx = x - fw/2 + w;
        int ty = y - fh/2 + h;
        if (tx<0||tx>=width||ty<0||ty>=height) continue;
        binary[ty*width+tx]=IN_CONTOUR;
        passed[ty][tx]=true;
      }
    }
  }

  public void filterUnconditionally(int[] binary, int width, int height, boolean[][] filter,
                     int x, int y, boolean[][] passed) {
    int fh = filter.length, fw = filter[0].length;

    for (int h=0; h<fh; h++) {
      for (int w=0; w<fw; w++) {
        if (!filter[h][w]) continue;
        int tx = x - fw/2 + w;
        int ty = y - fh/2 + h;
        if (tx<0||tx>=width||ty<0||ty>=height) continue;
        binary[ty*width+tx]=IN_CONTOUR;
        passed[ty][tx]=true;
      }
    }
  }

  public boolean isFilterable(boolean[][] passed, int width, int height,
                              boolean[][] filter, int x, int y) {
    int fh = filter.length, fw = filter[0].length;

    for (int h=0; h<fh; h++) {
      for (int w=0; w<fw; w++) {
        if (!filter[h][w]) continue;
        int tx = x - fw/2 + w;
        int ty = y - fh/2 + h;
        if (tx<0||tx>=width||ty<0||ty>=height) continue;
        if (passed[ty][tx]) return true;
      }
    }
    return false;
  }

  /**
   * Bounary will be labeled as 2 (BOUNDARY)
   * @param filter
   * @return
   */
  int[][] getBoundary(boolean[][] filter) {
    int filter_h = filter.length;
    int filter_w = filter[0].length;
    int[][] boundary = new int[filter_h+2][filter_w+2];
    for (int i=0;i<filter_h;i++) {
      for (int j=0;j<filter_w;j++) {
        boundary[i+1][j+1] = (filter[i][j])?1:0;
      }
    }
    for (int i=0;i<filter_h+2;i++) {
      for (int j=0;j<filter_w+2;j++) {
        if (boundary[i][j]!=0) continue;
        if (i-1>0 && boundary[i-1][j] ==1) boundary[i][j]=BOUNDARY;
        if (i+1<filter_h+2 && boundary[i+1][j] ==1) boundary[i][j]=BOUNDARY;
        if (j-1>0 && boundary[i][j-1] ==1) boundary[i][j]=BOUNDARY;
        if (j+1<filter_w+2 && boundary[i][j+1] ==1) boundary[i][j]=BOUNDARY;
        if (i-1>0 && j-1>0 && boundary[i-1][j-1] ==1) boundary[i][j]=BOUNDARY;
        if (i-1>0 && j+1<filter_w+2 && boundary[i-1][j+1] ==1) boundary[i][j]=BOUNDARY;
        if (i+1<filter_h+2 && j-1>0 && boundary[i+1][j-1] ==1) boundary[i][j]=BOUNDARY;
        if (i+1<filter_h+2 && j+1<filter_w+2 && boundary[i+1][j+1] ==1) boundary[i][j]=BOUNDARY;
      }
    }
    return boundary;
  }

  public boolean wheel(int[] binary, int width, int height, boolean[][] filter,
                     int x, int y, boolean[][] passed) {
    int fh = filter.length, fw = filter[0].length;

    for (int h=0; h<fh; h++) {
      for (int w=0; w<fw; w++) {
        if (!filter[h][w]) continue;
        int tx = x - fw/2 + w;
        int ty = y - fh/2 + h;
        if (tx<0||tx>=width||ty<0||ty>=height) continue;
        if (binary[ty*width+tx]!=IN_CONTOUR) return false;
      }
    }

    for (int h=0; h<fh; h++) {
      for (int w=0; w<fw; w++) {
        if (!filter[h][w]) continue;
        int tx = x - fw/2 + w;
        int ty = y - fh/2 + h;
        if (tx<0||tx>=width||ty<0||ty>=height) continue;
        passed[ty][tx]=true;
      }
    }
    return true;
  }

  public boolean isWheelable(boolean[][] passed, int width, int height,
                              boolean[][] filter, int x, int y) {
    return true;
  }

  public void getRegionWithSeed(int[] b, int w, int h) {
    ConnectComponent cc = new ConnectComponent();
    int[] label = cc.getProcessed(b, new Dimension(w,h));
    int t = label[m_seed_y*w+m_seed_x];
    for (int i=0;i<b.length;i++) {
      if (label[i]==t) b[i]=1;
      else b[i]=0;
    }
  }

  public void dump(int[] data, int w, int h, int x, int y, String c) {
//    System.out.println();
//    for (int i=0;i<h;i++) {
//      for (int j=0;j<w;j++) {
//        if (i==y&&j==x) System.out.print(c);
//        else System.out.print(data[i*w+j]);
//      }
//      System.out.println();
//    }
  }

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

    // read in image
    Image image = null;
    Dimension d = new Dimension();
    int[] pixels = null;
    try {
      image = yi.util.io.ImageReader.readImageFile(img_name);
      pixels = yi.util.io.ImageReader.getPixels(image, d);
    }
    catch (java.io.IOException e) {
      System.err.println("Can't read image " + img_name);
      e.printStackTrace();
      System.exit(2);
    }


    // process
    // canny
    Configuration.setParameter("SOURCE_NAME", img_name);
    CannyEdgeDector canny = new CannyEdgeDector();
    int[] binary = canny.getProcessed(pixels, d);
//    int[] edge = yi.util.ImageUtil.makeNewImageFromBinary(binary);
//    OutputResult.outputColorImage(img_name + "." + DOU.shortName(canny.getClass().getName()) + ".gif", edge, d.width, d.height);

//    int[] binary = new int[pixels.length];
//    for (int i=0;i<binary.length;i++) {
//      int r=(pixels[i]>>16)&0xff;
//      int g=(pixels[i]>>8)&0xff;
//      int b=(pixels[i])&0xff;
//      if (r>=128&&g>=128&&b>=128) binary[i]=1;
//    }

    // seed and wheel
    SeedAndWheel sw = new SeedAndWheel(d.width*3/7, d.height*4/7, 11, 11);
//    int[] contour = sw.getContour(binary, d.width, d.height);
//    yi.util.ImageUtil.makeImageFromBinary(contour);
//    OutputResult.outputColorImage(img_name + "." + DOU.shortName(sw.getClass().getName()) + ".gif", contour, d.width, d.height);
    int[] contour = sw.filter(binary, d.width, d.height);
//    int[] filtered = yi.util.ImageUtil.makeNewImageFromBinary(contour);
//    OutputResult.outputColorImage(img_name + "." + DOU.shortName(sw.getClass().getName()) + "_filtered.gif", filtered, d.width, d.height);
    contour = sw.wheel(contour, d.width, d.height);
//    int[] wheeled = yi.util.ImageUtil.makeNewImageFromBinary(contour);
//    OutputResult.outputColorImage(img_name + "." + DOU.shortName(sw.getClass().getName()) + "_wheeled.gif", wheeled, d.width, d.height);
    sw.getRegionWithSeed(contour, d.width, d.height);
    yi.util.ImageUtil.makeImageFromBinary(contour);
    OutputResult.outputGIFImage(img_name + "." + DOU.shortName(sw.getClass().getName()) + ".gif", contour, d.width, d.height);
    System.exit(0);
  }

}