package ip.gui.frames;

import gui.run.RunButton;
import ip.gabor.FilterCanvas;
import ip.gabor.GaborPanel;
import ip.gabor.MartelliParams;
import ip.gabor.MartelliView;
import ip.gui.*;

import java.awt.*;
import java.awt.event.ActionEvent;

import graphics.ImageUtils;

public class MartelliFrame extends PaintFrame {
    private EdgeElements finalEdgeList = new EdgeElements();
    // a list of all edges
    //private EdgeElements expandedEdgeElements = new EdgeElements();


    //Point Begin;
    //Point End;

    // The following number is image specific.
    // Martelli only finds one countour at a time.
    // on multiple runs, you will
    // alter the following.
    public static final int maximumNumberOfEdges = 15000;
    public static final int edgeLength = 20;
    private MartelliParams mp = new MartelliParams();


    // *I have set the search time here, ------\/  - DL
    public static final int runTimeInSeconds = 10;

    private Timer t = new Timer();

    public void erase() {
        super.eraseShapes();
        finalEdgeList = new EdgeElements();
        setPolyList(new Polygons());
        initialize();
    }


    Menu heuristicMenu = getMenu("heuristics");
    MenuItem processUserPoints_mi =
            addMenuItem(heuristicMenu, "[E-M]process user points");
    MenuItem erase_mi =
            addMenuItem(heuristicMenu, "erase path");
    MenuItem negativeRobertsOnGreen_mi =
            addMenuItem(heuristicMenu, "use NegativeRobertsOnGreen");
    MenuItem averageWithChild_mi =
            addMenuItem(getFileMenu(), "averageWithChild");

    MenuItem printPath_mi = addMenuItem(heuristicMenu, "printPath");
    MenuItem gabor_mi = addMenuItem(heuristicMenu, "Gabor...");
    MenuItem grabGabor_mi = addMenuItem(heuristicMenu, "grab gabor");

    GaborPanel gp = null;

// Inputs the green plane and outputs edges found on the
// red plane. The red plane is cleared upon initialization.

    public MartelliFrame(String title) {
        super(title);
        getBoundaryMenu().add(heuristicMenu);
    }

    public static void main(String args[]) {
        MartelliFrame xf = new MartelliFrame("MartelliFrame");
    }


    public void averageWithChild() {
        for (int x = 0; x < getImageWidth(); x++)
            for (int y = 0; y < getImageHeight(); y++) {
                getR()[x][y] = (short) ((getR()[x][y] + getChild().getR()[x][y]) / 2);
                getG()[x][y] = (short) ((getR()[x][y] + getChild().getG()[x][y]) / 2);
                getB()[x][y] = (short) ((getR()[x][y] + getChild().getB()[x][y]) / 2);
            }
        short2Image();
    }


    public void negativeRobertsOnGreen() {
        int p[] = new int[4];
        float delta_u = 0;
        float delta_v = 0;
        for (int x = 0; x < getImageWidth() - 1; x++)
            for (int y = 0; y < getImageHeight() - 1; y++) {
                p[0] = getR()[x][y];
                p[1] = getR()[x + 1][y];
                p[2] = getR()[x][y + 1];
                p[3] = getR()[x + 1][y + 1];
                delta_u = p[0] - p[3];
                delta_v = p[1] - p[2];
                getG()[x][y] =
                        (short) (255 - Math.sqrt(delta_u * delta_u + delta_v * delta_v));
            }
    }

    public static int clip(int x) {
        if (x < 0) return 0;
        return x;
    }

    //evaluates whether Martelli should stop searching or not
    private boolean terminateSearch(EdgeElement el, Point nextPoint) {
        //if (edges.size() > maximumNumberOfEdges ) return true;
        if (t.getElapsedTime() > runTimeInSeconds) {
            t.start(); // restart timer
            return true;
        }
        if (el.distance(nextPoint) < 2) {
            t.start(); // restart timer
            return true;
        }
        return false;
    }


// Finds out whether the current edge coordinates
// have been visited
    EdgeElement getMarked(EdgeElement inputEdgeElement) {
        for (int i = 0; i < finalEdgeList.getSize(); i++) {
            EdgeElement el = finalEdgeList.getElementAt(i);
            if ((el.p1.x == inputEdgeElement.p1.x) &&
                    (el.p1.y == inputEdgeElement.p1.y) &&
                    (el.p2.x == inputEdgeElement.p2.x) &&
                    (el.p2.y == inputEdgeElement.p2.y)) {
                return el;
            }
        }
        return null;
    }

//The cost of an edge element
    private int C22(EdgeElement e, Point nextPoint) {
        int pc = 0;
        EdgeElement p = e.getParent();
        int d = e.distance(nextPoint);
        if (p != null) ;//pc = p.getCost()/e.getPly();
        return
                clip(pc +
                d + //MaxI
                -(
                getG()[e.p1.x][e.p1.y] -
                getG()[e.p2.x][e.p2.y])
                );
    }

    FilterCanvas subBands[];

    /**
     *  getSubBand inputs a point and selects the correct subband direction
     */

    int getMinSubBand(Point p) {
        int subBand = 0;
        int maxValue = Integer.MIN_VALUE;
        int i;
        for (i = 1; i < subBands.length; i++) {
            int v = subBands[i].getGreenValueAt(p);
            if (v > maxValue) {
                maxValue = v;
                subBand = i;
            }
        }
        return subBand;
    }

    int getMaxSubBand(Point p) {
        int subBand = 0;
        int minValue = Integer.MAX_VALUE;
        int i;
        for (i = 1; i < subBands.length; i++) {
            int v = subBands[i].getGreenValueAt(p);
            if (v < minValue) {
                minValue = v;
                subBand = i;
            }
        }
        return subBand;
    }

    /**
     *
     *
     *  Subbands are directional Gabor detectors that
     *  are selected based in the subBand parameter
     */
    private int C(EdgeElement e, Point nextMarker, int subBand, MartelliParams mp) {
        Point edgeEndPoint = e.p2;
        //Iterate through the subbands, selecting for lowest cost
        // Favor the bright, white edges.
        subBand = getMinSubBand(edgeEndPoint);
        int v = subBands[subBand].getGreenValueAt(edgeEndPoint);

        v = v; // brighter edges yield lower cost
        // Favor longer edge chains
        int ply = -e.getPly();

        // The farther from the marker, the higher the cost
        int d = e.distance(nextMarker);
        //if (p != null) pc = p.getCost()-e.getPly();
        return
                (int) (ply * mp.getPly()) +
                (int) (d * mp.getGreediness()) +
                (int) (v * mp.getPixel());
    }

    /**
     *  C is a means to determine the cost of the present edge element.
     */
    private int CSimple(EdgeElement e, Point nextPoint) {
        int pc = 0;
        EdgeElement p = e.getParent();
        int d = 2 * e.distance(nextPoint);
        //if (p != null) pc = p.getCost()-e.getPly();
        return
                clip(pc +
                d + getChild().getG()[e.p1.x][e.p1.y]);
    }

//adds an edge element to the 'expanded' list,
//if it is within the boundaries of the image
    private void addElementToExpandedList(
            Point p1, Point p2,
            EdgeElement parentEdgeElement, Point nextMarker,
            EdgeElements expandedEdgeElements,
            int subBand) {
        if (!Points.isRangeValid(p1, p2, new Dimension(getImageWidth(), getImageHeight()))) return;

        EdgeElement e = new EdgeElement();
        e.setCoordinates(p1, p2);
        e.setParent(parentEdgeElement);
        e.setCost(C(e, nextMarker, subBand, mp));
        expandedEdgeElements.add(e);

    }

    public void paint(Graphics g) {
        super.paint(g);
        Polygons p = getPolyList();
        if (p != null)
            p.drawPolys(g);
    }

    private void expand(EdgeElement el, Point nextMarker,
                        EdgeElements expandedEdgeElements,
                        int subBand) {
        Points nextPoints = GeometryUtils.getNextPoints(el.p2, nextMarker);
        while (nextPoints.hasMorePoints()) {
            Point p = nextPoints.nextPoint();
            addElementToExpandedList(el.p2, p, el, nextMarker, expandedEdgeElements, subBand);

        }

    }

    private void initialize() {
        finalEdgeList = new EdgeElements();
    }

    private void processUserPoints(Points userPoints) {
        if (userPoints.getSize() < 2) {
            System.out.println("Select start and end point(s)");
            return;
        }
        if (subBands == null)
            lowLevelPreProcessForMartelli();

        Polygons polyList = new Polygons();
        setPolyList(polyList);
        Point startPoint = userPoints.getPointAt(0);

        for (int i = 1; i < userPoints.getSize(); i++) {
            Point nextPoint = userPoints.getPointAt(i);
            Polygon p1 =
                    searchFromPoint(
                            new Point(startPoint.x, startPoint.y), nextPoint).getPath();
            startPoint = nextPoint;
            polyList.addElement(p1);
        }

    }

    private void lowLevelPreProcessForMartelli() {
        copyToChildFrame();
        //child.roberts2();

        getChild().gauss3();
        getChild().unahe();
        NegateFrame.negate(getChild());
        subBands = ImageUtils.getSubBands(getChild().getImage(), getChild());
        show();
    }


// the A* algorithm
    public EdgeElement searchFromPoint(Point startPoint, Point nextMarker) {
        initialize();
        EdgeElement lowestCostEdgeElement;

        //A* starts here
        EdgeElement startEdgeElement = new EdgeElement();
        EdgeElements expandedEdgeElements = new EdgeElements();

        Point nextPoint = GeometryUtils.getNextPointOnLine(startPoint, nextMarker);

        startEdgeElement.setCoordinates(startPoint, nextPoint);
        startEdgeElement.setOpen(true);
        finalEdgeList.add(startEdgeElement);
        // Select a filter band, assuming the domain expert's
        // markers are on a good edge.
        int subBand = getMaxSubBand(startPoint);
        System.out.println("new subband=" + subBand);

        while (
                (lowestCostEdgeElement = finalEdgeList.getMinOpenNode())
                != null) {
            if (terminateSearch(lowestCostEdgeElement, nextMarker)) break;

            lowestCostEdgeElement.setOpen(false);
            expand(lowestCostEdgeElement, nextMarker, expandedEdgeElements, subBand);
            if (expandedEdgeElements.getSize() == 0) continue;
            processExpandedNodes(expandedEdgeElements);
            //drawTracks(lowestCostEdgeElement);
        }
        lowestCostEdgeElement = finalEdgeList.getMinOpenNode();
        //System.out.println("ply for lcn="+lowestCostNode.getPly());
        //System.out.println("cost for lcn="+lowestCostNode.getCost());
        return lowestCostEdgeElement;
    } // end martelli

// Let the user see the search gui.run...
// great fun!
//    private void drawTracks(EdgeElement lowestCostNode) {
//        if (child == null)
//            System.out.println(
//                    "You need a child frame for a cost matrix");
//        Graphics g = child.getGraphics();
    //       g.setColor(Color.red);
    //       g.setXORMode(Color.red);
//        g.drawOval(lowestCostNode.p1.x - 1, lowestCostNode.p1.y - 1, 1, 1);
    //   }

    private void processExpandedNodes(EdgeElements expandedEdgeElements) {
        for (int i = 0; i < expandedEdgeElements.getSize(); i++) {
            EdgeElement e = expandedEdgeElements.getElementAt(i);
            EdgeElement MarkedNode = getMarked(e);
            if (MarkedNode == null) {
                finalEdgeList.add(e);
                continue;
            }
            if (e.getCost() < MarkedNode.getCost())
                MarkedNode = e;
        }
    }


    public void gabor() {
        gp = new GaborPanel(getImage());
        Frame f = new Frame();
        f.setLayout(new BorderLayout());
        f.add(gp, BorderLayout.CENTER);
        gp.init();
        f.show();
        f.setSize(200, 200);
    }


    public void grabGabor() {
        //copyToChildFrame();

        //child.roberts2();

        // child.gauss3();
        //child.unahe();
        //child.negate();
        //child.gabor();
        subBands = gp.getFilters();
        MartelliView mv = new MartelliView(mp);
        RunButton rb = new RunButton("Apply") {
            public void run() {
                processUserPoints();
            }
        };
        mv.addRunButton(rb);
        //super.setImageResize(gp.getGaborImage());

        //setImage(
        //       FFTImage.filter(super.getImage(),gp.getGaborImage()));
        //child.setImage(gp.getGaborImage());
        // perform a filter op with the GaborImage.
        //child.fftR2();
        //child.child = new TopFrame("filtered image",
        //            super.getImage());
        //child.child.fftR2();
        //child.complexMultR2();
        //child.ifftR2();

    }


    public void actionPerformed(ActionEvent e) {
        if (match(e, gabor_mi)) {
            gabor();
            return;
        }

        if (match(e, grabGabor_mi)) {
            grabGabor();

            return;
        }

        if (match(e, averageWithChild_mi)) {
            averageWithChild();
            return;
        }
        if (match(e, negativeRobertsOnGreen_mi)) {
            negativeRobertsOnGreen();
            return;
        }
        if (match(e, erase_mi)) {
            erase();
            return;
        }
        if (match(e, processUserPoints_mi)) {
            processUserPoints();

            return;
        }
        super.actionPerformed(e);
    }

    private void processUserPoints() {
        System.out.println("shapes.size()=" + userPoints.getSize());
        Timer martelliTimer = new Timer();
        martelliTimer.start();
        processUserPoints(userPoints);
        martelliTimer.print("Martelli done");
    }
}