function [ObjHandle, varargout] = guipanel(varargin)

%% Parse Child -----------------------------------------------------------%
child = inputParser;
child.StructExpand = true;
child.KeepUnmatched = true;

%Interuptible: Defaults to off
child.addParamValue('Interruptible','off')

%BusyAction: Defaults to cancel
child.addParamValue('BusyAction','queue')

%Child's Parent: Defaults to new fig if Param/Value pair is missing
if (nargin >= 1) && ~ischar(varargin{1})
    assert(ishandle(varargin{1}),'guipanel:badHandle',...
        ['When creating a guipanel inside an existing figure/panel, ',...
        'the first argument must be a currently existing/valid graphics handle'])
    child.addOptional('Parent',varargin{1})
else
    %-% NOTICE %-%
    % The figure handle is generated by evaluating a figure command at the
    % time the addParamValue method is called.
    child.addParamValue('Parent',figure);
end

%% Finish Parsing and Instantiate ----------------------------------------%
%   We have only slightly differed from the calling syntax of uipanel.
%   Specifically, guipanel may be called with a single argument, the handle
%   of a pre-existing figure or pre-exisiting panel (this allows easy
%   nesting of panels).

child.parse(varargin{:});

if ~any(strcmp('BusyAction',child.UsingDefaults));
    warning('guipanel:defaultOverWrite',...
        'Setting ''BusyAction'' to ''cancel'' may cause unexpected gui behavior')
end

if ~any(strcmp('Interruptible',child.UsingDefaults));
    warning('guipanel:defaultOverWrite',...
        'Setting ''Interruptible'' to ''on'' may cause unexpected gui behavior')
end

assert(ishandle(child.Results.Parent),'guipanel:badHandle',...
    '%s is not a valid graphics handle',num2str(child.Results.Parent))
    %Create a panel in the specified container (Figure or Panel)
    if isempty(fieldnames(child.Unmatched))
        ObjHandle = uipanel(child.Results,...
            'ButtonDownFcn',@PanelButtonDownFcn);
    else
        ObjHandle = uipanel(child.Results,...
            child.Unmatched,...
            'ButtonDownFcn',@PanelButtonDownFcn);
    end
   %inputParser class is poorly behaved, manually free mem
    clear child
    
    %Create togglebutton in the panel.  Togglebutton will lock panel in
    %place when activated, and release the panel when deactivated.
    uicontrol(ObjHandle,...
        'Style','togglebutton',...
        'Units','pixels',...
        'BackgroundColor','red',...
        'Position',[4 2 9 9],...
        'ToolTipString','Lock',...
        'Tag','Lock',...
        'Callback',@LockPanelCallback);

    %% Begin callbacks and guipanel subfcns

    function PanelButtonDownFcn(src,event)

        uistack(src,'top') %just in case
        if ~get(findobj('Parent',src,...
                'Type','uicontrol',...
                'Style','togglebutton',...
                'Tag','Lock'),'Value')

            %Get parent container of src
            parent = get(src,'Parent');
            
            %Get parent figure of src
            fig = ancestor(src,'figure');
            
            %Sync units
            old_units = SyncUnits([0,src,parent,fig],'pixels');
            
            %Position of src relative to parent figure
            pos_r2fig = getpixelposition(src,true);
            
            %Position of src relative to parent container
            pos_r2par = getpixelposition(src);
            
            %Position of pointer relative to parent figure
            point_r2fig = Root2Fig(get(0,'PointerLocation'),fig);

            [epsX,epsY] = MakeEpsilon(point_r2fig,pos_r2fig);
            if any(abs(epsX) <= 10) || any(abs(epsY) <= 10)
                %RESIZE
                set(fig,'Pointer','fleur')
                if fig == parent
                    position = rbbox(pos_r2par);    
                    setpixelposition(src,position)
                else
                    position = rbbox(pos_r2fig);
                    setpixelposition(src,position,true)
                end
            else
                %MOVE
                set(fig,'Pointer','fleur')
                if fig == parent
                    position = dragrect(pos_r2fig);
                    setpixelposition(src,position)
                else
                    position = dragrect(pos_r2fig);
                    setpixelposition(src,position,true)
                end
            end
            set(fig,'Pointer','arrow')
            UnSyncUnits(old_units)
            refresh(fig)
        end
    end

    function LockPanelCallback(src,event)
        if get(src,'Value')
            set(src,'BackgroundColor','white')
        else
            set(src,'BackgroundColor','red')
        end
    end

    varargout{1} = struct(...
        'Root2Fig',@Root2Fig,...
        'Parent2Child',@Parent2Child,...
        'MakeEpsilon',@MakeEpsilon,...
        'MakeRect',@MakeRect,...
        'SyncUnits',@SyncUnits,...
        'UnSyncUnits',@UnSyncUnits,...
        'OverlapingAt',@OverlappingAt);
end

%% Utility Functions -----------------------------------------------------%
%

function coords = Root2Fig(point, fig)
% Transforms point from root-centric to child-centric coordinates.
% Point is measured in the parent's reference frame

if fig == 0
    coords = point;
    return
end

oldUnits = SyncUnits([0,fig],'Pixels');

%Get position of user specified point relative to the Root
PxRoot = point(1); PyRoot = point(2);

%Get position of Figure relative to the Root
figloc = get(fig,'Position');
FigOriginX = figloc(1); FigOriginY = figloc(2);

%Send cursor coordinates from Root to Figure reference frame:
PxRoot2Fig = (PxRoot - FigOriginX); PyRoot2Fig = (PyRoot - FigOriginY);

%Clean up and return result
coords = [PxRoot2Fig,PyRoot2Fig];
UnSyncUnits(oldUnits)

end
function coords = Parent2Child(point, child)
% Transforms point from parent-centric to child-centric coordinates.
% Point is measured in the parent's reference frame
parent = ancestor(child,{'figure','uipanel'});
fig = ancestor(child,'figure');
oldUnits = SyncUnits([0,fig,parent,child],'pixels');

%Root->Figure
r2f = Root2Fig(point, fig);

%Parent's position in Figure frame
if (parent ~= 0) && (parent ~= fig)
    parent_position = getpixelposition(parent,true);
    x = r2f(1) - parent_position(1);
    y = r2f(2) - parent_position(2);
    coords = [x,y];
else
    coords = r2f;
end

UnSyncUnits(oldUnits)


end
function varargout = MakeEpsilon(point, position_vector)
%Determine a position vector's left/right & top/bottom distances from a point
%
%   Calling syntax:
%       [epsX,epsY] = MakeEpsilon(point, vector)
%       [X_right,X_left,Y_bottom,Y_top] = MakeEpsilon(point,vector)
%       Where point is 1x2
%       or
%
%       [X_right,X_left,Y_bottom,Y_top] = MakeEpsilon(vector1,vector2)
%       Where vector1 is 1x4xN, and vector2 is 1x4.  In the case that
%       N > 1, each of X_right, X_left, Y_bottom, and Y_top will be
%       1x1xN.
%
%   If epsX(1) <= 0 &&  epsX(2) >= 0 then px is inside the
%   horizontal boundaries.
%
%   If epsY(1) <= 0 &&  epsY(2) >= 0 then py is inside the vertical
%   boundaries.
%
    assert(nargin == 2,'Too few/Too many inputs, MakeEpsilon requires two input args')
    if nargout
        %assert((nargout == 2) || (nargout == 4), 'MakeEpsilon takes either two or four output args')
    end
    
    sz = size(point);

    switch mat2str(sz)
        case '[1 2]'         
            [epsX,epsY] = epsilonPointSubFcn(point, position_vector);
            varargout{1} = epsX; varargout{2} = epsY;
            return
        case '[1 4]'
            [X_right,X_left,Y_bottom,Y_top] = epsilonVectorSubFcn(point,position_vector);
        otherwise
            try
                [X_right,X_left,Y_bottom,Y_top] = epsilonVectorSubFcn(point,position_vector);
            catch ME
                ME.message
                error('guiSubFcn:MakeEpsilon','Cannot reconcile input')
            end
    end

    varargout{1} = X_right; varargout{2} = X_left;
    varargout{3} = Y_bottom; varargout{4} = Y_top;

    function varargout = epsilonPointSubFcn(pnt, pos)  
        %Do horz
        leftXlim = pos(1)-pnt(:,1);
        rightXlim = pos(1)+pos(3)-pnt(:,1);
        epsX = [leftXlim, rightXlim];
        %Do vert
        bottomYlim = pos(2)-pnt(:,2);
        topYlim = pos(2)+pos(4)-pnt(:,2);
        epsY = [bottomYlim, topYlim];
        %Parse outputs
        if nargout ==2
            varargout{1} = epsX; varargout{2} = epsY;
        else
            varargout{1} = epsX(:,1); varargout{2} = epsX(:,2);
            varargout{3} = epsY(:,1); varargout{4} = epsY(:,2);
        end
    end
    function varargout = epsilonVectorSubFcn(pos1,pos2)
       [rectX,rectY] = MakeRect(pos1); %todo, overload MakeRect to accept pos vecs
       %rectX, rectY will each be 4x1
       pnt_array = [rectX,rectY];
       [X_right,X_left,Y_bottom,Y_top] = epsilonPointSubFcn(pnt_array, pos2);
       varargout{1} = X_right; varargout{2} = X_left;
       varargout{3} = Y_bottom; varargout{4} = Y_top;
    end

end
function [rectX,rectY] = MakeRect(position_vector)
%MakeRect takes a position vector and creates X,Y vertices for the
%corresponding rectangle. The position vector can be a (1,4,N)
%multidimensional array.
bottomLeft = [position_vector(:,1,:),position_vector(:,2,:)];
bottomRight = [position_vector(:,1,:)+position_vector(:,3,:),position_vector(:,2,:)];
topRight = [position_vector(:,1,:)+position_vector(:,3,:),position_vector(:,2,:)+position_vector(:,4,:)];
topLeft = [position_vector(:,1,:),position_vector(:,2,:)+position_vector(:,4,:)];
coords = [bottomLeft; bottomRight; topRight; topLeft];
rectX = coords(:,1,:); rectY = coords(:,2,:);
end
function oldHandleUnits = SyncUnits(handles, trgt_units)
% oldUnits is a n x 2 cell array where oldUnits{:,1} == handles
% and oldUnits{:,2} == "Units"
if nargout < 1
    warning('utilityFcns:reconcileUnits',...
        'old units not recorded, new units still set')
elseif nargout > 1
    error('Too many output arguments')
elseif nargin < 2 || ~ischar(trgt_units)
    error('Target units not specified')
end
[m,n] = size(handles);
handles = reshape(handles,m*n,1); % Turn handles into column vector
oldUnits = get(handles,'Units');

handles_cell = mat2cell(handles, ones(1,m*n), 1); % Turn handles into cell
oldHandleUnits = [handles_cell, oldUnits];

%Set new units last as a precautionary -- if exception arises at
%least we might recover the old units.
try
    set(handles, 'Units', trgt_units)
catch ME
    ME.message
    error('Unable to set units')
end

end
function UnSyncUnits(old_handle_units)
    arrayfun(@(k)set(old_handle_units{k,1},'Units',old_handle_units{k,2}),...
        1:1:size(old_handle_units,1))
end
function [adjacencyMatrix,varargout] = OverlappingAt(position_vector)
%Returns the adjacency matrix indicating the overlap of any of N rectangles
%as specified by the Nx4 array of position vectors, position_vector.  If
%called with two outputs, the second output is matrix where the (i,j)th
%element is the area of overlap between position_vector(i,:) and position_vector(j,:).
%

area = rectint(position_vector,position_vector);
density = nnz(area) / numel(area);
if density <= 0.4
    area = sparse(area);
end
area = area - diag(diag(area)); %removes self-reference
if nargout == 2
    varargout{1} = area;
end
adjacencyMatrix = (area ~= 0);
end



