classdef interface_controller < handle
    %INTERFACE_CONTROLLER Control all the things
    %   This is the main program. This starts a 'ControlUI' item as well as
    %   other functions, such as the serial port. Commands to the
    %   instrument are routed through the "sendcommand" function (which
    %   passes them along to an instance of the commandprocessor class.
    
    properties
        log
        
        ser
        comproc
        prgmmgr
        ctrlui
        axes
        pixdata
        files
        dataview
        refview
        settings
        tempview
        calview
        % This is necessary to keep track of how the various data loading
        % states. This is "IDLE", "CONNECTED", "DISCONNECTED", or "LOADED" (as
        % in a file has been loaded). When we're idle, we should be able to
        % connect to the serial port or load a data file. When we're
        % connected, this should prevent us from connecting again or
        % loading a file. When we've disconnected, we now get the
        % opportunity to reconnect, which keeps the displayed data and the
        % saved file intact. Once we've switched back to LOADED, the option
        % to reconnect disappears.
        datastatus
    end
    
    methods
        function obj = interface_controller()
            % this will create all of the other controllers as well as the 
            % figures (windows) associated with those particular
            % controllers. This allows for all of them to be closed when
            % the program is closed, which is why we have the "dataview,
            % tempview, calview, and refview" properties.
            obj.log = log4m.getLogger('spr.log');
            
            obj.datastatus = 'IDLE';
            
            % These objects are given a reference to this object so they
            % can call its methods, such as sending commands.
            % The settings are loaded from the settings.xml file.
            obj.settings = SettingsManager();
            obj.comproc = commandprocessor(obj);
            obj.ser = SerialLoader(obj.settings.serialport, obj.comproc, obj);
            obj.prgmmgr = programmanager(obj);
            obj.ctrlui = controlui();
            x = get(obj.ctrlui, 'Children');
            for y = 1:length(x)
                if strcmp('axes1', get(x(y), 'Tag'))
                    obj.axes = x(y);
                end
            end
            set(obj.ctrlui, 'UserData', obj);
            obj.dataview = dataviewer(obj.axes, obj);
            obj.files = FileLoader(obj, obj.settings.filedir);
            obj.tempview = tempcontroller(obj);
            obj.calview = calcontroller(obj);
            obj.refview = referenceui(obj);
            
            obj.prgmmgr.autopause = obj.settings.autopause;
        end
        
        % This function is called by the serial port when it receives a
        % line of data. These are transmitted roughly once per second and
        % contain information about the fluidics steps, the temperature as
        % well as the data from the sensors.
        function adddata(obj, data)
            if length(data) <= 1
                return;
            end
            % textscan will separate the data into an array of floating
            % points. If there are less than 39 seperate numbers in the
            % line of text from the serial port, we don't have properly
            % formatted data.
            points = textscan(data, '%f');
            points = points{1};
            if length(points) ~= 39
                return; % We have not received a good line of text
            end
            switch obj.datastatus
                case 'IDLE'
                    % Where'd that come from? Ignore.
                case 'DISCONNECTED'
                    % This is also bizarre. 
                case 'CONNECTED'
                    % Save the entire line of data (not the processed
                    % elements)
                    obj.files.append(data);
                    % Add the sensor data to the currently displayed data
                    obj.dataview.adddata(points(8:19));
                    % Add the fluidics sequence data to the fluidics
                    % program manager
                    obj.prgmmgr.updatecurrentstep(points(2:4));
                    % Add the temperature data to the temperature graph
                    obj.tempview.adddata(points(5:6));
                case 'LOADED'
                    % This is roughly identical to the previous step, but
                    % since this is coming from a loaded file rather than
                    % from the serial port (hence obj.datastatus ==
                    % 'LOADED') we do not save that in a file.
                    obj.dataview.adddata(points(8:19));
                    obj.prgmmgr.updatecurrentstep(points(2:4));
                    obj.tempview.adddata(points(5:6));
            end
        end
        
        function adddatalines(obj, data)
            % Here we should get pre-formatted data encompassing multiple
            % lines.
            if size(data, 1) ~= 39
                return;
            end
            switch obj.datastatus
                case 'IDLE'
                    % Where'd that come from? Ignore.
                case 'DISCONNECTED'
                    % This is also bizarre. 
                case 'CONNECTED'
                    % obj.files.append(data); % this shouldn't normally
                    % run. If we are connected, it won't send lines in
                    % chunks.
                    obj.dataview.adddatablock(data(8:19, :));
                    obj.prgmmgr.updatecurrentstep(data(2:4, :));
                    obj.tempview.adddatablock(data(5:6, :));
                case 'LOADED'
                    % This calls the datablock version to add multiple
                    % lines of data at once.
                    obj.dataview.adddatablock(data(8:19, :));
                    obj.prgmmgr.updatecurrentstep(data(2:4, :));
                    obj.tempview.adddatablock(data(5:6, :));
            end
        end
        
        function delete(obj)
            % This is called when this object is being removed. It will
            % delete (and thus close corresponding windows) of the various
            % other controllers in the system.
            obj.settings.SaveSettings();
            obj.ser.disconnect();
            try
                delete(obj.prgmmgr);
            catch err
            end
            try
                delete(obj.pixdata);
            catch err
            end
            try
                delete(obj.refview);
            catch err
            end
            try
                delete(obj.dataview);
            catch err
            end
            try
                delete(obj.tempview);
            catch err
            end
        end
        
        function sendcommand(obj, command)
            % The command processor processes command objects
            obj.comproc.sendcommand(command);
        end
        
        function baseline(obj)
            % The baseline command is performed by the program manager
            % object since this requires changing the programmed fluidics
            % steps in the instrument.
            obj.prgmmgr.baseline();
        end
        
        function enableholds(obj)
            % We disable and enable the "Hold" commands (with the exception
            % of "Stop") when a programmed sequence is running. This
            % prevents the user from interrupting the running sequence by
            % switching to a different "flow" or "flush" or other step
            % without intentionally pushing the "stop" button.
            handles = guidata(obj.ctrlui);
            set(handles.regenbutton, 'Enable', 'on');
            set(handles.flowbutton, 'Enable', 'on');
            set(handles.flushbutton, 'Enable', 'on');
            set(handles.pressurizebutton, 'Enable', 'on');
            set(handles.collectbutton, 'Enable', 'on');
        end
        
        function disableholds(obj)
            % We disable and enable the "Hold" commands (with the exception
            % of "Stop") when a programmed sequence is running. This
            % prevents the user from interrupting the running sequence by
            % switching to a different "flow" or "flush" or other step
            % without intentionally pushing the "stop" button.
            handles = guidata(obj.ctrlui);
            set(handles.regenbutton, 'Enable', 'off');
            set(handles.flowbutton, 'Enable', 'off');
            set(handles.flushbutton, 'Enable', 'off');
            set(handles.pressurizebutton, 'Enable', 'off');
            set(handles.collectbutton, 'Enable', 'off');
            set(handles.uipanel1, 'SelectedObject', []);
        end
        
        function setprogramview(obj, n)
            % This selects the program which the "run program" command will
            % begin. 
            if n > 0 && n < 5
                x = get(obj.ctrlui, 'Children');
                for y = 1:length(x)
                    if strcmp('popupmenu1', get(x(y), 'Tag'))
                        set(x(y), 'Value', n);
                    end
                end
            end
        end
        
        % sendmessage is a way to show error or other messages in a
        % persistent way to the user. This puts the newest warning at the
        % top of the warning text box.
        function sendmessage(obj, message)
            x = get(obj.ctrlui, 'Children');
            for y = 1:length(x)
                if strcmp('warnbox', get(x(y), 'Tag'))
                    z = get(x(y), 'String');
                    if isempty(z)
                        z = {message};
                    else
                        z = vertcat(message, z);
                    end
                    set(x(y), 'String', z);
                end
            end
        end
        
        %         Line 1: Data warnings.
        %         Line 2: Serial Port warnings.
        %         Line 3: File system warnings.
        %         Line 4: Other warnings.
        % Similar to the sendmessage command, this shows a status on a
        % particular line (1-4). It is designed to show if any sensors are
        % not reading out correctly, if the serial port has some error or
        % if data is not being saved properly.
        function sendwarning(obj, message, line)
            if line > 4 || line < 1
                return
            end
            x = get(obj.ctrlui, 'Children');
            for y = 1:length(x)
                if strcmp('msgbox', get(x(y), 'Tag'))
                    z = get(x(y), 'String');
                    z{line} = message;
                    set(x(y), 'String', z);
                end
            end
        end
        
        %This is the callback for the raw viewer menu item. We only really
        %need to keep track of this to close out the data/windows when we
        %exit this particular window. The raw viewer will keep a reference
        %to the interface object so it can send commands (request for raw
        %data) to the serial port.
        function startrawviewer(obj)
            if isempty(obj.pixdata)
                obj.pixdata = pixeldata(obj);
            else
                obj.pixdata.show();
            end
        end
        
        % When we connect to the instrument or load a new file, we want to
        % remove any current data and save that data in a new file.
        function resetdata(obj)
            obj.files.newfile();
            obj.dataview.resetdata();
        end
        
        % This is called by the Program Fluidics Step menu option in the
        % controlui. This lets the user change how the sequences of steps
        % are programmed. There are 4 different sequences with 15 steps
        % each. This is handled by the programmanager object and the
        % corresponding fluidicsprogram.fig ui.
        function startfluidicsprogramingviewer(obj)
            obj.prgmmgr.changeprograms();
        end
        
        % This starts one of the 4 fluidics sequences
        function startsequence(obj, sequence)
            obj.prgmmgr.start(sequence);
        end
        
        % This enables the connect menus when we disconnect from the
        % instrument.
        function serialportdisconnected(obj)
            h = guidata(obj.ctrlui);
            set(h.connectmenu, 'Enable', 'on');
            set(h.disconnectmenu, 'Enable', 'off');
            set(h.reconnectmenu, 'Enable', 'on');
            set(h.loadmenu, 'Enable', 'on');
            set(h.fluidicsprogrammenu, 'Enable', 'off');
        end

        % This enables the disconnect menus when we connect to the
        % instrument.
        function serialportconnected(obj)
            obj.enableholds();
            h = guidata(obj.ctrlui);
            set(h.connectmenu, 'Enable', 'off');
            set(h.disconnectmenu, 'Enable', 'on');
            set(h.reconnectmenu, 'Enable', 'off');
            set(h.loadmenu, 'Enable', 'off');
            set(h.fluidicsprogrammenu, 'Enable', 'on');
        end
        
        
        % based on the command type, this will connect or disconnect the
        % serial port.
        function serialcommand(obj, type)
            switch type
                case 'Connect'
                    % Tell Serial Loader to connect
                    if obj.ser.connect()
                        % obj.datastatus = 'CONNECTED';
                        obj.changedatamode('CONNECTED');
                        if obj.settings.loadprogramconnect
                            obj.prgmmgr.loadsequences();
                        else
                            obj.prgmmgr.loadcurrentprograms();
                        end
                        if obj.settings.acquireconnect
                            obj.refview.justdark();
                        end
                        if obj.settings.stopconnect
                            obj.holdfcn('Stop');
                        end
                        obj.files.newfile();
                        obj.dataview.resetdata();
                        obj.tempview.clear();
                    else
                        % obj.serialportdisconnected();
                    end
                case 'Disconnect'
                    obj.comproc.clear();
                    obj.ser.disconnect();
                    obj.changedatamode('DISCONNECTED');
                case 'Reconnect'
                    if ~obj.ser.connect()
                        obj.changedatamode('DISCONNECTED');
                        % obj.serialportdisconnected();
                    else
                        obj.changedatamode('CONNECTED');
                    end
            end
        end
        
        
        % Hold functions allow the user to specify what mode they want the
        % instrument to run in (and stay in that mode) rather than
        % specifying this in the automated sequences (where it runs through
        % one step onto the next one). The controls are in the main window
        % (controlui.fig) but the commands are sent through the
        % programmanager.
        function holdfcn(obj, type, pumpspeed)
            switch type
                case 'Flow'
                    if isempty(pumpspeed)
                        obj.sendmessage('Pump speed not set, setting to 10');
                        pumpspeed = 10;
                    elseif pumpspeed < 0
                        obj.sendmessage('Pump speed too low, setting to zero');
                        pumpspeed = 0;
                    elseif pumpspeed > 100
                        obj.sendmessage('Pump speed too high, setting to 100');
                        pumpspeed = 100;
                    end
                    obj.prgmmgr.holdflow(pumpspeed);
                case 'Regenerate'
                    if isempty(pumpspeed)
                        obj.sendmessage('Pump speed not set, setting to 10');
                        pumpspeed = 10;
                    elseif pumpspeed < 0
                        obj.sendmessage('Pump speed too low, setting to zero');
                        pumpspeed = 0;
                    elseif pumpspeed > 100
                        obj.sendmessage('Pump speed too high, setting to 100');
                        pumpspeed = 100;
                    end
                    obj.prgmmgr.holdregen(pumpspeed);
                case 'Flush'
                    obj.prgmmgr.holdflush();
                case 'Pressurize'
                    obj.prgmmgr.holdpressurize();
                case 'Collect'
                    obj.prgmmgr.holdcollect();
                case 'Stop'
                    obj.prgmmgr.holdstop();
            end
        end
        
        function setaxes(obj, axes)
            obj.axes = axes;
        end
        
        % Unlike the other windows, this is opened once, it sets the
        % interfacecontroller as the 'UserData' and opens a new window each
        % time this is called. This lets the user change which COM port we
        % connect to.
        function changeserial(obj)
            x = SelectCOM(obj.ser.port);
            set(x, 'UserData', obj);
        end
        
        function showplot(obj, plot)
            obj.dataview.showplot(plot);
        end
        
        function hideplot(obj, plot)
            obj.dataview.hideplot(plot);
        end
        
        % This displays the 4 lines of data (for four sensors) on the main
        % window. We can check and uncheck these mainly when we switch
        % between plotting individual channels (of which there are twelve)
        % and plotting averaged channels. That part is controlled by the
        % dataviewer.
        function checkallplots(obj)
            handles = guidata(obj.ctrlui);
            set(handles.plotbox1, 'Value', 1);
            set(handles.plotbox2, 'Value', 1);
            set(handles.plotbox3, 'Value', 1);
            set(handles.plotbox4, 'Value', 1);
        end
        
        function uncheckallplots(obj)
            handles = guidata(obj.ctrlui);
            set(handles.plotbox1, 'Value', 0);
            set(handles.plotbox2, 'Value', 0);
            set(handles.plotbox3, 'Value', 0);
            set(handles.plotbox4, 'Value', 0);
        end
        
        % This loads the referenceui figure or creates a new object to
        % allow the user to perform a high-sucrose reference.
        function refui(obj)
            if isempty(obj.refview)
                obj.refview = referenceui(obj);
            else
                obj.refview.show();
            end
        end
        
        function changedatamode(obj, datamode)
            % datamode is 'idle', 'disconnected', 'loading', 'connected'
            % if we are idle, we just started the instrument. There should be
            % no data. If we reset file/data while disconnected, we go back to
            % 'idle'. If we're disconnected, we have the opportunity to
            % reconnect. And if we're loading, we shouldn't be able to
            % reconnect, but we can connect (which will clear our data).
            % handles = guidata(obj.ctrlui);
            switch datamode
                case 'IDLE'
                    obj.datastatus = 'IDLE';
                case 'DISCONNECTED'
                    obj.datastatus = 'DISCONNECTED';
                    obj.serialportdisconnected();
                case 'LOADED'
                    obj.datastatus = 'LOADED';
                case 'CONNECTED'
                    obj.datastatus = 'CONNECTED';
                    obj.serialportconnected();
            end
        end
        
        % The "pause" is a way to stop commands from being sent when it
        % would interrupt an important instrument function. This is
        % normally true during referencing of the sensors.
        function serialpause(obj, time, unpauseobj)
            obj.comproc.pause(time, unpauseobj);
        end
        
        % Loads and displays a data file.
        function loadfile(obj, file)
            obj.changedatamode('LOADED');
            obj.dataview.resetdata();
            if obj.files.loadfile(file);
                obj.tempview.clear();
            else
                obj.changedatamode('DISCONNECTED');
            end
        end
        
        % Shows the data control window
        function dataui(obj)
            obj.dataview.show();
        end
        
        % Shows the temperature control window
        function tempui(obj)
            obj.tempview.show();
        end
        
        % Shows the calibration window
        function calibrateui(obj)
            obj.calview.show();
        end
        
        % Shows the settings control window
        function settingsui(obj)
            obj.settings.ShowPreferencesUI();
        end
        
        % This pauses the current automated fluidics sequence. There is a
        % "pause" checkbox on the controlui window. Also, the settings have
        % an option to "autopause during inject steps" which cause the
        % system to pause when the the user needs to inject a sample.
        function pause(obj, pause)
            handles = guidata(obj.ctrlui);
            set(handles.pausebox, 'Value', pause);
            obj.prgmmgr.pause(pause);
        end
        
        
        % This function is maybe not well thought out. Most of the settings
        % we're updating in the other direction: i.e. obj.settings will
        % change via it's UI and then send a message to the interface
        % controller. Now, if we want to update the obj.settings but not
        % from the UI, that would only be applicable in a couple
        % circumstances, when we update the serial port location or the
        % data saving location. At least currently. I think I'll leave this
        % all here anyway. For the time being, it will be redundant, as we
        % will call 'autopause' to update the program manager and it will
        % update the settings file as well (which did the calling).
        function updatesetting(obj, type, value)
            switch type
                case 'autostop'
                    obj.settings.stopconnect = value;
                case 'autodark'
                    obj.settings.acquireconnect = value;
                case 'autoload'
                    obj.settings.loadprogramconnect = value;
                case 'autopause'
                    obj.settings.autopause = value;
                    obj.prgmmgr.autopause = value;
                case 'filedir'
                    obj.settings.filedir = value;
                    obj.files.setDirectory(value);
                case 'serialport'
                    obj.settings.serialport = value;
                    obj.ser.updateserial(value);
            end
            obj.settings.SaveSettings();
        end
        
        function dir = getdatadir(obj)
            dir = obj.files.getDirectory();
        end
    end
end
