classdef polariMBAngleSpan < internal.polariMouseBehavior
    
    methods
        function obj = polariMBAngleSpan(isButtonDown)
            
            if nargin > 0 && isButtonDown
                obj.InstallFcn = @showToolTipAndPtr_spanDown;
                obj.MotionEvent = @wbdrag;
                obj.DownEvent = [];
                obj.UpEvent = @wbup;
                obj.ScrollEvent = [];
            else
                obj.InstallFcn = @showToolTipAndPtr_spanHover;
                obj.MotionEvent = @wbmotion;
                obj.DownEvent = @wbdown;
                obj.UpEvent = [];
                obj.ScrollEvent = @internal.polariMBMagTicks.wbscroll;
            end
        end
    end
end

function wbmotion(p,ev)

s = computeHoverLocation(p,ev);

firstEntry = p.ChangedState;
if firstEntry
    p.ChangedState = false;
    p.SpanDrag_PrevCplx = complex(cos(s.angle),sin(s.angle));
    
    showToolTipAndPtr_spanHover(p);
end

if s.overSpan
    % Did mouse cross the span midpoint angle?
    %
    % If so, update the hilite arrows so depict this:
    
    % Is mouse closer to spanStart or spanEnd?
    % Do this via angle-diffs from complex values:
    c = p.hAngleSpan.SpanCplx;
    endpts = [c{1} c{2}];
    if numel(endpts) < 2
        % not really over span, must be an HG ghost
        
        % Not over angle span (possibly not over axes)
        if ~isempty(p.hAngleSpan)
            hoverOverSpanChange(p.hAngleSpan,'stop');
        end
        hiliteSpanDrag_Init(p,'off');
        autoChangeMouseBehavior(p,s);
        return % EARLY EXIT
    end
    
    % Get angle distance from last stored mouse point to
    % start and end points
    %
    % d_prev is a 2-element vector of absolute angle diffs,
    % [diffang_start diffang_end]
    d_prev = internal.polariCommon.cangleAbsDiff(p.SpanDrag_PrevCplx,endpts);
    
    % Get angle distance from current mouse to start and
    % end points
    %
    % d_curr is a 2-element vector of absolute angle diffs,
    % [diffang_start diffang_end]
    c_curr = complex(cos(s.angle),sin(s.angle));
    d_curr = internal.polariCommon.cangleAbsDiff(c_curr,endpts);
    
    % true if mouse is closer to start than end,
    %   or if prev  is closer to start than end:
    e_curr = d_curr(1) < d_curr(2);  % mouse
    e_prev = d_prev(1) < d_prev(2);  % prev stored mouse
    
    if firstEntry || e_curr~=e_prev
        % Only trigger if mouse changed from "closer to
        % start edge" to "closer to end edge", or
        % vice-versa.
        %
        % Update stored cplx value so it reflect "this"
        % side of the midpoint:
        p.SpanDrag_PrevCplx = c_curr;
        
        % Update arrow hilites on edges of span
        hiliteSpanDrag_Update(p);
    end
    
    if firstEntry
        hoverOverSpanChange(p.hAngleSpan,'start');
    end
else
    % Not over angle span (possibly not over axes)
    if ~isempty(p.hAngleSpan)
        hoverOverSpanChange(p.hAngleSpan,'stop');
    end
    hiliteSpanDrag_Init(p,'off');
    autoChangeMouseBehavior(p,s);
end
end

function wbdown(p,ev)
% Click on angle span allows:
%   - reassociation of start/end point with adjacent markers

p.hFigure.CurrentPoint = ev.Point;

% Get mouse coords before other work
pt = get(p.hAxes,'CurrentPoint');

st = p.hFigure.SelectionType;
if strcmpi(st,'open')
    % double-click on angle span region
    %
    return
elseif strcmpi(st,'alt')
    % right-click: this action launches the context menu.
    %
    % Prevent further reaction to right-click here, so we don't
    % do any hilite and confuse the user when the context
    % menu launches.
    resetToolTip(p);
    return
end

% User can't move span if only 2 markers are present in plot.
% Give them this insight:
showToolTipAndPtr_spanDown(p);

% Cache mouse angle as a cplx value, at the start of the drag
% operation.  Normalize the mouse coordinate so it lies on the
% unit circle:
c = complex(pt(1,1),pt(1,2));
c = c./abs(c);
p.SpanDrag_PrevCplx = c;

isStartPt = isCloserToSpanStart(p,c);
dc.SpanDrag_StartPtChanging = isStartPt; % true = CW-most pt when facing north

span = p.hAngleSpan;
spanIDs = span.SpanIDs;
if isStartPt
    staticID = spanIDs{2};
else
    staticID = spanIDs{1};
end

% Compute range of angles valid for startpoint and endpoint
% Each has an angular range over which it can move.
%
% 1- Determine available markers
%    either empty, one marker, or
%           [m_nearCCW/start m_nearCW/end]
%
mStart  = findAngleMarkerByID(p,spanIDs{1}); % most CW in span
mEnd    = findAngleMarkerByID(p,spanIDs{2}); % most CCW in span
mStatic = findAngleMarkerByID(p,staticID);

s = markersSurroundingSpan(p);
mi = s.inside;
mo = s.outside;
if isStartPt
    % This is the most CW span pt for a north-facing span
    %
    % - CW limit: farthest .inside
    % - CCW limit: farthest .outside
    if isempty(mo)
        lim1 = mStart;
    else
        lim1 = mo(1);
    end
    if isempty(mi)
        lim2 = mStart;
    else
        lim2 = mi(1);
    end
else
    % This is the most CCW span pt for a north-facing span
    %
    if isempty(mi)
        lim1 = mEnd;
    else
        lim1 = mi(end);
    end
    if isempty(mo)
        lim2 = mEnd;
    else
        lim2 = mo(end);
    end
end

dc.RefAngle_Static = ...
    getNormalizedAngle(p,getAngleFromVec(mStatic));

dc.SpanDrag_AngleLimits = internal.polariCommon.angleDiff( ...
    dc.RefAngle_Static, ...
    getNormalizedAngle(p,getAngleFromVec([lim1 lim2])));

dc.SpanDrag_AllMarkers = s.all;
dc.SpanDrag_StaticMarker = mStatic;

dc.SpanDrag_AllMarkerAngles = ...
    getNormalizedAngle(p, ...
    getAngleFromVec(s.all));

% Clear this before a new drag:
dc.SpanDrag_LastHiliteMarkerIdx = [];
p.SpanDrag_CacheInfo = dc;

% Before beginning marker drag, we update span object to the
% latest set of angle markers:
span = p.hAngleSpan;
assert(~isempty(span));
updateAngleMarkersList(span);

changeMouseBehavior(p,'anglespan_buttondown');
end

function wbdrag(p,ev)
% Drag endpoint of span.
%  - graphical updates to visualize the drag location
%  - apply angle constraints to each endpoint (they differ)
%  - hilighlight "drop marker" dynamically

% After engaging the anglespan_buttondown, we have a
% first-time call that we utilize:
%
% We suppress the span-edge hilight arrows once actual
% drag-motion occurs via the mouse.  We leave the highlight
% visible as long as possible, so that if a user drags in an
% invalid initial direction, the highlight remains as guidance.
firstEntry = p.ChangedState;
if firstEntry
    p.ChangedState = false;
    
    hiliteSpanDrag_Init(p,'off');
end

% Get change in angle for the span endpoint being dragged
%
p.hFigure.CurrentPoint = ev.Point;
pt = get(p.hAxes,'CurrentPoint');
c = complex(pt(1,1),pt(1,2));
curr_cplx = c./abs(c);
prev_cplx = p.SpanDrag_PrevCplx; % cplx value prior to drag
delta = internal.polariCommon.cangleDiffRel(prev_cplx,curr_cplx);

span    = p.hAngleSpan;
spanPts = span.SpanCplx;
cstart  = spanPts{1}; % span startpoint (complex value)
cend    = spanPts{2}; % span endpoint (complex value)

dc = p.SpanDrag_CacheInfo;
ref = dc.RefAngle_Static;
ang_start = angle(cstart);
ang_end = angle(cend);

% Compute diff from reference angle (static span endpoint) to
% moving point:
if dc.SpanDrag_StartPtChanging
    % new startpoint, keep endpoint
    ang_moving = internal.polariCommon.angleDiff(ref,ang_start+delta);
else
    % keep startpoint, new endpoint
    ang_moving = internal.polariCommon.angleDiff(ref,ang_end+delta);
end

% Apply angle constraints
ang_lims = dc.SpanDrag_AngleLimits;
if ang_moving < ang_lims(1)
    ang_moving = ang_lims(1);
elseif ang_moving > ang_lims(2)
    ang_moving = ang_lims(2);
end
ang_moving = ang_moving + ref;

% Update visualization
if dc.SpanDrag_StartPtChanging
    % new startpoint, keep endpoint
    new_ang = [ang_moving, ang_end];
else
    % keep startpoint, new endpoint
    new_ang = [ang_start, ang_moving];
end
tempChangeToSpanEndpoints(span,new_ang);

% Highlight nearest valid marker
%  - indicates a "drop" at this point will reassociate span
%    endpoint with the highlighted marker
%
% Find which marker we're closest to:
all_angles = dc.SpanDrag_AllMarkerAngles;
marker_dist = internal.polariCommon.angleDiffRel(ang_moving, all_angles);
[~,newIdx] = min(abs(marker_dist));

% Update marker hilites if this is a change
%
lastIdx = dc.SpanDrag_LastHiliteMarkerIdx;
if ~isequal(lastIdx,newIdx)
    mAll = dc.SpanDrag_AllMarkers;
    
    % Get last marker, reset hilite
    if ~isempty(lastIdx)
        mAll(lastIdx).HiliteMarker = false;
    end
    
    % Get new marker, set hilite
    newMarker = mAll(newIdx);
    newMarker.HiliteMarker = true;
    
    % Install new permanent span endpoints:
    if dc.SpanDrag_StartPtChanging
        m1 = newMarker;
        m2 = dc.SpanDrag_StaticMarker;
    else
        m1 = dc.SpanDrag_StaticMarker;
        m2 = newMarker;
    end
    tempChangeToSpanColorAndReadout(span,m1,m2);
    % update readout
    
    dc.SpanDrag_LastHiliteMarkerIdx = newIdx;
    p.SpanDrag_CacheInfo = dc;
end
end

function wbup(p,ev)

% Set point into axes:
p.hFigure.CurrentPoint = ev.Point;

% Shut down temp hilite of angle marker (if any)
%
% Index will be empty if no motion occurred
dc = p.SpanDrag_CacheInfo;
idx = dc.SpanDrag_LastHiliteMarkerIdx;
if ~isempty(idx)
    assert(~isempty(idx));
    moving_marker = dc.SpanDrag_AllMarkers(idx);
    moving_marker.HiliteMarker = false;
    
    % Install new permanent span endpoints:
    span = p.hAngleSpan;
    if dc.SpanDrag_StartPtChanging
        ID1 = moving_marker.ID;
        ID2 = span.SpanIDs{2};
    else
        ID1 = span.SpanIDs{1};
        ID2 = moving_marker.ID;
    end
    changeSpanEndpoints(span,ID1,ID2);
    %revertSpanEndpoints(span);
end

% Clear out drag-cache, so we release marker handles etc
p.SpanDrag_CacheInfo = [];

% Update mouse handlers, and kick it once manually for mouse
% pointer updates etc:
%
% Check where mouse is to determine next step:
s = computeHoverLocation(p,ev);
if s.overSpan
    p.ChangedState = false;
    signalChangedState = false;
    changeMouseBehavior(p,'anglespan',signalChangedState);
    
    hiliteSpanDrag_Init(p,'on');
    showToolTipAndPtr_spanHover(p);
else
    hiliteSpanDrag_Init(p,'off'); % shut down arrows
    hoverOverSpanChange(p.hAngleSpan,'stop');
    
    autoChangeMouseBehavior(p,s);
end
end

function showToolTipAndPtr_spanDown(p)
if p.ToolTips && ~isempty(p.hToolTip) && isvalid(p.hToolTip)
    %span = p.hAngleSpan;
    %spanIDs = span.SpanIDs;
    %anyCursorsAttached = strcmpi(spanIDs{1}(1),'c') ...
    %    || strcmpi(spanIDs{2}(1),'c');
    if numel(p.hPeakAngleMarkers) ...
            + numel(p.hCursorAngleMarkers) < 3
        % Can't jump to new markers
        str = {'ANGLE SPAN',...
            'Move cursors to modify span'};
    else
        % Can jump to new markers
        str = {'ANGLE SPAN','Drag span edge to another cursor'};
    end
    start(p.hToolTip,str);
end
setptr(p.hFigure,'closedhand');
end

function showToolTipAndPtr_spanHover(p)
if p.ToolTips && ~isempty(p.hToolTip) && isvalid(p.hToolTip)
    Np = numel(p.hPeakAngleMarkers);
    Nc = numel(p.hCursorAngleMarkers);
    N = Nc+Np;
    
    span = p.hAngleSpan;
    spanIDs = span.SpanIDs;
    if isempty(spanIDs{1}) || isempty(spanIDs{2})
        return % EARLY EXIT
    end
    a1_cursor = strcmpi(spanIDs{1}(1),'c');
    a2_cursor = strcmpi(spanIDs{2}(1),'c');
    any_cursor = a1_cursor || a2_cursor;
    
    %str = sprintf('ANGLE SPAN (%s>%s)',spanIDs{1},spanIDs{2});
    str = 'ANGLE SPAN';
    if N > 2
        if any_cursor
            str = {str,'Drag cursors or span edges'};
        else % no cursors involved in span, just peaks
            str = {str,'Drag span edges to another cursor'};
        end
    else % N==2 exactly
        if any_cursor
            str = {str,'Drag cursors to modify span'};
        else % no cursors involved in span, just peaks
            str = {str,'Drag span edges to another cursor'};
        end
    end
    start(p.hToolTip,str);
end
setptr(p.hFigure,'hand');
end
