/*
 * @(#)AutoScroll.java 12/7/2006
 *
 * Copyright 2002 - 2006 JIDE Software Inc. All rights reserved.
 */

package com.jidesoft.swing;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;

/**
 * Please note: we are still polishing this class and may change the public interface in the future. Please do not use
 * it for now until we remove this notice. You may choose to use it as long as you don't complain when you find your
 * code won't compile after upgrading to a new JIDE release.
 */
abstract public class AutoScroll {
    public static final String CLIENT_PROPERTY_AUTO_SCROLL = "AutoScroll";
    protected Timer _timer;
    protected boolean _autoScrolling = false;
    protected int _scrollDirection = SCROLL_UP;
    protected boolean _hasEntered;

    public static final int SCROLL_UP = 0;
    public static final int SCROLL_DOWN = 1;
    public static final int SCROLL_LEFT = 2;
    public static final int SCROLL_RIGHT = 4;

    protected Component _component;
    protected boolean _vertical = true;

    protected int _autoScrollInterval = 100;
    private boolean _componentSelfScrollable = true;

    private boolean _enabled = true;

    protected AutoScroll(Component component) {
        _component = component;
        updateClientProperty(component, this);
    }

    protected AutoScroll(Component component, boolean vertical) {
        _component = component;
        _vertical = vertical;
        updateClientProperty(component, this);
    }

    public int getAutoScrollInterval() {
        return _autoScrollInterval;
    }

    public void setAutoScrollInterval(int autoScrollInterval) {
        _autoScrollInterval = autoScrollInterval;
    }

    /**
     * Checks if the AutoScroll is enabled.
     *
     * @return true if enabled. Otherwise false.
     */
    public boolean isEnabled() {
        return _enabled;
    }

    /**
     * Enables or disables the AutoScroll feature.
     *
     * @param enabled true or false.
     */
    public void setEnabled(boolean enabled) {
        _enabled = enabled;
    }

    private class AutoScrollActionHandler implements ActionListener {
        private int _direction;

        AutoScrollActionHandler(int direction) {
            _direction = direction;
        }

        public void actionPerformed(ActionEvent e) {
            autoScrolling(_direction);
        }
    }

    public void startAutoScrolling(int direction) {
        if (_autoScrolling) {
            _timer.stop();
        }

        _autoScrolling = true;
        _scrollDirection = direction;
        autoScrollingStarted(_scrollDirection);
        _timer = new Timer(_autoScrollInterval, new AutoScrollActionHandler(_scrollDirection));
        _timer.start();
    }

    /**
     * This protected method is implementation specific and should be private. do not call or override.
     */
    public void stopAutoScrolling() {
        _autoScrolling = false;

        if (_timer != null) {
            _timer.stop();
            _timer = null;
        }

        autoScrollingEnded(_scrollDirection);
    }

    public boolean isAutoScrolling() {
        return _autoScrolling;
    }

    public int getScrollDirection() {
        return _scrollDirection;
    }

    public boolean isComponentSelfScrollable() {
        return _componentSelfScrollable;
    }

    public void setComponentSelfScrollable(boolean scrollable) {
        _componentSelfScrollable = scrollable;
    }

    protected MouseEvent convertMouseEvent(MouseEvent e) {
        if (e.getSource() == _component) {
            return e;
        }

        Point convertedPoint = SwingUtilities.convertPoint((Component) e.getSource(),
                e.getPoint(), _component);
        return new MouseEvent((Component) e.getSource(),
                e.getID(),
                e.getWhen(),
                e.getModifiers(),
                convertedPoint.x,
                convertedPoint.y,
                e.getClickCount(),
                e.isPopupTrigger());
    }


    public void mouseReleased(MouseEvent e) {
        if (!isEnabled()) {
            return;
        }
        _hasEntered = false;
        stopAutoScrolling();
    }

    public void mousePressed(MouseEvent e) {
        if (!isEnabled()) {
            return;
        }
        stopAutoScrolling();
    }

    public void mouseDragged(MouseEvent e) {
        if (!isEnabled()) {
            return;
        }
        if (_componentSelfScrollable && e.getSource() == _component) {
            return;
        }
        if (_component.isVisible()) {
            MouseEvent newEvent = convertMouseEvent(e);
            Rectangle r = new Rectangle();
            if (_component instanceof JComponent) {
                ((JComponent) _component).computeVisibleRect(r);
            }
            else {
                r = _component.getBounds();
            }
            if (newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 &&
                    newEvent.getPoint().x >= r.x && newEvent.getPoint().x <= r.x + r.width - 1) {
                _hasEntered = true;
                if (_autoScrolling) {
                    stopAutoScrolling();
                }
                Point location = newEvent.getPoint();
                if (r.contains(location)) {
                    updateSelectionForEvent(newEvent, false);
                }
            }
            else {
                if (_hasEntered) {
                    int directionToScroll;
                    if (newEvent.getPoint().y < r.y) {
                        directionToScroll = SCROLL_UP;
                    }
                    else if (newEvent.getPoint().x < r.x) {
                        directionToScroll = SCROLL_LEFT;
                    }
                    else if (newEvent.getPoint().y > r.y + r.height) {
                        directionToScroll = SCROLL_DOWN;
                    }
                    else /*if(e.getPoint().x > r.x + r.width)*/ {
                        directionToScroll = SCROLL_RIGHT;
                    }

                    if (_autoScrolling && _scrollDirection != directionToScroll) {
                        stopAutoScrolling();
                        startAutoScrolling(directionToScroll);
                    }
                    else if (!_autoScrolling) {
                        startAutoScrolling(directionToScroll);
                    }
                }
            }
        }
    }

    public void mouseMoved(MouseEvent e) {
        if (!isEnabled()) {
            return;
        }
        if (e.getSource() == _component) {
            Point location = e.getPoint();
            Rectangle r = new Rectangle();
            if (_component instanceof JComponent) {
                ((JComponent) _component).computeVisibleRect(r);
                if (r.contains(location)) {
                    updateSelectionForEvent(e, false);
                }
            }
            else {
                updateSelectionForEvent(e, false);
            }
        }
    }

    public void autoScrollingStarted(int direction) {
    }

    public void autoScrollingEnded(int direction) {
    }

    abstract public void autoScrolling(int direction);

    abstract public void updateSelectionForEvent(MouseEvent e, boolean shouldScroll);

    /**
     * Gets the AutoScroll installed on the component. Null is no AutoScroll was installed.
     *
     * @param component the component
     * @return the AutoScroll installed. Null is no AutoScroll was installed.
     */
    public static AutoScroll getAutoScroll(Component component) {
        Object clientProperty = component instanceof JComponent ? ((JComponent) component).getClientProperty(CLIENT_PROPERTY_AUTO_SCROLL) : null;
        if (clientProperty instanceof AutoScroll) {
            return ((AutoScroll) clientProperty);
        }
        else {
            return null;
        }
    }

    private void updateClientProperty(Component component, AutoScroll autoScroll) {
        if (component != null) {
            Object clientProperty = ((JComponent) _component).getClientProperty(CLIENT_PROPERTY_AUTO_SCROLL);
            if (clientProperty instanceof AutoScroll) {
                ((AutoScroll) clientProperty).stopAutoScrolling();
            }
            if (component instanceof JComponent) {
                ((JComponent) component).putClientProperty(CLIENT_PROPERTY_AUTO_SCROLL, autoScroll);
            }
        }
    }
}
