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.iu.processor.filter.*;
import yi.util.io.*;
import yi.util.io.output.*;
import yi.iu.processor.channel.IntensityMaker;

public class AckContour {

  int m_seed_x, m_seed_y;
  int m_filter_struct = Morph.DISK;
  int m_filter_size;
  int m_opening_struct = Morph.DISK;
  int m_opening_size;

  public AckContour(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_opening_size = wheel_size;
  }
  public int[] getContour(int[] binary, int width, int height) {
    int[] rst = filter(binary, width, height);
    rst = opening(rst, width, height);
    return rst;
  }

  public int[] filter(int[] binary, int width, int height) {
    // filter
    if (m_filter_size > 0) {
      int[] filtered = (int[])binary.clone();
      boolean[][] filter = Morph.get2DStruct(m_filter_struct, m_filter_size);
      int[][] boundary = getBoundary(filter);


      // 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, boundary, w, h);
        }
      }
      return filtered;

    }
    return binary;
  }

  public int[] opening(int[] binary, int width, int height) {
    // filter
    if (m_opening_size > 0) {
      Opening opening = new Opening(m_opening_struct, m_opening_size);
      int[] filtered = opening.getProcessed(binary, new Dimension(width, height));
      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[][] boundary, int x, int y) {
    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;
      }
    }
  }



  /**
   * 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.labeling(b, new Dimension(w,h), false);
//    int[] li = yi.util.ImageUtil.makeNewImageFromLabel(label);
//    OutputResult.outputColorImage("t_cc.ppm", li, w, h);
    int t = label[m_seed_y*w+m_seed_x];
    for (int i=0;i<b.length;i++) {
      b[i]=(label[i]==t)?1: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();
//    }
  }
  int WALL_THICKNESS = 40;
  int MAX_LEAK = 60;
  public int[] confidence(int[] p, int[] b, int width, int height) {
    int[] demo = yi.util.ImageUtil.makeNewImageFromBinary(b);
    OutputResult.outputGIFImage("binary.gif", demo, width, height);

    Opening opening= new Opening(Morph.DISK, MAX_LEAK);
    int[] opened = opening.getProcessed(b, new Dimension(width,height));
    getRegionWithSeed(opened, width, height);
    demo = yi.util.ImageUtil.makeNewImageFromBinary(opened);
    OutputResult.outputGIFImage("opened.gif", demo, width, height);

    Dilation dilation = new Dilation(Morph.DISK, WALL_THICKNESS/2);
    int[] dilated = dilation.getProcessed(opened, new Dimension(width,height));
    demo = yi.util.ImageUtil.makeNewImageFromBinary(dilated);
    OutputResult.outputGIFImage("dilated.gif", demo, width, height);

//    int[] boundary =  Boundary.getBoundary(dilated, width, height, 1);
    int[] boundary = Boundary.getBoundary(opened, width, height, 1);
    demo = yi.util.ImageUtil.makeNewImageFromBinary(boundary);
    int[] b3 = Boundary.getBoundary(b, width, height, 1);
    for (int i=0;i<demo.length;i++) {
      if (demo[i]!=0xffffffff) demo[i]=p[i];
      if (b3[i]==1) demo[i]=0xffff0000;
    }
    OutputResult.outputGIFImage("boundary.gif", demo, width, height);

    int[] confid = new int[width*height];

    int[] contour_b = Boundary.getBoundary(b, width, height, 1);
    // begin confidence calculation
    // smooth
    double theta = 2;
    IntensityMaker in = new yi.iu.processor.channel.IntensityMaker();
    p = in.getProcessed(p, new Dimension(width, height));
    int[] smoothed= GaussianSmooth.smoothing(p, width, height, theta);
    // delta x, y
    int[] delta_x= new int[width*height];
    int[] delta_y= new int[width*height];
    derrivative_x_y(smoothed, new Dimension(width, height), delta_x, delta_y);
//    OutputResult.outputResult("dx", delta_x, width, height);
//    OutputResult.outputResult("dy", delta_y, width, height);
    // mask delta x, y
    boolean[] mask_disk_b = Morph.getStruct(Morph.DISK, WALL_THICKNESS);
    int[] mask_disk = new int[mask_disk_b.length];
    for (int i=0;i<mask_disk.length;i++) mask_disk[i]=(mask_disk_b[i])?1:0;
    Masker m = new Masker(mask_disk, WALL_THICKNESS, WALL_THICKNESS);

    int[] smoothed_delta_x = new int[width*height];
    int[] smoothed_delta_y = new int[width*height];
//    delta_x  = m.getProcessed(delta_x, new Dimension(width, height));
//    delta_y  = m.getProcessed(delta_y, new Dimension(width, height));

    for (int h=0;h<height;h++) {
      for (int w=0;w<width;w++) {
        if (boundary[h*width+w]==1&&opened[h*width+w]==0&&b[h*width+w]==1) { // possible closing line
          smoothed_delta_x[h*width+w] = m.getProcessed(delta_x, new Dimension(width, height), w, h);
          smoothed_delta_y[h*width+w] = m.getProcessed(delta_y, new Dimension(width, height), w, h);
          System.err.println("("+w+","+h+")"+"->("+smoothed_delta_x[h*width+w]+","+smoothed_delta_y[h*width+w]+")");
          confid[h*width+w] = 2;
        } else if (contour_b[h*width+w]==1&&opened[h*width+w]==0) {  // original line
//          smoothed_delta_x[h*width+w] = m.getProcessed(delta_x, new Dimension(width, height), w, h);
//          smoothed_delta_y[h*width+w] = m.getProcessed(delta_y, new Dimension(width, height), w, h);
//          System.err.println("("+smoothed_delta_x[h*width+w]+","+smoothed_delta_y[h*width+w]+")");
          confid[h*width+w] = 1;
        }
        else {
          confid[h*width+w] = 0;
        }
      }
    }


//    OutputResult.outputResult("smoothed_delta_x", smoothed_delta_x, width, height);
//    OutputResult.outputResult("smoothed_delta_y", smoothed_delta_y, width, height);
//    CannyGray can = new CannyGray();
//    confid= can.cannyFromDelta(smoothed_delta_x, smoothed_delta_y, new Dimension(width, height));
    return confid;
  }

  public void derrivative_x_y(int[] smoothedim, Dimension d, int[] delta_x, int[] delta_y)
  {
      int rows= d.height, cols= d.width;
      int r, c, pos;
      // Compute the x-derivative. Adjust the derivative at the borders to avoid losing pixels.
      for(r=0;r<rows;r++){
          pos = r * cols;
          delta_x[pos] = smoothedim[pos+1] - smoothedim[pos];
          pos++;
          for(c=1;c<(cols-1);c++,pos++){
              delta_x[pos] = smoothedim[pos+1] - smoothedim[pos-1];
          }
          delta_x[pos] = smoothedim[pos] - smoothedim[pos-1];
      }
      // Compute the y-derivative. Adjust the derivative at the borders to avoid
      // losing pixels.
      for(c=0;c<cols;c++){
          pos = c;
          delta_y[pos] = smoothedim[pos+cols] - smoothedim[pos];
          pos += cols;
          for(r=1;r<(rows-1);r++,pos+=cols){
              delta_y[pos] = smoothedim[pos+cols] - smoothedim[pos-cols];
          }
          delta_y[pos] = smoothedim[pos] - smoothedim[pos-cols];
      }
  }

  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);
    for (int i=0;i<binary.length;i++) binary[i]=(binary[i]>0)?0:1;
//    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.lengt(d.width*3/7, d.height*4/7, 25, 10);

    // seed and wheel
    AckContour ac = new AckContour(d.width*3/7, d.height*4/7, 25, 10);
//    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;
//    contour = ac.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 = ac.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);

    contour = binary;
//    int[] beforeopened = yi.util.ImageUtil.makeNewImageFromBinary(contour);
//    OutputResult.outputGIFImage(img_name + "." + DOU.shortName(ac.getClass().getName()) + "_beforeopened.gif", beforeopened, d.width, d.height);
    contour = ac.opening(contour, d.width, d.height);

//    int[] opened = yi.util.ImageUtil.makeNewImageFromBinary(contour);
//    for (int i=0;i<opened.length;i++) {
//      if (binary[i]==0&&opened[i]!=0xff000000) opened[i]=0xffff0000;
//      if (binary[i]==0&&opened[i]==0xff000000) opened[i]=0xff00ff00;
//    }
//    OutputResult.outputGIFImage(img_name + "." + DOU.shortName(ac.getClass().getName()) + "_opened.gif", opened, d.width, d.height);

    ac.getRegionWithSeed(contour, d.width, d.height);
//    yi.util.ImageUtil.makeImageFromBinary(contour);
//    OutputResult.outputGIFImage(img_name + "." + DOU.shortName(ac.getClass().getName()) + ".gif", contour, d.width, d.height);
    int[] confid = ac.confidence(pixels, contour, d.width, d.height);
    for (int i=0;i<confid.length;i++) {
      if (confid[i]>0) confid[i]=0xff000000|(i<<16)|(i<<8)|i;
//      if (confid[i]==255) confid[i]=0xffff0000;
//      else if (confid[i]==127) confid[i]=0xff00ff00;
//      else confid[i]=(binary[i]==1)?0xffffffff:0xff000000;
    }
//    OutputResult.outputColorImage(img_name + "." + DOU.shortName(ac.getClass().getName()) + "_confid.ppm", confid, d.width, d.height);
    OutputResult.outputColorImage("confid.ppm", confid, d.width, d.height);
    System.exit(0);
  }

}