classdef commandprocessor < handle
    %COMMANDPROCESSOR Processes low level commands
    %   This class converts commands into strings for the serial port
    % and verifies that they are successful. Information from the 
    % serial port is also processed here and sent to the system.
    % If the string involved is fluidics program related, that is
    % sent to the programmanager function
    
    properties
        interface
        
        %Some functions we have take a few iterations to run. We want to
        %pause output until it's complete.
        pausenum
        unpauseobj
        
        receivingdata
        lastcommand
        commandqueue
        serialobject
        sentcommands
    end
    
    methods
        function obj = commandprocessor(inface)
            obj.receivingdata = 0;
            obj.interface = inface;
            obj.lastcommand = [];
            obj.commandqueue = [];
            obj.pausenum = 0;
            obj.unpauseobj = [];
            obj.sentcommands = 0;
        end
        
        function clear(obj)
            obj.receivingdata = 0;
            obj.sentcommands = 0;
            obj.lastcommand = [];
            obj.commandqueue = [];
        end
        
        function pause(obj, time, unpauseobj)
            if obj.pausenum == 0 && time > 0
                obj.pausenum = floor(time);
                obj.unpauseobj = unpauseobj;
            end
        end
        
        function receive(obj, text)
            %disp(text);
            if isempty(text)
                %We don't do anything
            elseif commandprocessor.isdata(text)
                obj.interface.adddata(text);
                if obj.pausenum > 1
                    obj.pausenum = obj.pausenum - 1;
                elseif obj.pausenum == 1
                    obj.pausenum = 0;
                    obj.sendqueuedcommand;
                    obj.unpauseobj.unpause();
                elseif obj.pausenum == 0 && isempty(obj.lastcommand) && ~isempty(obj.commandqueue)
                    obj.sendqueuedcommand();
                end
            else
                % Process single segment commands first
                if text(1) == '$'
                    % echos of sent commands. We ignore these.
                    %disp(['Received command' text]);
                elseif text(1) == '%'
                    % We just received a command. Depending on the command
                    % structure, we will pull the first item out of the
                    % list which matches that command. But we start by
                    % initializing the variable as empty. If it remains
                    % empty later, we ignore it.
                    %disp(['Received command' text]);
                    obj.sentcommands = obj.sentcommands - 1;
                    sendcmd = [];
                    cd = commanddata();
                    cd.Error = 0;
                    first = textscan(text, '%s', 1);
                    if isempty(first{1})
                        cd.Error = 1;
                    else
                        first = first{1}{1};
                        if ~isempty(obj.lastcommand)
                            sendcmd = obj.lastcommand(1);
                            obj.lastcommand(1) = [];
                        end
                        obj.sendqueuedcommand();
                        switch first
                            case '%error'
                                cd.Error = 1;
                            case '%write_sequences'
                            case '%read_sequences'
                            case '%hold_on'
                            case '%hold_off'
                            case '%calibrate'
                                x = textscan(text, '%*s %f');
                                cd.Calibration = x{1};
                            case '%read_phase'
                                x = textscan(text, '%*s %u %u %s %u %u %s');
                                cd.Sequence = x{1} + 1; %0-3 to 1-4
                                cd.Phase = x{2} + 1; %0-14 to 1-15
                                cd.Name = lower(x{3}{1});
                                cd.Duration = x{4};
                                cd.Pumpspeed = floor(x{5}*100/255);
                                cd.AV = x{6}{1}; %Just port the strings over
                            case '%write_phase'
                                x = textscan(text, '%*s %u %u %s %u %u %s');
                                cd.Sequence = x{1} + 1; %0-3 to 1-4
                                cd.Phase = x{2} + 1; %0-14 to 1-15
                                cd.Name = lower(x{3}{1});
                                cd.Duration = x{4};
                                cd.Pumpspeed = x{5};
                                cd.AV = x{6}{1}; %Just port the strings over
                            case '%start'
                                x = textscan(text, '%*s %u %u');
                                cd.Sequence = x{1} + 1; %0-3 to 1-4
                                cd.Sequence = x{2} + 1; %0-14 to 1-15
                            case '%read_pixels'
                                x = textscan(text, '%*s %*u %*s %s');
                                x = x{1}{1};
                                channel = textscan(text, '%*s %u %*s %*s');
                                channel = channel{1} + 1;
                                type = textscan(text, '%*s %*u %s %*s');
                                type = type{1}{1};
                                data = zeros(1, length(x)/3);
                                for i = 1:3:length(x)
                                    data((i-1)/3+1) = hex2dec([x(i) x(i+1) x(i+2)]);
                                end
                                cd.PixelValues = data;
                                cd.Type = type;
                                cd.Channel = channel;
                            case '%acquire'
                                type = textscan(text, '%*s %s');
                                type = type{1}{1};
                                cd.Type = type;
                            case '%setpoint'
                            otherwise
                                cd.Error = 1;
                                obj.interface.sendmessage(['Bad command: ' text]);
                        end
                        if ~isempty(sendcmd)
                            sendcmd.sender.receive(cd, sendcmd);
                        end
                    end
                else
                    disp(text);
                end
            end
        end
        
        % This processes commands sent here. If there are items in the
        % queue, we want to add this to the queue. If there are too many
        % items in the list, we've sent too many items to the serial port
        % and not heard back, so we want to start adding items to the
        % queue. 
        function sendcommand(obj, command)
            obj.commandqueue = [obj.commandqueue command];
            % obj.sendqueuedcommand() happens either here or in the receive
            % function. If we are adding commands, there's a queue and we
            % aren't waiting to receive our five commands, we should start
            % sending queued commands.
            % Okay, so if we have items in the commandqueue, we're rather
            % send them all at once.
            if obj.pausenum ~= 0
                % return
            elseif obj.sentcommands == 0
                obj.sendqueuedcommand();
            end
        end
        
        function sendqueuedcommand(obj)
            if (obj.sentcommands == 0)
                while(obj.sentcommands < 10 && ~isempty(obj.commandqueue))
                    if ~isempty(obj.commandqueue) && length(obj.commandqueue) > 1
                        obj.writecommand(obj.commandqueue(1));
                        obj.commandqueue = obj.commandqueue(2:end);
                    else
                        obj.writecommand(obj.commandqueue(1));
                        obj.commandqueue = [];
                    end
                    %disp('Sending command');
                    obj.sentcommands = obj.sentcommands + 1;
                end
            end
        end
        
        function writecommand(obj, command)
            obj.lastcommand = [obj.lastcommand command];
            switch(command.type)
                case commandtype.readphase
                    str = '$read_phase ';
                    sequence = command.data.Sequence - 1;
                    phase = command.data.Phase - 1;
                    succeed = obj.interface.ser.sendtext([str num2str(sequence) ' ' num2str(phase)]);
                case commandtype.writephase
                    str = '$write_phase ';
                    sequence = command.data.Sequence - 1;
                    str = [str num2str(sequence) ' '];
                    phase = command.data.Phase - 1;
                    str = [str num2str(phase) ' '];
                    name = command.data.Name;
                    str = [str name ' '];
                    dur = command.data.Duration;
                    str = [str num2str(dur) ' '];
                    ps = floor(command.data.Pumpspeed/100*255);
                    str = [str num2str(ps) ' '];
                    av = command.data.AV;
                    str = [str av ' '];
                    succeed = obj.interface.ser.sendtext(str);
                case commandtype.setpoint
                    str = '$setpoint ';
                    t = command.data.Temperature;
                    succeed = obj.interface.ser.sendtext([str num2str(t)]);
                case commandtype.calibrate
                    str = '$calibrate ';
                    c = command.data.Calibration;
                    succeed = obj.interface.ser.sendtext([str num2str(c)]);
                case commandtype.start
                    str = '$start ';
                    sequence = command.data.Sequence - 1;
                    phase = command.data.Phase - 1;
                    succeed = obj.interface.ser.sendtext([str num2str(sequence) ' ' num2str(phase)]);
                case commandtype.holdon
                    succeed = obj.interface.ser.sendtext('$hold_on');
                case commandtype.holdoff
                    succeed = obj.interface.ser.sendtext('$hold_off');
                case commandtype.writesequences
                    succeed = obj.interface.ser.sendtext('$write_sequences');
                case commandtype.readsequences
                    succeed = obj.interface.ser.sendtext('$read_sequences');
                case commandtype.readpixels
                    succeed = obj.interface.ser.sendtext(['$read_pixels ' num2str(command.data.Channel - 1) ' ' command.data.Type]);
                case commandtype.acquire
                    succeed = obj.interface.ser.sendtext(['$acquire ' command.data.Type]);
                otherwise
                    % We didn't send any data. Remove the last item from
                    % the commandlist.
                    succeed = 0;
            end
            if ~succeed
                obj.lastcommand(1) = [];
            end
        end
        
    end
    
    methods (Static)
        function isit = isdata(text)
            isit = 0;
            if text(1) == '%' || text(1) == '$'
                isit = 0;
            else
                m = textscan(text, ' %s ');
                if length(m{1}) == 39
                    isit = 1;
                end
            end
        end
    end
end


