classdef dataviewer < handle
    %DATAVIEWER This organizes our data
    %   This system links our relevant data to the proper axes. The system
    %   will use the function adddata which will both add the data to the
    %   "data" object, removing error signals and giving each signal a
    %   relevant time point (i.e. {{time0, signal0} {time0, signal1}
    %   /*error, remove*/ {time0, signal3}...} etc
    %   
    %   By default, the "plotteddata" will average each 3 channels of the
    %   sensors. This is 
    
    properties
        axes
        % data shows our full dataset, all 24 (!) channels, no errors rm
        data
        % plotteddata shows the averaged (modified) channels.
        plotteddata
        plottedchannels
        currenttime
        plots
        interface
        
        ui
        queueddata
        reset
        temporaryplots
        singleplots
        singlelines
        
        recalculating
    end
    
    methods
        function obj = dataviewer(axes, interface)
            obj.interface = interface;
            obj.axes = axes;
            obj.data = zeros(5000, 12);
            obj.plotteddata = cell(2, 4);
            for i = 1:size(obj.plotteddata, 2)
                obj.plotteddata{1, i} = 0; % Current time index
                obj.plotteddata{2, i} = zeros(5000, 1); % Ys
            end
            obj.currenttime = 0;
            
            obj.plottedchannels = cell(2, 4);
            for i = 1:4
                % Average each three of the latter four channels
                obj.plottedchannels{1, i} = [1 + (i-1).*3
                    2 + (i-1).*3 
                    3 + (i-1).*3];
                % Subtract no channels
                obj.plottedchannels{2, i} = [];
            end
            
            % !!!! This is now specialized for the case where we
            % average each of the last four channels. In a more
            % sophisticated scenario, we will allow the user to change
            % this setting (such as only displaying the selected
            % channels or averaging and subtracting a reference channel
            obj.plots = [];
            xdummy = 0;
            ydummy = 0;
            obj.plots = [obj.plots plot(obj.axes, xdummy, ydummy, 'Color', 'Blue', 'DisplayName', 'Sensor 1')];
            set(obj.axes, 'NextPlot', 'add');
            setappdata(obj.axes,'PlotHoldStyle',true);
            obj.plots = [obj.plots plot(obj.axes, xdummy, ydummy, 'Color', 'Red', 'DisplayName', 'Sensor 2')];
            obj.plots = [obj.plots plot(obj.axes, xdummy, ydummy, 'Color', 'Green', 'DisplayName', 'Sensor 3')];
            obj.plots = [obj.plots plot(obj.axes, xdummy, ydummy, 'Color', 'Cyan', 'DisplayName', 'Sensor 4')];
            
            obj.singleplots = zeros(1, 12);
            
            obj.recalculating = 0;
        end
        
        function startsingles(obj)
            for i = 1:length(obj.plots)
                set(obj.plots(i), 'Visible', 'Off');
            end
        end
        
        function stopsingles(obj)
            try
                delete(obj.singleplots(obj.singleplots ~= 0));
            catch err
            end
            obj.singleplots(obj.singleplots ~= 0) = 0;
            set(obj.plots, 'Visible', 'On');
            obj.interface.checkallplots();
        end
        
        function addsingleplot(obj, channel)
            if obj.singleplots(channel) ~= 0
                try
                    delete(obj.singleplots(channel));
                catch err
                    % This is just here to ignore InvalidHandle errors
                end
            end
            obj.singleplots(channel) = plot(obj.axes, obj.data(1:obj.currenttime, channel));
        end
        
        function removesingleplot(obj, channel)
            if obj.singleplots(channel) ~= 0
                try
                    delete(obj.singleplots(channel));
                catch err
                    % This is just here to ignore InvalidHandle errors
                end
            end
            obj.singleplots(channel) = 0;
        end
        % Goes back to no data for either plots or the other data obj
        function resetdata(obj)
            obj.data = zeros(5000, 12);
            obj.plotteddata = cell(2, 4);
            for i = 1:size(obj.plotteddata, 2)
                obj.plotteddata{1, i} = 0; % Current time index
                obj.plotteddata{2, i} = zeros(5000, 1); % Ys
            end
            obj.currenttime = 0;
        end
        
        function updatetemporaryplot(obj, plot, channel, type)
            if plot == 5
                switch type
                    case 'On'
                        obj.addsingleplot(channel);
                    case 'Off'
                        obj.removesingleplot(channel);
                end
                % Now we want to 
            else
                % When a button is pressed, we want to update where the data is
                % stored.
                switch type
                    case 'On'
                        % Remove channel from subchannels for this plot
                        obj.temporaryplots{2, plot}(obj.temporaryplots{2, plot} == channel) = [];
                        % Add channel to avechannels
                        if ~any(obj.temporaryplots{1, plot} == channel)
                            obj.temporaryplots{1, plot} = [obj.temporaryplots{1, plot}; channel];
                        end
                    case 'Off'
                        % Remove channel from avechannels and subchannels
                        obj.temporaryplots{1, plot}(obj.temporaryplots{1, plot} == channel) = [];
                        obj.temporaryplots{2, plot}(obj.temporaryplots{2, plot} == channel) = [];
                    case 'Subtract'
                        % Remove channel from avechannels for this plot
                        obj.temporaryplots{1, plot}(obj.temporaryplots{1, plot} == channel) = [];
                        % Add channel to subchannels
                        if ~any(obj.temporaryplots{2, plot} == channel)
                            obj.temporaryplots{2, plot} = [obj.temporaryplots{2, plot}; channel];
                        end
                end
            end
        end
        
        
        function updatecurrentplots(obj)
            % For plots 1 through 4, we will set the respective
            % "plotteddata" back to the temporary plots. We will also set
            % the current time to zero for each plot and clear data.
            changedplots = zeros(1, size(obj.plottedchannels, 2));
            for i = 1:size(obj.plottedchannels, 2)
                obj.temporaryplots{2, i} = sort(obj.temporaryplots{2, i}, 1);
                obj.plottedchannels{2, i} = sort(obj.plottedchannels{2, i}, 1);
                obj.temporaryplots{1, i} = sort(obj.temporaryplots{1, i}, 1);
                obj.plottedchannels{1, i} = sort(obj.plottedchannels{1, i}, 1);
                
                if  length(obj.plottedchannels{1, i}) ~= length(obj.temporaryplots{1, i}) ...
                        || length(obj.plottedchannels{2, i}) ~= length(obj.temporaryplots{2, i}) ...
                        || xor(isempty(obj.plottedchannels{1, i}), isempty(obj.temporaryplots{1, i})) ...
                        || xor(isempty(obj.plottedchannels{2, i}), isempty(obj.temporaryplots{2, i})) ...
                        || ~all(obj.plottedchannels{1, i} == obj.temporaryplots{1, i})...
                        || ~all(obj.plottedchannels{2, i} == obj.temporaryplots{2, i})
                    changedplots(i) = 1;
                    obj.plotteddata{1, i} = 0;
                else
                    changedplots(i) = 0;
                end 
            end
            obj.plottedchannels = obj.temporaryplots;
            x = 1:length(changedplots);
            x = x(changedplots == 1);
            m = obj.currenttime;
            obj.recalculating = 1;
            if ~isempty(x)
                for i = 1:obj.currenttime
                    for plot = 1:length(x)
                        obj.calcplotat(x(plot), i);
                    end
                    % obj.addlinetoplots(obj.data(i, :));
                end
            end
            if obj.currenttime > m % We've added more lines since we started running this calculation
                m = m+1; % This was our last time
                for i = m:obj.currenttime
                    obj.calclineat(i)
                end
            end
            obj.recalculating = 0;
            obj.updatefigure();
            
            % If we're set on five we will create new plots for each bit of
            % data we're running and store them in "Individual Plots". Once
            % we're done with individual plots, we will delete all of these
            % and set the original plots back to "Visible."
        end
        
        
        function show(obj)
            obj.temporaryplots = obj.plottedchannels;
            if isempty(obj.ui)
                obj.ui = datacontroller();
                set(obj.ui, 'UserData', obj);
            else
                set(obj.ui, 'Visible', 'On');
            end
            handles = guidata(obj.ui);
            obj.setguibuttons(handles, 1);
            set(handles.plotlist, 'Value', 1);
        end
        
        function setguibuttons(obj, handles, plot)
            persistent lastplot;
            if plot == 5
                obj.hideplot(1);
                obj.hideplot(2);
                obj.hideplot(3);
                obj.hideplot(4);
                obj.interface.uncheckallplots();
                avechannels = obj.singleplots;
                subchannels = [];
            else
                avechannels = obj.temporaryplots{1, plot};
                subchannels = obj.temporaryplots{2, plot};
                if lastplot == 5
                    obj.stopsingles();
                end
            end
            handles.setboxes(handles, avechannels, subchannels);
            lastplot = plot;
        end
        
        function adddatablock(obj, data)
            if size(data, 1) == 12
                data(data > 10000) = NaN;
                if obj.currenttime + size(data, 2) < size(obj.data, 2)
                    obj.data(obj.currenttime + 1:obj.currenttime + size(data, 2) , :) = flipdim(rot90(data, -1), 2);
                else
                    obj.data = vertcat(obj.data, zeros(size(data, 2) + 500, 12));
                    obj.data(obj.currenttime + 1:obj.currenttime + size(data, 2) , :) = flipdim(rot90(data, -1), 2);
                end
            elseif size(data, 2) == 12
                data(data > 10000) = NaN;
                if obj.currenttime + size(data, 2) < size(obj.data, 2)
                    obj.data(obj.currenttime + 1:obj.currenttime + size(data, 2) , :) = data;
                else
                    obj.data = vertcat(obj.data, zeros(size(data, 2) + 500, 12));
                    obj.data(obj.currenttime + 1:obj.currenttime + size(data, 2) , :) = data;
                end
            else
                return %We can't add odd shaped data!
            end
            if ~obj.recalculating
                for i = obj.currenttime+1:obj.currenttime + size(data, 2)
                    calclineat(obj, i);
                end
                obj.updatefigure();
            end
            obj.currenttime = obj.currenttime + size(data, 2);
        end
        
        % Data should come in the form of a row of 12 digits.
        function adddata(obj, data)
            if length(data) == 12
                data(data > 10000) = NaN;
                m = 1:12;
                m = m(isnan(data));
                if ~isempty(m)
                    chstring = '';
                    for k = 1:4
                        km = m(floor((m-1)/3)==k-1) - 3*(k-1);
                        if ~isempty(km)
                            chstring = [chstring ' S:' num2str(k) 'Ch:' num2str(km)];
                        end
                    end
                    obj.interface.sendwarning(['Bad Channels:' chstring], 1);
                else 
                    obj.interface.sendwarning('All Channels Reading.', 1);
                end
                % Add each data point to the obj.data
                obj.currenttime = obj.currenttime + 1;
                if obj.currenttime < length(obj.data);
                    obj.data(obj.currenttime, :) = data;
                else
                    obj.data = vertcat(obj.data, zeros(5000, 12));
                    obj.data(obj.currenttime, :) = data;
                end
            end
            if ~obj.recalculating
                calclineat(obj, obj.currenttime);
                obj.updatefigure();
            else
                disp('Blocked!');
            end
        end
        
        function calclineat(obj, time)
            % Average each three points and add them to "plotteddata"
            % at the time index
            % Don't average error signals
            for i = 1:size(obj.plottedchannels, 2)
                obj.calcplotat(i, time);
            end
        end
        
        
        function calcplotat(obj, i, time)
            line = obj.data(time, :);
            averagevals = line(obj.plottedchannels{1, i});
            averagevals(isnan(averagevals)) = [];
            subtractvals = line(obj.plottedchannels{2, i});
            subtractvals(isnan(subtractvals)) = [];
            
            % If "subtractvals" is empty, mean([]) returns NaN, which
            % won't get plotted. However, if "averagevalues" is real,
            % we don't want to subtract NaN and turn our real value
            % into nothing.
            % On the other hand, if "averagevals" is empty, we *DO*
            % want this to return NaN so it doesn't get plotted by the
            % system.
            if isempty(subtractvals)
                vals = mean(averagevals);
            else
                vals = mean(averagevals) - mean(subtractvals);
            end
            % We store an extra value to preallocate the array
            % and keep track of the current index.
            % With plotteddata, set the first index of the cell to the
            % number of plotted data points.
            obj.plotteddata{1, i} = obj.plotteddata{1, i} + 1;
            if obj.plotteddata{1, i} >= size(obj.plotteddata{2, i}, 1)
                % Expand our datafield if necessary
                obj.plotteddata{2, i} = vertcat(obj.plotteddata{2, i}, zeros(5000, 1));
            end
            % Add to the second index of the cell the time and
            % data
            obj.plotteddata{2, i}(obj.plotteddata{1, i}, 1) = vals;
            % Update our plots.
            % set(obj.plots(i), 'XData', obj.plotteddata{2, i}(1:obj.plotteddata{1, i}, 1));
        end
        
        function updatefigure(obj)
            for i = 1:length(obj.plots)
                set(obj.plots(i), 'XData', 1:obj.plotteddata{1, i});
                set(obj.plots(i), 'YData', obj.plotteddata{2, i}(1:obj.plotteddata{1, i}, 1));
            end
            
            i = 1:12;
            for h = i(obj.singleplots ~= 0)
                set(obj.singleplots(h), 'XData', 1:obj.currenttime);
                set(obj.singleplots(h), 'YData', obj.data(1:obj.currenttime, h));
            end
        end
                
        
        function showplot(obj, plot)
            set(obj.plots(plot), 'Visible', 'On');
        end
        
        function hideplot(obj, plot)
            set(obj.plots(plot), 'Visible', 'Off');
        end
        
        function delete(obj)
            try
                delete(obj.ui);
            catch err
            end
        end
    end
end

