classdef programmanager < handle
    %PROGRAMMANAGER Manages Fluidics Programs
    %   This sends commands to the manage the serial port functions. There
    %   are a couple important flags to note. The "receive" function
    %   activates, that means we've sent a command to the serial port and
    %   we're getting the data back. This is in "commanddata" format, which
    %   is a dynamic function and will contain the relevant properties
    %   depending on the command. See the receive function for more details
    
    % Of note is the "holdmode" functionality. If we are to run the system
    % on "hold," we'll want to construct 
    
    properties
        interface
        programui
        currentprograms
        temporarystep
        swappedstep
        currentphase
        currentsequence
        
        autopause
        
        queuedchanges
        % Boolean items that change how the system processes received
        % commands (e.g. if we just swapped out a "hold" function)
        holdmode
        running
        runningprogram
        
        programloadstate % 0, Not loading, 1-60
        
        resettimer
    end
    
    methods
        function obj = programmanager(intface)
            obj.interface = intface;
            obj.temporarystep = fluidstep('stop', 10, 0, 'AV_NONE');
            obj.swappedstep = [];
            obj.holdmode = 0;
            obj.running = 0;
            obj.programloadstate = 0;
            obj.currentprograms = [program() program() program() program()];
            % obj.queuedchanges{1, 5} = fluidstep means
            % that we should update that particular step when we run the
            % changes (when the sequence is stopped or we switch to
            % holdmode.
            obj.queuedchanges = cell(4, 15);
            obj.resettimer = timer('StartDelay', 3,'TimerFcn',{@resetholdfcn, obj});
        end
        
        % This is called by the callback for the "change fluidics program"
        % funcation in the main program ui. It calls the
        % "loadcurrentprograms" function. This loads each program in. The
        % "programloadstate" is the key variable. As the "receive" function
        % processes returns from the serial port, this state switches when
        % we hit the last one and runs the "runui" function.
        %
        % If we had business where we loadcurrentprograms but *didn't* want
        % to run the ui, we would need additional state. This convoluted
        % process ensures when the UI runs, we display all relevant (and
        % current) data.
        function changeprograms(obj)
            obj.runui();
        end
        
        function updatecurrentstep(obj, vals)
            persistent phasetime; 
            persistent lastphase;
            if isempty(phasetime)
                phasetime = 0;
            end
            if isempty(lastphase)
                lastphase = 7; % Stop
            end
            switch vals(1)
                case 0
                    type = 'Flow';
                case 1
                    type = 'Inject';
                case 2
                    type = 'Flush';
                case 3
                    type = 'Amplify';
                case 4
                    type = 'Regenerate';
                case 5
                    type = 'Pause';
                case 6
                    type = 'Loop';
                case 7
                    type = 'Stop';
                    phasetime = 0;
                    obj.interface.enableholds();
                case 8
                    type = 'Baseline';
                case 9
                    type = 'Eval';
                otherwise
                    type = 'Unknown';
            end
            if obj.running
                if phasetime > vals(2) || lastphase ~= vals(1)
                    % If we've reset the clock, we're on a new phase.
                    % Update the current step and display it in the message
                    % window.
                    obj.currentphase = obj.currentphase + 1;
                    fstep = obj.currentprograms(obj.currentsequence).getstep(obj.currentphase);
                    if ~strcmp(type, 'Stop');
                        obj.interface.sendmessage(['Running ' type ' for ' num2str(vals(3)) ' sec.']);
                    else
                        obj.running = 0;
                        obj.interface.sendmessage('Experiment completed.');
                        obj.makequeuedchanges();
                    end
                    if obj.autopause == 1 && vals(1) == 1 %Inject Step! We should pause.
                        obj.interface.pause(1);
                        obj.interface.sendmessage('Pausing for Injection.');
                    end
                end 
            else
                if phasetime > vals(2) || lastphase ~= vals(1)
                    obj.interface.sendmessage(['Holding at step: ' type '.']);
                end
            end
            lastphase = vals(1);
            phasetime = vals(2);
        end
        
        function runui(obj)
			% We keep track of temporary programs here. If we need to update,
			% we will use the 'UserData' property (link to this handle) to get
			% the requisite programs. The updatetemporary(seq, phase, fluidstep) 
			% is used to update those tempory programs as we browse them in the 
			% viewer. The 'loadstep' function actually writes a specific step to
			% the function.
            if obj.holdmode
                obj.currentprograms(4).addstep(obj.swappedstep, 15);
            end
            if isempty(obj.programui)
                obj.programui = fluidicsprogram(obj);
            else
                set(obj.programui, 'Visible', 'on');
                % We need to update the state of these things.
                handles = guidata(obj.programui);
                handles.resetfcn(handles);
            end
        end
        
        function loadcurrentprograms(obj)
            obj.programloadstate = 1;
            for i = 1:4
                for j = 1:15
                    cd = commanddata();
                    cd.Sequence = i;
                    cd.Phase = j;
                    obj.interface.sendcommand(command(cd, commandtype.readphase, obj));
                end
            end
        end
        
        function updateprogram(obj, program, phase, fs)
            if ~obj.running
                obj.currentprograms(program).addstep(fs, phase);
                cd = commanddata();
                cd.Sequence = program;
                cd.Phase = phase;
                cd = fs.addcommanddata(cd);
                obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
            else
                obj.queuedchanges{program, phase} = fs;
            end
        end
        
        function makequeuedchanges(obj)
            for i = 1:4
                for j = 1:15
                    if ~isempty(obj.queuedchanges{i, j})
                        cd = commanddata();
                        cd.Phase = j;
                        cd.Sequence = i;
                        cd = addcommanddata(obj.queuedchanges{i, j}, cd);
                        obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
                        obj.currentprograms(i).addstep(obj.queuedchanges{i, j}, j);
                    end
                end
            end
        end
        
        function receive(obj, data, cmd)
            if(data.Error)
                %Deal with the error
            elseif cmd.type == commandtype.holdon || cmd.type == commandtype.holdoff || cmd.type == commandtype.writephase
                %This is just a confirmation message
            elseif cmd.type == commandtype.readphase
                % We will read the phase in a few different conditions. One
                % is when we switch to holdmode. In this case, we always
                % collect the last step in the last program from the
                % instrument. This way when we leave hold mode we can
                % switch this step back into the system.
                if obj.holdmode && isempty(obj.swappedstep)
                    obj.swappedstep = fluidstep.setfromcommanddata(data);
                elseif ~isempty(data.Sequence) && data.Sequence >= 0 && data.Sequence <= 4 && ...
                    ~isempty(data.Phase) && data.Phase >= 0 && data.Phase <= 15
                    % Just write it into the current sequences
                    % Write the sequence into the proper program.
                    seq = data.Sequence;
                    ph = data.Phase;
                    fs = fluidstep.setfromcommanddata(data);
                    obj.currentprograms(seq).addstep(fs, ph);
                    if obj.programloadstate > 0
                        obj.programloadstate = obj.programloadstate + 1;
                        if obj.programloadstate == 61
                            obj.programloadstate = 0;
                            if ~isempty(obj.programui)
                                handles = guidata(obj.programui);
                                handles.resetfcn(handles);
                            end
                        end
                    end
                end
            end
        end
        
        function savesequences(obj)
            obj.interface.sendcommand(command(commanddata(), commandtype.writesequences, obj));
        end
        
        function loadsequences(obj)
            obj.interface.sendcommand(command(commanddata(), commandtype.readsequences, obj));
            obj.loadcurrentprograms();
        end
        
        function start(obj, sequence)
            obj.startat(sequence, 1);
        end
        
        function startat(obj, sequence, phase)
            if obj.holdmode
                obj.holdmode = 0;
            end
            obj.interface.disableholds();
            obj.interface.setprogramview(sequence);
            cd = commanddata();
            cd.Sequence = sequence;
            cd.Phase = phase;
            obj.running = 1;
            obj.currentphase = phase;
            obj.currentsequence = sequence;
            obj.interface.sendcommand(command(cd, commandtype.start, obj));
            obj.interface.sendcommand(command(commanddata(), commandtype.holdoff, obj));
        end
        
        function holdflow(obj, pumpspeed)
            % We tell the system to read out the current phase.
            cd = commanddata();
            cd.Sequence = 4;
            cd.Phase = 15;
            if ~obj.holdmode
            % Since we've set 'holdmode' to 1, the next phase read will be
            % set to the "swappedstep" property as long as that isn't
            % filled
                obj.holdmode = 1;
                obj.interface.sendcommand(command(cd, commandtype.readphase, obj));
            end
            % Then tell the system to hold
            obj.interface.sendcommand(command(commanddata(), commandtype.holdon, obj));
            if ~isempty(pumpspeed)
                fs = fluidstep('flow', 10, pumpspeed, 'AV_NONE');
            else
                fs = fluidstep('flow', 10, 10, 'AV_NONE');
            end
            cd = fs.addcommanddata(cd);
            obj.temporarystep = fs;
            obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
            obj.interface.sendcommand(command(cd, commandtype.start, obj));
        end
        
        function holdregen(obj, pumpspeed)
            % We tell the system to read out the current phase.
            cd = commanddata();
            cd.Sequence = 4;
            cd.Phase = 15;
            if ~obj.holdmode
            % Since we've set 'holdmode' to 1, the next phase read will be
            % set to the "swappedstep" property as long as that isn't
            % filled
                obj.holdmode = 1;
                obj.interface.sendcommand(command(cd, commandtype.readphase, obj));
            end
            % Then tell the system to hold
            obj.interface.sendcommand(command(commanddata(), commandtype.holdon, obj));
            if ~isempty(pumpspeed)
                fs = fluidstep('regenerate', 10, pumpspeed, 'AV_NONE');
            else
                fs = fluidstep('regenerate', 10, 10, 'AV_NONE');
            end
            cd = fs.addcommanddata(cd);
            obj.temporarystep = fs;
            obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
            obj.interface.sendcommand(command(cd, commandtype.start, obj));
        end
            
        function holdflush(obj)
            cd = commanddata();
            cd.Sequence = 4;
            cd.Phase = 15;
            if ~obj.holdmode
                obj.holdmode = 1;
                obj.interface.sendcommand(command(cd, commandtype.readphase, obj));
            end
            % Then tell the system to hold
            obj.interface.sendcommand(command(commanddata(), commandtype.holdon, obj));
            fs = fluidstep('flush', 10, 0, 'AV_AIR');
            cd = fs.addcommanddata(cd);
            obj.temporarystep = fs;
            obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
            obj.interface.sendcommand(command(cd, commandtype.start, obj));
        end
        
        function holdpressurize(obj)
            cd = commanddata();
            cd.Sequence = 4;
            cd.Phase = 15;
            if ~obj.holdmode
                obj.holdmode = 1;
                obj.interface.sendcommand(command(cd, commandtype.readphase, obj));
            end
            % Then tell the system to hold
            obj.interface.sendcommand(command(commanddata(), commandtype.holdon, obj));
            fs = fluidstep('flow', 10, 0, 'AV_AIR');
            cd = fs.addcommanddata(cd);
            obj.temporarystep = fs;
            obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
            obj.interface.sendcommand(command(cd, commandtype.start, obj));
        end
        
        function holdcollect(obj)
            cd = commanddata();
            cd.Sequence = 4;
            cd.Phase = 15;
            if ~obj.holdmode
                obj.holdmode = 1;
                obj.interface.sendcommand(command(cd, commandtype.readphase, obj));
            end
            % Then tell the system to hold
            obj.interface.sendcommand(command(commanddata(), commandtype.holdon, obj));
            fs = fluidstep('collect', 10, 0, 'AV_NONE');
            cd = fs.addcommanddata(cd);
            obj.temporarystep = fs;
            obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
            obj.interface.sendcommand(command(cd, commandtype.start, obj));
        end
        
        function holdstop(obj)
            cd = commanddata();
            cd.Sequence = 4;
            cd.Phase = 15;
            if ~obj.holdmode
                obj.holdmode = 1;
                obj.interface.sendcommand(command(cd, commandtype.readphase, obj));
            end
            if obj.running
                obj.running = 0;
                obj.makequeuedchanges();
            end
            % Then tell the system to hold
            obj.interface.sendcommand(command(commanddata(), commandtype.holdon, obj));
            fs = fluidstep('stop', 10, 0, 'AV_NONE');
            cd = fs.addcommanddata(cd);
            obj.temporarystep = fs;
            obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
            obj.interface.sendcommand(command(cd, commandtype.start, obj));
        end
        
        function baseline(obj)
            if ~obj.running
                cd = commanddata();
                cd.Sequence = 4;
                cd.Phase = 15;
                fs = fluidstep('base', 10, 0, 'AV_NONE');
                cd = fs.addcommanddata(cd);
                obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
                obj.interface.sendcommand(command(cd, commandtype.start, obj));
                start(obj.resettimer);
                %  cd = fs.addcommanddata(cd);
                %  obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
                %  obj.interface.sendcommand(command(cd, commandtype.start, obj));
            end
        end
        
        function pause(obj, doPause)
            if obj.running && doPause
                obj.interface.sendcommand(command(commanddata, commandtype.holdon, obj));
            elseif obj.running
                obj.interface.sendcommand(command(commanddata, commandtype.holdoff, obj));
            end
        end
        
        function restartholdfcn(obj)
            cd = commanddata();
            cd.Sequence = 4;
            cd.Phase = 15;
            cd = obj.temporarystep.addcommanddata(cd);
            obj.interface.sendcommand(command(cd, commandtype.writephase, obj));
            obj.interface.sendcommand(command(cd, commandtype.start, obj));
        end
        
        function delete(obj)
            if ~isempty(obj.programui)
                delete(obj.programui);
            end
        end
    end
end

function resetholdfcn(~, ~, pmobj)
pmobj.restartholdfcn();
end

