classdef SerialLoader < handle
    %SerialLoader Reads serial data from SPIRIT Device, writes file
    %   Reads out data and writes it to a file. Removes lines formed
    %   during system reset containing error codes.
    
    properties
        port
        ser
        processor
        interface
        failures
        waiting
        datatimer
        
        connecttimer
    end
    
    methods
        function obj = SerialLoader(port, commandproc, interface)
            obj.waiting = 0;
            obj.failures = 0;
            obj.interface = interface;
            obj.port = port;
            obj.ser = serial(port, 'BAUD', 115200, 'DataBits',8);% , 'FlowControl', 'hardware');, 'Timeout', 0
            obj.ser.BytesAvailableFcnMode = 'terminator';
            obj.ser.BytesAvailableFcn = {@rtext, obj};
            obj.ser.ErrorFcn = {@errorfn, obj};
            obj.processor = commandproc;
            obj.datatimer = timer('StartDelay', 5, 'TimerFcn', {@datatim, obj});
            
            obj.connecttimer = timer('StartDelay', 2, 'TimerFcn', {@delayconnect, obj});
            
            % obj.ser.RecordDetail = 'verbose';
            % obj.ser.RecordName = 'serialinfo.txt';
        end
        
        function updateserial(obj, port)
            if strcmp(get(obj.ser, 'Status'), 'open')
                obj.interface.sendwarning('Serial Port busy, can''t update port.', 2);
                obj.disconnect();
                obj.port = port;
                obj.ser = serial(port, 'BAUD', 115200, 'DataBits',8, 'FlowControl', 'hardware');
                obj.ser.BytesAvailableFcnMode = 'terminator';
                obj.ser.BytesAvailableFcn = {@rtext, obj};
                obj.ser.ErrorFcn = {@errorfn, obj};
                obj.connect();
            else
                obj.port = port;
                obj.ser = serial(port, 'BAUD', 115200, 'DataBits',8);%, 'FlowControl', 'hardware');
                obj.ser.BytesAvailableFcnMode = 'terminator';
                obj.ser.BytesAvailableFcn = {@rtext, obj};
                obj.ser.ErrorFcn = {@errorfn, obj};
            end
            %obj.ser.BytesAvailableFcnMode = 'terminator';
            %obj.ser.BytesAvailableFcn = {@rtext, obj};
        end
        
        function yes = connect(obj)
            try
                obj.interface.log.info('SerialLoader', 'Connecting to serial port');
                obj.waiting = 1;
                fopen(obj.ser);
                % record(obj.ser, 'on');
                yes = 1;
                obj.interface.sendwarning('Waiting for Serial Data...', 2)
                obj.failures = 0;
            catch err
                obj.failures = obj.failures + 1;
                switch err.identifier
                    case ''
                    otherwise
                end
                obj.interface.log.warn('SerialLoader', ['Serial error while connecting: ' err.message ' Failure#: ' obj.failures]);
                obj.interface.sendwarning(['Serial: ' err.message], 2);
                obj.interface.sendmessage(['Serial: ' err.message]);
                obj.interface.serialportdisconnected();
                obj.delayedconnect([]);
                yes = 0;
            end
        end
        
        function yes = disconnect(obj)
            try
                obj.interface.log.info('SerialLoader', 'Disconnecting from serial port');
                fclose(obj.ser);
                yes = 1;
                obj.interface.sendwarning('Serial Port Disconnected.', 2);
                stop(obj.datatimer);
            catch err
                obj.interface.log.warn('SerialLoader', ['Serial error while disconnecting: ' err.message]);
                obj.interface.sendwarning(['Serial: ' err.message], 2);
            end
        end
        
        function delayedconnect(obj, time)
            if strcmp(get(obj.connecttimer, 'Running'), 'on')
                % We want to avoid running if we're already running
                return
            end
            if ~isempty(time)
                set(obj.connecttimer, 'StartDelay', time);
            else
                if (obj.failures < 5)
                    time = 2^(obj.failures - 4);
                else
                    time = 2;
                end
                set(obj.connecttimer, 'StartDelay', time);
            end
            start(obj.connecttimer);
        end
        
        function errorreceived(obj, text)
            obj.failures = obj.failures + 1;
            obj.interface.log.warn('SerialLoader', ['Serial error received: ' text]);
            obj.interface.sendmessage(['Serial error: ' text]);
            
            if ~obj.waiting
                obj.delayedconnect([]);
            end
        end
        
        function receivetext(obj, text)
            obj.failures = 0;
            obj.waiting = 0;
            obj.interface.sendwarning('Receiving Serial Data.', 2);
            stop(obj.datatimer);
            start(obj.datatimer);
            obj.processor.receive(text);
        end
        
        function yes = sendtext(obj, text)
            try
                fprintf(obj.ser, text);
                yes = 1;
            catch err
                obj.interface.sendwarning(['Serial: ' err.message], 2);
                obj.interface.log.warn('SerialLoader', ['Serial error received while sending data: ' text]);
                obj.interface.sendmessage(['Serial error: ' err.message]);
                yes = 0;
                obj.errorreceived(err.message);
            end
        end
    end
end

function datatim(~, ~, serobj)
serobj.interface.sendwarning('WARNING: Lost device connection.', 2);
serobj.errorreceived('WARNING: Lost device connection.');
end

function delayconnect(~, ~, serobj)
serobj.disconnect();
serobj.connect();
end

function errorfn(~, event, serobj)
disp(event.Data.Message);
serobj.errorreceived(event.Data.Message);
end

function rtext(obj, ~, serobj)
text = fgetl(obj);
receivetext(serobj, text);
end