package ip.gui;

import ip.gui.frames.ShortCutFrame;
import ip.gui.frames.TopFrame;
import ip.gui.frames.XformFrame;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import math.Mat3;

public class DotArray extends ShortCutFrame
        implements MouseListener, MouseMotionListener {

    XformFrame xf = null;

    MenuBar mb = new MenuBar();
    Menu transformMenu = getMenu("Transform");
    Menu pointMenu = getMenu("Point...");

    MenuItem revert_mi = addMenuItem(
            transformMenu, "[l]oad image from file");
    MenuItem applyBilinear4Points_mi = addMenuItem(
            transformMenu, "[a]applyBilinear4Points");

    MenuItem movePointd_mi = addMenuItem(
            pointMenu, "[d]ont move points");
    MenuItem printPoints_mi = addMenuItem(
            pointMenu, "[p]rint points");
    MenuItem selection = movePointd_mi;

    private Polygon p = new Polygon();
    Image img = null;
    boolean doRevert = false;
    short rn[][];
    short gn[][];
    short bn[][];
    Mat3 at;
    int x1 = 0;
    int y1 = 0;
    int xPoints = 5; //grid X
    int yPoints = 5; //grid Y
    boolean busy = false;
    int width;
    int height;
    int centroid[] = {width / 2, height / 2};
    int xtranslate = 10;
    int ytranslate = 50;

    int pointToMove = -1;

    public Polygon getPolygon() {
        return at.transform(p);
    }

    public void actionPerformed(ActionEvent e) {
        if (match(e, printPoints_mi)) {
            printPoints();
            return;
        }
        if (match(e, movePointd_mi)) {
            pointToMove = -1;
            return;
        }
        if (match(e, applyBilinear4Points_mi)) {
            xf.revert();
            applyBilinear();
            return;
        }
        if (match(e, revert_mi)) {
            revert();
            return;
        }
        super.actionPerformed(e);
    }

    public DotArray(String title, TopFrame _xf) {
        super(title);
        xf = _xf;
        width = xf.getImageWidth();
        height = xf.getImageHeight();
        init();
        addMouseListener(this);
        addMouseMotionListener(this);
        transformMenu.add(pointMenu);
        mb.add(transformMenu);
        setMenuBar(mb);
        setSize(width, height);
    }

    private void init() {
        for (int i = 0; i <= yPoints; i++)
            for (int j = 0; j <= xPoints; j++)
                p.addPoint((int) (j * width / xPoints), (int) (i * height / yPoints));
        centroid = Mat3.centroid(p);
        setPose(0, 1, 1);
    }

    private void revert() {
        xf.revert();
        width = xf.getImageWidth();
        height = xf.getImageHeight();
        img = xf.getImage();
        doRevert = true;
        p = new Polygon();
        init();
        setSize(width + 2 * xtranslate, height + ytranslate + 15);
        repaint();
    }

    public void setPose(double theta, double sx, double sy) {
        Mat3 tr1 = new Mat3();
        Mat3 tr2 = new Mat3();
        Mat3 rt = new Mat3();
        Mat3 sc = new Mat3();
        centroid = rt.centroid(p);

        tr1.setTranslation(centroid[0], centroid[1]);
        sc.setScale(sx, sy);
        rt.setRotation(theta);
        tr2.setTranslation(-centroid[0], -centroid[1]);
        at = tr1.multiply(rt);
        at = at.multiply(sc);
        at = at.multiply(tr2);
    }


    public void update(Graphics g) {
        paint(g);
    }

    public void paint(Graphics g) {
        if (doRevert) {
            if ((img != null))
                g.drawImage(img, xtranslate, ytranslate, width, height, this);
        }
        Polygon pt = at.transform(p);
        g.translate(xtranslate, ytranslate);
        for (int i = 0; i <= yPoints; i++)
            for (int j = 0; j <= xPoints; j++) {
                if (j != (xPoints))
                    g.drawLine(pt.xpoints[j + i * (xPoints + 1)],
                            pt.ypoints[j + i * (xPoints + 1)],
                            pt.xpoints[j + 1 + i * (xPoints + 1)],
                            pt.ypoints[j + 1 + i * (xPoints + 1)]);
                if (i != (yPoints))
                    g.drawLine(pt.xpoints[j + i * (xPoints + 1)],
                            pt.ypoints[j + i * (xPoints + 1)],
                            pt.xpoints[j + (i + 1) * (xPoints + 1)],
                            pt.ypoints[j + (i + 1) * (xPoints + 1)]);
            }
        for (int i = 0; i < pt.npoints; i++)
            g.drawRect(pt.xpoints[i] - 2, pt.ypoints[i] - 2, 4, 4);
    }

    public void applyBilinear() {
        rn = new short[width][height];
        gn = new short[width][height];
        bn = new short[width][height];

        for (int i = 0; i < yPoints; i++)
            for (int j = 0; j < xPoints; j++) {
                Polygon sourcePoly = new Polygon();
                sourcePoly.addPoint((int) (j * width / xPoints), (int) (i * height / yPoints));
                sourcePoly.addPoint((int) ((j + 1) * width / xPoints), (int) (i * height / yPoints));
                sourcePoly.addPoint((int) ((j + 1) * width / xPoints), (int) ((i + 1) * height / yPoints));
                sourcePoly.addPoint((int) (j * width / xPoints), (int) ((i + 1) * height / yPoints));
                Polygon destPoly = new Polygon();
                destPoly.addPoint(p.xpoints[j + i * (xPoints + 1)], p.ypoints[j + i * (xPoints + 1)]);
                destPoly.addPoint(p.xpoints[j + 1 + i * (xPoints + 1)], p.ypoints[j + 1 + i * (xPoints + 1)]);
                destPoly.addPoint(p.xpoints[j + 1 + (i + 1) * (xPoints + 1)], p.ypoints[j + 1 + (i + 1) * (xPoints + 1)]);
                destPoly.addPoint(p.xpoints[j + (i + 1) * (xPoints + 1)], p.ypoints[j + (i + 1) * (xPoints + 1)]);
                solve(sourcePoly, destPoly);
            }
        xf.setR(rn);
        xf.setG(gn);
        xf.setB(bn);
        xf.short2Image();
    }

    int Dist(Point a, Point b) {
        return (int) (Math.sqrt(((b.x - a.x) * (b.x - a.x)) + ((b.y - a.y) * (b.y - a.y))));
    }

    public void solve(Polygon S, Polygon D) {
        int w = width;
        int h = height;
        double xStart1, xEnd1,yStart1, yEnd1;
        double xStart2, xEnd2,yStart2, yEnd2;
        Point s0,s1,s2,s3,d0,d1,d2,d3;
        int MAX = 0;
        int MAX1 = 0;
        int xc, yc, xp, yp;
        s0 = new Point(S.xpoints[0], S.ypoints[0]);
        s1 = new Point(S.xpoints[1], S.ypoints[1]);
        s2 = new Point(S.xpoints[2], S.ypoints[2]);
        s3 = new Point(S.xpoints[3], S.ypoints[3]);
        d0 = new Point(D.xpoints[0], D.ypoints[0]);
        d1 = new Point(D.xpoints[1], D.ypoints[1]);
        d2 = new Point(D.xpoints[2], D.ypoints[2]);
        d3 = new Point(D.xpoints[3], D.ypoints[3]);

        MAX = Dist(d0, d1);
        MAX1 = Dist(d1, d2);
        if (MAX1 > MAX) MAX = MAX1;
        MAX1 = Dist(d2, d3);
        if (MAX1 > MAX) MAX = MAX1;
        MAX1 = Dist(d3, d0);
        if (MAX1 > MAX) MAX = MAX1;

        MAX1 = Dist(s0, s1);
        if (MAX1 > MAX) MAX = MAX1;
        MAX1 = Dist(s1, s2);
        if (MAX1 > MAX) MAX = MAX1;
        MAX1 = Dist(s2, s3);
        if (MAX1 > MAX) MAX = MAX1;
        MAX1 = Dist(s3, s0);
        if (MAX1 > MAX) MAX = MAX1;
        MAX = MAX + 20;

        for (int i = 0; i < MAX; i++) {
            xStart1 = d0.x + ((d3.x - d0.x) * i / MAX);
            xEnd1 = d1.x + ((d2.x - d1.x) * i / MAX);

            yStart1 = d0.y + ((d3.y - d0.y) * i / MAX);
            yEnd1 = d1.y + ((d2.y - d1.y) * i / MAX);

            xStart2 = s0.x + ((s3.x - s0.x) * i / MAX);
            xEnd2 = s1.x + ((s2.x - s1.x) * i / MAX);

            yStart2 = s0.y + ((s3.y - s0.y) * i / MAX);
            yEnd2 = s1.y + ((s2.y - s1.y) * i / MAX);

            for (int j = 0; j < MAX; j++) {
                xc = (int) (xStart1 + ((xEnd1 - xStart1) * j / MAX));
                yc = (int) (yStart1 + ((yEnd1 - yStart1) * j / MAX));
                xp = (int) (xStart2 + ((xEnd2 - xStart2) * j / MAX));
                yp = (int) (yStart2 + ((yEnd2 - yStart2) * j / MAX));

                if ((xp < w) && (yp < h) &&
                        (xp >= 0) &&
                        (yp >= 0) && (xc < w) && (yc < h) &&
                        (xc >= 0) && (yc >= 0)) {
                    rn[xc][yc] = xf.getR()[xp][yp];
                    gn[xc][yc] = xf.getG()[xp][yp];
                    bn[xc][yc] = xf.getB()[xp][yp];
                }
            }
        }
    }

    public void movePoints(MouseEvent e) {
        int i = pointToMove;
        p.xpoints[i] = getX(e);
        p.ypoints[i] = getY(e);
        repaint();
    }

    public void printPoints() {
        for (int i = 0; i < p.xpoints.length; i++) {
            System.out.println("af.setPoint(" + i + "," + p.xpoints[i] + "," + p.ypoints[i] + ");");
        }
    }

    public void setPoint(int i, int x, int y) {
        p.xpoints[i] = x;
        p.ypoints[i] = y;
        repaint();
    }

    private int getX(MouseEvent e) {
        return (int) (e.getX() - xtranslate);
    }

    private int getY(MouseEvent e) {
        return (int) (e.getY() - ytranslate);
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseClicked(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
        busy = false;
        repaint();
    }

    public void mouseDragged(MouseEvent e) {
        e.consume();
        double dx = getX(e);
        double dy = getY(e);
        double sx, sy;
        double ray = 100000;
        if (!busy) {
            busy = true;
            pointToMove = -1;
            for (int i = 0; i < p.npoints; i++) {
                sx = (p.xpoints[i] - dx) * (p.xpoints[i] - dx);
                sy = (p.ypoints[i] - dy) * (p.ypoints[i] - dy);
                if ((sx + sy) < ray) {
                    ray = sx + sy;
                    pointToMove = i;
                }
            }
        }
        if (pointToMove != -1) {
            movePoints(e);
            return;
        }
        repaint();
    }

    public void mouseMoved(MouseEvent e) {
    }

}