classdef tScenarioData < matlab.unittest.TestCase
    %TSCENARIODATA Tests for tdcs_scenario_data (Task 2).
    %
    %   Authored per TDD: written before tdcs_scenario_data.m existed, so
    %   it is expected to fail until the function is implemented. As of
    %   this commit MATLAB is not licensed in this environment, so this
    %   suite has been authored and reviewed by hand but has NOT been
    %   executed. Expected per-scenario subject-per-group counts were
    %   hand-derived from the Task 1 curated counts (Naive=4,
    %   Electrode-Box-A2=3, Electrode-Box-B2=3, Electrode-Box-A=1,
    %   Right-Electrode=1) plus each scenario's merge map, and cross-
    %   checked against task-2-brief.md's table (see task-2-report.md).

    properties (TestParameter)
        scenarioCase = struct( ...
            'unmerged_full', struct( ...
                'scenario', 'unmerged_full', ...
                'maxDay', 26, ...
                'counts', containers.Map( ...
                    {'Electrode-Box-A', 'Electrode-Box-A2', ...
                     'Electrode-Box-B2', 'Naive', 'Right-Electrode'}, ...
                    {1, 3, 3, 4, 1})), ...
            'unmerged_d0_10', struct( ...
                'scenario', 'unmerged_d0_10', ...
                'maxDay', 10, ...
                'counts', containers.Map( ...
                    {'Electrode-Box-A', 'Electrode-Box-A2', ...
                     'Electrode-Box-B2', 'Naive', 'Right-Electrode'}, ...
                    {1, 3, 3, 4, 1})), ...
            'mergeA2_full', struct( ...
                'scenario', 'mergeA2_full', ...
                'maxDay', 26, ...
                'counts', containers.Map( ...
                    {'Electrode-Box-A2', 'Electrode-Box-B2', 'Naive'}, ...
                    {4, 4, 4})), ...
            'mergeA2_d0_10', struct( ...
                'scenario', 'mergeA2_d0_10', ...
                'maxDay', 10, ...
                'counts', containers.Map( ...
                    {'Electrode-Box-A2', 'Electrode-Box-B2', 'Naive'}, ...
                    {4, 4, 4})), ...
            'mergeB2_full', struct( ...
                'scenario', 'mergeB2_full', ...
                'maxDay', 26, ...
                'counts', containers.Map( ...
                    {'Electrode-Box-A2', 'Electrode-Box-B2', 'Naive'}, ...
                    {3, 5, 4})), ...
            'mergeB2_d0_10', struct( ...
                'scenario', 'mergeB2_d0_10', ...
                'maxDay', 10, ...
                'counts', containers.Map( ...
                    {'Electrode-Box-A2', 'Electrode-Box-B2', 'Naive'}, ...
                    {3, 5, 4})) ...
            )
    end

    methods (Test)
        function testGroupCountsWindowAndReference(testCase, scenarioCase)
            [S, meta] = tdcs_scenario_data(scenarioCase.scenario);

            % Day window.
            testCase.verifyEqual(meta.maxDay, scenarioCase.maxDay);
            testCase.verifyLessThanOrEqual(max(S.day), scenarioCase.maxDay);

            % meta fields derived from the scenario name.
            expectedMergeKey = regexprep(scenarioCase.scenario, '_full$|_d0_10$', '');
            testCase.verifyEqual(meta.mergeKey, expectedMergeKey);
            testCase.verifyEqual(meta.isUnmerged, strcmp(expectedMergeKey, 'unmerged'));

            % Reference (first) category of S.group is Electrode-Box-B2.
            allCats = categories(S.group);
            testCase.verifyEqual(allCats{1}, 'Electrode-Box-B2');

            % Per-group subject counts.
            actualCounts = tScenarioData.localSubjectCountsByGroup(S);
            expectedCounts = scenarioCase.counts;

            testCase.verifyEqual(actualCounts.Count, expectedCounts.Count);
            actualNames = keys(actualCounts);
            for i = 1:numel(actualNames)
                name = actualNames{i};
                testCase.verifyTrue(isKey(expectedCounts, name), ...
                    sprintf('Unexpected group "%s" in scenario "%s".', ...
                    name, scenarioCase.scenario));
                testCase.verifyEqual(actualCounts(name), expectedCounts(name), ...
                    sprintf('Subject count mismatch for group "%s" in scenario "%s".', ...
                    name, scenarioCase.scenario));
            end
        end

        function testMergeB2FullCsvIsWrittenAndReloads(testCase)
            [S, ~] = tdcs_scenario_data('mergeB2_full');

            thisDir = fileparts(mfilename('fullpath'));
            matlabDir = fileparts(thisDir);
            csvFile = fullfile(matlabDir, 'derived', 'mergeB2_full.csv');

            testCase.verifyTrue(isfile(csvFile), ...
                'derived/mergeB2_full.csv was not written.');

            Sreload = readtable(csvFile);
            Sreload.group = categorical(Sreload.group);

            countsOrig = tScenarioData.localSubjectCountsByGroup(S);
            countsReload = tScenarioData.localSubjectCountsByGroup(Sreload);

            testCase.verifyEqual(countsReload.Count, countsOrig.Count);
            namesOrig = keys(countsOrig);
            for i = 1:numel(namesOrig)
                name = namesOrig{i};
                testCase.verifyTrue(isKey(countsReload, name), ...
                    sprintf('Group "%s" missing after CSV reload.', name));
                testCase.verifyEqual(countsReload(name), countsOrig(name), ...
                    sprintf('Subject count mismatch for group "%s" after CSV reload.', name));
            end
        end
    end

    methods (Static)
        function m = localSubjectCountsByGroup(T)
            %LOCALSUBJECTCOUNTSBYGROUP containers.Map group name -> #unique subjects.
            subjGroup = unique(T(:, {'subject', 'group'}));
            group = subjGroup.group;
            if ~iscategorical(group)
                group = categorical(group);
            end
            counts = countcats(group);
            catNames = categories(group);
            m = containers.Map('KeyType', 'char', 'ValueType', 'double');
            for i = 1:numel(catNames)
                m(catNames{i}) = counts(i);
            end
        end
    end
end
