/*
 * Copyright (c) 2019, Metron, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Metron, Inc. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.metsci.glimpse.plot.timeline.event.listener;

import static com.metsci.glimpse.plot.timeline.data.EventSelection.Location.Center;
import static com.metsci.glimpse.plot.timeline.data.EventSelection.Location.End;
import static com.metsci.glimpse.plot.timeline.data.EventSelection.Location.Start;

import java.util.Set;

import com.metsci.glimpse.event.mouse.GlimpseMouseAllListener;
import com.metsci.glimpse.event.mouse.GlimpseMouseEvent;
import com.metsci.glimpse.event.mouse.MouseButton;
import com.metsci.glimpse.plot.timeline.data.EventSelection;
import com.metsci.glimpse.plot.timeline.data.EventSelection.Location;
import com.metsci.glimpse.plot.timeline.event.Event;
import com.metsci.glimpse.plot.timeline.event.EventPlotInfo;
import com.metsci.glimpse.util.units.time.TimeStamp;

/**
 * Helper class which supports user dragging of Events to adjust their start/end times.
 * @author ulman
 */
public class DragListener implements EventPlotListener, GlimpseMouseAllListener
{
    protected Location dragType = null;
    protected TimeStamp anchorTime = null;
    protected TimeStamp eventStart = null;
    protected TimeStamp eventEnd = null;
    protected Event dragEvent = null;

    protected boolean enabled = true;

    protected EventPlotInfo info;

    public DragListener( EventPlotInfo info )
    {
        this.info = info;
    }

    public boolean isEnabled( )
    {
        return this.enabled;
    }

    public void setEnabled( boolean enabled )
    {
        this.enabled = enabled;
        this.reset( );
    }

    public void reset( )
    {
        this.dragType = null;
        this.anchorTime = null;
        this.eventStart = null;
        this.eventEnd = null;
        this.dragEvent = null;
    }

    @Override
    public void eventsClicked( GlimpseMouseEvent ev, Set<EventSelection> events, TimeStamp time )
    {
        if ( !this.enabled ) return;

        if ( this.dragEvent == null && ev.isButtonDown( MouseButton.Button1 ) )
        {
            for ( EventSelection selection : events )
            {
                if ( selection.isLocation( Center, Start, End ) && selection.getEvent( ).isEditable( ) )
                {
                    this.dragEvent = selection.getEvent( );
                    this.eventStart = dragEvent.getStartTime( );
                    this.eventEnd = dragEvent.getEndTime( );
                    this.anchorTime = time;
                    ev.setHandled( true );

                    if ( selection.isCenterSelection( ) )
                    {
                        this.dragType = Center;
                    }
                    else if ( selection.isStartTimeSelection( ) )
                    {
                        this.dragType = Start;
                    }
                    else if ( selection.isEndTimeSelection( ) )
                    {
                        this.dragType = End;
                    }

                    return;
                }
            }
        }
    }

    @Override
    public void mouseMoved( GlimpseMouseEvent ev )
    {
        if ( !this.enabled ) return;

        if ( this.dragEvent != null )
        {
            this.doDrag( ev );
            ev.setHandled( true );
        }
    }

    @Override
    public void mouseReleased( GlimpseMouseEvent ev )
    {
        if ( !enabled ) return;

        if ( this.dragEvent != null && ev.isButtonDown( MouseButton.Button1 ) )
        {
            // TODO: Find a less kludgy way to distinguish mouse-release from mouse-drag
            // If the mouse is outside the canvas when released, the incoming clickCount is zero; subtract 1 so that releaseClickCount is always negative
            int releaseClickCount = ( -1 * ev.getClickCount( ) ) - 1;
            GlimpseMouseEvent releaseEv = new GlimpseMouseEvent( ev.getTargetStack( ), ev.getModifiers( ), ev.getButtons( ), ev.getAllX( ), ev.getAllY( ), ev.getWheelIncrement( ), releaseClickCount, false );

            this.doDrag( releaseEv );
            this.reset( );
            ev.setHandled( true );
        }
    }

    protected void doDrag( GlimpseMouseEvent ev )
    {
        TimeStamp time = this.info.getTime( ev );

        if ( this.dragType == Center )
        {
            double diff = time.durationAfter( this.anchorTime );
            this.dragEvent.setTimes( ev, this.eventStart.add( diff ), this.eventEnd.add( diff ), false );
        }
        else if ( this.dragType == End && this.eventStart.isBefore( time ) )
        {
            this.dragEvent.setTimes( ev, this.eventStart, time, false );
        }
        else if ( this.dragType == Start && this.eventEnd.isAfter( time ) )
        {
            this.dragEvent.setTimes( ev, time, this.eventEnd, false );
        }
    }

    //@formatter:off
    @Override public void eventsHovered( GlimpseMouseEvent ev, Set<EventSelection> events, TimeStamp time ) { }
    @Override public void eventsExited( GlimpseMouseEvent ev, Set<EventSelection> events, TimeStamp time ) { }
    @Override public void eventsEntered( GlimpseMouseEvent ev, Set<EventSelection> events, TimeStamp time ) { }
    @Override public void eventUpdated( GlimpseMouseEvent ev, Event event ) { }
    @Override public void mouseEntered( GlimpseMouseEvent ev ) { }
    @Override public void mouseExited( GlimpseMouseEvent ev ) { }
    @Override public void mousePressed( GlimpseMouseEvent ev ) { }
    @Override public void mouseWheelMoved( GlimpseMouseEvent ev ) { }
    //@formatter:on
}