package yi.util;

import yi.iu.SImage;
import Acme.JPM.Decoders.*;

import java.awt.image.*;
import java.awt.*;
import java.io.*;
import java.applet.*;
import java.util.*;

public final class ImageUtil {
  /**
   * Used to store the global image pixels so that they are available for
   * every image processing module
   */
  public static int[] pixels;
  /**
   * Used to store the global image dimension so that they are available for
   * every image processing module
   */
  public static Dimension dimension;

  public static void setPixels(int[] pixels) {
    ImageUtil.pixels = pixels;
  }

  public static int[] getPixels() {
    return pixels;
  }

  public static void setDimension(Dimension d) {
    dimension = d;
  }

  public static Dimension getDimension() {
    return dimension;
  }

  final static boolean VERBOSE = false;

  //given the filename, return the pixel values and the dimension of the image
  //the Dimension argument must be initialized before invoking this method
  //try to be usable for applet (using getDocumentBase() first)
  //the fname could be "image01.jpg" , "image/imaeg01.jpg" or "/groundtruth/image01.jpg"
  //remember use "/" instead of "\", otherwise can't be used in applet
  //try to get ppm file using the acm packet (can be used in applet?)
  static public int[] read(String fname, Dimension d) {
    int[] img = null;
    java.awt.Image origImage = null;
    if (fname.endsWith(".ppm")) {
      try {
        DataInputStream in = new DataInputStream(new FileInputStream(fname));
        PpmDecoder ppmd = new PpmDecoder(in);
        Toolkit tk = Toolkit.getDefaultToolkit();
        origImage = tk.createImage(ppmd);

      }
      catch (Exception e) {
        System.err.println("read ppm error!" + e);
      }
    }
    else {
      Applet ap = new java.applet.Applet();
      origImage = ap.getImage(ap.getDocumentBase(), fname);
    }
    if (VERBOSE)
      System.err.println("Loading image: " + fname + " ...");

    PixelGrabber pg = new PixelGrabber(origImage, 0, 0, -1, -1, true);
    boolean result = false;
    try {
      result = pg.grabPixels();
    }
    catch (InterruptedException e) {
      System.err.println("Interrupted pixel grabbing!");
      e.printStackTrace();
    }

    if (result == false) {
      System.err.println("Couldn't Grab the Pixels!");
      return null;
    }

    img = (int[]) pg.getPixels();
    d.width = pg.getWidth();
    d.height = pg.getHeight();
    pg = null;
    System.gc();

    if (VERBOSE) {
      if (img != null)
        System.err.println("Image Loaded! " + d.width + "*" + d.height);
      else
        System.err.println("Image not Loaded!");
    }
    return img;
  }

  static public SImage getPixelFromFile(String fname) {
    java.awt.Image img = Toolkit.getDefaultToolkit().getImage(fname);
    PixelGrabber pg = new PixelGrabber(img, 0, 0, -1, -1, true);

    try {
      pg.grabPixels();
    }
    catch (InterruptedException e) {
      System.err.println("Interrupted pixel grabbing!" + e);
      System.exit(0);
    }

    int[] pixels = (int[]) pg.getPixels();
    int width = pg.getWidth();
    int height = pg.getHeight();
    SImage simage = new SImage(width, height, SImage.RGB, pixels);
    img = null;
    pg = null;
    System.gc();
    return simage;
  }

  //replaced by read
  //given the filename, return the pixel values and the dimension of the image
  static public int[] getPixelFromFile(String fname, Dimension d) {
    java.awt.Image img = Toolkit.getDefaultToolkit().getImage(fname);
    PixelGrabber pg = new PixelGrabber(img, 0, 0, -1, -1, true);

    try {
      pg.grabPixels();
    }
    catch (InterruptedException e) {
      System.err.println("Interrupted pixel grabbing!" + e);
      return null;
    }

    int[] pixels = (int[]) pg.getPixels();
    d.width = pg.getWidth();
    d.height = pg.getHeight();
    img = null;
    pg = null;
    System.gc();
    return pixels;
  }

  static public int[] makeNewImageFromBinary(int[] s) {
    int[] color = new int[s.length];
    for (int i = 0; i < s.length; i++) {
      if (s[i] != 0)
        color[i] = 0xffffffff;
      else
        color[i] = 0xff000000;
    }
    return color;
  }

  static public int[] makeNewImageFromBinary(int[][] s2) {
    int h = s2.length, w = s2[0].length;
    int[] s = new int[h * w];
    for (int i = 0; i < h; i++) {
      for (int j = 0; j < w; j++) {
        s[i * w + j] = s2[i][j];
      }
    }
    return makeNewImageFromBinary(s);
  }

  static public void makeImageFromBinary(int[] s) {
    for (int i = 0; i < s.length; i++) {
      if (s[i] != 0)
        s[i] = 0xffffffff;
      else
        s[i] = 0xff000000;
    }
  }

  static public int[] makeNewImageFromGray(int[] gray) {
    int[] color = new int[gray.length];
    for (int i = 0; i < gray.length; i++) {
      int g = gray[i];
      if (g > 255) {
        System.err.println("Gray image has value greater than 255!");
        g = 255;
      }
      if (g < 0) {
        System.err.println("Gray image has value less than 0!");
        g = 0;
      }
      color[i] = 0xff000000 | g << 16 | g << 8 | g;
    }
    return color;
  }

  static public void makeImageFromGray(int[] gray) {
    for (int i = 0; i < gray.length; i++) {
      int g = gray[i];
      if (g > 255) {
        System.err.println("Gray image has value greater than 255!");
        g = 255;
      }
      if (g < 0) {
        System.err.println("Gray image has value less than 0!");
        g = 0;
      }
      gray[i] = 0xff << 24 | g << 16 | g << 8 | g;
    }
  }

  /**
   * the given gray levels are within [0,1]
   * @param gray
   * @return
   */
  static public int[] makeNewImageFromNormalizedGray(double[] gray) {
    int[] color = new int[gray.length];
    for (int i = 0; i < gray.length; i++) {
      int g = (int)(gray[i] * 255);
      if (g > 255) {
        g = 255;
      }
      if (g < 0) {
        g = 0;
      }
      color[i] = 0xff000000 | g << 16 | g << 8 | g;
    }
    return color;
  }

  /**
   * the given gray levels are within [0,1]
   * @param gray
   * @return
   */
  static public void makeImageFromNormalizedGray(double[] gray) {
    for (int i = 0; i < gray.length; i++) {
      int g = (int)(gray[i] * 255);
      if (g > 255) {
        g = 255;
      }
      if (g < 0) {
        g = 0;
      }
      gray[i] = 0xff << 24 | g << 16 | g << 8 | g;
    }
  }

  static public int[] makeNewImageFromBare(int[] bare) {
    int[] color = new int[bare.length];
    for (int i = 0; i < bare.length; i++) {
      color[i] = 0xff << 24 | bare[i];
    }
    return color;
  }

  static public int[] makeNewImageFromBare(char[][] bare) {
    int h = bare.length, w = bare[0].length;
    int[] color = new int[h * w];
    int index = 0;
    for (int y = 0; y < h; y++)
      for (int x = 0; x < w; x++) {
        int c = (int) bare[y][x];
        color[index++] = 0xff << 24 | c << 16 | c << 8 | c;
      }
    return color;
  }

  final static int BIGPRIME = 10009;
  final static int BACKGROUND_COLOR = 0xffffffff;

  /**
   * labels from 1..num-1 will be mapped to colors.
   * label=0 ==> background  ==> 0xff000000
       * see the big prime at http://www.utm.edu/research/primes/lists/small/10000.txt
   * @param label
   * @return color image
   */
  static public int[] makeNewImageFromLabel(int[] label,
                                            boolean zeroAsBackground) {

    int max = yi.stat.StatUtil.maximum(label);

    return makeNewImageFromLabel(label, max, zeroAsBackground);
  }

  /**
   * labels from 1..num-1 will be mapped to colors.
   * label=0 ==> background  ==> 0xff000000
       * see the big prime at http://www.utm.edu/research/primes/lists/small/10000.txt
   * @param label
   * @param max max label
   * @return color image
   */
  static public int[] makeNewImageFromLabel(int[] label, int max,
                                            boolean zeroAsBackground) {


    int[] mapc = labels2Colors(max, zeroAsBackground);

    return makeNewImageFromLabel(label, mapc);
  }

  /**
   * Make a new image.  Each pixel is assigned to a label and each label is
   * given an color in the parameters
   * @param label
   * @param mapc
   * @return
   */
  static public int[] makeNewImageFromLabel(int[] label, int[] mapc) {
    // make the image
    int[] colimage = new int[label.length];
    for (int i = 0; i < label.length; ++i) {
      colimage[i] = mapc[label[i]];
    }
    return colimage;
  }

  static public int[] makeNewColorImageFromLabel(int[] label, int[] image, boolean zeroAsBackground, String colorSpace) {
    if (colorSpace.compareToIgnoreCase("lab") == 0) {
      double[][] lab = new double[label.length][3];
      yi.iu.processor.color.CIELab.rgb2lab(image, lab);
      int num_label = yi.stat.StatUtil.maximum(label)+1;

      // get the summation of each color
      double[][] avg_lab = new double[num_label][3];
      int[] num_lab = new int[num_label];
      for (int i=0; i<lab.length; i++) {
        int l = label[i];
        avg_lab[l][0] += lab[i][0];
        avg_lab[l][1] += lab[i][1];
        avg_lab[l][2] += lab[i][2];

        num_lab[l] ++;
      }

      // average
      for (int i=0; i<num_label; i++) {
        if (num_lab[i] == 0) continue;
        avg_lab[i][0] /= num_lab[i];
        avg_lab[i][1] /= num_lab[i];
        avg_lab[i][2] /= num_lab[i];
      }

      // convert the average color to rgb color space
      int[] avg_rgb = new int[num_label];
      for (int i=0; i<num_label; i++) {
        avg_rgb[i] = yi.iu.processor.color.CIELab.lab2rgb(avg_lab[i][0], avg_lab[i][1], avg_lab[i][2]);
        System.out.println("color[" + i + "]=(" + avg_lab[i][0] + "," + avg_lab[i][1] + "," +
                           avg_lab[i][2] + ")");
      }

      // if background as 0, then make it white
      if (zeroAsBackground) avg_rgb[0] = 0xffffffff;
      return makeNewImageFromLabel(label, avg_rgb);
    }
    return null;
  }

  /**
   * labels from 0 or 1 to max-1 will be mapped to colors.
   * if label 0 is background, it will be mapped to black (0xff000000)
   * @param labels
   * @param max max label
   * @return color image
   */
  static public int[] labels2Colors(int max, boolean zeroAsBackground) {
    // map store the label -> rgb color (0->(0,0,0))
    int map[][] = new int[max + 1][]; // store r, g, b
    int[] mapc = new int[max + 1]; // store integer rgb

    if (zeroAsBackground) {
      map[0] = new int[] {
          0, 0, 0};
      mapc[0] = BACKGROUND_COLOR;
      for (int i = 1; i <= max; ++i) {
        int index = (i - 1) * BIGPRIME % max;
        map[i] = hsv_to_rgb(index * Math.PI * 2 / max, 1.0, 1.0);
        mapc[i] = yi.iu.processor.color.ColorUtil.intArray2RGB(map[i]);
      }
    }
    else {
      for (int i = 0; i <= max; ++i) {
        int index = i * BIGPRIME % (max + 1);
        map[i] = hsv_to_rgb(index * Math.PI * 2 / (max + 1), 1.0, 1.0);
        mapc[i] = yi.iu.processor.color.ColorUtil.intArray2RGB(map[i]);
      }
    }
    return mapc;
  }

  /**
   * labels from 1..num-1 will be mapped to colors.
   * label=0 ==> background  ==> 0xff000000
       * see the big prime at http://www.utm.edu/research/primes/lists/small/10000.txt
   * @param label
   * @return color image
   */
  static public int[] makeNewImageFromLabel(int[] label) {
    return makeNewImageFromLabel(label, true);
  }

  /**
   * labels from 0..num-1 will be mapped to colors.
   * label<0 ==> background  ==> 0xffffffff
       * see the big prime at http://www.utm.edu/research/primes/lists/small/10000.txt
   * @param label
   * @return the new image
   * @deprecated replace by makeNewImageFromLabel(label, false)
   */
  static public int[] makeNewImageFromLabel_back(int[] label) {
    return makeNewImageFromLabel(label, false);
  }

  static int[] hsv_to_rgb(double h, double s, double v) {
    int i;
    double f;
    int p, q, t, z;
    int r, g, b;

    if (s == 0)
      r = g = b = (int) (v * 255);

    else {
      h /= (Math.PI / 3);

      i = (int) (h);
      f = h - i;

      p = (int) (255 * v * (1 - s));
      q = (int) (255 * v * (1 - s * f));
      t = (int) (255 * v * (1 - (s * (1 - f))));
      z = (int) (255 * v);

      r = (i == 0 || i == 5) ? z : ( (i == 2 || i == 3) ? p : (i == 1 ? q : t));
      g = (i == 1 || i == 2) ? z : ( (i == 4 || i == 5) ? p : (i == 3 ? q : t));
      b = (i == 3 || i == 4) ? z : ( (i == 0 || i == 1) ? p : (i == 5 ? q : t));
    }
    int rst[] = new int[3];
    rst[0] = r;
    rst[1] = g;
    rst[2] = b;
    return rst;
  }

  static public int[] twoD2oneD(int[][] twoD) {
    int h = twoD.length, w = twoD[0].length;
    int result[] = new int[h * w];
    for (int y = 0; y < h; y++) {
      for (int x = 0; x < w; x++) {
        int i = y * w + x;
        result[i] = twoD[y][x];
      }
    }
    return result;
  }

  static public int[] twoD2oneD(char[][] twoD) {
    int h = twoD.length, w = twoD[0].length;
    int result[] = new int[h * w];
    for (int y = 0; y < h; y++) {
      for (int x = 0; x < w; x++) {
        int i = y * w + x;
        result[i] = (int) twoD[y][x];
      }
    }
    return result;
  }

  /**
   * Convert a 1D pixel array to a 2D pixel array.
   * @param oneD
   * @param d
   * @return the 2D array with y on the first dimension and x on the second
   */
  static public int[][] oneD2twoD(int[] oneD, Dimension d) {
    int h = d.height, w = d.width;
    int[][] result = new int[h][w];
    int index = 0;
    for (int y = 0; y < h; y++) {
      for (int x = 0; x < w; x++) {
        result[y][x] = oneD[index++];
      }
    }
    return result;
  }

  /**
   * Make a background image based on the given image.  The color of the pixels
   * in background is darker than the those in the given image
   * @param img
   * @return
   */
  static public int[] makeBackground(int[] img) {
    int[] bg = new int[img.length];
    for (int i = 0; i < img.length; i++) {
      int p = img[i];
      int r = (p >> 16) & 0xff;
      int g = (p >> 8) & 0xff;
      int b = (p) & 0xff;
      r /= 2;
      g /= 2;
      b /= 2;
      bg[i] = 0xff000000 | r << 16 | g << 8 | b;
    }
    return bg;
  }

  /**
   * Make a background image initialized with the given color
   * @param d
   * @param color
   * @return
   */
  static public int[] makeBackground(Dimension d, int color) {
    int[] bg = new int[d.width * d.height];
    for (int i = 0; i < bg.length; i++) {
      bg[i] = color;
    }
    return bg;
  }

  static public int[] readOrigPixels(java.awt.Image origImage, Dimension d) {
    int[] origPixels;
    PixelGrabber pg = new PixelGrabber(origImage, 0, 0, -1, -1, true);
    boolean result = false;
    try {
      result = pg.grabPixels();
    }
    catch (InterruptedException e) {
      System.err.println("Interrupted pixel grabbing!");
      e.printStackTrace();
    }

    if (result == false) {
      System.err.println("Couldn't Grab the Pixels!");
      //statusLabel.setText("Couldn't Grab the Pixels!");
      origPixels = null;
      return origPixels;
    }

    origPixels = (int[]) pg.getPixels();
    d.width = pg.getWidth();
    d.height = pg.getHeight();
    pg = null;
    System.gc();
    return origPixels;
  }

  public static int[] readImage(String imageName, Dimension d) {
    java.awt.Image origImage = null;
    if (imageName.endsWith(".ppm")) {
      try {
        DataInputStream in = new DataInputStream(new FileInputStream(imageName));
        PpmDecoder ppmd = new PpmDecoder(in);
        Toolkit tk = Toolkit.getDefaultToolkit();
        origImage = tk.createImage(ppmd);

      }
      catch (Exception e) {
        System.err.println("read ppm error!" + e);
      }
    }
    else {
      //origImage= getImage(getDocumentBase(), imageName);
      origImage = Toolkit.getDefaultToolkit().getImage(imageName);
    }
    if (VERBOSE)
      System.err.println("Loading image: " + imageName + " ...");
    int[] origPixels = yi.util.ImageUtil.readOrigPixels(origImage, d);

    if (origPixels != null) {
      if (VERBOSE)
        System.err.print("Image Loaded! ");
      if (VERBOSE)
        System.err.println(d.width + "*" + d.height);
    }
    return origPixels;
  }

  /**
   * Zoom in on a small image and returned the zoom-in-ed image
   * @param img the small image to zoom in
   * @param width image widht
   * @param height image height
   * @param wscale zoom in scale on horizontal direction
   * @param hscale zoom in scale on vertical direction
   * @return the zoom-in-ed image
   */
  public static int[][] zoomIn(int[][] img, int width, int height, int wscale,
                               int hscale) {
    int[][] zoom_in_ed = new int[height * hscale][width * wscale];
    for (int h = 0; h < height; h++) {
      for (int w = 0; w < width; w++) {
        for (int i = h * hscale; i < h * hscale + hscale; i++) {
          for (int j = w * wscale; j < w * wscale + wscale; j++) {
            zoom_in_ed[i][j] = img[h][w];
          }
        }
      }
    }
    return zoom_in_ed;
  }

  /**
   * Zoom in on a small image and returned the zoom-in-ed image
   * @param img the small image to zoom in
   * @param width image widht
   * @param height image height
   * @param wscale zoom in scale on horizontal direction
   * @param hscale zoom in scale on vertical direction
   * @return the zoom-in-ed image
   */
  public static int[] zoomIn(int[] img, int width, int height, int wscale,
                             int hscale) {
    int[][] zoom_in_ed = zoomIn(
        yi.util.data.Conversion.from1Dto2D(img, width, height),
        width, height, wscale, hscale
        );
    return yi.util.data.Conversion.from2Dto1D(zoom_in_ed, width * wscale,
                                              height * hscale);
  }

  /**
   * Make a random image
   * @param width image width
   * @param height image height
   * @return the random image
   */
  public static int[] makeRandomGrayImage(int width, int height) {
    int[] img = new int[width * height];
    Random r = new Random();

    for (int i = 0; i < img.length; i++) {
      int rand = r.nextInt(256);
      img[i] = 0xff000000 | rand << 16 | rand << 8 | rand;
    }

    return img;
  }

  public static class TestMakeRandomGrayImage {
    public static void main(String[] args) {
      int w = 256, h = 256;
      int[] img = ImageUtil.makeRandomGrayImage(w, h);
      yi.util.io.OutputResult.outputGrayImage("random.pgm", img, w, h);
    }
  }

  /**
   * Make one image contains tiled images.  The image dimension must be the
   * same.
   * @param images the images to contain, given in row first order
   * @param cols how many columns
   * @param rows how many rows
   * @return the tiled image.  Return null if no image is given in the images
   *         paramter
   */
  public static int[] makeTiledImage(int[][] images, int width, int height,
                                     int cols, int rows) {
    // construct the tiled image
    int tiled_w = width * cols;
    int tiled_h = height * rows;
    int[] image = new int[tiled_h * tiled_w];
    for (int r = 0, count = 0; r < rows; r++) {
      for (int c = 0; c < cols; c++, count++) {
        if (images[count] == null)
          continue;
        int[] image_cell = images[count];
        for (int y = 0, ptr = 0; y < height; y++) {
          int to_ptr = (r * height + y) * tiled_w + c * width;
          for (int x = 0; x < width; x++, ptr++) {
            image[to_ptr++] = image_cell[ptr];
          }
        }
      }
    }
    return image;
  }

  /**
   * Mark the region boundary with a special color
   * @param image
   * @param labels
   * @param width
   * @param height
   * @param zeroAsBg
   * @return
   */
  public static int[] markRegionBoundary(int[] image, int[] label, int width, int height, int color, boolean zeroAsBg) {
    int[] marked = (int[])image.clone();
    if (zeroAsBg) {
      for (int i=0; i<image.length; i++) {
        if (label[i]==0) marked[i] =  0xffffffff;
      }
    }
    for (int y=0; y<height; y++) {
      for (int x=0; x<width; x++) {
        int p=y*width+x;
        if (x<width-1 && label[p] != label[p+1]) {
          marked[p] = color;
        }
        if (y < height - 1 && label[p] != label[p + width]) {
          marked[p] = color;
        }
      }
    }
    return marked;
  }

  /**
   * Paste one image into another image
   * @param dest
   * @param dw destination image width
   * @param dh destination image height
   * @param source
   * @param sw source image width
   * @param sh source image height
   * @param dx pasted position (left-top) in dest image
   * @param dy pasted position (left-top) in dest image
   * @param sx pasted position (left-top) in source image
   * @param sy pasted position (left-top) in source image
   * @param pw pasted width
   * @param ph pasted height
   */
  public static void PasteImage(int[] dest, int dw, int dh,
                                int[] source, int sw, int sh,
                                int dx, int dy,
                                int sx, int sy,
                                int pw, int ph) {
    for (int h = 0; h < ph; h++) {
      int dest_i = (dy + h) * dw + dx;
      int src_i = (sy + h) * sw + sx;
      for (int w = 0; w < pw; w++) {
        dest[dest_i ++]= source[src_i++];
      }
    }
  }


  public static void main(String[] args) {
    int[] i1 = {
        1, 2, 3, 4};
    int[] i2 = {
        5, 6, 7, 8};
    int[] i3 = {
        9, 10, 11, 12};
    int[] i4 = {
        13, 14, 15, 16};
    int[][] images = {
        i1, i2, i3, i4};
    int[] i = makeTiledImage(images, 2, 2, 2, 2);

  }
}