package ip.hak;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;

public class MyPanel extends Panel implements ItemListener, ActionListener {

  int w,h;
  SmallImage si1, si2;
  Button compButton;
  ImageComponent mi;
  Label nl, il1, il2, cl, wl, hl, sl;
  TextField tf[][] = new TextField[3][2];
  Checkbox cb[] = new Checkbox[3];
  Label nof;
  TextField noftf;
  int isize[][] = new int[3][2];
  Image im[] = new Image[2];
  boolean imageState[] = new boolean[2];
  Dimension di;
  ColorModel cm;
  short r[][][] = new short[3][][];
  short g[][][] = new short[3][][];
  short b[][][] = new short[3][][];
  Image iar[];
  int nf;
  SmallImageFrame sif = null;
  Polygon sp[];
  Polygon dp[];
  Polygon tp[];
  ip.gui.ImageSequence is = new ip.gui.ImageSequence();

  public MyPanel(int wid, int hei) {
    w = wid;
    h = hei;
    setSize(w, h);
    init();
    reLocate();
    imageState[0] = imageState[1] = false;
  }

  public void makeSmallImageFrame(Image ig) {
    sif = new SmallImageFrame("Morph Image");

    sif.loadImage(ig);
    sif.init();
    sif.setVisible(true);
  }


  public void setImageState(int ind) {
    imageState[ind] = true;
  }

  public void initPoint() {
    Dimension d = si2.getSize();
    Point pl = si2.getLocation();

    P4 p4 = new P4(new Point(pl.x + d.width / 2, pl.y + d.height / 2), 0, 0);
    add(p4);
  }

  public void setImageSize(int wid, int hei, int index) {
    isize[index][0] = wid;
    isize[index][1] = hei;
    tf[index][0].setText("" + wid);
    tf[index][1].setText("" + hei);
  }

  public void init() {
    setLayout(null);
    int inten = 200;
    Color bgColor = new Color(inten, inten, inten);
    setBackground(bgColor);
    si1 = new SmallImage(10, 10, this, 0);
    add(si1);
    si2 = new SmallImage(10, 10, this, 1);
    add(si2);
    mi = new ImageComponent(20, 20, this);
    add(mi);

    compButton = new Button("Compute");
    add(compButton);
    compButton.addActionListener(this);

    il1 = new Label("Image 1");
    add(il1);
    il2 = new Label("Image 2");
    add(il2);
    cl = new Label("Custom");
    add(cl);
    wl = new Label("Width");
    add(wl);
    hl = new Label("Height");
    add(hl);
    sl = new Label("Set");
    add(sl);
    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 2; j++) {
        tf[i][j] = new TextField("0");
        add(tf[i][j]);
        tf[i][j].setEditable(false);
      }
    for (int i = 0; i < 3; i++) {
      cb[i] = new Checkbox();
      add(cb[i]);
      cb[i].addItemListener(this);
    }
    cb[0].setState(true);


    nof = new Label("Number of Frame ");
    add(nof);
    noftf = new TextField("10");
    add(noftf);
  }

  public void reLocate() {
    Dimension siz = getSize();
    w = siz.width;
    h = siz.height;
    int qw = w / 4;
    int qh = h / 4;
    int g = 10;

    // Small Image
    int sish = (3 * qh - 3 * g) / 2;
    si1.setSize(sish, sish);
    si1.setLocation(g, g);

    si2.setSize(sish, sish);
    si2.setLocation(g, sish + 2 * g);

    // Main Image
    mi.setSize(3 * qh - 2 * g, 3 * qh - 2 * g);
    mi.setLocation(sish + 3 * g, g);

    // Buttons
    Dimension d = new Dimension(80, 25);
    compButton.setSize(d.width, d.height);
    compButton.setLocation(3 * qw + (qw - d.width) / 2, qh * 3 + (qh - d.height) / 2);

    // Width, Height, Set Label
    d = new Dimension(50, 20);
    int gv = (qh - d.height * 4) / 5;
    int gh = (qw * 2 - d.width * 4) / 5;
    wl.setSize(d.width, d.height);
    wl.setLocation(gh + 2, qh * 3 + d.height + gv * 2);
    hl.setSize(d.width, d.height);
    hl.setLocation(gh, qh * 3 + d.height * 2 + gv * 3);
    sl.setSize(d.width, d.height);
    sl.setLocation(gh + 6, qh * 3 + d.height * 3 + gv * 4);

    il1.setSize(d.width, d.height);
    il1.setLocation(d.width + gh * 2 + 2, qh * 3 + gv);
    il2.setSize(d.width, d.height);
    il2.setLocation(d.width * 2 + gh * 3 + 2, qh * 3 + gv);
    cl.setSize(d.width, d.height);
    cl.setLocation(d.width * 3 + gh * 4 + 2, qh * 3 + gv);

    // Text Fields
    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 2; j++) {
        tf[i][j].setSize(d.width, d.height);
        tf[i][j].setLocation(d.width * (i + 1) + gh * (i + 2), qh * 3 + d.height * (j + 1) + gv * (j + 2));
      }

    // CheckBoxes
    for (int i = 0; i < 3; i++) {
      cb[i].setSize(20, 20);
      cb[i].setLocation(d.width * (i + 1) + gh * (i + 2) + 15, qh * 3 + d.height * 3 + gv * 4);
    }

    // Number of Frame Label & TextField
    d = new Dimension(100, 20);
    nof.setSize(d.width, d.height);
    nof.setLocation(qw * 2 + g, qh * 3 + (qh - d.height) / 2);
    noftf.setSize(40, d.height);
    noftf.setLocation(qw * 2 + 2 * g + d.width, qh * 3 + (qh - d.height) / 2);

    repaint();
  }

  public Dimension getWnH() {
    // get selected checkbox
    int id = 0;
    for (int i = 0; i < 3; i++)
      if (cb[i].getState()) {
        id = i;
        break;
      }

    // Check 2 images are already loaded.
    if (!imageState[0] || !imageState[1]) {
      MessageDialog md = new MessageDialog(new Frame(), "Error!!!", false, "Load image first!", 200, 100);
      return null;
    }

    // Check width & height are valid.
    String sw = tf[id][0].getText();
    String sh = tf[id][1].getText();
    Dimension d = new Dimension(Integer.parseInt(sw), Integer.parseInt(sh));
    if (d.width <= 0 || d.height <= 0) {
      MessageDialog md = new MessageDialog(new Frame(), "Error!!!", false, "Width or Height should be greater than 0.", 300, 100);
      return null;
    }
    return d;
  }

  public void doCompute() {
    di = getWnH();
    if (di == null)
      return;


    // get & check the # of Frame
    String sn = noftf.getText();
    nf = Integer.parseInt(sn);
    if (nf <= 0) {
      MessageDialog md = new MessageDialog(new Frame(), "Error!!!", false, "Number of Frame should be greater than 0.", 300, 100);
      return;
    }

    r[2] = new short[di.width][di.height];
    g[2] = new short[di.width][di.height];
    b[2] = new short[di.width][di.height];

    write2Memory();
  }

  public void write2Memory() {

    // get 2 Image
    im[0] = si1.getResizedImage(di.width, di.height);
    im[1] = si2.getResizedImage(di.width, di.height);
    image2Short(0);
    image2Short(1);

    sp = sif.getSourcePoly();
    dp = sif.getDestPoly();
    checkPolygon(sp, dp); //test
    tp = new Polygon[sp.length];

    float t = 1f / nf;

    iar = new Image[nf];
    mi.switchMessage();
    float i = 0.0f;
    for (int j = 0; j < nf; j++) {
      makeTP(i);
      checkPolygon(sp, tp); //test
      double a[][][] = new double[sp.length][][];

      for (int it = 0; it < sp.length; it++) {
        a[it] = infer4PointA(sp[it], tp[it]);
      }
      inverseBilinearXform(a, tp);
      morphImage(i);
      iar[j] = short2Image();
      is.add(iar[j]);
      i += t;
    }
    mi.switchMessage();
    is.save();
    mi.showImage(iar, di);
  }

  public void checkPolygon(Polygon[] s, Polygon[] d) {
    for (int j = 0; j < s.length; j++) {
      int xp1[] = s[j].xpoints;
      int yp1[] = s[j].ypoints;
      int xp2[] = d[j].xpoints;
      int yp2[] = d[j].ypoints;


      if (xp1.length != xp2.length || xp1.length != yp1.length || xp1.length != yp2.length)
        System.out.println("length is diff");
      for (int i = 0; i < xp1.length; i++) {
        if (xp1[i] != xp2[i] || yp1[i] != yp2[i])
          System.out.println("point is diff");
      }
    }
  }

  public void makeTP(float t1) {
    for (int j = 0; j < tp.length; j++) {
      int xp1[] = sp[j].xpoints;
      int yp1[] = sp[j].ypoints;
      int xp2[] = dp[j].xpoints;
      int yp2[] = dp[j].ypoints;

      int psize = xp1.length;
      int xp3[] = new int[psize];
      int yp3[] = new int[psize];

      for (int k = 0; k < psize; k++) {
        xp3[k] = xp1[k] + (int) (t1 * (xp2[k] - xp1[k]));
        yp3[k] = yp1[k] + (int) (t1 * (yp2[k] - yp1[k]));
      }
      tp[j] = new Polygon(xp3, yp3, psize);
    }
  }

  public double[][] infer4PointA(Polygon s, Polygon d) {
    // D is destination
    // S is source
    int xd[] = d.xpoints;
    int yd[] = d.ypoints;
    int xs[] = s.xpoints;
    int ys[] = s.ypoints;

    // d4 is a 2x4
    double d4 [][] = {
      {xd[0], xd[1], xd[2], xd[3]},
      {yd[0], yd[1], yd[2], yd[3]},
    };
    // s4 is a 4x4
    double s4[][] = {
      {xs[0], xs[1], xs[2], xs[3]},
      {ys[0], ys[1], ys[2], ys[3]},
      {xs[0] * ys[0], xs[1] * ys[1], xs[2] * ys[2], xs[3] * ys[3]},
      {1, 1, 1, 1},
    };
    math.Mat4 s4Mat = new math.Mat4(s4);
    math.Mat4 s4MatInverse = s4Mat.invert();
    // 2x4*4x4 = 2x4
    double[][] a = s4MatInverse.multiply2x4(d4);
    return a;
  }

  public void inverseBilinearXform(double a[][][], Polygon d[]) {
    int w = di.width;
    int h = di.height;
    short rn[][] = new short[w][h];
    short gn[][] = new short[w][h];
    short bn[][] = new short[w][h];
    double p[] = new double[2];
    int red, green, blue;
    int xp, yp, i, j;
    for (int x = 0; x < w; x++)
      for (int y = 0; y < h; y++) {
        int s = selectPolygon(d, x, y);
        if (s == -1)
          continue;
        p = inverseMap4(a[s], x, y);
        xp = (int) (p[0]);
        yp = (int) (p[1]);
        if ((xp < w - 1) && (yp < h - 1) && (xp > 0) && (yp > 0)) {
          rn[x][y] = r[1][xp][yp];
          gn[x][y] = g[1][xp][yp];
          bn[x][y] = b[1][xp][yp];
        }
      }
    r[1] = rn;
    g[1] = gn;
    b[1] = bn;
    //short2Image();
  }

  public double[] inverseMap4(double a[][], double xp, double yp) {
    double as =
        -a[1][1] * a[0][2]
        + a[1][2] * a[0][1];
    double b =
        a[0][2] * yp + a[1][0] * a[0][1] - a[0][0] * a[1][1]
        - a[1][2] * xp + a[1][2] * a[0][3] - a[0][2] * a[1][3];
    double c = yp * a[0][0]
        - a[1][0] * xp
        + a[1][0] * a[0][3]
        - a[1][3] * a[0][0];
    double y = quadraticRoot(as, b, c);
    double x =
        (xp - a[0][1] * y - a[0][3]) / (a[0][0] + a[0][2] * y);
    double p[] = {x, y};
    return p;
  }

  public double quadraticRoot(double a, double b, double c) {
    if (a == 0) a = 0.00001;
    double sqrtArg = b * b - 4 * a * c;
    double aa = 2 * a;
    if (sqrtArg < 0) return -b / aa; // ignore imaginary part.
    double root1 = (-b + Math.sqrt(sqrtArg)) / aa;
    double root2 = (-b - Math.sqrt(sqrtArg)) / aa;
    if ((root1 >= 0) && (root1 < di.height)) return root1;
    if ((root2 >= 0) && (root2 < di.height)) return root2;
    if (root1 > di.height) return di.height;
    return 0;
  }

  public int selectPolygon(Polygon d[], int x, int y) {
    for (int i = 0; i < d.length; i++) {
      if (d[i].contains(x, y))
        return i;
    }
    return -1;
  }

  public void image2Short(int idx) {
    r[idx] = new short[di.width][di.height];
    g[idx] = new short[di.width][di.height];
    b[idx] = new short[di.width][di.height];

    int pels[] = new int[di.width * di.height];
    cm = ColorModel.getRGBdefault();

    PixelGrabber grabber = new PixelGrabber(im[idx], 0, 0, di.width, di.height, pels, 0, di.width);

    try {
      grabber.grabPixels();
    } catch (InterruptedException e) {
    }
    ;

    int i = 0;
    for (int x = 0; x < di.width; x++)
      for (int y = 0; y < di.height; y++) {
        i = x + y * di.width;
        b[idx][x][y] = (short) cm.getBlue(pels[i]);
        g[idx][x][y] = (short) cm.getGreen(pels[i]);
        r[idx][x][y] = (short) cm.getRed(pels[i]);
      }
  }

  public void morphImage(float t) {
    for (int x = 0; x < di.width; x++)
      for (int y = 0; y < di.height; y++) {
        r[2][x][y] = (short) (r[0][x][y] + r[1][x][y] * t - r[0][x][y] * t);
        g[2][x][y] = (short) (g[0][x][y] + g[1][x][y] * t - g[0][x][y] * t);
        b[2][x][y] = (short) (b[0][x][y] + b[1][x][y] * t - b[0][x][y] * t);
      }
  }

  public Image short2Image() {
    Toolkit tk = Toolkit.getDefaultToolkit();
    int pels[] = new int[di.width * di.height];
    for (int x = 0; x < di.width; x++)
      for (int y = 0; y < di.height; y++) {
        pels[x + y * di.width] = 0xff000000 | (r[2][x][y] << 16)
            | (g[2][x][y] << 8)
            | b[2][x][y];
      }
    Image i = tk.createImage(new MemoryImageSource(di.width, di.height, cm, pels, 0, di.width));
    return i;
  }


  public void paint(Graphics g) {
    super.paint(g);
    int p2 = h / 4 * 3;
    int p1 = (p2 - 30) / 2 + 20;

    g.drawLine(p1, 0, p1, p2);
    g.drawLine(0, p2, w, p2);
  }

  public void actionPerformed(ActionEvent e) {
    if (e.getSource() == compButton) {
      doCompute();
      return;
    }
  }

  public void itemStateChanged(ItemEvent e) {
    if (e.getSource() == cb[0]) {
      cb[0].setState(true);
      cb[1].setState(false);
      cb[2].setState(false);
      tf[2][0].setEditable(false);
      tf[2][1].setEditable(false);
      return;
    }
    if (e.getSource() == cb[1]) {
      cb[0].setState(false);
      cb[1].setState(true);
      cb[2].setState(false);
      tf[2][0].setEditable(false);
      tf[2][1].setEditable(false);
      return;
    }
    if (e.getSource() == cb[2]) {
      cb[0].setState(false);
      cb[1].setState(false);
      cb[2].setState(true);
      tf[2][0].setEditable(true);
      tf[2][1].setEditable(true);
      tf[2][0].requestFocus();
      return;
    }
  }
}