function [monitorLabels monitorCaseIDs figureNames subplotHandles] = PlotHdfFileV2(filename, varargin)
% NOTE: V2-only implementation for H5 files using /Sensors/<ID> structure.
%PlotHdfFileV2: Plots all data within an APDM HDF file. Data from each individual monitor
%             is plotted as a separate figure. Only data from enabled sensors
%             (e.g., accelerometers) is plotted. Figures can optionally be saved in any
%             supported format.
%
%   [figureNames monitorLabels monitorCaseIDs] = PlotHdfFileV2(filename, varargin)
%
% Required input parameter:
%
%   filename           The path to the HDF file to be plotted.
%
% Example usage:
%
%   PlotHdfFileV2(''/Users/mahmoud/Downloads/20260120-10515068_Walk.h5'');
%   PlotHdfFileV2(''/Users/mahmoud/Downloads/20260120-10515068_Walk.h5'', ...
%       ''useMonitorCaseIDs'', {''7230''});
%   PlotHdfFileV2(''/Users/mahmoud/Downloads/20260120-10515068_Walk.h5'', ...
%       ''useMonitorCaseIDs'', {''7230'',''5889''}, ...
%       ''useMonitorLabels'', {''Right Wrist'',''Left Wrist''});
%
% Notes:
%   Monitor IDs in this H5 format are taken from paths under /Sensors/<ID>.
%
% Optional input parameter:
%
%   useMonitorLabels   A cell array specifying the monitor labels to plot.
%                      For example {'Lumbar', 'Right Arm'} would plot only
%                      data that originated from monitors with either of
%                      these labels.
%                      Default: {}, all data will be plotted
%
%   useMonitorCaseIDs  A cell array specifying monitor IDs to plot.
%                      For example {'7230','5889'} would plot only these
%                      sensors.
%                      Default: {}, all IDs will be plotted
%
%   baseSaveName       The base file name to use when data is plotted.
%                      For example, 'Foo' would generate files of the
%                      format 'Foo_CaseID_MonitorLabel.jpg'.
%                      Default: 'Plot'
%
%   saveDir            Specifies the directory to save the figures in
%                      (if saving is enabled)
%                      Default: '.'
%
%   showFigures        Boolean parameter specifying whether to make the
%                      figures visible after plotting. Setting this to
%                      false is useful if this function is being used
%                      for saving figures only, and viewing them is
%                      unimportant
%                      Default: true
%
%   showLegend         Boolean parameter specifying whether to include
%                      the x,y,z legend to the right of the figures.
%                      Default: false
%
%   saveFigures        Boolean parameter specifying whether to save the
%                      figures to disk.
%                      Default: false
%
%   format             String value specifying what format to save the
%                      figures in. Examples include: 'jpeg', 'eps','epsc',
%                      'pdf','eps2','epsc2','tiff', and 'png'
%                      Default: 'jpeg'
%
%   width              The width of saved figures, in inches.
%                      Default: 6 inches
%
%   height             The height of saved figures, in inches.
%                      Default: 5 inches
%
%   resolution         The resolution of saved figures, in dots/inch
%                      Default: 300 dpi
%
%   setAxes            Specifies whether to apply the custom axes to minimize
%                      whitespace. The settings are optimized for the default
%                      figure size (7x9 in) and is suitable for plotting a
%                      single figure on a complete page. Note that using
%                      these custome axes with different figure sizes may
%                      result in bad axes placement.
%                      Default: false
%
%   removeMean         Specifies whether to remove the mean of each signal
%                      before plotting. This is particularly useful when
%                      plotting accelerometer, where gravity typically results
%                      in significantly different ranges for the different axes.
%                      Default: false
%
% Output parameters:
%
%   monitorLabels      A cell array listing the monitor names corresponding
%                      to plotted data.
%
%   monitorCaseIDs     A cell array listing the case IDs corresponding
%                      to plotted data.
%
%   figureNames        A cell array listing the names of the saved files
%
%   subplotHandles     An (nMonitors x nSensors) array of subplot handles
%
%   Version 1.00 LH

% Suppress certain warnings
%#ok<*CTCH>
%#ok<*AGROW>

%==============================================================================
% User-Specified Parameters
%==============================================================================
nMandatoryArguments = 1;
useMonitorLabels = {};
useMonitorCaseIDs = {};
saveDir = './';
baseSaveName = 'Plot';
showFigures = true;
saveFigures = false;
showLegend = false;
format = 'jpeg';
width = 7;
height = 9;
resolution = 300;
setAxes = false;
removeMean = false;

if nargin>nMandatoryArguments
    if ~isstruct(varargin{1})
        if rem(length(varargin),2)~=0, error('Optional input arguments must be in name-value pairs.'); end;
        Parameters = struct;
        for c1=1:2:length(varargin)-1
            if ~ischar(varargin{c1}), error(['Error parsing arguments: Expected property name string at argument ' num2str(c1+1)]); end
            Parameters.(varargin{c1}) = varargin{c1+1};
        end
    else
        Parameters = varargin{1};
    end
    
    parameterNames = fieldnames(Parameters);
    for c1 = 1:length(parameterNames)
        parameterName  = parameterNames{c1};
        if(~ischar(parameterName))
            error(['Error parsing arguments: Expected property name string at argument ' num2str(c1+1)]);
        end
        parameterValue = Parameters.(parameterName);
        switch lower(parameterName)
            case lower('showFigures'), showFigures = parameterValue;
            case lower('useMonitorLabels'), useMonitorLabels = parameterValue;
            case lower('useMonitorCaseIDs'), useMonitorCaseIDs = parameterValue;
            case lower('saveDir'), saveDir = parameterValue;
            case lower('baseSaveName'), baseSaveName = parameterValue;
            case lower('saveFigures'), saveFigures = parameterValue;
            case lower('format'), format = parameterValue;
            case lower('width'), width = parameterValue;
            case lower('height'), height = parameterValue;
            case lower('resolution'), resolution = parameterValue;
            case lower('showLegend'), showLegend = parameterValue;
            case lower('setAxes'), setAxes = parameterValue;
            case lower('removeMean'), removeMean = parameterValue;
            otherwise, error(['Unrecognized property: ''' varargin{c1} '''']);
        end
    end
end

if ischar(useMonitorCaseIDs)
    useMonitorCaseIDs = {useMonitorCaseIDs};
elseif isstring(useMonitorCaseIDs)
    useMonitorCaseIDs = cellstr(useMonitorCaseIDs);
end

if showFigures
    visibility = 'on';
else
    visibility = 'off';
end

try
    vers = h5readatt(filename, '/', 'FileFormatVersion');
catch
    error('This V2 function requires FileFormatVersion at root and current /Sensors/<ID> structure.');
end

try
    sensorsInfo = h5info(filename, '/Sensors');
catch
    error('This V2 function requires /Sensors group with /Sensors/<ID> layout.');
end

if isempty(sensorsInfo.Groups)
    error('No sensor groups found under /Sensors. Expected /Sensors/<ID> layout.');
end

fileVersion = normalizeVersionValue(vers);
if ~isnan(fileVersion) && fileVersion < 2
    error('Unsupported FileFormatVersion (%g). Expected modern /Sensors/<ID> files.', fileVersion);
end

figureNames = {};
monitorLabels = {};
monitorCaseIDs = {};
subplotHandles = [];
figureIdx = 0;

for iMonitor = 1:length(sensorsInfo.Groups)
    sensorGroup = sensorsInfo.Groups(iMonitor);
    caseID = GetLastPathToken(sensorGroup.Name);
    configPath = [sensorGroup.Name '/Configuration'];
    monitorLabel = ReadLabel0(filename, configPath, caseID);

    if ~isempty(useMonitorCaseIDs) && ~any(strcmp(caseID, useMonitorCaseIDs))
        continue;
    end
    
    if ~isempty(useMonitorLabels) && isempty(strmatch(monitorLabel, useMonitorLabels, 'exact'))
        continue;
    end
    
    accPath = [sensorGroup.Name '/Accelerometer'];
    gyroPath = [sensorGroup.Name '/Gyroscope'];
    magPath = [sensorGroup.Name '/Magnetometer'];

    includeAcc = HasDataset(sensorGroup, 'Accelerometer');
    includeGyro = HasDataset(sensorGroup, 'Gyroscope');
    includeMag = HasDataset(sensorGroup, 'Magnetometer');

    try
        fs = double(h5readatt(filename, configPath, 'Sample Rate'));
    catch
        fs = [];
    end
    
    data = {};
    labels = {};
    units = {};
    
    nPlots = 0;
    if includeAcc
        nPlots = nPlots + 1;
        data{nPlots} = NormalizeXYZData(h5read(filename, accPath));
        labels{nPlots} = 'Accelerometers';
        units{nPlots} = 'm/s^2';
    end
    if includeGyro
        nPlots = nPlots + 1;
        data{nPlots} = NormalizeXYZData(h5read(filename, gyroPath));
        labels{nPlots} = 'Gyroscopes';
        units{nPlots} = 'rad/s';
    end
    if includeMag
        nPlots = nPlots + 1;
        data{nPlots} = NormalizeXYZData(h5read(filename, magPath));
        labels{nPlots} = 'Magnetometers';
        units{nPlots} = 'a.u.';
    end

    if isempty(fs) || ~isfinite(fs) || fs <= 0
        try
            tData = double(h5read(filename, [sensorGroup.Name '/Time']));
            if numel(tData) > 1
                dt = median(diff(tData));
                if dt > 0
                    fs = 1 / dt;
                end
            end
        catch
        end
    end
    if isempty(fs) || ~isfinite(fs) || fs <= 0
        fs = 1;
    end
    
    if nPlots == 0
        continue;
    end
    
    figureIdx = figureIdx + 1;
    monitorCaseIDs{figureIdx} = caseID;
    monitorLabels{figureIdx} = monitorLabel;
    subplotHandles{iMonitor} = [];
    figure('visible', visibility);
    for iPlot = 1:nPlots
        hS = subplot(nPlots,1,iPlot);
        subplotHandles{iMonitor} = [subplotHandles{iMonitor} hS];
        [p1 p2 p3 p4] = MakePlot(fs, data{iPlot}, labels{iPlot}, units{iPlot}, removeMean);
        if iPlot == 1 && showLegend
            hL = legend([p2 p3 p4],{'x','y','z'},'Location','EastOutside');
            legend(hL, 'boxoff');
        end
        if iPlot ~= nPlots
            set(gca, 'XTick', []);
        end
        if iPlot == nPlots
            xlabel('Time (s)');
        end
        if setAxes
            p = get(hS, 'pos');
            p(1) = 0.1;
            if showLegend
                p(3) = 0.75;
            else
                p(3) = 0.87;
            end
            switch nPlots
                case 1
                    p(4) = 0.9;
                    p(2) = p(2) - 0.05;
                case 2
                    p(4) = 0.42;
                    p(2) = p(2) - 0.05;
                case 3
                    p(4) = 0.26;
                    p(2) = p(2) - 0.04;
            end
            set(hS,'pos',p);
        end
    end
    
    linkaxes(subplotHandles{iMonitor},'x');
    
    figureName = [baseSaveName '_' caseID];
    figurePath = fullfile(saveDir, figureName);
    if saveFigures
        PrintFigureV2(figurePath, format, width, height, resolution, gcf);
        figureNames{figureIdx} = figureName;
    end
end
end

function caseID = GetLastPathToken(pathText)
parts = strsplit(pathText, '/');
parts = parts(~cellfun(@isempty, parts));
caseID = parts{end};
end

function tf = HasDataset(groupInfo, datasetName)
tf = any(strcmp({groupInfo.Datasets.Name}, datasetName));
end

function label = ReadLabel0(filename, configPath, fallback)
try
    label = h5readatt(filename, configPath, 'Label 0');
catch
    label = fallback;
end
if isnumeric(label)
    label = char(label(:).');
elseif isstring(label)
    label = char(label);
end
z = find(label == 0, 1, 'first');
if ~isempty(z)
    label = label(1:z-1);
end
label = strtrim(label);
end

function data = NormalizeXYZData(data)
data = double(data);
if isvector(data)
    data = data(:).';
end
if size(data,1) ~= 3 && size(data,2) == 3
    data = data.';
end
if size(data,1) ~= 3
    error('Expected XYZ data with 3 axes, got [%d x %d].', size(data,1), size(data,2));
end
end

function [minY maxY] = GetYRange(data)
minY = min(min(data));
maxY =  max(max(data));
range = maxY - minY;
minY = minY - 0.05*range;
maxY =  maxY + 0.05*range;
end

function [p1 p2 p3 p4] = MakePlot(fs, data, label, units, removeMean)
t = (1:size(data,2))/fs;
if removeMean
    data = data - repmat(mean(data,2),1,size(data,2));
end
colors = [1 0 0 ; 0 0.8 0 ; 0 0 1];
hold on;
p1 = plot(t,zeros(1,length(t)),'color',[0.8 0.8 0.8]);
p4 = plot(t,data(3,:),'color',colors(3,:));
p3 = plot(t,data(2,:),'color',colors(2,:));
p2 = plot(t,data(1,:),'color',colors(1,:));
xlim([min(t) max(t)])
[minY maxY] = GetYRange(data);
ylim([minY maxY])
title(label);
ylabel(units);
end

function v = normalizeVersionValue(raw)
if isnumeric(raw)
    v = double(raw);
    if ~isscalar(v)
        v = v(1);
    end
    return;
end

if isstring(raw)
    raw = char(raw);
end

if ischar(raw)
    raw = strtrim(raw);
    z = find(raw == 0, 1, 'first');
    if ~isempty(z)
        raw = raw(1:z-1);
    end
    v = str2double(raw);
    if isnan(v)
        v = NaN;
    end
    return;
end

v = NaN;
end