package pipe.actions.gui;

import pipe.controllers.PetriNetController;
import pipe.controllers.application.PipeApplicationController;
import pipe.controllers.arcCreator.ArcActionCreator;
import pipe.gui.PetriNetTab;
import pipe.historyActions.component.AddPetriNetObject;
import pipe.views.ArcHead;
import pipe.views.TemporaryArcView;
import uk.ac.imperial.pipe.exceptions.PetriNetComponentException;
import uk.ac.imperial.pipe.models.petrinet.*;
import uk.ac.imperial.pipe.visitor.connectable.arc.ArcSourceVisitor;

import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class ArcAction extends CreateAction {

    /**
     * Class logger
     */
    private static final Logger LOGGER = Logger.getLogger(ArcAction.class.getName());

    /**
     * Visits the source to determine if we can start the arc type here
     */
    private final ArcSourceVisitor sourceVisitor;

    private final ArcActionCreator arcCreator;

    private final PipeApplicationController controller;


    private final ArcHead arcHead;

    private TemporaryArcView<? extends Connectable> temporaryArcView = null;

    public ArcAction(String name, String tooltip, int key, int modifiers, ArcSourceVisitor sourceVisitor,
                     ArcActionCreator arcCreator, PipeApplicationModel applicationModel,
                     PipeApplicationController controller, ArcHead arcHead) {
        super(name, tooltip, key, modifiers, applicationModel);
        this.sourceVisitor = sourceVisitor;
        this.arcCreator = arcCreator;
        this.controller = controller;
        this.arcHead = arcHead;
    }

    /**
     * Changes the temporary arc's end point
     *
     * @param event              mouse event that has just been fired
     * @param petriNetController current petri net controller for the tab showing
     */
    @Override
    public void doAction(MouseEvent event, PetriNetController petriNetController) {
        if (temporaryArcView != null) {
            temporaryArcView.setEnd(event.getPoint());
            if (event.getClickCount() > 0) {
                Point point = event.getPoint();
                temporaryArcView.addIntermediatePoint(new ArcPoint(point, event.isShiftDown()));
            }

            PetriNetTab tab = petriNetController.getPetriNetTab();
            tab.validate();
            tab.repaint();
        }
    }

    /**
     * @param connectable        the item clicked on
     * @param petriNetController the controller for the current petrinet
     */
    @Override
    public <T extends Connectable> void doConnectableAction(T connectable, PetriNetController petriNetController) {
        PetriNetTab tab = petriNetController.getPetriNetTab();
        if (temporaryArcView == null && sourceVisitor.canStart(connectable)) {
            createTemporaryArc(connectable, tab);

        } else if (temporaryArcView != null && canCreateArcHere(connectable)) {
            createArc(connectable, tab);
        }
    }

    /**
     * @param connectable Source of the temporary arc
     * @param tab         Tab to add TemporaryArc to
     * @param <T>         Source class
     * @return created Temporary arc
     */
    private <T extends Connectable> void createTemporaryArc(T connectable, final PetriNetTab tab) {

        temporaryArcView = new TemporaryArcView<>(connectable, arcHead);
        temporaryArcView.addKeyListener(new ArcViewKeyListener(tab));
        tab.add(temporaryArcView);
        temporaryArcView.requestFocusInWindow();
    }

    private <T extends Connectable> boolean canCreateArcHere(T connectable) {
        return arcCreator.canCreate(temporaryArcView.getSourceConnectable(), connectable);
    }

    private <T extends Connectable> void createArc(T connectable, PetriNetTab tab) {
        try {
            ArcCreatorVisitor visitor = new ArcCreatorVisitor();

            temporaryArcView.getSourceConnectable().accept(visitor);
            boolean inbound = visitor.place != null;
            connectable.accept(visitor);
            PetriNetController petriNetController = controller.getActivePetriNetController();
            PetriNet net = petriNetController.getPetriNet();
            Arc<? extends Connectable, ? extends Connectable> arc;
            if (inbound) {
                arc = arcCreator.createInboundArc(visitor.place, visitor.transition,
                        temporaryArcView.getIntermediatePoints());

            } else {
                arc = arcCreator.createOutboundArc(visitor.place, visitor.transition,
                        temporaryArcView.getIntermediatePoints());
            }

            registerUndoEvent(new AddPetriNetObject(arc, net));
            net.add(arc);
        } catch (PetriNetComponentException e) {
            LOGGER.log(Level.SEVERE, e.getMessage());
        }

        tab.remove(temporaryArcView);
        tab.repaint();
        temporaryArcView = null;
    }

    /**
     * Sets place and transition properties accordingly
     */
    private static class ArcCreatorVisitor implements PlaceVisitor, TransitionVisitor {

        private Place place = null;

        private Transition transition = null;

        @Override
        public void visit(Place place) {
            this.place = place;
        }

        @Override
        public void visit(Transition transition) {
            this.transition = transition;
        }
    }


    private final class ArcViewKeyListener implements KeyListener {

        private final PetriNetTab tab;

        private ArcViewKeyListener(PetriNetTab tab) {
            this.tab = tab;
        }

        @Override
        public void keyTyped(KeyEvent e) {
            //Not needed
        }

        @Override
        public void keyPressed(KeyEvent e) {

            switch (e.getKeyCode()) {
                case KeyEvent.VK_META:
                case KeyEvent.VK_WINDOWS:
                case KeyEvent.VK_SPACE:
                    tab.setMetaDown(true);
                    break;

                case KeyEvent.VK_ESCAPE:
                case KeyEvent.VK_DELETE:
                    tab.remove(temporaryArcView);
                    tab.repaint();
                    temporaryArcView = null;
                default:
                    break;
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
            //Not needed
        }
    }

}

