function progressbar(fractiondone, position)
%PROGRESSBAR Modern lightweight progress bar with time-remaining estimate.
%
% Usage:
%   progressbar               % initialize/reset
%   progressbar(0)            % initialize/reset
%   progressbar(0,4)          % initialize/reset and set position
%   progressbar(0,[0.2 0.7])  % initialize/reset and set normalized position
%   progressbar(x)            % update, x in [0,1]
%   progressbar(1)            % close/clear
%
% Notes:
%   - Uses uifigure/uiaxes when available; falls back to figure/axes.
%   - Click bar/axes to randomize color (legacy behavior retained).

persistent S
% S fields (created lazily):
%   .fig, .ax, .patch, .startTic, .lastUpdateTic, .useUI, .width, .height

% -------------------------------------------------------------------------
% Headless / no-figure fallback
% -------------------------------------------------------------------------
% In -nodisplay/-nodesktop/-batch sessions, figure windows cannot be shown.
% In that case we fall back to periodic text updates.
canShow = usejava('desktop') && feature('ShowFigureWindows');

% -----------------------------
% Defaults
% -----------------------------
if nargin < 1 || isempty(fractiondone)
    fractiondone = 0;
end
if nargin < 2 || isempty(position)
    position = 0;
end

% Clamp
fractiondone = max(0, min(1, fractiondone));
percentdone  = floor(100*fractiondone);

% Text fallback for headless sessions
if ~canShow
    persistent lastPct
    if isempty(lastPct) || fractiondone==0
        lastPct = -1;
    end
    if percentdone ~= lastPct
        fprintf('Progress: %3d%%\n', percentdone);
        lastPct = percentdone;
    end
    if percentdone >= 100
        lastPct = [];
    end
    return
end

% -----------------------------
% Close if done
% -----------------------------
if percentdone >= 100
    closeIfValid(S);
    S = [];
    return;
end

% -----------------------------
% Detect existing and reset
% -----------------------------
if isempty(S) || ~isValidFig(S)
    S = [];
else
    if fractiondone == 0
        closeIfValid(S);
        S = [];
    end
end

% -----------------------------
% Create if needed
% -----------------------------
if isempty(S)
    S = createPB(position);
    S.startTic = tic;
    S.lastUpdateTic = 0;
end

% -----------------------------
% Enforce minimum update interval
% -----------------------------
minInterval = 0.02; % seconds (50 Hz max update)
if S.lastUpdateTic ~= 0
    if toc(S.lastUpdateTic) < minInterval
        return;
    end
end

% -----------------------------
% Update graphics
% -----------------------------
set(S.patch, 'XData', [0 fractiondone fractiondone 0]);

% Title text with remaining estimate
if fractiondone <= 0
    titlebarstr = ' 0%';
else
    runtime  = toc(S.startTic);
    timeleft = runtime / fractiondone - runtime;
    titlebarstr = sprintf('%2d%%    %s remaining', percentdone, sec2timestr(timeleft));
end

% uifigure uses 'Name' too; classic figure uses 'Name' as well
try
    S.fig.Name = titlebarstr;
catch
    set(S.fig, 'Name', titlebarstr);
end

drawnow limitrate
S.lastUpdateTic = tic;

end % main


% =========================================================================
% Helpers
% =========================================================================

function S = createPB(position)
% Decide whether to use uifigure (preferred) or classic figure
% Only use UIFIGURE when the Desktop is available; otherwise uifigure may
% fail silently or never appear.
useUI = exist('uifigure','file') == 2 && usejava('desktop') && feature('ShowFigureWindows');

% Normalized screen unit assumptions (for positioning)
scr = get(0,'ScreenSize'); % pixels: [left bottom width height]
w_px = round(scr(3)/4);
h_px = max(28, round(scr(4)/50)); % a bit taller than legacy on HiDPI

% Compute pixel position from legacy "position" argument
pos_px = computePositionPx(position, scr, w_px, h_px);

if useUI
    % Create UIFIGURE
    fig = uifigure( ...
        'Name',        ' 0%', ...
        'NumberTitle', 'off', ...
        'Resize',      'off', ...
        'Position',    pos_px);

    % Ensure it appears even if created while other callbacks are running
    fig.Visible = 'on';

    ax = uiaxes(fig, ...
        'Position',    [6 6 pos_px(3)-12 pos_px(4)-12], ...
        'XLim',        [0 1], ...
        'YLim',        [0 1], ...
        'XTick',       [], ...
        'YTick',       [], ...
        'Box',         'on');
    ax.Toolbar.Visible = 'off';
    ax.Interactions = []; % disable pan/zoom for a clean widget

    p = patch(ax, ...
        'XData', [0 0 0 0], ...
        'YData', [0 0 1 1]);

    % Click callbacks (uifigure uses different event plumbing; use ButtonDownFcn on objects)
    fig.WindowButtonDownFcn = @(~,~) changecolor(p);
    ax.ButtonDownFcn        = @(~,~) changecolor(p);
    p.ButtonDownFcn         = @(~,~) changecolor(p);

else
    % Classic figure fallback
    fig = figure( ...
        'Units',       'pixels', ...
        'Position',    pos_px, ...
        'NumberTitle', 'off', ...
        'Name',        ' 0%', ...
        'Resize',      'off', ...
        'MenuBar',     'none', ...
        'ToolBar',     'none');

    set(fig,'Visible','on');

    ax = axes('Parent', fig, ...
        'Position',    [0.02 0.15 0.96 0.70], ...
        'XLim',        [0 1], ...
        'YLim',        [0 1], ...
        'Box',         'on', ...
        'XTick',       [], ...
        'YTick',       []);

    p = patch('Parent', ax, ...
        'XData', [0 0 0 0], ...
        'YData', [0 0 1 1]);

    set(fig,'WindowButtonDownFcn', @(~,~) changecolor(p));
    set(ax, 'ButtonDownFcn',       @(~,~) changecolor(p));
    set(p,  'ButtonDownFcn',       @(~,~) changecolor(p));
end

% Random initial color (legacy behavior)
changecolor(p);

% Ensure it becomes visible and rendered immediately
try
    if useUI
        fig.Visible = 'on';
        drawnow;
        try, bringToFront(fig); catch, end
    else
        set(fig,'Visible','on');
        figure(fig);
        drawnow;
    end
catch
end

% Force render now (helps on some systems where the window otherwise
% appears late or not at all during tight loops)
drawnow;

S.fig   = fig;
S.ax    = ax;
S.patch = p;
S.useUI = useUI;
S.width = w_px;
S.height = h_px;

end


function pos_px = computePositionPx(position, scr, w_px, h_px)
% scr = [left bottom width height] in px
screenW = scr(3);
screenH = scr(4);

hpad = round(screenW/64);
vpad = round(screenH/24);

% default centered
left   = round(screenW/2 - w_px/2);
bottom = round(screenH/2 - h_px/2);

if isscalar(position)
    switch position
        case 0 % center (default)
        case 1 % top-right
            left   = screenW - w_px - hpad;
            bottom = screenH - h_px - vpad;
        case 2 % top-left
            left   = hpad;
            bottom = screenH - h_px - vpad;
        case 3 % bottom-left
            left   = hpad;
            bottom = vpad;
        case 4 % bottom-right
            left   = screenW - w_px - hpad;
            bottom = vpad;
        case 5 % random
            left   = randi([0 max(1, screenW - w_px)]);
            bottom = randi([0 max(1, screenH - h_px)]);
        otherwise
            warning('progressbar:BadPosition','position must be 0-5 or [x y]. Using center.');
    end
elseif isnumeric(position) && numel(position)==2
    % Treat as normalized [x y] for lower-left corner (legacy style)
    left   = round(position(1) * (screenW - w_px));
    bottom = round(position(2) * (screenH - h_px));
else
    warning('progressbar:BadPosition','position must be 0-5 or [x y]. Using center.');
end

% Clamp to visible region
left   = max(0, min(left,   screenW - w_px));
bottom = max(0, min(bottom, screenH - h_px));

% MATLAB figure Position expects [x y w h] in pixels
pos_px = [left bottom w_px h_px];

end


function changecolor(progpatch)
% Random but not too light (legacy behavior)
colorlim = 2.8;
c = rand(1,3);
while sum(c) > colorlim
    c = rand(1,3);
end
set(progpatch,'FaceColor',c,'EdgeColor','none');
end


function tf = isValidFig(S)
tf = isstruct(S) && isfield(S,'fig') && ~isempty(S.fig);
if tf
    try
        tf = isvalid(S.fig);
    catch
        tf = ishghandle(S.fig);
    end
end
end


function closeIfValid(S)
if isstruct(S) && isfield(S,'fig') && ~isempty(S.fig)
    try
        if isvalid(S.fig), delete(S.fig); end
    catch
        try
            if ishghandle(S.fig), delete(S.fig); end
        catch
        end
    end
end
end


function timestr = sec2timestr(sec)
% Convert seconds into a readable string (days/hours/min/sec)
sec = max(0, sec);

d = floor(sec/86400); sec = sec - d*86400;
h = floor(sec/3600);  sec = sec - h*3600;
m = floor(sec/60);    sec = sec - m*60;
s = floor(sec);

if d > 0
    if d > 9
        timestr = sprintf('%d day', d);
    else
        timestr = sprintf('%d day, %d hr', d, h);
    end
elseif h > 0
    if h > 9
        timestr = sprintf('%d hr', h);
    else
        timestr = sprintf('%d hr, %d min', h, m);
    end
elseif m > 0
    if m > 9
        timestr = sprintf('%d min', m);
    else
        timestr = sprintf('%d min, %d sec', m, s);
    end
else
    timestr = sprintf('%d sec', s);
end
end
