package scriptbuilder.gui.panels;

import event.editor.frame.Editor;
import event.editor.frame.Properties;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.event.MouseInputAdapter;
import scriptbuilder.gui.IncidentEditorFrame;
import scriptbuilder.gui.ScriptBuilderFrame;
import scriptbuilder.gui.ScriptBuilderGuiConstants;
import scriptbuilder.gui.drawers.CursorDrawer;
import scriptbuilder.gui.drawers.EventIconDrawer;
import scriptbuilder.gui.drawers.IncidentTimelineDrawer;
import scriptbuilder.structures.ScriptEvent;
import scriptbuilder.structures.ScriptEvent.ScriptEventType;
import scriptbuilder.structures.ScriptIncident;
import scriptbuilder.structures.SimulationScript;
import scriptbuilder.structures.TimeSlice;
import scriptbuilder.structures.events.I_ScriptEvent;

/**
 * Represents a single incident timeline in the GUI. Listens for mouse actions.
 *
 * @author Greg Eddington <geddingt@calpoly.edu>
 * @author Bryan McGuffin
 * @version 2017/06/30
 */
public class IncidentTimelinePanel extends JPanel
{

    /**
     * The incident this panel represents.
     */
    ScriptIncident incident;
    /**
     * If true, this panel is in its minimized state.
     */
    //boolean collapsed;
    /**
     * If false, this panel won't be drawn.
     */
    boolean visible;
    /**
     * If true, this panel has focus.
     */
    boolean focused;

    /**
     * If true, right-clicking on this panel will produce the popup menu.
     */
    private boolean hasPopupAccess;

    int cursorTime, lastSlice, x, y;

    /**
     * Filler time at the end of the screen for a particular incident
     */
    public int requestedEditorFillerTime;

    /**
     * Filler time at the end of the screen for the whole script
     */
    public static int requestedScriptBuilderFillerTime = 0;

    /**
     * Constant for amount of filler to add, in seconds. Set to 15 minutes
     */
    public static final int FILLER_INTERVAL_SECONDS = 900;

    /**
     * The map representing the properties of this incident's events. Keys:
     * event types. Values: Properties objects for those events.
     */
    public static Map<ScriptEventType, Properties> eventTypeToPropertyMap;

    /**
     * Listener for the mouse. Receives notifications when the mouse enters,
     * exits, moves through, or clicks inside the panel.
     */
    public class IncidentTimelineMouseListener extends MouseInputAdapter
    {

        /**
         * Action to take when the mouse enters the panel. Here, the incident
         * corresponding to this panel gains focus.
         *
         * @param e the mouse event
         */
        @Override
        public void mouseEntered(MouseEvent e)
        {
            incident.setIncidentActive();
            focused = true;
        }

        /**
         * Action to take when the mouse leaves the panel. Here, the incident
         * loses focus and the panel gets repainted.
         *
         * @param e the mouse event
         */
        @Override
        public void mouseExited(MouseEvent e)
        {
            focused = false;
            repaint();
        }

        /*
         *   Popup menu for incident actions
         */
        private JPopupMenu createPopup()
        {
            JPopupMenu menu = new JPopupMenu();
            JMenuItem eventsMenuItem = new JMenuItem("Events");
            JMenuItem propsMenuItem = new JMenuItem("Properties");
            eventsMenuItem.setActionCommand("Edit Events");
            propsMenuItem.setActionCommand("Modify Incident Properties");

            PopupMenuItemListener menuItemListener = new PopupMenuItemListener();

            eventsMenuItem.addActionListener(menuItemListener);
            propsMenuItem.addActionListener(menuItemListener);

            menu.add(eventsMenuItem);
            menu.add(propsMenuItem);
            return menu;
        }

        class PopupMenuItemListener implements ActionListener
        {

            public void actionPerformed(ActionEvent e)
            {
                JFrame topFrame = (JFrame) getTopLevelAncestor();
                if (topFrame instanceof ScriptBuilderFrame)
                {
                    SimulationScript script = ((ScriptBuilderFrame) topFrame).getScript();
                    if (e.getActionCommand().equals("Edit Events"))
                    {
                        IncidentEditorFrame editor = new IncidentEditorFrame(incident, (ScriptBuilderFrame) topFrame);
                        script.addObserver(editor);
                        editor.setVisible(true);
                        ((ScriptBuilderFrame) topFrame).update(script, script);
                    }
                    if (e.getActionCommand().equals("Modify Incident Properties"))
                    {
                        ((ScriptBuilderFrame) topFrame).incidentDetailsScreen(incident);
                        ((ScriptBuilderFrame) topFrame).update(script, script);
                    }
                }
                topFrame.repaint();
            }
        }

        @Override
        public void mousePressed(MouseEvent e)
        {
            int currentMouseX = e.getX();
            int currentMouseY = e.getY();

            // Does user want a popup menu?
            if (e.isPopupTrigger() && hasPopupAccess)
            {
                JPopupMenu popup = createPopup();
                popup.show(e.getComponent(), currentMouseX, currentMouseY);
            }
        }

        /**
         * Determine if the mouse click happened within a valid timeSlice on
         * this incident; if so, activate the Editor window for that timeSlice.
         *
         * @param e the mouse event
         */
        @Override
        public void mouseClicked(MouseEvent e)
        {
            Editor ed = null;
            ScriptBuilderFrame f = null;
            IncidentEditorFrame g = null;
            if (getTopLevelAncestor() instanceof ScriptBuilderFrame)
            {
                f = (ScriptBuilderFrame) getTopLevelAncestor();

            }
            else if (getTopLevelAncestor() instanceof IncidentEditorFrame)
            {
                g = (IncidentEditorFrame) getTopLevelAncestor();
                ed = new Editor(g);
            }

            x = cursorTime = e.getX();
            y = e.getY();

            if (e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK
                    > ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK / 2)
            {
                cursorTime += ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK
                        - e.getX()
                        % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK;
            }
            else
            {
                cursorTime -= e.getX()
                        % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK;
            }

            int newSlice = (cursorTime / ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK);
            newSlice *= ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION;
            /**
             * Check if click is out of bounds *
             */
            if (newSlice < 0 || incident == null)
            {
                return;
            }

            if (ed != null)
            {
                ed.setSlice(incident.slices.get(newSlice));
            }

            /**
             * Add a new icon if left mouse button was clicked *
             */
            if (e.getButton() == MouseEvent.BUTTON1)
            {

                if (getTopLevelAncestor() instanceof IncidentEditorFrame)
                {
                    if (incident.slices.size() == 0)
                    {
                        newSlice = 0;
                    }
                    if (g.currentEventType != null)
                    {
                        I_ScriptEvent s = ScriptEvent.factoryByType(g.currentEventType);
                        if (ed != null)
                        {
                            ed.addEvent(eventTypeToPropertyMap.get(g.currentEventType), s);
                        }
                        if (incident.slices.get(newSlice) == null)
                        {
                            incident.addNewEvent(s, newSlice);
                        }
                        else
                        {
                            incident.slices.get(newSlice).addEvent(s);
                        }
                        g.update(null, g.getIncident());
                    }
                }
            }

            if (incident.slices.get(newSlice) != null
                    && getTopLevelAncestor() instanceof IncidentEditorFrame)
            {
                ed.setVisible(true);
            }
        }

        /**
         * Determine if the mouse is now hovering over a valid timeslice; if so,
         * alter tooltip text and info window text to reflect the new timeslice.
         *
         * @param e the mouse event
         */
        @Override
        public void mouseMoved(MouseEvent e)
        {
            x = cursorTime = e.getX();
            y = e.getY();

            if (e.getX() % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK
                    > ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK / 2)
            {
                cursorTime += ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK
                        - e.getX()
                        % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK;
            }
            else
            {
                cursorTime -= e.getX()
                        % ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK;
            }

            if (incident != null)
            {
                int newSlice = (cursorTime / ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK);
                newSlice *= ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION;
                if (newSlice >= 0 && incident.slices.get(newSlice) != null)
                {
                    incident.setSliceActive(newSlice);
                    lastSlice = newSlice;
                    String newToolTip;

                    newToolTip = incident.slices.get(newSlice).getToolTipText(y);

                    setToolTipText((newToolTip == null || newToolTip.equals(""))
                            ? null : newToolTip);
                }
            }

            repaint();
        }
    }

    /**
     * Constructor. Generates a HashMap of all possible event types.
     */
    public IncidentTimelinePanel()
    {
        super();

        hasPopupAccess = true;

        requestedEditorFillerTime = 0;
//        FACILITATOR_EVAL_EVENT, RADIO_EVAL_EVENT
        eventTypeToPropertyMap = new HashMap();
        eventTypeToPropertyMap.put(ScriptEventType.AUDIO_EVENT, Properties.Audio);
        eventTypeToPropertyMap.put(ScriptEventType.CAD_EVENT, Properties.CADLog);
        eventTypeToPropertyMap.put(ScriptEventType.CCTV_EVENT, Properties.CCTV);
        eventTypeToPropertyMap.put(ScriptEventType.CHP_RADIO_EVENT, Properties.CHPRadio);
        eventTypeToPropertyMap.put(ScriptEventType.PARAMICS_EVENT, Properties.Paramics);
        eventTypeToPropertyMap.put(ScriptEventType.TOW_EVENT, Properties.Tow);
        eventTypeToPropertyMap.put(ScriptEventType.UNIT_EVENT, Properties.Unit);
        eventTypeToPropertyMap.put(ScriptEventType.WITNESS_EVENT, Properties.Witness);
        eventTypeToPropertyMap.put(ScriptEventType.MAINTENANCE_RADIO_EVENT, Properties.MaintenanceRadio);
        eventTypeToPropertyMap.put(ScriptEventType.TMT_RADIO_EVENT, Properties.TMTRadio);
        eventTypeToPropertyMap.put(ScriptEventType.TELEPHONE_EVENT, Properties.Telephone);
        eventTypeToPropertyMap.put(ScriptEventType.ATMS_EVAL_EVENT, Properties.ATMS);
        eventTypeToPropertyMap.put(ScriptEventType.ACTIVITY_LOG_EVAL_EVENT, Properties.ActivityLog);
        eventTypeToPropertyMap.put(ScriptEventType.CAD_EVAL_EVENT, Properties.CAD);
        eventTypeToPropertyMap.put(ScriptEventType.CMS_EVAL_EVENT, Properties.CMS);
        eventTypeToPropertyMap.put(ScriptEventType.FACILITATOR_EVAL_EVENT, Properties.Facilitator);
        eventTypeToPropertyMap.put(ScriptEventType.RADIO_EVAL_EVENT, Properties.Radio);

        // Add the mouse listener
        IncidentTimelineMouseListener mouseListener
                = new IncidentTimelineMouseListener();
        addMouseMotionListener(mouseListener);
        addMouseListener(mouseListener);
    }

    /**
     * Constructor. Generates a HashMap of all possible event types.
     *
     * @param usesPopup determines whether or not right-clicking on this panel
     * will display a popup menu.
     */
    public IncidentTimelinePanel(boolean usesPopup)
    {
        super();

        hasPopupAccess = usesPopup;

        requestedEditorFillerTime = 0;
//        FACILITATOR_EVAL_EVENT, RADIO_EVAL_EVENT
        eventTypeToPropertyMap = new HashMap();
        eventTypeToPropertyMap.put(ScriptEventType.AUDIO_EVENT, Properties.Audio);
        eventTypeToPropertyMap.put(ScriptEventType.CAD_EVENT, Properties.CADLog);
        eventTypeToPropertyMap.put(ScriptEventType.CCTV_EVENT, Properties.CCTV);
        eventTypeToPropertyMap.put(ScriptEventType.CHP_RADIO_EVENT, Properties.CHPRadio);
        eventTypeToPropertyMap.put(ScriptEventType.PARAMICS_EVENT, Properties.Paramics);
        eventTypeToPropertyMap.put(ScriptEventType.TOW_EVENT, Properties.Tow);
        eventTypeToPropertyMap.put(ScriptEventType.UNIT_EVENT, Properties.Unit);
        eventTypeToPropertyMap.put(ScriptEventType.WITNESS_EVENT, Properties.Witness);
        eventTypeToPropertyMap.put(ScriptEventType.MAINTENANCE_RADIO_EVENT, Properties.MaintenanceRadio);
        eventTypeToPropertyMap.put(ScriptEventType.TMT_RADIO_EVENT, Properties.TMTRadio);
        eventTypeToPropertyMap.put(ScriptEventType.TELEPHONE_EVENT, Properties.Telephone);
        eventTypeToPropertyMap.put(ScriptEventType.ATMS_EVAL_EVENT, Properties.ATMS);
        eventTypeToPropertyMap.put(ScriptEventType.ACTIVITY_LOG_EVAL_EVENT, Properties.ActivityLog);
        eventTypeToPropertyMap.put(ScriptEventType.CAD_EVAL_EVENT, Properties.CAD);
        eventTypeToPropertyMap.put(ScriptEventType.CMS_EVAL_EVENT, Properties.CMS);
        eventTypeToPropertyMap.put(ScriptEventType.FACILITATOR_EVAL_EVENT, Properties.Facilitator);
        eventTypeToPropertyMap.put(ScriptEventType.RADIO_EVAL_EVENT, Properties.Radio);

        // Add the mouse listener
        IncidentTimelineMouseListener mouseListener
                = new IncidentTimelineMouseListener();
        addMouseMotionListener(mouseListener);
        addMouseListener(mouseListener);
    }

    /**
     * Update the panel if it's changed collapsed status. Redraw it with the
     * correct dimensions for its status.
     *
     * @param incident the incident this panel represents
     */
    public void timelinePanelUpdate(ScriptIncident incident)
    {
        this.incident = incident;
        this.visible = (incident != null);

        Dimension newSize;
        if (visible)
        {

            if (getTopLevelAncestor() instanceof IncidentEditorFrame)
            {

                newSize = new Dimension(((incident.length + incident.offset + requestedEditorFillerTime)
                        / ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION
                        * ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK)
                        + ScriptBuilderGuiConstants.EVENT_ICON_WIDTH,
                        ScriptBuilderGuiConstants.TIMELINE_COLLAPSED_HEIGHT
                        + ScriptBuilderGuiConstants.SCRIPT_EVENT_ICON_STEP * 2);
            }
            else
            {
                newSize = new Dimension(((incident.length + incident.offset + requestedScriptBuilderFillerTime)
                        / ScriptBuilderGuiConstants.HORIZONTAL_TICK_RESOLUTION
                        * ScriptBuilderGuiConstants.PIXEL_WIDTH_PER_HORIZONTAL_TICK)
                        + ScriptBuilderGuiConstants.EVENT_ICON_WIDTH,
                        ScriptBuilderGuiConstants.TIMELINE_COLLAPSED_HEIGHT
                        + ScriptBuilderGuiConstants.SCRIPT_EVENT_ICON_STEP * 2);
            }

        }
        else
        {
            newSize = new Dimension(0, 0);
        }
        this.setSize(newSize);
        this.setPreferredSize(newSize);

        invalidate();
    }

    /**
     * Redraw this panel and all the icons inside it. If user is holding an
     * event, draw that icon under the mouse.
     *
     * @param g the graphics component
     */
    @Override
    public void paint(Graphics g)
    {
        super.paint(g);

        if (!visible)
        {
            return;
        }

        Graphics2D g2d = (Graphics2D) g;
//        jdalbey removed this decision and replaced it with ELSE clause
//                so it will work with any alternate ancestor.
//        if (getTopLevelAncestor() instanceof ScriptBuilderFrame)
//        {
//            IncidentTimelineDrawer.DrawScriptBuilderTimeline(g2d, incident);
//        }
        if (getTopLevelAncestor() instanceof IncidentEditorFrame)
        {
            IncidentTimelineDrawer.DrawIncidentTimeline(g2d, incident, false);
        }
        else
        {
            IncidentTimelineDrawer.DrawScriptBuilderTimeline(g2d, incident);
        }

        if (focused)
        {
            CursorDrawer.DrawCursor(g2d, cursorTime, false);
            if (this.getTopLevelAncestor() instanceof ScriptBuilderFrame)
            {
                if (((ScriptBuilderFrame) this.getTopLevelAncestor()).currentEventType != null)
                {
                    EventIconDrawer.DrawEventIcon(g2d,
                            ((ScriptBuilderFrame) this.getTopLevelAncestor()).currentEventType,
                            x + 5, y + 10);
                }
            }
            if (this.getTopLevelAncestor() instanceof IncidentEditorFrame)
            {
                if (((IncidentEditorFrame) this.getTopLevelAncestor()).currentEventType != null)
                {
                    EventIconDrawer.DrawEventIcon(g2d,
                            ((IncidentEditorFrame) this.getTopLevelAncestor()).currentEventType,
                            x + 5, y + 10);
                }
            }
        }
    }

    /**
     * Local main for viewing this panel only.
     *
     * @author jdalbey
     * @param args not used
     */
    public static void main(String[] args)
    {
        JFrame frame = new JFrame("ScriptBuilderTimelinePanel Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        IncidentTimelinePanel pnl = new IncidentTimelinePanel();

        // Create a script
        File inFile = new File("test/scriptbuilder/structures/test_input_file.xml");
        SimulationScript script = new SimulationScript();
        script.loadScriptFromFile(inFile);
        // retrieve a single incident from the script
        ScriptIncident inci = script.incidents.get(2);
        // update this panel with an incident
        pnl.timelinePanelUpdate(inci);

        frame.getContentPane().add(pnl, BorderLayout.CENTER);
        frame.pack();

        frame.setVisible(true);

    }
}
