/*
 *  DragContext.java
 *  (FScape)
 *
 *  Copyright (c) 2001-2021 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is published under the GNU General Public License v3+
 *
 *
 *	For further information, please contact Hanns Holger Rutz at
 *	contact@sciss.de
 */

package de.sciss.fscape.gui;

import de.sciss.fscape.util.Slots;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;

/**
 *  Provides useful information during a
 *	drag-n-drop gesture.
 */
public class DragContext
		implements KeyListener {

// -------- public variables --------

    public static final int MOVE	=	0;	// drag-type
    public static final int LINK	=	1;

// -------- private variables --------

    private Component	source;
    private Component	target			= null;
    private String		srcName;
    private Container	container;
    private int			type;

    private	Rectangle	srcBounds;

    private	Point		initialLoc;
//	private Point		lastLoc;
    private Point		currentLoc;

    private Cursor		dragCursor;
    private Cursor		oldCursor;

    private int			threshold;
    private	boolean		dragStarted		= false;
    private boolean		dragSuccessful	= false;

    private int targetLastState;			// previous to error state

    private	Point		srcPoint, targetPoint;		// fuer Link-Pfeil

// -------- public methods --------

    public DragContext( MouseEvent e, int type )
    {
        this.source	= e.getComponent();
        srcName		= source.toString();
        this.type	= type;

        srcBounds	= source.getBounds();
        initialLoc	= new Point( srcBounds.x + e.getX(), srcBounds.y + e.getY() );
//		lastLoc		= initialLoc;
        currentLoc	= initialLoc;
        srcPoint	= new Point( 0, 0 );
        targetPoint	= new Point( 0, 0 );

        dragStarted	= false;

        if( srcName.equals ( OpPanel.OBJ_NAME ) ) {
            container = (Container) source;
        } else {
            container = source.getParent();
        }

        dragCursor	= new Cursor( (( srcName.equals ( OpIcon.OBJ_NAME ) ) && (type == MOVE)) ?
                                  Cursor.MOVE_CURSOR : Cursor.CROSSHAIR_CURSOR );
        threshold	= 5;
    }

    public Component getSource()
    {
        return source;
    }

    public int getType()
    {
        return type;
    }

    public Component getTarget()
    {
        return target;
    }

    /**
     *	Schwellwert in Pixels setzen, den die Maus
     *	mindestens ueberschreiten muss, damit der Drag beginnt
     */
    public void setThreshold( int dist )
    {
        threshold	= dist;
    }

    /**
     *	@return	true, wenn der Schwellwert ueberschritten wurde
     */
    public boolean hasDragStarted()
    {
        return dragStarted;
    }

    /**
     *	@return	true, wenn der Drag gueltig war
     */
    public boolean wasDragSuccessful()
    {
        return( dragSuccessful );
    }

    /**
     *	Always call when you get a new Message;
     *	this will update the DragContext's internal state
     */
    public void mouseDragged( MouseEvent e )
    {
        if( e.getComponent() != source ) return;

        Component newTarget;

        if( dragStarted ) {
            paintScheme( false );
        }

//		lastLoc		= currentLoc;
        currentLoc	= new Point( srcBounds.x + e.getX(), srcBounds.y + e.getY() );

        if( !dragStarted ) {

            if( (currentLoc.x - initialLoc.x) * (currentLoc.x - initialLoc.x) +
                (currentLoc.y - initialLoc.y) * (currentLoc.y - initialLoc.y) >=
                threshold * threshold ) {

                oldCursor = container.getCursor();
                container.setCursor( dragCursor );
                source.requestFocus();
                source.addKeyListener( this );

                dragStarted	= true;
                target		= null;
            }
        }

        if( dragStarted ) {

            if( srcName.equals ( OpIcon.OBJ_NAME ) ) {

                switch( type ) {
                case MOVE:
                    newTarget = ((OpPanel) container).getOpIconAround(
                                    currentLoc.x, currentLoc.y, (OpIcon) source, 0, 0 );

                    if( newTarget != target ) {		// sth changed
                        if( target != null ) {
                            ((OpIcon) target).setSelected( targetLastState );
                        }
                        target = newTarget;
                        if( target != null ) {
                            targetLastState = ((OpIcon) target).isSelected();
                            ((OpIcon) target).setSelected( OpIcon.STATE_ERROR );
                        }
                    }

                    dragSuccessful = (target == null);
                    break;

                case LINK:
                    newTarget = ((OpPanel) container).getOpIconAt(
                                    currentLoc.x, currentLoc.y );
                    if( newTarget == source ) {
                        newTarget = null;
                    }
                    if( newTarget != target ) {		// sth changed
                        if( target != null ) {
                            ((OpIcon) target).setSelected( targetLastState );
                        }

                        dragSuccessful	= false;
                        target			= newTarget;

                        if( target != null ) {		// pruefen ob Link moeglich ist
                            targetLastState = ((OpIcon) target).isSelected();
                            OpConnector.calcArrow( source, target, srcPoint, targetPoint,2,2);

                            if( !((OpIcon) target).getOperator().getSlots(
                                    Slots.SLOTS_READER | Slots.SLOTS_FREE ).isEmpty() ) {

                                ((OpIcon) target).setSelected( OpIcon.STATE_FRIENDLY );	// we could link to this
                                dragSuccessful = true;

                            } else {
                                ((OpIcon) target).setSelected( OpIcon.STATE_ERROR );	// we cannot
                            }
                        }
                    }
                    break;

                default:
                    break;
                }

            } else if( srcName.equals ( OpConnector.OBJ_NAME ) ) {
                // XXX
                dragSuccessful = true;
            }
            paintScheme( true );
        }
    }

    /**
     *	Always call when you get dis Message;
     *	this will terminate the drag
     */
    public void mouseReleased( MouseEvent e )
    {
        if( dragStarted ) {

            if( (target != null) && ( target.toString ().equals ( OpIcon.OBJ_NAME ) ) ) {
                ((OpIcon) target).setSelected( targetLastState );
            }
            paintScheme( false );
            container.setCursor( oldCursor );
            source.removeKeyListener( this );
            dragStarted	= false;
        }
    }

    protected void paintScheme( boolean mode )
    {
        Graphics2D	g = (Graphics2D) container.getGraphics();
        Graphics2D	g2;
        Point		loc;

        if( g == null ) return;

        // g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        g.setXORMode(container.getBackground());
        g.setColor(container.getForeground());

        switch( type ) {
        case MOVE:
            ((Dragable) source).paintScheme( g, currentLoc.x, currentLoc.y, mode );
            break;

        case LINK:
            if( target == null ) {
                OpConnector.drawArrow( g, srcBounds.x + (srcBounds.width >> 1),
                                       srcBounds.y + (srcBounds.height >> 1),
                                       currentLoc.x, currentLoc.y, true );
            } else {
                OpConnector.drawArrow( g, srcPoint.x, srcPoint.y,
                                       targetPoint.x, targetPoint.y, true );
                g2 = (Graphics2D) target.getGraphics();	// nochmal "innerhalb" des OpIcons zeichnen
                if( g2 != null ) {
                    g2.setXORMode( container.getBackground() );
                    // g2.setColor( Color.black );
                    g2.setColor( container.getForeground () );
                    loc = target.getLocation();
                    OpConnector.drawArrow( g2, srcPoint.x - loc.x, srcPoint.y - loc.y,
                                           targetPoint.x - loc.x, targetPoint.y - loc.y,
                                           true );
                    g2.dispose();
                }
            }
            break;

        default:
            break;
        }

        g.dispose();
    }

// -------- (static) Utility methods --------

// -------- Key Listener methods --------

    public void keyPressed(KeyEvent e) {
        MouseEvent virtual;
        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {

            virtual = new MouseEvent(source, MouseEvent.MOUSE_RELEASED,
                    System.currentTimeMillis(), 0,
                    currentLoc.x, currentLoc.y, 0, false);

            dragSuccessful = false;
            mouseReleased(virtual);        // sicherheitshalber jetzt schon
            source.dispatchEvent(virtual);
        }
    }

    public void keyReleased(KeyEvent e) {
    }

    public void keyTyped(KeyEvent e) {
    }
}