package ui.view;

import classes.*;
import ui.controller.Control;
import ui.controller.UpdateController;
import ui.model.Model;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.io.File;
import java.util.ArrayList;
import java.util.TimerTask;

/**
 * Collection of methods and values needed in both <code>MyCanvas</code> and <code>UpperNodeCanvas</code>
 * <p>
 * Although Java works on references we chose to add explicit return values for clearer code understanding in most cases
 *
 * @author: I. Dix
 */
public abstract class AbstractCanvas extends JPanel {
    final JMenuItem itemDelete = new JMenuItem(Languages.getLanguage()[98]);
    final JMenuItem itemCut = new JMenuItem(Languages.getLanguage()[95]);
    final JMenuItem itemCopy = new JMenuItem(Languages.getLanguage()[96]);
    final JMenuItem itemPaste = new JMenuItem(Languages.getLanguage()[97]);
    final JMenuItem itemGroup = new JMenuItem(Languages.getLanguage()[99]);
    final JMenuItem itemUngroup = new JMenuItem(Languages.getLanguage()[100]);
    final JMenuItem itemTrack = new JMenuItem(Languages.getLanguage()[101]);
    final JMenuItem itemUntrack = new JMenuItem(Languages.getLanguage()[102]);
    final int ANIMTIME = 500; // animation Time
    private final int animFPS = 60;
    final int animDelay = 1000 / animFPS; // animation Delay
    protected Model model;
    protected Control controller;
    protected int x = 0;
    protected int y = 0;
    // Selection
    AbstractCpsObject tempCps = null;
    UpdateController updCon;
    // PopUpMenu
    JPopupMenu popmenu = new JPopupMenu();
    // Tooltip
    boolean toolTip; // Tooltip on or off
    Position toolTipPos = new Position(); // Tooltip Position
    String toolTipText = "";
    ArrayList<HolonElement> dataSelected = new ArrayList<>();
    ArrayList<AbstractCpsObject> tempSelected = new ArrayList<>();
    boolean[] showedInformation = new boolean[5];
    boolean dragging = false; // for dragging
    boolean dragged = false; // if an object/objects was/were dragged
    boolean drawEdge = false; // for drawing edges
    boolean doMark = false; // for double click
    CpsEdge edgeHighlight = null;
    Point mousePosition = new Point(); // Mouse Position when
    ArrayList<Position> savePos;
    // edge Object Start Point
    int cx, cy;
    int sx, sy; // Mark Coords
    Position unPos;
    // Animation
    Timer animT; // animation Timer
    int animDuration = ANIMTIME; // animation Duration
    int animSteps = animDuration / animDelay; // animation Steps;
    ArrayList<AbstractCpsObject> animCps = null;
    // Graphics
    Image img = null; // Contains the image to draw on the Canvas
    Graphics2D g2; // For Painting
    float scalediv20;
    // Mouse
    private boolean click = false;

    // ------------------------------------------ METHODS ------------------------------------------
    String paintEdge(CpsEdge con, String maxCap) {
        if (con.getA().getId() != model.getSelectedObjectID() && con.getB().getId() != model.getSelectedObjectID()
                && con != edgeHighlight) {
            if (con.getConnected() == CpsEdge.CON_UPPER_NODE || con.getConnected() == CpsEdge.CON_UPPER_NODE_AND_INSIDE) {
                setEdgeState(con);
            } else {
                g2.setColor(Color.DARK_GRAY);
                g2.setStroke(new BasicStroke(2));
            }
            g2.drawLine(con.getA().getPosition().x, con.getA().getPosition().y, con.getB().getPosition().x,
                    con.getB().getPosition().y);

            maxCap = setCapacityString(con, maxCap);

            if (showedInformation[0]) {
                if (con.getConnected() == CpsEdge.CON_UPPER_NODE
                        || con.getConnected() == CpsEdge.CON_UPPER_NODE_AND_INSIDE) {
                    g2.drawString(con.getFlow() + "/" + maxCap,
                            (con.getA().getPosition().x + con.getB().getPosition().x) / 2,
                            (con.getA().getPosition().y + con.getB().getPosition().y) / 2);
                } else {
                    g2.drawString("not connected", (con.getA().getPosition().x + con.getB().getPosition().x) / 2,
                            (con.getA().getPosition().y + con.getB().getPosition().y) / 2);
                }
            }
        }

        return maxCap;
    }


    void setEdgeState(CpsEdge con) {
        if (con.isWorking()) {
            g2.setColor(Color.GREEN);
            if (con.getCapacity() != CpsEdge.CAPACITY_INFINITE) {
                g2.setStroke(new BasicStroke(Math.min(((con.getFlow() / con.getCapacity() * 3) + 1), 4)));
            }
        } else {
            g2.setColor(Color.RED);
            g2.setStroke(new BasicStroke(2));
        }
    }


    String setCapacityString(CpsEdge con, String maxCap) {
        if (con.getCapacity() == -1) {
            maxCap = Character.toString('\u221e');
        } else if (con.getCapacity() == -2) {
            maxCap = "???";
        } else {
            maxCap = String.valueOf(con.getCapacity());
        }
        return maxCap;
    }


    String drawEdgeLine(CpsEdge con, String maxCap) {
        if (con.getA().getId() == model.getSelectedObjectID()
                || model.getSelectedCpsObjects().contains(con.getA()) || tempSelected.contains(con.getA())
                || con.getB().getId() == model.getSelectedObjectID()
                || model.getSelectedCpsObjects().contains(con.getB())
                || tempSelected.contains(con.getB()) && con != edgeHighlight) {
            g2.drawLine(con.getA().getPosition().x, con.getA().getPosition().y, con.getB().getPosition().x,
                    con.getB().getPosition().y);

            maxCap = setCapacityString(con, maxCap);

            if (showedInformation[0]) {
                if (con.getConnected() == CpsEdge.CON_UPPER_NODE
                        || con.getConnected() == CpsEdge.CON_UPPER_NODE_AND_INSIDE) {
                    g2.drawString(con.getFlow() + "/" + maxCap,
                            (con.getA().getPosition().x + con.getB().getPosition().x) / 2,
                            (con.getA().getPosition().y + con.getB().getPosition().y) / 2);
                } else {
                    g2.drawString("not connected",
                            (con.getA().getPosition().x + con.getB().getPosition().x) / 2,
                            (con.getA().getPosition().y + con.getB().getPosition().y) / 2);
                }
            }
        }

        return maxCap;
    }


    void setEdgePictureAndHighlighting(AbstractCpsObject cps) {
        // node image
        if (cps instanceof CpsNode && (cps == tempCps || model.getSelectedCpsObject() == cps
                || model.getSelectedCpsObjects().contains(cps) || tempSelected.contains(cps))) {
        	img = Util.loadImage(this, "/Images/node_selected.png");
        } else {
            if (cps instanceof HolonSwitch) {
                if (((HolonSwitch) cps).getActiveAt()[model.getCurIteration()]) {
                    ((HolonSwitch) cps).setAutoState(true);
                } else {
                    ((HolonSwitch) cps).setAutoState(false);
                }
            }
            // Highlighting
            if ((cps == tempCps && model.getSelectedCpsObjects().size() == 0 && tempSelected.size() == 0)
                    || model.getSelectedCpsObjects().contains(cps) || tempSelected.contains(cps)) {
                g2.setColor(Color.BLUE);
                g2.fillRect((int) (cps.getPosition().x - controller.getScaleDiv2() - scalediv20),
                        (int) (cps.getPosition().y - controller.getScaleDiv2() - scalediv20),
                        (int) (controller.getScale() + (scalediv20 * 2)),
                        (int) (controller.getScale() + (scalediv20 * 2)));
                if (showedInformation[1] && cps instanceof HolonObject) {
                    g2.setColor(Color.BLACK);
                    float totalEnergy = ((HolonObject) cps).getCurrentEnergyAtTimeStep(model.getCurIteration());
                    g2.drawString(Float.toString(totalEnergy), cps.getPosition().x - controller.getScaleDiv2(),
                            cps.getPosition().y - controller.getScaleDiv2() - 10);
                }
            } else if (cps instanceof HolonObject) {
                g2.setColor(((HolonObject) cps).getColor());

                g2.fillRect((int) (cps.getPosition().x - controller.getScaleDiv2() - scalediv20),
                        (int) (cps.getPosition().y - controller.getScaleDiv2() - scalediv20),
                        (int) (controller.getScale() + (scalediv20 * 2)),
                        (int) (controller.getScale() + (scalediv20 * 2)));

                if (showedInformation[1]) {
                    g2.setColor(Color.BLACK);
                    float totalEnergy = ((HolonObject) cps).getCurrentEnergyAtTimeStep(model.getCurIteration());
                    g2.drawString(Float.toString(totalEnergy), cps.getPosition().x - controller.getScaleDiv2(),
                            cps.getPosition().y - controller.getScaleDiv2() - 10);
                }
            }
            // draw image
            File checkPath = new File(cps.getImage());
            if (checkPath.exists()) {
                img = new ImageIcon(cps.getImage()).getImage();
            } else {
            	img = Util.loadImage(this, cps.getImage());
            }
        }
    }


    void drawMarker() {
        if (sx > x && sy > y) {
            g2.drawRect(x, y, sx - x, sy - y);
        } else if (sx < x && sy < y) {
            g2.drawRect(sx, sy, x - sx, y - sy);
        } else if (sx >= x) {
            g2.drawRect(x, sy, sx - x, y - sy);
        } else if (sy >= y) {
            g2.drawRect(sx, y, x - sx, sy - y);
        }
    }


    void showTooltip(Graphics g) {
        if (toolTip) {
            g2.setColor(new Color(255, 225, 150));
            g2.setStroke(new BasicStroke(1));
            int textWidth = g.getFontMetrics().stringWidth(toolTipText) + 2; // Text
            // width

            // fixed x and y Position to the screen
            int fixXPos = toolTipPos.x - (textWidth >> 1) + model.getScaleDiv2();
            int fixYPos = toolTipPos.y;

            if (fixXPos < 0) {
                fixXPos = 0;
            } else if (fixXPos + textWidth + 1 > this.getWidth()) {
                fixXPos -= (fixXPos + textWidth + 1) - this.getWidth();
            }
            if (fixYPos + 16 > this.getHeight()) {
                fixYPos -= (fixYPos + 16) - this.getHeight();
            }
            g2.fillRect(fixXPos, fixYPos, textWidth, 15);
            g2.setColor(Color.BLACK);
            g2.drawRect(fixXPos, fixYPos, textWidth, 15);
            g2.drawString(toolTipText, fixXPos + 2, fixYPos + 12);
        }
    }


    void setConsoleTextAfterSelect(AbstractCpsObject cps) {
        if (model.getShowConsoleLog()) {
            controller.addTextToConsole("Selected: ", Color.BLACK, 12, false, false, false);
            controller.addTextToConsole("" + cps.getName(), Color.BLUE, 12, true, false, false);
            controller.addTextToConsole(", ID:", Color.BLACK, 12, false, false, false);
            controller.addTextToConsole("" + cps.getId(), Color.RED, 12, true, false, true);
        }
    }


    void setRightClickMenu(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON3) {
        	itemPaste.setEnabled(true);
            if (tempCps != null) {
            	itemPaste.setEnabled(true);
                itemDelete.setEnabled(true);
                itemCut.setEnabled(true);
                itemCopy.setEnabled(true);
                if (tempCps != null) {
                    itemGroup.setEnabled(true);
                    itemTrack.setEnabled(true);
                    itemUntrack.setEnabled(true);
                }
                if (tempCps instanceof CpsUpperNode)
                    itemUngroup.setEnabled(true);
                else
                    itemUngroup.setEnabled(false);
                if (model.getSelectedCpsObjects().size() == 0) {
                    controller.addSelectedObject(tempCps);
                }
            }else{
                itemCut.setEnabled(false);
                itemCopy.setEnabled(false);
                itemGroup.setEnabled(false);
                itemUngroup.setEnabled(false);
                itemTrack.setEnabled(false);
                itemUntrack.setEnabled(false);
        		if(edgeHighlight != null)
        		{
                    itemDelete.setEnabled(true);
                    itemPaste.setEnabled(false);
        		}
        		else
        		{
        			itemDelete.setEnabled(false);
                    itemPaste.setEnabled(true);
        		}
            }
            mousePosition = this.getMousePosition();
            popmenu.show(e.getComponent(), e.getX(), e.getY());
        }
    }
    
    void markObjects() {
        if (doMark) {
            doMark = false;
            for (AbstractCpsObject cps : tempSelected) {
                if (!model.getSelectedCpsObjects().contains(cps)) {
                    controller.addSelectedObject(cps);
                }
            }
            controller.getObjectsInDepth();
            tempSelected.clear();
        }
    }

    int[] determineMousePositionOnEdge(CpsEdge p) {
        int lx, ly, hx, hy;

        if (p.getA().getPosition().x > p.getB().getPosition().x) {
            hx = p.getA().getPosition().x + model.getScaleDiv2() + 7;
            lx = p.getB().getPosition().x + model.getScaleDiv2() - 7;
        } else {
            lx = p.getA().getPosition().x + model.getScaleDiv2() - 7;
            hx = p.getB().getPosition().x + model.getScaleDiv2() + 7;
        }
        if (p.getA().getPosition().y > p.getB().getPosition().y) {
            hy = p.getA().getPosition().y + model.getScaleDiv2() + 7;
            ly = p.getB().getPosition().y + model.getScaleDiv2() - 7;
        } else {
            ly = p.getA().getPosition().y + model.getScaleDiv2() - 7;
            hy = p.getB().getPosition().y + model.getScaleDiv2() + 7;
        }

        return new int[]{lx, ly, hx, hy};
    }


    /**
     * Checks if a double click was made.
     *
     * @return true if doublecklick, false if not
     */
    boolean doubleClick() {
        if (click) {
            click = false;
            return true;
        } else {
            click = true;
            java.util.Timer t = new java.util.Timer("doubleclickTimer", false);
            t.schedule(new TimerTask() {
                @Override
                public void run() {
                    click = false;
                }
            }, 500);
        }
        return false;
    }

    boolean setToolTipInfoAndPosition(boolean on, AbstractCpsObject cps) {
        if (x - controller.getScale() <= cx && y - controller.getScale() <= cy && x >= cx && y >= cy) {
            on = true;
            toolTipPos.x = cps.getPosition().x - controller.getScaleDiv2();
            toolTipPos.y = cps.getPosition().y + controller.getScaleDiv2();
            toolTipText = cps.getName() + ", " + cps.getId();
        }

        return on;
    }


    abstract void drawDeleteEdge();


    void triggerUpdateController() {
        updCon.paintProperties(tempCps);
        updCon.refreshTableHolonElement(model.getMultiTable(), model.getSingleTable());
        updCon.refreshTableProperties(model.getPropertyTable());
    }
}