classdef DiameterDetector < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        UIFigure             matlab.ui.Figure
        SaveButton           matlab.ui.control.Button
        VesselsListBox       matlab.ui.control.ListBox
        VesselsListBoxLabel  matlab.ui.control.Label
        UIAxes2              matlab.ui.control.UIAxes
        UIAxes               matlab.ui.control.UIAxes
    end


    properties (Access = public)
        direction
        output_name
        linescans
        dloc
        
        number
        mat
        dt
        
        points_upper
        points_lower
        
        threshold_handle_upper
        threshold_handle_lower
        
        diameter_handle_upper
        diameter_handle_lower
        
        data_table
    end
    
    methods (Access = public)
        
        function update_upper(app)
            points = [app.points_upper.Position];
            x = points(1:2:end);
            y = points(2:2:end);
            
            I = app.data_table.linescan_id == app.linescans.linescan_id(app.number);
            app.data_table.upper_time(I) = x;
            app.data_table.upper_threshold(I) = y;
            
            [vessel_upper, X, Y] = yucca.processing.detect_linescan_diameter.threshold_linescan(app.mat, app.dt, x, y, app.direction, "upper");
            
            if isempty(app.threshold_handle_upper)
                app.threshold_handle_upper = plot(app.UIAxes2, X, Y, "-r");
            else
                app.threshold_handle_upper.YData = Y;
            end
            
            if isempty(app.diameter_handle_upper)
                app.diameter_handle_upper = plot(app.UIAxes, X, vessel_upper, "-r");
            else
                app.diameter_handle_upper.YData = vessel_upper;
            end
            
            
        end
        
        function update_lower(app)
            points = [app.points_lower.Position];
            x = points(1:2:end);
            y = points(2:2:end);
            
            I = app.data_table.linescan_id == app.linescans.linescan_id(app.number);
            app.data_table.lower_time(I) = x;
            app.data_table.lower_threshold(I) = y;
            
            [vessel_lower, X, Y] = yucca.processing.detect_linescan_diameter.threshold_linescan(app.mat, app.dt, x, y, app.direction, "lower");
            
            if isempty(app.threshold_handle_lower)
                app.threshold_handle_lower = plot(app.UIAxes2, X, Y, "-g");
            else
                app.threshold_handle_lower.YData = Y;
            end
            
            if isempty(app.diameter_handle_lower)
                app.diameter_handle_lower = plot(app.UIAxes, X, vessel_lower, "-g");
            else
                app.diameter_handle_lower.YData = vessel_lower;
            end
        end
        
        function setup_vessel(app, number)
            app.number = number;
            app.mat = double(app.linescans.linescan{app.number});
            app.dt = 1 / app.linescans.fs(app.number);
            
            app.threshold_handle_upper = [];
            app.threshold_handle_lower = [];
            app.diameter_handle_upper = [];
            app.diameter_handle_lower = [];
        
            low = prctile(app.mat(:), 5);
            high = prctile(app.mat(:), 95);
            
            %% Setup axes with linescan
            t = (0:size(app.mat,2)-1) * app.dt;
            y = 1:size(app.mat,1);
            cla(app.UIAxes);
            imagesc(app.UIAxes, t, y, app.mat, [low, high]);
            colormap(app.UIAxes, "gray");
            hold(app.UIAxes, "on");
            app.UIAxes.XLim = [0, size(app.mat, 2)*app.dt];
            app.UIAxes.YLim = [0, size(app.mat, 1)];
            
            %% Setup axes with threshold
            cla(app.UIAxes2);
            hold(app.UIAxes2, "on");
            disableDefaultInteractivity(app.UIAxes2)
            app.UIAxes2.XLim = [0, size(app.mat, 2)*app.dt];
            app.UIAxes2.YLim = [low, high];
            
            %% Initiate the draggable points.
            I = app.data_table.linescan_id == app.linescans.linescan_id(app.number);
            x = app.data_table.upper_time(I);
            y = app.data_table.upper_threshold(I);
            app.points_upper = [];
            for i = 1:length(x)
                point_upper = images.roi.Point(app.UIAxes2, ...
                    "Position", [x(i), y(i)], ...
                    "Color", "red");
                
                if isempty(app.points_upper)
                    app.points_upper = point_upper;
                else
                    app.points_upper(i) = point_upper;
                end
            end
            addlistener(app.points_upper, 'MovingROI', @(~,~) app.update_upper());
            
            I = app.data_table.linescan_id == app.linescans.linescan_id(app.number);
            x = app.data_table.lower_time(I);
            y = app.data_table.lower_threshold(I);
            app.points_lower = [];
            for i = 1:length(x)
                point_lower = images.roi.Point(app.UIAxes2, ...
                    "Position", [x(i), y(i)], ...
                    "Color", "green");
                
                if isempty(app.points_lower)
                    app.points_lower = point_lower;
                else
                    app.points_lower(i) = point_lower;
                end
            end
            addlistener(app.points_lower, 'MovingROI', @(~,~) app.update_lower());

            linkaxes([app.UIAxes, app.UIAxes2], "x");
            
            app.update_upper();
            app.update_lower();
        end
    end
    

    % Callbacks that handle component events
    methods (Access = private)

        % Code that executes after component creation
        function startupFcn(app, dloc, linescans, output_name, direction)
            app.linescans = linescans;
            app.direction = string(direction);
            app.output_name = output_name;
            app.dloc = dloc;
            
            app.VesselsListBox.Items = app.linescans.linescan_id;
            
            % Generate the table that will hold all the output data.
            app.data_table = dloc.load_var(output_name,[]);
            if isempty(app.data_table)
                app.data_table = table;
                
                N_linescans = height(linescans);
                N_points = 2;
                % Counter for filling the table.
                row = 1;
                for i = 1:N_linescans
                    % Calculate default threshold values.
                    mat = double(app.linescans.linescan{i});
                    dim = size(mat);
                    low = prctile(mat(:), 5);
                    high = prctile(mat(:), 95);
                    default_threshold = (high - low) / 2 + low;
                    
                    % Calculate the default timepoints.
                    default_time1 = round(dim(2) * [1/20, 19/20]) / app.linescans.fs(i);
                    default_time2 = round(dim(2) * [2/20, 18/20]) / app.linescans.fs(i);
                    
                    warning off
                    for j = 1:N_points
                        app.data_table.linescan_id(row) = app.linescans.linescan_id(i);
                        app.data_table.upper_time(row) = default_time1(j);
                        app.data_table.upper_threshold(row) = default_threshold;
                        app.data_table.lower_time(row) = default_time2(j);
                        app.data_table.lower_threshold(row) = default_threshold;
                        
                        row = row + 1;
                    end
                    warning on
                end
            end
            
            app.setup_vessel(1);
        end

        % Callback function
        function UpperredthresholdSliderValueChanging(app, event)
            
        end

        % Button pushed function: SaveButton
        function SaveButtonPushed(app, event)
            begonia.logging.log(1, "Saving " + app.output_name)
            app.data_table
            app.dloc.save_var(app.output_name, app.data_table);
        end

        % Value changed function: VesselsListBox
        function VesselsListBoxValueChanged(app, event)
            i = find(ismember(app.VesselsListBox.Items, app.VesselsListBox.Value));
            app.setup_vessel(i(1));
        end
    end

    % Component initialization
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create UIFigure and hide until all components are created
            app.UIFigure = uifigure('Visible', 'off');
            app.UIFigure.Position = [100 100 1063 704];
            app.UIFigure.Name = 'MATLAB App';

            % Create UIAxes
            app.UIAxes = uiaxes(app.UIFigure);
            xlabel(app.UIAxes, 'Frame')
            ylabel(app.UIAxes, 'Pixels')
            zlabel(app.UIAxes, 'Z')
            app.UIAxes.Position = [1 358 1063 347];

            % Create UIAxes2
            app.UIAxes2 = uiaxes(app.UIFigure);
            xlabel(app.UIAxes2, 'Frame')
            ylabel(app.UIAxes2, 'Threshold')
            zlabel(app.UIAxes2, 'Z')
            app.UIAxes2.Toolbar.Visible = 'off';
            app.UIAxes2.Position = [1 77 1063 282];

            % Create VesselsListBoxLabel
            app.VesselsListBoxLabel = uilabel(app.UIFigure);
            app.VesselsListBoxLabel.HorizontalAlignment = 'right';
            app.VesselsListBoxLabel.Position = [15 41 46 22];
            app.VesselsListBoxLabel.Text = 'Vessels';

            % Create VesselsListBox
            app.VesselsListBox = uilistbox(app.UIFigure);
            app.VesselsListBox.Items = {'Item 1', 'Item 2'};
            app.VesselsListBox.ValueChangedFcn = createCallbackFcn(app, @VesselsListBoxValueChanged, true);
            app.VesselsListBox.Position = [76 13 391 52];

            % Create SaveButton
            app.SaveButton = uibutton(app.UIFigure, 'push');
            app.SaveButton.ButtonPushedFcn = createCallbackFcn(app, @SaveButtonPushed, true);
            app.SaveButton.Position = [522 41 100 22];
            app.SaveButton.Text = 'Save';

            % Show the figure after all components are created
            app.UIFigure.Visible = 'on';
        end
    end

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = DiameterDetector(varargin)

            % Create UIFigure and components
            createComponents(app)

            % Register the app with App Designer
            registerApp(app, app.UIFigure)

            % Execute the startup function
            runStartupFcn(app, @(app)startupFcn(app, varargin{:}))

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.UIFigure)
        end
    end
end