classdef MarkTimePoints < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        UIFigure  matlab.ui.Figure
        ListBox   matlab.ui.control.ListBox
        UIAxes    matlab.ui.control.UIAxes
    end

    
    properties (Access = public)
        dnode
        out_varname
        time_series
        t0_name
        static_episodes
        
        time_points
        
        time_series_line
        t0_lines
        static_rectangles
        trace_green
        
        mouse_down = false
        selected_time_point = ""
    end
    
    methods
        
        function add_time_point(app,t0)
            t0_id = string(begonia.util.make_uuid());
            ts_id = string(app.ListBox.Value);
            
            new_time_point = table(t0, t0_id, ts_id);
            if isempty(app.time_points)
                app.time_points = new_time_point;
            else
                app.time_points = cat(1,app.time_points,new_time_point);
            end
            
            app.plot_time_points();
        end
        
        function load_plot(app)
            cla(app.UIAxes);
            hold(app.UIAxes,'on');
            
            % Plot episodes.
            i = app.ListBox.Value == app.time_series.ts_id;
            box_size = [min(app.time_series.y{i}), max(app.time_series.y{i})];
            h = iris.episodes.plot_episodes(app.static_episodes, app.UIAxes, box_size);
            legend(h, "AutoUpdate", "Off");
            
            % Plot selected time series.
            app.time_series_line = iris.time_series.plot_time_series(app.time_series(i,:), app.UIAxes);
            
            % Plot time points.
            app.plot_time_points();
        end
        
        function save(app)
            if isempty(app.time_points)
                begonia.logging.log(1,"Clearing episodes")
                app.dnode.clear_var('dilation_time_points');
            else
                begonia.logging.log(1,"Saving episodes")
                time_points = app.time_points;
                [~,I] = sort(time_points.t0);
                time_points = time_points(I,:);
                disp(time_points);
                app.dnode.save_var(app.out_varname, time_points);
            end
        end
        
        function plot_time_points(app)
            % Find the y-extent of the trace.
            i = app.ListBox.Value == app.time_series.ts_id;
            minmax = [min(app.time_series.y{i}), max(app.time_series.y{i})];
            
            % Only select time points to the chosen time series.
            if isempty(app.time_points)
                time_points = table;
            else
                time_points = app.time_points(app.ListBox.Value == app.time_points.ts_id,:);
            end
            
            % Make the number of t0_lines match the number of time_points.
            if length(app.t0_lines) > height(app.time_points)
                delete(app.t0_lines(height(app.time_points)+1:end))
            end
            
            % Remove invalid t0_lines from the list.
            if ~isempty(app.t0_lines)
                app.t0_lines(~isvalid(app.t0_lines)) = [];
            end
            
            % Make a line for each time_point.
            for i = 1:height(time_points)
                x = time_points.t0(i);
                
                if isempty(app.t0_lines)
                    app.t0_lines = plot(app.UIAxes, [x,x], minmax, ...
                        "Color", "black", ...
                        "LineWidth", 1, ...
                        "LineStyle","--");
                    app.move_trace_top();
                elseif length(app.t0_lines) < i
                    app.t0_lines(end+1) = plot(app.UIAxes, [x,x], minmax, ...
                        "Color", "black", ...
                        "LineWidth", 1, ...
                        "LineStyle","--");
                    app.move_trace_top();
                else
                    app.t0_lines(i).XData = [x,x];
                    app.t0_lines(i).YData = minmax;
                end
                
                % Hightlight the selected time point.
                if app.selected_time_point == time_points.t0_id(i)
                    app.t0_lines(i).Color = "red";
                else
                    app.t0_lines(i).Color = "black";
                end
            end
        end
        
        
        function move_trace_top(app)
            % Move red trace in front of the time points / lines.
            if ~isempty(app.UIAxes.Children)
                arr = app.UIAxes.Children;
                i = ismember(arr,app.time_series_line);
                if ~isempty(i)
                    arr(i) = [];
                end
                app.UIAxes.Children = [app.time_series_line;arr];
            end
        end
        
    end
    

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

        % Code that executes after component creation
        function startupFcn(app, dnode, out_varname, time_series, t0_name, static_episodes)
            if nargin < 6
                static_episodes = [];
            end
            
            %% Assign instance variables.
            app.dnode = dnode;
            app.out_varname = out_varname;
            app.time_series = time_series;
            app.time_points = dnode.load_var(out_varname, table);
            app.static_episodes = static_episodes;
            
            app.ListBox.Items = time_series.ts_id;
            app.ListBox.Value = time_series.ts_id(1);
            
            app.load_plot();
        end

        % Window button down function: UIFigure
        function UIFigureWindowButtonDown(app, event)
            app.mouse_down = true;
            
            app.selected_time_point = "";
            
            if isempty(app.time_series_line)
                return;
            end
            
            % Only select time points to the chosen time series.
            if isempty(app.time_points)
                time_points = table;
            else
                time_points = app.time_points(app.ListBox.Value == app.time_points.ts_id,:);
            end
            
            % Select the time point close to the cursor.
            if ~isempty(time_points)
                t0 = time_points.t0;
                x = app.UIAxes.CurrentPoint(1,1);
                
                % Find the closest time_point.
                i = begonia.util.val2idx(t0,x);
                dist = abs(x - t0(i));
                
                % The line must be closer than 2.5% of the view to be selected.
                threshold = 0.025 * diff(app.UIAxes.XLim);
                
                if dist < threshold
                    app.selected_time_point = time_points.t0_id(i);
                end
            end
            
            app.plot_time_points();
        end

        % Window button up function: UIFigure
        function UIFigureWindowButtonUp(app, event)
            app.mouse_down = false;
        end

        % Window button motion function: UIFigure
        function UIFigureWindowButtonMotion(app, event)
            
            % Move the selected line/time_point to the cursor position.
            if app.mouse_down && app.selected_time_point ~= ""
                x = app.UIAxes.CurrentPoint(1,1);
                i = app.selected_time_point == app.time_points.t0_id;
                app.time_points.t0(i) = x;
                app.plot_time_points();
            end
            
        end

        % Key press function: UIFigure
        function UIFigureKeyPress(app, event)
            
            if event.Key == "a"
                x = app.UIAxes.CurrentPoint(1,1);
                app.add_time_point(x);
            elseif event.Key == "d"
                if app.selected_time_point ~= ""
                    I = app.time_points.t0_id == app.selected_time_point;
                    app.time_points(I,:) = [];
                    app.plot_time_points();
                end
            elseif event.Key == "s"
                app.save();
            end
            
        end

        % Value changed function: ListBox
        function ListBoxValueChanged(app, event)
            app.load_plot();
        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 1072 498];
            app.UIFigure.Name = 'MATLAB App';
            app.UIFigure.WindowButtonDownFcn = createCallbackFcn(app, @UIFigureWindowButtonDown, true);
            app.UIFigure.WindowButtonUpFcn = createCallbackFcn(app, @UIFigureWindowButtonUp, true);
            app.UIFigure.WindowButtonMotionFcn = createCallbackFcn(app, @UIFigureWindowButtonMotion, true);
            app.UIFigure.KeyPressFcn = createCallbackFcn(app, @UIFigureKeyPress, true);

            % Create UIAxes
            app.UIAxes = uiaxes(app.UIFigure);
            zlabel(app.UIAxes, 'Z')
            app.UIAxes.Position = [2 109 1071 390];

            % Create ListBox
            app.ListBox = uilistbox(app.UIFigure);
            app.ListBox.ValueChangedFcn = createCallbackFcn(app, @ListBoxValueChanged, true);
            app.ListBox.Position = [14 25 1046 74];

            % 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 = MarkTimePoints(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