classdef Extractor
    %UNTITLED Summary of this class goes here
    %   Detailed explanation goes here

    properties
        filename
        saveDirectory
        fhand
        fs
        times
    end

    %% Utility Methods
    methods
        function load_CED_libs(~, path)
            if nargin < 2
                path = "C:\CEDMATLAB\CEDS64ML";
            end
%             if isempty(getenv('CEDS64ML'))
%                 setenv('CEDS64ML', path);
%             end
            setenv('CEDS64ML', path);
            cedpath = getenv('CEDS64ML');
            addpath(cedpath);
            CEDS64LoadLib(cedpath);
        end
        
        function result = removeCloseElements(~, arr, threshold)
            diffArr = diff(arr);  % Calculate the differences between consecutive elements
            closeIndices = abs(diffArr) < threshold;  % Find indices where the difference is less than the threshold
            result = arr([true; ~closeIndices]);  % Include the first element and elements where the difference is not close
        end

    end

    %% Object Methods
    methods
        function obj = Extractor(filename)
            obj.load_CED_libs();
            obj.filename = filename;
            obj.fhand = CEDS64Open(filename, 1);
            if (obj.fhand <= 0); CEDS64ErrorMessage(obj.fhand); unloadlibrary ceds64int; return; end
            obj.fs = 1.0/(CEDS64ChanDiv(obj.fhand, 1)*CEDS64TimeBase(obj.fhand));
        end

        function [output, times] = getAverage(obj, event_channel, wave_channel, timePairs, duration, compDur, minPobs, maxEvents)
            %METHOD1 Summary of this method goes here
            %   Detailed explanation goes here
            nduration = floor(obj.fs*duration);
            compLength = floor(obj.fs*compDur);
            nPairs = size(timePairs, 1);
            events = [];
            for n = 1: 1: nPairs
                nstart = CEDS64SecsToTicks(obj.fhand, timePairs(n, 1));
                nend = CEDS64SecsToTicks(obj.fhand, timePairs(n, 2));
                [obj, e] = obj.get_events(event_channel, nstart, nend, maxEvents);
                events = cat(1, events, e);
            end
            events = unique(events);
            events = events(1:maxEvents);
            [obj, data] = obj.get_wave(wave_channel, nduration, events);
            if gpuDeviceCount > 0
                subsetIndices = obj.getOptimalAverageGPU(data, compLength, minPobs);
            else
                subsetIndices = obj.getOptimalAverageCPU(data, compLength, minPobs);
            end
            fprintf("Total observations = %d\n", size(data, 1));
            fprintf("Averaged observations = %d\n", length(subsetIndices));
            output = mean(data(subsetIndices, :), 1);
            times = linspace(0, duration, nduration);
        end

        function obj = lock(obj)
            obj.fhand = CEDS64Open(obj.filename, 1);
            if (obj.fhand <= 0); CEDS64ErrorMessage(obj.fhand); unloadlibrary ceds64int; return; end
        end

        function obj = unlock(obj)
            CEDS64Close(obj.fhand);
        end

        function [obj, events] = get_events(obj, channel, nstart, nend, nevents)
            obj = obj.lock();

            [~, events] = CEDS64ReadEvents(obj.fhand, channel, nevents, nstart, nend);
            events = obj.removeCloseElements(events, CEDS64SecsToTicks(2e-3));
            obj = obj.unlock();
        end

        function [obj, data] = get_wave(obj, channel, nduration, events)
            obj = obj.lock();
            nevents = size(events, 1);
            data = zeros(nevents, nduration);
            for i = 1: nevents
                [~, data(i, :), ~] = CEDS64ReadWaveF(obj.fhand, channel, nduration, events(i));
            end
            obj = obj.unlock();
        end

        function subsetIndices = getOptimalAverageCPU(~, data, compLength, minPobs)
            cpuMatrix = data(:, 1:compLength);
            minNobs = floor(minPobs*size(data, 1));
            nobs = size(cpuMatrix, 1);
            binary = dec2bin(0:2^nobs-1, nobs);
            nobsBinary = sum(binary == '1', 2);
            binary = binary(nobsBinary >= minNobs, :);
            varSubsets = zeros(size(binary, 1), 1) + Inf;
            for i = 1: 1: size(binary, 1)
                indices = find(binary(i, :) == '1');
                if ~isempty(indices)
                    varSubsets(i, :) = sum(var(cpuMatrix(indices, :), 0, 1), 2);
                end
            end
            varSubsets = gather(varSubsets);
            [~, minVarIndex] = min(varSubsets);
            subsetIndices = find(binary(minVarIndex, :) == '1');
        end

        function subsetIndices = getOptimalAverageGPU(~, data, compLength, minPobs)
            gpuMatrix = gpuArray(data(:, 1:compLength));
            minNobs = floor(minPobs*size(data, 1));
            nobs = size(gpuMatrix, 1);
            binary = dec2bin(0:2^nobs-1, nobs);
            nobsBinary = sum(binary == '1', 2);
            binary = binary(nobsBinary >= minNobs, :);
            varSubsets = gpuArray(zeros(size(binary, 1), 1) + Inf);
            for i = 1: 1: size(binary, 1)
                indices = find(binary(i, :) == '1');
                if ~isempty(indices)
                    varSubsets(i, :) = sum(var(gpuMatrix(indices, :), 0, 1), 2);
                end
            end
            varSubsets = gather(varSubsets);
            [~, minVarIndex] = min(varSubsets);
            subsetIndices = find(binary(minVarIndex, :) == '1');
        end
    end
end