classdef Manager < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        RootFigure           matlab.ui.Figure
        PlaybackPanel        matlab.ui.container.Panel
        FrameSliderLabel     matlab.ui.control.Label
        frame_slider         matlab.ui.control.Slider
        play_button          matlab.ui.control.Button
        step_back_button     matlab.ui.control.Button
        step_forward_button  matlab.ui.control.Button
        time_label           matlab.ui.control.Label
        StepLabel            matlab.ui.control.Label
        step_input           matlab.ui.control.NumericEditField
        GotoframeLabel       matlab.ui.control.Label
        goto_input           matlab.ui.control.NumericEditField
        guide_button         matlab.ui.control.Button
        new_view_button      matlab.ui.control.Button
        raise_button         matlab.ui.control.Button
        time_label_frame     matlab.ui.control.Label
        time_label_total     matlab.ui.control.Label
        save_image_button    matlab.ui.control.Button
        save_image_button_2  matlab.ui.control.Button
        ZLabel               matlab.ui.control.Label
        frame_slider_2       matlab.ui.control.Slider
        channel_button       matlab.ui.control.Button
        rois_button          matlab.ui.control.Button
        FPSLabel             matlab.ui.control.Label
        fps_input            matlab.ui.control.NumericEditField
        fps_achieved_label   matlab.ui.control.Label
        reset_fps_button     matlab.ui.control.Button
        guide_button_2       matlab.ui.control.Button
        fps_2x_button        matlab.ui.control.Button
        fps_4x_button        matlab.ui.control.Button
        roas_button          matlab.ui.control.Button
    end

    
    properties (Access = private)
        manager % Description
        application % Description
        
        frame_change_event
    end
    
    methods (Access = private)
        
        function handle_frame_changed(app)
            frame = app.manager.data.read("current_frame", 1);
            frame_time = app.manager.data.read("current_time", 0);
            
            %prev = app.frame_slider.Value;
            app.frame_slider.Value = frame;
            %disp("Manager: Handling frame change : " + frame + " / " + app.frame_slider.Value + " vs. " + prev);
            
            dur = seconds(frame_time);
            tot = seconds(app.manager.frame_times(end));
            
            fmt = "HH:MM:SS.FFF";
            dur_str = datestr(dur, fmt);
            tot_str = datestr(tot, fmt);
            
            app.time_label.Text = dur_str;
            app.time_label_frame.Text = string(frame);
            app.time_label_total.Text = "/" + tot_str;
            
            app.RootFigure.Name = app.manager.name;
            
            % update FPS achieved:
            app.fps_achieved_label.Text = app.manager.data.read("fps_achieved", "--") + " fps (actual)";
        end
        
        function handle_view_changed(app, src, ev)
            disp("Manager: handle_view_changed");
            
            % when the view manaer hanges, we need to delete old listeners,
            % and re-subscribe to events + update the user interface:
            app.manager = ev.viewmanager;
            delete(app.frame_change_event)
            if isempty(app.manager)
                return;
            end
            
            % clear dependency to old manager:
            
                  
            % alter autoplay status:
            if app.manager.autoplay
                app.play_button.Text = "Stop";
            else
                app.play_button.Text = "Play";
            end
            
            % change to new limits:
            app.frame_slider.Limits = [1 app.manager.frame_count];
            
            % we want to react to frame changes + do one initial 
            % FIXME: make this work on the .data prop instead
            app.frame_change_event = addlistener(app.manager, "on_frame_changed"... 
                , @(~,~) app.handle_frame_changed());
            
            app.handle_frame_changed();
            
            % setup flag for mode change (will overwrite existing):
            app.manager.data.assign_changeflag("mode", "manager_redraw_flagged");
            fps = app.manager.data.read("fps_default", 10);
            app.fps_input.Value = fps;
        end
  
    end
    

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

        % Code that executes after component creation
        function startupFcn(app, application)
            app.application = application;
            
            % when the current view manager changes, the tool should adapt
            % to the new data (potentially by hiding itself):
            addlistener(application, "on_view_changed", ...
                @(src, ev) app.handle_view_changed(src, ev))
        end

        % Button pushed function: play_button
        function handle_play_button(app, event)
            playing = app.play_button.Text == "Stop";
            if playing
                app.play_button.Text = "Play";
                app.manager.autoplay_end();
            else
                app.play_button.Text = "Stop";
                fps = app.fps_input.Value;
                app.manager.autoplay_start(fps);
            end
        end

        % Value changing function: frame_slider
        function handle_frame_slider_changing(app, event)
            frame = ceil(event.Value);            
            app.manager.goto(frame);
            app.manager.playhead_to_frame();
        end

        % Value changed function: goto_input
        function handle_goto(app, event)
            frame = app.goto_input.Value;
            max_frames = app.manager.frame_count;
            if frame > max_frames
                beep();
                frame = max_frames;
                app.goto_input.Value = frame;
            end
            
            app.manager.goto(frame);
            app.manager.playhead_to_frame();
        end

        % Button pushed function: step_forward_button
        function handle_forward(app, event)
            frame = app.manager.current_frame;
            frame = frame + app.step_input.Value;
            if frame > app.manager.frame_count
                frame = frame - app.manager.frame_count;
                app.manager.data.write("message", "LOOPED TO START");
            end
            app.manager.goto(frame);
            app.manager.playhead_to_frame();
        end

        % Button pushed function: step_back_button
        function handle_backwards(app, event)
            frame = app.manager.current_frame;
            frame = frame - app.step_input.Value;
            if frame < 1
                frame = app.manager.frame_count + frame;
                app.manager.data.write("message", "LOOPED TO END");
            end
            app.manager.goto(frame);
            app.manager.playhead_to_frame();
        end

        % Button pushed function: new_view_button
        function handle_new_view(app, event)
            ch2 = roiman.modules.Channel("Astrocyte (Ch2)", 1);
            app.manager.new_view("New view", [ch2]);
        end

        % Button pushed function: save_image_button
        function handle_save_image(app, event)
            fig = app.manager.current_view.figure();
            image = getframe(fig);
            [file, path] = uiputfile(".png");
            imwrite(image.cdata, fullfile(path, file));
        end

        % Button pushed function: raise_button
        function handle_raise_windows(app, event)
            app.application.raise_all_windows();
        end

        % Button pushed function: guide_button
        function handle_guide_button(app, event)
            app.application.open_tool("guide");
        end

        % Close request function: RootFigure
        function handle_close(app, event)
            answer = questdlg("Closing the playback manager will close the program. Do you wish to continue?", "Close and quit?","Quit", "Cancel", "Cancel");
            if answer ~= "Quit"
                return;
            end
            
            if isvalid(app.application)
                app.application.quit();
            else
                delete(app);
            end
        end

        % Button pushed function: channel_button
        function handle_channel_button(app, event)
            app.application.open_tool("channel");
        end

        % Button pushed function: rois_button
        function handle_roi_button(app, event)
            app.application.open_tool("rois");
        end

        % Value changed function: fps_input
        function handle_fps_set(app, event)
            fps = app.fps_input.Value;
            app.manager.autoplay_set_fps(fps);
        end

        % Button pushed function: reset_fps_button
        function handle_reset_fps(app, event)
            fps = app.manager.data.read("fps_default", 10);
            app.fps_input.Value = fps;
            app.manager.autoplay_set_fps(fps);
        end

        % Button pushed function: fps_2x_button
        function handle_fps_2x(app, event)
            fps = 2 * app.manager.data.read("fps_default", 10);
            app.fps_input.Value = fps;
            app.manager.autoplay_set_fps(fps);
        end

        % Button pushed function: fps_4x_button
        function handle_fps_6x(app, event)
            fps = 6 * app.manager.data.read("fps_default", 10);
            app.fps_input.Value = fps;
            app.manager.autoplay_set_fps(fps);
        end

        % Button pushed function: roas_button
        function handle_roa_button(app, event)
            app.application.open_tool("roas");
        end
    end

    % Component initialization
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create RootFigure and hide until all components are created
            app.RootFigure = uifigure('Visible', 'off');
            app.RootFigure.AutoResizeChildren = 'off';
            app.RootFigure.Position = [100 100 665 130];
            app.RootFigure.Name = '<TSeries name>';
            app.RootFigure.Resize = 'off';
            app.RootFigure.CloseRequestFcn = createCallbackFcn(app, @handle_close, true);

            % Create PlaybackPanel
            app.PlaybackPanel = uipanel(app.RootFigure);
            app.PlaybackPanel.AutoResizeChildren = 'off';
            app.PlaybackPanel.BorderType = 'none';
            app.PlaybackPanel.Title = 'Playback';
            app.PlaybackPanel.Position = [1 1 665 130];

            % Create FrameSliderLabel
            app.FrameSliderLabel = uilabel(app.PlaybackPanel);
            app.FrameSliderLabel.HorizontalAlignment = 'right';
            app.FrameSliderLabel.Position = [133 73 40 22];
            app.FrameSliderLabel.Text = 'Frame';

            % Create frame_slider
            app.frame_slider = uislider(app.PlaybackPanel);
            app.frame_slider.MajorTicks = [];
            app.frame_slider.ValueChangingFcn = createCallbackFcn(app, @handle_frame_slider_changing, true);
            app.frame_slider.Position = [187 83 297 3];

            % Create play_button
            app.play_button = uibutton(app.PlaybackPanel, 'push');
            app.play_button.ButtonPushedFcn = createCallbackFcn(app, @handle_play_button, true);
            app.play_button.FontWeight = 'bold';
            app.play_button.Position = [601 66 59 38];
            app.play_button.Text = 'Play';

            % Create step_back_button
            app.step_back_button = uibutton(app.PlaybackPanel, 'push');
            app.step_back_button.ButtonPushedFcn = createCallbackFcn(app, @handle_backwards, true);
            app.step_back_button.Position = [500 66 46 38];
            app.step_back_button.Text = '<';

            % Create step_forward_button
            app.step_forward_button = uibutton(app.PlaybackPanel, 'push');
            app.step_forward_button.ButtonPushedFcn = createCallbackFcn(app, @handle_forward, true);
            app.step_forward_button.Position = [545 66 46 38];
            app.step_forward_button.Text = '>';

            % Create time_label
            app.time_label = uilabel(app.PlaybackPanel);
            app.time_label.FontSize = 20;
            app.time_label.FontWeight = 'bold';
            app.time_label.Position = [9 71 124 24];
            app.time_label.Text = '00:00:00.000';

            % Create StepLabel
            app.StepLabel = uilabel(app.PlaybackPanel);
            app.StepLabel.HorizontalAlignment = 'right';
            app.StepLabel.FontSize = 10;
            app.StepLabel.Position = [598 10 29 22];
            app.StepLabel.Text = 'Step:';

            % Create step_input
            app.step_input = uieditfield(app.PlaybackPanel, 'numeric');
            app.step_input.FontSize = 10;
            app.step_input.Position = [630 9 29 22];
            app.step_input.Value = 10;

            % Create GotoframeLabel
            app.GotoframeLabel = uilabel(app.PlaybackPanel);
            app.GotoframeLabel.HorizontalAlignment = 'right';
            app.GotoframeLabel.FontSize = 10;
            app.GotoframeLabel.Position = [506 10 53 22];
            app.GotoframeLabel.Text = {'Goto frame:'; ''};

            % Create goto_input
            app.goto_input = uieditfield(app.PlaybackPanel, 'numeric');
            app.goto_input.ValueChangedFcn = createCallbackFcn(app, @handle_goto, true);
            app.goto_input.FontSize = 10;
            app.goto_input.Position = [567 10 33 22];
            app.goto_input.Value = 1;

            % Create guide_button
            app.guide_button = uibutton(app.PlaybackPanel, 'push');
            app.guide_button.ButtonPushedFcn = createCallbackFcn(app, @handle_guide_button, true);
            app.guide_button.Tooltip = {'Show roi management tools'};
            app.guide_button.Position = [257 9 53 24];
            app.guide_button.Text = 'Guide';

            % Create new_view_button
            app.new_view_button = uibutton(app.PlaybackPanel, 'push');
            app.new_view_button.ButtonPushedFcn = createCallbackFcn(app, @handle_new_view, true);
            app.new_view_button.Enable = 'off';
            app.new_view_button.Position = [10 9 67 24];
            app.new_view_button.Text = 'New view';

            % Create raise_button
            app.raise_button = uibutton(app.PlaybackPanel, 'push');
            app.raise_button.ButtonPushedFcn = createCallbackFcn(app, @handle_raise_windows, true);
            app.raise_button.Tooltip = {'Raise all windows'};
            app.raise_button.Position = [377 9 35 24];
            app.raise_button.Text = '🔳⬆';

            % Create time_label_frame
            app.time_label_frame = uilabel(app.PlaybackPanel);
            app.time_label_frame.FontSize = 8;
            app.time_label_frame.Position = [10 88 62 22];
            app.time_label_frame.Text = 'HH:MM:SS.FFF';

            % Create time_label_total
            app.time_label_total = uilabel(app.PlaybackPanel);
            app.time_label_total.FontSize = 9;
            app.time_label_total.FontWeight = 'bold';
            app.time_label_total.Position = [10 56 113 22];
            app.time_label_total.Text = 'Total time';

            % Create save_image_button
            app.save_image_button = uibutton(app.PlaybackPanel, 'push');
            app.save_image_button.ButtonPushedFcn = createCallbackFcn(app, @handle_save_image, true);
            app.save_image_button.Tooltip = {'Save image'};
            app.save_image_button.Position = [420 9 33 24];
            app.save_image_button.Text = '📸';

            % Create save_image_button_2
            app.save_image_button_2 = uibutton(app.PlaybackPanel, 'push');
            app.save_image_button_2.Enable = 'off';
            app.save_image_button_2.Tooltip = {'Record video'};
            app.save_image_button_2.Position = [452 9 33 24];
            app.save_image_button_2.Text = '🎥';

            % Create ZLabel
            app.ZLabel = uilabel(app.PlaybackPanel);
            app.ZLabel.HorizontalAlignment = 'right';
            app.ZLabel.Enable = 'off';
            app.ZLabel.Position = [149 48 25 22];
            app.ZLabel.Text = 'Z';

            % Create frame_slider_2
            app.frame_slider_2 = uislider(app.PlaybackPanel);
            app.frame_slider_2.MajorTicks = [];
            app.frame_slider_2.Enable = 'off';
            app.frame_slider_2.Position = [188 58 297 3];

            % Create channel_button
            app.channel_button = uibutton(app.PlaybackPanel, 'push');
            app.channel_button.ButtonPushedFcn = createCallbackFcn(app, @handle_channel_button, true);
            app.channel_button.Tooltip = {'Show roi management tools'};
            app.channel_button.Position = [198 9 60 24];
            app.channel_button.Text = 'Channel';

            % Create rois_button
            app.rois_button = uibutton(app.PlaybackPanel, 'push');
            app.rois_button.ButtonPushedFcn = createCallbackFcn(app, @handle_roi_button, true);
            app.rois_button.Tooltip = {'Show roi management tools'};
            app.rois_button.Position = [95 9 47 24];
            app.rois_button.Text = 'RoIs';

            % Create FPSLabel
            app.FPSLabel = uilabel(app.PlaybackPanel);
            app.FPSLabel.HorizontalAlignment = 'right';
            app.FPSLabel.FontSize = 10;
            app.FPSLabel.Position = [602 37 25 22];
            app.FPSLabel.Text = 'FPS';

            % Create fps_input
            app.fps_input = uieditfield(app.PlaybackPanel, 'numeric');
            app.fps_input.ValueChangedFcn = createCallbackFcn(app, @handle_fps_set, true);
            app.fps_input.FontSize = 10;
            app.fps_input.Position = [630 38 29 22];
            app.fps_input.Value = 30;

            % Create fps_achieved_label
            app.fps_achieved_label = uilabel(app.PlaybackPanel);
            app.fps_achieved_label.FontSize = 9;
            app.fps_achieved_label.FontWeight = 'bold';
            app.fps_achieved_label.Position = [9 44 113 22];
            app.fps_achieved_label.Text = '-- FPS';

            % Create reset_fps_button
            app.reset_fps_button = uibutton(app.PlaybackPanel, 'push');
            app.reset_fps_button.ButtonPushedFcn = createCallbackFcn(app, @handle_reset_fps, true);
            app.reset_fps_button.FontSize = 10;
            app.reset_fps_button.Position = [500 38 46 22];
            app.reset_fps_button.Text = 'Reset';

            % Create guide_button_2
            app.guide_button_2 = uibutton(app.PlaybackPanel, 'push');
            app.guide_button_2.Enable = 'off';
            app.guide_button_2.Tooltip = {'Show roi management tools'};
            app.guide_button_2.Position = [309 9 62 24];
            app.guide_button_2.Text = 'Measure';

            % Create fps_2x_button
            app.fps_2x_button = uibutton(app.PlaybackPanel, 'push');
            app.fps_2x_button.ButtonPushedFcn = createCallbackFcn(app, @handle_fps_2x, true);
            app.fps_2x_button.FontSize = 10;
            app.fps_2x_button.Position = [545 38 28 22];
            app.fps_2x_button.Text = '2X';

            % Create fps_4x_button
            app.fps_4x_button = uibutton(app.PlaybackPanel, 'push');
            app.fps_4x_button.ButtonPushedFcn = createCallbackFcn(app, @handle_fps_6x, true);
            app.fps_4x_button.FontSize = 10;
            app.fps_4x_button.Position = [572 38 28 22];
            app.fps_4x_button.Text = '6X';

            % Create roas_button
            app.roas_button = uibutton(app.PlaybackPanel, 'push');
            app.roas_button.ButtonPushedFcn = createCallbackFcn(app, @handle_roa_button, true);
            app.roas_button.Tooltip = {'Show roi management tools'};
            app.roas_button.Position = [142 9 47 24];
            app.roas_button.Text = 'RoAs';

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

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = Manager(varargin)

            % Create UIFigure and components
            createComponents(app)

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

            % 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.RootFigure)
        end
    end
end