function asiGui
% a basic GUI that interfaces with an Applied Scientific Instrumentation
% microscope stage micropositioning system

if ~isappdata(0, 'asiGui')
	if ~ispref('locations', 'asiGui')
		setpref('locations', 'asiGui', [100 50 46 10.4]);
	end

    figHandle = hgload('asiGui');
    handles = guihandles(figHandle);
    setappdata(0, 'asiGui', figHandle);
    set(figHandle, 'position', getpref('locations', 'asiGui'), 'closeRequestFcn', @closeMe);
    
    set(handles.xStep, 'callback', @setXStep);
    set(handles.yStep, 'callback', @setYStep);
    set(handles.zStep, 'callback', @setZStep);
    set(handles.incX, 'callback', @incX);
    set(handles.decX, 'callback', @decX);
    set(handles.incY, 'callback', @incY);
    set(handles.decY, 'callback', @decY);
    set(handles.incZ, 'callback', @incZ);
    set(handles.decZ, 'callback', @decZ);
    set(handles.doZStack, 'callback', @doZStack);
    
	if ~ispref('ASI', 'commPort')
		setupASI;
	end
	
    try
		commPort = serial(['COM' sprintf('%0.0f', getpref('ASI', 'commPort'))]);
		set(commPort,...
			'baudrate', 9600,...
			'parity', 'none',...
			'databits', 8,...
			'stopbits', 1,...
			'requesttosend', 'on',...
			'dataterminalready', 'on',...
			'timeout', 1,...
			'outputbuffersize', 512,...
			'inputbuffersize', 1024,...
            'bytesAvailableFcnMode', 'byte',...
            'bytesAvailableFcnCount', 3,...
            'terminator', '',...
            'flowControl', 'hardware',...
            'recordName', 'myRecord.txt',...
            'recordDetail', 'verbose');
		fopen(commPort)
	catch
		error(['Error with serial port COM' sprintf('%0.0f', getpref('ASI', 'commPort')) ' in setting up ASI']);
    end	
    setappdata(0, 'asiPort', commPort);
	
    % let the port open and get set
        pause(1)
        asiTimer =timer('name', 'asiLocation', 'TimerFcn', @asiLocation, 'Period', 0.5, 'executionMode', 'fixedDelay', 'busyMode', 'drop');
    
    % setup increments
        fwrite(commPort, [char(255) char(66)]); % low level commands mode
        setXStep;
        setYStep;
        setZStep;
        start(asiTimer);
        
    % set the starting path for z stacks
        path = pwd;
        
else
    % restack the windows
    figHandle = figure(getappdata(0, 'asiGui'));
end

tempPos = get(figHandle, 'position');
set(0, 'units', get(figHandle, 'units'));
screenLims = get(0, 'monitorPosition');
screenLims = [min(screenLims(:,1:2), [], 1) max(screenLims(:,3:4), [], 1)];
set(figHandle, 'position', [max([screenLims(1) min([screenLims(3) - tempPos(3) tempPos(1)])]) max([screenLims(2) min([screenLims(4) - tempPos(4) tempPos(2)])]) tempPos(3:4)]);
	
	function decX(varargin)
		fwrite(commPort, [char(24) char(45) char(0) char(58)]);
	end

	function incX(varargin)
		fwrite(commPort, [char(24) char(43) char(0) char(58)]);
	end

	function decY(varargin)
		fwrite(commPort, [char(25) char(45) char(0) char(58)]);
	end

	function incY(varargin)
		fwrite(commPort, [char(25) char(43) char(0) char(58)]);
	end

	function decZ(varargin)
		fwrite(commPort, [char(26) char(45) char(0) char(58)]);
	end

	function incZ(varargin)
		fwrite(commPort, [char(26) char(43) char(0) char(58)]);
	end

	function setXStep(varargin)
		fwrite(commPort, [char(24) char(68) char(3) char(mod(str2double(get(handles.xStep, 'string')) * 10, 256)) char(round(mod(str2double(get(handles.xStep, 'string')) * 10 / 256, 256))) char(round(mod(str2double(get(handles.xStep, 'string')) * 10 / 256 / 256, 256))) char(58)]);
	end

	function setYStep(varargin)
		fwrite(commPort, [char(25) char(68) char(3) char(mod(str2double(get(handles.yStep, 'string')) * 10, 256)) char(round(mod(str2double(get(handles.yStep, 'string')) * 10 / 256, 256))) char(round(mod(str2double(get(handles.yStep, 'string')) * 10 / 256 / 256, 256))) char(58)]);      
	end

	function setZStep(varargin)
		fwrite(commPort, [char(26) char(68) char(3) char(mod(str2double(get(handles.zStep, 'string')) * 10, 256)) char(round(mod(str2double(get(handles.zStep, 'string')) * 10 / 256, 256))) char(round(mod(str2double(get(handles.zStep, 'string')) * 10 / 256 / 256, 256))) char(58)]);                  
    end

    function doZStack(varargin)
        % take a Z-stack image
        stop(asiTimer);
        tempPath = path;
        path = uigetdir(path, 'Enter location for stack');
        if path == 0
            path = tempPath;
            return
        end
        
        currentPosition = readASI;
        stopPoint = currentPosition(3) + str2double(get(handles.stackHeight, 'string'));
        cellName = get(handles.txtCellName, 'string');
		cellName(cellName == '.') = '_';
		stackName = get(handles.txtStackName, 'string');
		stackName(stackName == '.') = '_';
        if stopPoint >= currentPosition(3)
            comparisonFunction = @le;
            movementFunction = @incZ;
        else
            comparisonFunction = @ge;
            movementFunction = @decZ;
        end
        
        imageCounter = 1;
        while comparisonFunction(currentPosition(3), stopPoint)
            % call the image acquisition function, letting it know the
            % current position
            rasterScan([path filesep cellName '.' stackName '.' num2str(imageCounter)], readASI);
            
            movementFunction();
            pause(.5); % wait for the movement to settle
            
            asiLocation;
            imageCounter = imageCounter + 1;
            currentPosition = readASI;
        end
        start(asiTimer);
    end

	function asiLocation(varargin)
        % update the position of the ASI on the graphical display
        currentPosition = readASI;
		tempLabel{1} = ['X = ' num2str(currentPosition(1)) char(181) 'm'];
		tempLabel{2} = ['Y = ' num2str(currentPosition(2)) char(181) 'm'];
		tempLabel{3} = ['Z = ' num2str(currentPosition(3)) char(181) 'm'];
		set(handles.lblLocation, 'string', tempLabel);
    end

    function currentPosition = readASI
        % return the current position of the ASI
        fwrite(commPort, [char(24) char(97) char(3) char(58)]);
        tempData = fread(commPort, 3, 'char');
        if numel(tempData)
            currentPosition(1) = (tempData(1) + 256 * tempData(2) + 256 * 256 * tempData(3)) / 10;
            if currentPosition(1) > 2^23 / 10
                currentPosition(1) = -2^24 / 10 + currentPosition(1);
            end
        else
            currentPosition(1) = nan;
            warning off last
        end

        fwrite(commPort, [char(25) char(97) char(3) char(58)]);
        tempData = fread(commPort, 3, 'char');
        if numel(tempData)
            currentPosition(2) = (tempData(1) + 256 * tempData(2) + 256 * 256 * tempData(3)) / 10;
            if currentPosition(2) > 2^23 / 10
                currentPosition(2) = -2^24 / 10 + currentPosition(2);
            end        
        else
            currentPosition(2) = nan;
            warning off last
        end

        fwrite(commPort, [char(26) char(97) char(3) char(58)]);
        tempData = fread(commPort, 3, 'char');
        if numel(tempData)
            currentPosition(3) = (tempData(1) + 256 * tempData(2) + 256 * 256 * tempData(3)) / 10;
            if currentPosition(3) > 2^23 / 10
                currentPosition(3) = -2^24 / 10 + currentPosition(3);
            end        
        else
            currentPosition(3) = nan;
            warning off last
        end
    end

    function closeMe(varargin)
        % close the serial ports and clean up data
        stop(asiTimer);                
        delete(asiTimer);        
        fclose(commPort);
        delete(commPort);
        rmappdata(0, 'asiPort');
		setpref('locations', 'asiGui', get(figHandle, 'position'));
		rmappdata(0, 'asiGui');
        delete(figHandle);
    end
end

function setupASI
    % ask what serial port the ASI is connected to
	commPort = inputdlg('Enter serial port to which ASI is connected', 'Comm ', 1, {'1'});
	if isempty(commPort)
		error('Must set a value for ASI serial port')
	end
	if any(commPort{1} > 57 | commPort{1} < 48)
		error('Value must be a number')
	end
	setpref('ASI', 'commPort', str2double(commPort));
end

function rasterScan(filename, currentLocation)
    % this is just a dummy function that you ought to replace with one that
    % takes images on your system    
    disp([filename ' taken at X = ' num2str(currentLocation(1)) char(181) 'm, Y = ' num2str(currentLocation(2)) char(181) 'm, Z = ' num2str(currentLocation(3)) char(181) 'm']);
    pause(1)
end