package usda.weru.mcrew.timeline;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.Cursor;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import org.apache.log4j.Logger;
import usda.weru.mcrew.JulianCalendar;
import usda.weru.mcrew.OperationDlg;
import usda.weru.mcrew.OperationObject;
import usda.weru.mcrew.RowInfo;
import usda.weru.mcrew.RowInfo.OpType;
import usda.weru.mcrew.XMLConstants;

/**
     * The class that renders our little Icons, whether they be images or colors
     */
public  class OperationIcon extends JComponent implements Cloneable
{
    public static final long serialVersionUID = 8888888L;
    private final RowInfo interior;
    protected final TimelinePanelData data;
    protected int origX, origY;
    private final static Dimension ICONCOLORSPLIT = new Dimension(15, 15);
    protected TimelinePanel time;
    private boolean selected;
    private Logger LOGGER = Logger.getLogger(OperationIcon.class);

    /**
     * Basic constructor for an icon.  It takes in a row, which is the data that
     * lies under the image, the data object for the timeline to coordinate changes
     * and the timeline itself for refreshing purposes.
     * @param row
     * @param panelData
     * @param pan 
     */
    public OperationIcon(RowInfo row, TimelinePanelData panelData, TimelinePanel pan)
    {
        interior = row;
        data = panelData;
        time = pan;
        addMouseListener(new MouseHandler());
    }

    /**
     * Returns the row info associated with the opIcon.
     * @return 
     */
    public RowInfo getRow() { return interior; }

    /**
     * Changes the date of this operation to be the day passed in.
     * @param day 
     */
    public void setDate(JulianCalendar day) { interior.setDate(day); }

    /**
     * This resets the location to be at the new pixel location fed in as x and y.
     * @param x
     * @param y 
     */
    public void specifyLocation(int x, int y)
    { 
        origX = x;
        origY = y;
        String date = time.pixelsToDate(x).getDate();
        OperationObject opOb = (OperationObject) interior.getDataObject(XMLConstants.soperation);
        String name = opOb.mOperationName;
        this.setToolTipText("<html>" + date + "<br>" + name +"</html>");
    }

    /**
     * Returns the onscreen placement of the icon.  X coordinate.
     * @return 
     */
    public int getXOrigin() { return origX; }
    /**
     * Returns the onscreen placement of the icon.  Y coordinate.
     * @return 
     */
    public int getYOrigin() { return origY; }

    /**
     * Removes the selection lines from the icon.
     */
    public void deselect() { selected = false; }
    /**
     * Adds selection lines to the icon.
     */
    public void select() { selected = true; }

    /**
     * Determines by size of the component whether the icon should be rendered as
     * an image or a color block.  Then renders it.
     * @param grphc 
     */
    @Override
    public void paintComponent(Graphics grphc)
    {
        super.paintComponent(grphc);
        OpType type = this instanceof MultiOperationIcon ? OpType.MOR : interior.getType();
        if(TimelinePanelData.inside(new Dimension(TimelineConfig.height, 
                TimelineConfig.height), ICONCOLORSPLIT))
        {
            Color toPaint = type.col;
            if(toPaint.equals(Color.WHITE)) toPaint = new Color(0, 0, 0, 0);
            grphc.setColor(toPaint);
            grphc.fillRect(0, 0, TimelineConfig.height, 
                    TimelineConfig.height);
        }
        else
        {
            ImageIcon ico = (ImageIcon) type.pic;
            if(ico.equals(new ImageIcon(RowInfo.blank)))
            {
                grphc.setColor(new Color(0, 0, 0, 0));
                grphc.fillRect(0, 0, TimelineConfig.height, 
                        TimelineConfig.height);
            }
            else
            {
                grphc.drawImage(ico.getImage(), 0, 0, TimelineConfig.height, 
                        TimelineConfig.height, null);
            }
        }
        if(selected)
        {
//            OperationObject opOb = (OperationObject) interior.getDataObject(XMLConstants.soperation);
//            String name = opOb.mOperationName;
            grphc.setColor(Color.decode("#90C3D4"));
            grphc.drawRect(0, 0, TimelineConfig.height, TimelineConfig.height);
        }
    }

    private class MouseHandler extends  MouseAdapter
    {
        private boolean drag = false;
        private boolean exit = false;
        private boolean copy = false;
        private int beforePoint = 0;
        
        /**
         * When the mouse enters the cell, we want to set the current operation
         * text in timelinecontrols to the current operation name.
         * @param evt 
         */
        @Override
        public void mouseEntered(MouseEvent evt)
        {
            if(evt.getSource() == OperationIcon.this)
            {
                TimelineControls controls = time.getControls();
                if(controls == null) return;
                if(OperationIcon.this instanceof MultiOperationIcon)
                {
                    controls.currentOp.setValue("Multiple Operations");
                }
                else
                {
                    OperationObject opOb = (OperationObject) interior.getDataObject(XMLConstants.soperation);
                    String name = opOb.mOperationName;
                    controls.currentOp.setValue(name);
                }
                
                JulianCalendar day = interior.getDate();
                controls.currentDate.setValue(day);
            }
        }

        /**
         * When the mouse exits the cell, we want to clear the current operation
         * text in timelinecontrols of the current operation name.
         * If the mouse drag is active (a press without a corresponding release)
         * we need to ready the packet to transmit to the panel.
         * @param evt 
         */
        @Override
        public void mouseExited(MouseEvent evt)
        {
            if(evt.getSource() == OperationIcon.this)
            {
                TimelineControls controls = time.getControls();
                if(controls == null) return;

                controls.currentOp.setValue("None");
            }
            time.repaint();
            if(drag) 
            {
                time.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
                exit = true;
            }
        }

        /**
         * On a double click, we want to load the operation drill down for
         * this operation.
         * @param evt 
         */
        @Override
        public void mouseClicked(MouseEvent evt)
        {
            if(SwingUtilities.isLeftMouseButton(evt))
            {
                if((evt.getModifiers() & (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)) == (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK))
                {
                    time.closeInterval((evt.getPoint().x + origX), (evt.getPoint().y + origY));
                }
                else if((evt.getModifiers() & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK)
                {
                    time.addSelected(OperationIcon.this);
                }
                else if((evt.getModifiers() & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK)
                {
                    time.clearSelected();
                    time.closeInterval((evt.getPoint().x + origX), (evt.getPoint().y + origY));
                }
                else 
                {
                    if(evt.getClickCount() > 1) 
                    {
                        String dateString = interior.getDate().getDate();
                        int manRow = data.getRowOrigin(interior);
                        TimelinePanelData.ManageRow row = data.getBands().get(manRow);
                        OperationDlg operationDlg = new OperationDlg(row.getTable(),
                                (OperationObject) interior.getDataObject(XMLConstants.soperation), 
                                dateString, row.data, interior);
                        operationDlg.setModal(true);
                        operationDlg.setVisible(true);
                    }
                    time.clearSelected();
                    time.addSelected(OperationIcon.this);
                }
                time.registerClick((evt.getPoint().x + origX), (evt.getPoint().y + origY));
                selected = true;
                OperationIcon.this.repaint();
            }
            else if(SwingUtilities.isRightMouseButton(evt))
            {
                boolean init = time.noSelection();
                if(init) time.addSelected(OperationIcon.this); /*If there is no selection, 
                                                     we need to add this operation
                                                     for the proper right click menu.*/
                time.generatePopup((evt.getPoint().x + origX), (evt.getPoint().y + origY));
                if(init) time.clearSelected();       /*If there was no selection, 
                                                     we need to clear this operation
                                                     since it wasn't properly selected.*/
            }
        }

        /**
         * When the mouse is pressed within this cell, that might be the start
         * of a drag operation.  We need to set our state that someone might be
         * dragging us.
         * @param evt 
         */
        @Override
        public void mousePressed(MouseEvent evt) 
        { 
            drag = true; 
            if(SwingUtilities.isRightMouseButton(evt)) copy = true;
            beforePoint = (evt.getPoint().x + origX);
        }

        /**
         * When the mouse is released in this cell, without having exited, that
         * means we picked up an unnecessary press, and we need to descalate the
         * drag flag.
         * @param evt 
         */
        @Override
        public void mouseReleased(MouseEvent evt) 
        { 
            if(drag && exit)
            {
                time.setCursor(Cursor.getDefaultCursor());
                if(SwingUtilities.isRightMouseButton(evt) && copy)
                {
                    if(time.noSelection())
                    {
                        try
                        {
                            //Check to make sure this drops in all the correct locations.
                            OperationIcon clone; 
                            if(OperationIcon.this instanceof MultiOperationIcon) clone = (MultiOperationIcon) OperationIcon.this.clone();
                            else clone = (OperationIcon) OperationIcon.this.clone();
                            time.add(clone);
                            data.getBands().get(data.getRowOrigin(interior)).data.getRows().add(clone.interior);
                            data.getBands().get(data.getRowOrigin(interior)).icons.add(clone);
                            
                            clone.setDate(time.pixelsToDate((evt.getPoint().x + origX)));
                            int drop = time.getBandFromPixel((evt.getPoint().y + origY));
                            int orig = data.getRowOrigin(interior);
                            if(drop != orig) time.swap(clone, orig, drop);
                            time.getCoordinator().dateChange(time);
                            time.getTablePanelNoSelect(drop).setDataChanged(true);
                        }
                        catch(CloneNotSupportedException cnse)
                        {
                            LOGGER.error("How is clone not supported?  Should be at approx line 365.");
                        }
                    }
                    else
                    {
                        int drop = time.getBandFromPixel((evt.getPoint().y + origY));
                        int orig = data.getRowOrigin(interior);
                        if(drop != orig) return;
                        time.copySelectionByPixels((evt.getPoint().x + origX - beforePoint));
                    }
                }
                else
                {
                    if(time.noSelection())
                    {
                        setDate(time.pixelsToDate((evt.getPoint().x + origX)));
                        int drop = time.getBandFromPixel((evt.getPoint().y + origY));
                        int orig = data.getRowOrigin(interior);
                        if(drop != orig) time.swap(OperationIcon.this, orig, drop);
                        time.getCoordinator().dateChange(time);
                        time.getTablePanelNoSelect(drop).setDataChanged(true);
                    }
                    else 
                    {
                        int drop = time.getBandFromPixel((evt.getPoint().y + origY));
                        int orig = data.getRowOrigin(interior);
                        if(drop != orig) return;
                        time.repositionSelectionByPixels((evt.getPoint().x + origX - beforePoint));
                    }
                }
            }
            drag = false; 
            exit = false;
            copy = false;
        }
    }
    
    /**
     * Overriden to explicitly call the super method so that a straight object.equals
     * is enforced.  Done this way because code structure relies on object equality based
     * on objects rather than data equality.
     * @param other
     * @return 
     */
    @Override
    public boolean equals(Object other) { return super.equals(other); }
    /**
     * Overriden to explicitly call the super method so that a straight object.hashCode
     * is enforced.  Done this way because code structure relies on object hashCode based
     * on objects rather than data.
     * @param other
     * @return 
     */
    @Override
    public int hashCode() { return super.hashCode(); }
    
    /**
     * Provides a carbon copy of the existing rowinfo object for data manipulation.
     * @return The new copy of the rowinfo object.
     * @throws java.lang.CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException
    {
        OperationIcon clone = new OperationIcon((RowInfo) interior.clone(), data, time);
        return clone;
    }
    
    public OperationIcon copyAction()
    {
        try
        {
            OperationIcon clone = (OperationIcon) OperationIcon.this.clone();
            time.add(clone);
            data.getBands().get(data.getRowOrigin(interior)).data.getRows().add(clone.interior);
            data.getBands().get(data.getRowOrigin(interior)).icons.add(clone);
            
            return clone;
        }
        catch(CloneNotSupportedException cnse)
        {
            LOGGER.error("How is clone not supported?  Should be at approx line 365.");
            return null;
        }
    }
}    
