package at.ac.tuwien.dbai.alternation.gui.example;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Iterator;
import java.util.Vector;

import at.ac.tuwien.dbai.alternation.examples.CatAndMouse2;
import at.ac.tuwien.dbai.alternation.examples.Graph;
import at.ac.tuwien.dbai.alternation.examples.CatAndMouse2.Worktape;
import at.ac.tuwien.dbai.alternation.examples.Graph.Node;
import at.ac.tuwien.dbai.alternation.gui.DefaultFormat;

public class CatAndMouseTest {

    /**
     * This class fills the worktape-canvas for CatAndMouse. It shows the
     * position of the mouse and the cat and illustrates the possible ways each
     * one can take.
     *
     * To initialise the class a <code>Graph<Integer></code>-Object is needed
     * to read out nodes and edges.
     *
     * @author Stefan Weiser
     *
     */
    private static class CatAndMouse2FormatMath extends
            DefaultFormat<CatAndMouse2.Inputtape, CatAndMouse2.Worktape> {

        Graph<Integer> graph;

        CatAndMouse2FormatMath(Graph<Integer> graph) {
            this.graph = graph;
        }

        @Override
        public String getLongInformation(Worktape worktape, String state) {
            return worktape.toString();
        }

        @Override
        public String getShortInformation(Worktape worktape, String state) {

            java.util.StringTokenizer tokens = new java.util.StringTokenizer(
                    worktape.toString(), "; ");
            String returnString = tokens.nextToken() + "\n"
                    + tokens.nextToken();
            return returnString;
        }

        @Override
        public void paintWorktape(Worktape worktape, String state, Graphics g,
                Dimension size) {
            super.paintWorktape(worktape, state, g, size);
            Color mouseColor = Color.YELLOW;
            Color catColor = Color.RED;
            Color catEatMouseColor = Color.ORANGE;
            Color goal = Color.GREEN;

            g.setFont(new java.awt.Font("Arial", java.awt.Font.BOLD, 16));
            g.setColor(mouseColor);
            g.drawString("Mouse", 0, 18);
            g.setColor(catColor);
            g.drawString("Cat", 0, 36);
            g.setColor(goal);
            g.drawString("Goal", 0, 54);

            g.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 20));
            g.setColor(Color.BLACK);

            // Defining the coordinates of each point
            Vector<Coordinate> points = new Vector<Coordinate>();

            if (size.width > 510) {
                points.add(new Coordinate(((size.width - 60) / 6) * 0 + 30,
                        size.height / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 1 + 30,
                        (size.height / 2) / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 1 + 30,
                        (size.height / 2) / 2 + size.height / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 2 + 30,
                        size.height / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 3 + 30,
                        (size.height / 2) / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 3 + 30,
                        (size.height / 2) / 2 + size.height / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 4 + 30,
                        size.height / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 5 + 30,
                        (size.height / 2) / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 5 + 30,
                        (size.height / 2) / 2 + size.height / 2, 30, 30));
                points.add(new Coordinate(((size.width - 60) / 6) * 6 + 30,
                        size.height / 2, 30, 30));
            } else {
                points.add(new Coordinate(30, size.height / 2, 30, 30));
                points.add(new Coordinate(110, (size.height / 2) / 2, 30, 30));
                points.add(new Coordinate(110, (size.height / 2) / 2
                        + size.height / 2, 30, 30));
                points.add(new Coordinate(190, size.height / 2, 30, 30));
                points.add(new Coordinate(270, (size.height / 2) / 2, 30, 30));
                points.add(new Coordinate(270, (size.height / 2) / 2
                        + size.height / 2, 30, 30));
                points.add(new Coordinate(350, size.height / 2, 30, 30));
                points.add(new Coordinate(430, (size.height / 2) / 2, 30, 30));
                points.add(new Coordinate(430, (size.height / 2) / 2
                        + size.height / 2, 30, 30));
                points.add(new Coordinate(510, size.height / 2, 30, 30));
            }

            // Draw Arrows
            Iterator<Integer> iterator = graph.iterator();
            while (iterator.hasNext()) {
                Node<Integer> node = graph.get(iterator.next());
                Iterator<Node<Integer>> childIterator = node.getChildren().
                                                        iterator();
                while (childIterator.hasNext()) {
                    Node<Integer> child = childIterator.next();
                    drawArrow(g, 15, points.get(node.getObject() - 1), points
                            .get(child.getObject() - 1));
                }
            }

            // Draw Points
            FontMetrics fm = g.getFontMetrics();
            for (int i = 0; i < points.capacity(); i++) {
                Color thisColor;
                if (worktape.catPosition.getObject() == worktape.mousePosition
                        .getObject()
                        && worktape.mousePosition.getObject() == i + 1)
                    thisColor = catEatMouseColor;
                else if (worktape.catPosition.getObject() == i + 1)
                    thisColor = catColor;
                else if (worktape.mousePosition.getObject() == i + 1)
                    thisColor = mouseColor;
                else if (i + 1 == points.capacity())
                    thisColor = goal;
                else
                    thisColor = Color.LIGHT_GRAY;

                if (thisColor != null) {
                    g.setColor(thisColor);
                    g.fillRect(points.get(i).x - points.get(i).width / 2,
                            points.get(i).y - points.get(i).height / 2, points
                                    .get(i).width, points.get(i).height);
                    g.setColor(Color.BLACK);
                }
                g.drawRect(points.get(i).x - points.get(i).width / 2, points
                        .get(i).y
                        - points.get(i).height / 2, points.get(i).width, points
                        .get(i).height);

                int slen = fm.stringWidth("" + (i + 1));
                g.drawString("" + (i + 1), points.get(i).x
                        - points.get(i).width / 2 + points.get(i).width / 2
                        - slen / 2, points.get(i).y - points.get(i).height / 2
                        + 22);
            }

        }

        /**
         * Draws an arrow from point p1 to point p2.
         *
         * @param g
         *            the <code>Graphics</code>-Object where to draw the arrow.
         * @param alpha
         *            the angle of the arrowhead
         * @param p1
         *            the first point
         * @param p2
         *            the second point
         */
        private void drawArrow(Graphics g, int alpha,
                CatAndMouseTest.CatAndMouse2FormatMath.Coordinate p1,
                CatAndMouseTest.CatAndMouse2FormatMath.Coordinate p2) {

            Point intPoint = getIntersection(p1, p2);
            if (intPoint == null) {
                System.err.println("Intersection = null");
                intPoint = new Point(p2.x, p2.y);
            }
            g.drawLine(p1.x, p1.y, intPoint.x, intPoint.y);
            if (alpha > 90)
                alpha = 90;
            else if (alpha < 0)
                alpha = 0;

            boolean kleinerWinkel = false;
            double h = Math.sqrt(Math.pow(intPoint.x - p1.x, 2)
                    + Math.pow(intPoint.y - p1.y, 2));
            double beta1 = Math.toDegrees(Math.asin(Math.sqrt(Math.pow(
                    intPoint.x - p1.x, 2))
                    / h));
            double gamma = beta1 - alpha;
            double deltaX = Math.sin(Math.toRadians(gamma)) * 20;
            double deltaY = Math.sqrt(Math.pow(20, 2) - Math.pow(deltaX, 2));
            double beta2 = Math.toDegrees(Math.acos(Math.sqrt(Math.pow(
                    intPoint.x - p1.x, 2))
                    / h));

            int x1 = intPoint.x;
            int x2 = 0;
            int x3 = 0;
            int y1 = intPoint.y;
            int y2 = 0;
            int y3 = 0;

            // to right top
            if (p1.x < p2.x && p1.y > p2.y) {

                x2 = (int) (intPoint.x - deltaX);
                y2 = (int) (intPoint.y + deltaY);

                gamma = beta2 - alpha;
                if (gamma < 0) {
                    gamma = alpha - beta2;
                    kleinerWinkel = true;
                }
                deltaX = Math.cos(Math.toRadians(gamma)) * 20;
                deltaY = Math.sqrt(Math.pow(20, 2) - Math.pow(deltaX, 2));
                x3 = (int) (intPoint.x - deltaX);
                if (kleinerWinkel)
                    y3 = (int) (intPoint.y - deltaY);
                else
                    y3 = (int) (intPoint.y + deltaY);

            }

            // to right bottom
            else if (p1.x < p2.x && p1.y < p2.y) {

                x2 = (int) (intPoint.x - deltaX);
                y2 = (int) (intPoint.y - deltaY);

                gamma = beta2 - alpha;
                if (gamma < 0) {
                    gamma = alpha - beta2;
                    kleinerWinkel = true;
                }
                deltaX = Math.cos(Math.toRadians(gamma)) * 20;
                deltaY = Math.sqrt(Math.pow(20, 2) - Math.pow(deltaX, 2));
                x3 = (int) (intPoint.x - deltaX);
                if (kleinerWinkel)
                    y3 = (int) (intPoint.y + deltaY);
                else
                    y3 = (int) (intPoint.y - deltaY);

            }

            // to left bottom
            else if (p1.x > p2.x && p1.y < p2.y) {

                x2 = (int) (intPoint.x + deltaX);
                y2 = (int) (intPoint.y - deltaY);

                gamma = alpha - beta2;
                if (gamma < 0) {
                    gamma = beta2 - alpha;
                    kleinerWinkel = true;
                }
                deltaX = Math.cos(Math.toRadians(gamma)) * 20;
                deltaY = Math.sqrt(Math.pow(20, 2) - Math.pow(deltaX, 2));

                x3 = (int) (intPoint.x + deltaX);
                if (kleinerWinkel)
                    y3 = (int) (intPoint.y - deltaY);
                else
                    y3 = (int) (intPoint.y + deltaY);

            }

            // to left top
            else if (p1.x > p2.x && p1.y > p2.y) {

                x2 = (int) (intPoint.x + deltaX);
                y2 = (int) (intPoint.y + deltaY);

                gamma = alpha - beta2;
                if (gamma < 0) {
                    gamma = beta2 - alpha;
                    kleinerWinkel = true;
                }
                deltaX = Math.cos(Math.toRadians(gamma)) * 20;
                deltaY = Math.sqrt(Math.pow(20, 2) - Math.pow(deltaX, 2));
                x3 = (int) (intPoint.x + deltaX);
                if (kleinerWinkel)
                    y3 = (int) (intPoint.y + deltaY);
                else
                    y3 = (int) (intPoint.y - deltaY);

            }

            // horicontal
            else if (p1.y == p2.y) {

                deltaX = Math.cos(Math.toRadians(alpha)) * 20;
                deltaY = Math.sqrt(Math.pow(20, 2) - Math.pow(deltaX, 2));

                if (p1.x < intPoint.x) {
                    x2 = (int) (intPoint.x - deltaX);
                    y2 = (int) (intPoint.y - deltaY);
                    x3 = (int) (intPoint.x - deltaX);
                    y3 = (int) (intPoint.y + deltaY);
                } else {
                    x2 = (int) (intPoint.x + deltaX);
                    y2 = (int) (intPoint.y - deltaY);
                    x3 = (int) (intPoint.x + deltaX);
                    y3 = (int) (intPoint.y + deltaY);
                }

            }

            // vertical
            else if (p1.x == p2.x) {

                deltaX = Math.sin(Math.toRadians(alpha)) * 20;
                deltaY = Math.sqrt(Math.pow(20, 2) - Math.pow(deltaX, 2));

                if (p1.y < intPoint.y) {
                    x2 = (int) (intPoint.x - deltaX);
                    y2 = (int) (intPoint.y - deltaY);
                    x3 = (int) (intPoint.x + deltaX);
                    y3 = (int) (intPoint.y - deltaY);
                } else {
                    x2 = (int) (intPoint.x - deltaX);
                    y2 = (int) (intPoint.y + deltaY);
                    x3 = (int) (intPoint.x + deltaX);
                    y3 = (int) (intPoint.y + deltaY);
                }

            }

            int[] xCoordinates = { x1, x2, x3 };
            int[] yCoordinates = { y1, y2, y3 };
            g.fillPolygon(xCoordinates, yCoordinates, 3);

        }

        /**
         * Calculates the intersection point between the connecting line and the
         * edge of the endpoint (p2).
         *
         * @param p1
         *            the first point
         * @param p2
         *            the second point
         * @return the intersection point
         */
        private Point getIntersection(
                CatAndMouseTest.CatAndMouse2FormatMath.Coordinate p1,
                CatAndMouseTest.CatAndMouse2FormatMath.Coordinate p2) {

            int deltaX;
            int deltaY;
            int signumX = 1;
            int signumY = 1;
            int xk = p1.x;
            int yk = p1.y;
            int twoDeltaY;
            int diffDelta;
            int p;

            if (p1.x == p2.x) {
                if (p1.y < p2.y)
                    return new Point(p1.x, p2.y - p2.height / 2);
                else
                    return new Point(p1.x, p2.y + p2.height / 2);
            }
            if (p1.y == p2.y) {
                if (p1.x < p2.x)
                    return new Point(p2.x - p2.width / 2, p1.y);
                else
                    return new Point(p2.x + p2.width / 2, p1.y);
            }

            // Berechnung deltaX bzw. deltaY
            // Bestimmung, ob Inkrementierung oder Dekrementierung
            if (p2.x >= p1.x)
                deltaX = p2.x - p1.x;
            else {
                deltaX = p1.x - p2.x;
                signumX = -1;
            }
            if (p2.y >= p1.y)
                deltaY = p2.y - p1.y;
            else {
                deltaY = p1.y - p2.y;
                signumY = -1;
            }
            twoDeltaY = 2 * deltaY;
            diffDelta = 2 * deltaY - 2 * deltaX;
            p = 2 * deltaY - deltaX;

            if (deltaX > deltaY) {

                // Winkel <45
                for (int i = 1; i < deltaX; i++) {
                    if (p < 0) {
                        xk += signumX;
                        if (p2.x - p2.width / 2 == xk
                                || p2.x + p2.width / 2 == xk)
                            return new Point(xk, yk);
                        p = p + twoDeltaY;
                    } else {
                        xk += signumX;
                        yk += signumY;
                        if (p2.x - p2.width / 2 == xk
                                || p2.x + p2.width / 2 == xk)
                            return new Point(xk, yk);
                        p = p + diffDelta;
                    }
                }

            } else {

                // Winkel >=45
                for (int i = 1; i < deltaY; i++) {
                    if (p < 0) {
                        yk += signumY;
                        if (p2.y - p2.height / 2 == yk
                                || p2.y - p2.height / 2 == yk)
                            return new Point(xk, yk);
                        p = p + twoDeltaY;
                    } else {
                        xk += signumX;
                        yk += signumY;
                        if (p2.y - p2.height / 2 == yk
                                || p2.y - p2.height / 2 == yk)
                            return new Point(xk, yk);
                        p = p + diffDelta;
                    }
                }
            }
            return null;
        }

        @Override
        public void resetWorktape(Graphics g, int width, int height) {
            super.resetWorktape(g, width, height);
        }

        /**
         * This class represents the coordinates of the point of CatAndMouse. It
         * contains the Coordinates of the point (x,y) and width and height of
         * each point.
         *
         * @author Stefan Weiser
         *
         */
        private static class Coordinate {

            private final int x;
            private final int y;

            private final int width;
            private final int height;

            /**
             * Defines the Coordinates and the Dimension of CatAndMouse-points.
             */
            private Coordinate(int x, int y, int width, int height) {
                this.x = x;
                this.y = y;
                this.width = width;
                this.height = height;
            }

        }

    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Graph<Integer> graph = new Graph<Integer>();

        int n = 10;
        for (int i = 1; i <= n; i++) {
            graph.addNode(i);
        }

        graph.addEdge(1, 2);
        graph.addEdge(1, 3);
        graph.addEdge(2, 1);
        graph.addEdge(2, 6);
        graph.addEdge(3, 4);
        graph.addEdge(4, 1);
        graph.addEdge(4, 5);
        graph.addEdge(5, 6);
        graph.addEdge(5, 7);
        graph.addEdge(5, 8);
        graph.addEdge(6, 8);
        graph.addEdge(7, 8);
        graph.addEdge(7, 9);
        graph.addEdge(8, 7);
        graph.addEdge(8, 10);
        graph.addEdge(9, 10);

        Graph.Node<Integer> mouseStart = graph.get(1);
        Graph.Node<Integer> catStart = graph.get(2);
        Graph.Node<Integer> goal = graph.get(10);


        CatAndMouse2 catAndMouseAtm= new CatAndMouse2();
        catAndMouseAtm.compute(graph, catStart, mouseStart, goal);


        CatAndMouse2FormatMath format = new CatAndMouse2FormatMath(graph);
        format.setFormat(130, 55, 30, 30);
        new at.ac.tuwien.dbai.alternation.gui.MainFrame<CatAndMouse2.Inputtape,
        CatAndMouse2.Worktape>(
                catAndMouseAtm.getComputationTree(), format, 5);
    }

}