function SDRadarGUI()
%SDRADARGUI: A Graphical User Interface for Ancortek's Software-Defined
%Radar Kits
%   
% Protocol version: 10022019
%
% Dependencies: 
%     Matlab: R2017b or later
%     GUI Layout Toolbox by David Sampson: version 2.3.4
%
% Supported OS:
%    macOS, Windows
%    
% Copyright 2013-2020 Ancortek Inc.

% Note: ANCORTEK Inc fully supports any research work using
% SDRadar-KITs and provides MATLAB GUI and source codes upon request.
% The MATLAB GUI and MATLAB source codes are provided "as is", 
% with no guarantee whatsoever, and for non-commercial use only.  
% The ANCORTEK Inc will not be liable for any damage caused.

% suppress javacomponent warning
warning('off', 'MATLAB:ui:javacomponent:FunctionToBeRemoved');

% Add required dependencies
addpath(genpath('dependencies'))

% Instantiate an Ancortek.sdrdar object
sdr = ancortek.sdradar;

% Create GUI interface
gui = createInterface();

% Initial control element setup
controlElementEnable('off');

% Initial signal processing parameters
spParam = struct('nPulses', 32, 'waterfallHisSz', 50, 'decFactor', 16);

%-------------------------------------------------------------------------%
    function gui = createInterface()
        % Create the user interface for the application and return a
        % structure of handles for global use.
        gui = struct();
        
        % + Main window
        gui.MainWindow = figure('Name', 'Ancortek SDRadar GUI', ...
            'NumberTitle', 'off', 'MenuBar', 'none', 'Toolbar', 'none', ...
            'HandleVisibility', 'off', 'Visible', 'off', ...
            'Position', [100 100 1200 800]);
        
        % Maximize the main window        
        % screenSize = get(0, 'ScreenSize');
        % set(gui.MainWindow, 'OuterPosition', [0, 0, ...
        %        screenSize(3), screenSize(4)]);
        
        % move gui to center
        movegui(gui.MainWindow, 'onscreen');
        
        % + File menu
        gui.FileMenu = uimenu( gui.MainWindow, 'Label', 'File' );
        % uimenu( gui.FileMenu, 'Label', 'Save Radar Config.'); (TODO)
        % uimenu( gui.FileMenu, 'Label', 'Load Radar Config.'); (TODO)
        uimenu( gui.FileMenu, 'Label', 'Exit', 'Callback', @onExit );
        
        % + About menu
        % gui.AboutMenu = uimenu( gui.MainWindow, 'Label', 'About'); (TODO)

        % + Main layout
        mainLayout = uix.HBox('Parent', gui.MainWindow, 'Spacing', 3);
        
        % + Panels        
        panel1Layout = uix.VBox('Parent', mainLayout);
        viewPanel = uix.VBoxFlex('Parent', mainLayout);
        
        % Adjust the main window layout
        set(mainLayout, 'Widths', [300, -5])
        
        % + Box panels to panel 1 layout
        infoPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'SDR-KIT Information');
        gui.streamPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'TiTle', 'Streaming');
        gui.bwPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'Bandwidth Control');
        gui.chPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'Active Channel Selection');
        gui.chViewPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'Channel Viewing Selection');
        gui.paramPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'Radar Parameters');
        gui.filterPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'Filtering');
        gui.displayCtrlPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'Display Parameters');
        gui.recordPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'Data Recording');
        msgPanel = uix.BoxPanel('Parent', panel1Layout, ...
            'Title', 'Message');        
                
        % Adjust panel #1 layout
        set(panel1Layout, 'Heights', ...
            [50, 50, 200, 60, 60, 100, 50, 70, 100, 50]);
        
        % + control elements to infoPanel
        infoPanelLayout = uix.HBox('Parent', infoPanel, ...
            'Spacing', 10, 'Padding', 5);
        gui.modelDetect_pushButton = uicontrol(infoPanelLayout, ...
            'Style', 'pushbutton', 'String', 'Model Detection', ...
            'Callback', {@onModelDetect});
        gui.modelDetect_text = uicontrol(infoPanelLayout, ...
            'Style', 'text', 'BackgroundColor', 'w');

        set(infoPanelLayout, 'Widths', [-1, -2]);
        
        % + control elements to bwPanel
        bwPanelLayout = uix.VBox('Parent', gui.bwPanel);
        waveformPlotLayout = uix.HBox('Parent', bwPanelLayout);
        gui.bw_text = uicontrol(bwPanelLayout, ...
            'Style', 'text', 'BackgroundColor', 'w');
        set(bwPanelLayout, 'Heights', [-1 30]);

        % Bandwidth Range Slider
        jRangeSlider_BW = com.jidesoft.swing.RangeSlider;  % min,max,low,high        
        [gui.jhRangeSlider_BW, gui.hRangeSlider_BW]= ...
            javacomponent(jRangeSlider_BW, [0 0 30 60], waveformPlotLayout);
        set(gui.hRangeSlider_BW, 'Parent', waveformPlotLayout);
        set(gui.jhRangeSlider_BW, 'MajorTickSpacing',100,'MinorTickSpacing',10,'SnapToTicks',true,...
                          'Orientation', 1,'PaintTicks', 1,'PaintLabels',true, ...
                          'KeyPressedCallback', {@RangeSlider_BW_KeyPressedCallback},...
                          'MouseReleasedCallback',{@RangeSlider_BW_MouseReleasedCallback},...
                          'StateChangedCallback',{@RangeSlider_BW_StateChangedCallback});
        gui.jhRangeSlider_BW.show();
    
        % Create an axis
        gui.axes_waveformPlot = axes('Parent', waveformPlotLayout, ...
            'Color', 'black',  'XLim', [0 2], ...
            'XTick', [], 'YTick', [], 'Position', [0 0 0 0 ]);
        
        % Adjust waveformPlotLayout
        set(waveformPlotLayout, 'Widths', [60 -1], 'Padding', 10);

        % + control elements to streamPanel
        gui.startStream_togglebutton = uicontrol(gui.streamPanel, ...
            'Style', 'togglebutton', 'String', 'Start',...
            'Callback', {@startStreamCallback});
        set(gui.streamPanel, 'Padding', 5);
        
        % + control elements to chPanel
        chLayout = uix.Grid('Parent', gui.chPanel);
        uix.Empty('Parent', chLayout);
        gui.rx1_checkbox = uicontrol(chLayout, 'Style', 'checkbox',...
                                     'String', 'Rx 1', 'Callback', @RxCheckBox_Callback);
        gui.tx1_checkbox = uicontrol(chLayout, 'Style', 'checkbox',...
                                     'String', 'Tx 1', 'Callback', @TxCheckBox_Callback);
        gui.rx2_checkbox = uicontrol(chLayout, 'Style', 'checkbox',...
                                     'String', 'Rx 2', 'Callback', @RxCheckBox_Callback);
        gui.tx2_checkbox = uicontrol(chLayout, 'Style', 'checkbox',...
                                     'String', 'Tx 2', 'Callback', @TxCheckBox_Callback);
        gui.rx3_checkbox = uicontrol(chLayout, 'Style', 'checkbox',...
                                     'String', 'Rx 3', 'Callback', @RxCheckBox_Callback);
        uix.Empty('Parent', chLayout);
        gui.rx4_checkbox = uicontrol(chLayout, 'Style', 'checkbox',...
                                     'String', 'Rx 4', 'Callback', @RxCheckBox_Callback);
        set(chLayout, 'Widths', [-1 -1 -1 -1], 'Heights', [-1 -1])
        
        % + control elements to chViewPanel
        chViewLayout = uix.Grid('Parent', gui.chViewPanel);
        uicontrol(chViewLayout, 'Style', 'text', 'String', 'Viewing Tx #');
        uicontrol(chViewLayout, 'Style', 'text', 'String', 'Viewing Rx #');
        gui.txViewer_popupmenu = uicontrol(chViewLayout, 'Style', 'popupmenu', 'String', '1');
        gui.rxViewer_popupmenu = uicontrol(chViewLayout, 'Style', 'popupmenu', 'String', '1');

        set(chViewLayout, 'Widths', [-1 -1], 'Heights', [-1 -1], 'Spacing', 5, 'Padding', 5);

        % + control elements to paramPanel
        paramLayout = uix.Grid('Parent', gui.paramPanel);
        uicontrol(paramLayout, 'Style', 'text', 'String', 'Modulation');
        uicontrol(paramLayout, 'Style', 'text', 'String', 'Sweep time (us)');
        uicontrol(paramLayout, 'Style', 'text', 'String', 'Samples/sweep');
        gui.mod_popupmenu = uicontrol(paramLayout, 'Style', 'popupmenu', ...
            'String', {'FMCW Sawtooth', 'FMCW Triangle', 'CW'}, ...
            'Callback', @mod_popupmenu_Callback);
        gui.st_popupmenu = uicontrol(paramLayout, 'Style', 'popupmenu',...
                                     'String', {'125','250','500','1000','2000','4000','8000'}, 'Value', 4,...
                                     'Callback', @st_popupmenu_Callback);
        gui.nSamp_popupmenu = uicontrol(paramLayout, 'Style', 'popupmenu', ...
                                        'String', {'1024','512','256','128'}, 'Value', 4, ...
                                        'Callback', @nSamp_popupmenu_Callback);

        set(paramLayout, 'Widths', [-1 -1], 'Heights', [-1 -1 -1], 'Spacing', 5, 'Padding', 5);
        
        % + control elements to filterPanel
        gui.staticRemoval_checkbox = uicontrol(gui.filterPanel, 'Style', 'checkbox', ...
                                               'String', 'Remove Static Objects');
        
        % + control elements to displayCtrlPanel
        displayCtrlLayout = uix.VBox('Parent', gui.displayCtrlPanel);
        uicontrol(displayCtrlLayout, 'Style', 'text', 'String', 'Dynamic Range: ', 'HorizontalAlignment', 'left');
        dynamicRngLayout = uix.HBox('Parent',displayCtrlLayout);
        gui.autoDynamicRng_checkbox = uicontrol(dynamicRngLayout, 'Style', 'checkbox', 'String', 'Auto');
        jRangeSlider_DR = com.jidesoft.swing.RangeSlider(0,150,40,70);  % min,max,low,high
        [gui.jhRangeSlider_DR, gui.hRangeSlider_DR]= javacomponent(jRangeSlider_DR, [0 0 30 60], dynamicRngLayout);
        set(gui.jhRangeSlider_DR, 'MajorTickSpacing',10, 'MinorTickSpacing',1,...
                          'PaintTicks',true, 'MouseReleasedCallback', {@RangeSlider_DR_MouseReleaseCallback});
        
        set(displayCtrlLayout, 'Heights', [-4 -3], 'Padding', 5);
        set(dynamicRngLayout, 'Widths', [50, -1]);
        
        % + control elements to recordPanel
        recordPanelLayout = uix.VBox('Parent', gui.recordPanel);
        recordDurationLayout = uix.HBox('Parent', recordPanelLayout);
        
        currentVal = uint8(15);
        min = uint8(1);
        max = uint64(inf);
        step = uint8(1);
        numModel = javax.swing.SpinnerNumberModel(currentVal, min, max, step);
        recordJSpinner = javax.swing.JSpinner(numModel);
        [gui.recordJHSpinner, gui.recordHSpinner] = javacomponent(recordJSpinner, [0 0 30 60], recordDurationLayout);
        handle(gui.recordJHSpinner.getEditor.getTextField.getFormatter).setAllowsInvalid(0);
        handle(gui.recordJHSpinner.getEditor.getTextField.getFormatter).setCommitsOnValidEdit(1);
        gui.recordJHSpinner.setToolTipText('<html>Only positive integer values can be entered');

        uicontrol(recordDurationLayout, 'Style', 'text', 'String', 'Second(s)');
        
        gui.recordStart_pushButton = uicontrol(recordPanelLayout, ...
            'Style', 'pushbutton', 'String', 'Record I&Q', 'Callback', @startRecording);
        
        set(recordDurationLayout, 'Widths', [-2 -1], 'Spacing', 5, 'Padding', 5);
        set(recordPanelLayout, 'Heights', [-1 -1], 'Spacing', 5, 'Padding', 5);

        % + text field to msgPanel
        gui.msg_text = uicontrol(msgPanel, 'Style', 'text', 'BackgroundColor', 'w');

        % + Tab groups and axes to the view panel
        gui.tabgp_top = uitabgroup('Parent', viewPanel);
        gui.tab_time_scope = uitab(gui.tabgp_top, 'Title', 'Time-Scope');
        gui.axes_time_scope = axes('Parent', ...
            uicontainer('Parent', gui.tab_time_scope), 'Color', 'Black');
        
        gui.tab_panel = uix.TabPanel('Parent', viewPanel);
        gui.axes_range_scope = axes('Parent', ...
            uicontainer('Parent', gui.tab_panel), 'Color','Black');
        gui.axes_range_doppler = axes('Parent', ...
            uicontainer('Parent',gui.tab_panel),'Color','Black');
        gui.axes_range_waterfall = axes('Parent', ...
            uicontainer('Parent',gui.tab_panel),'Color','Black');
        gui.axes_velocity_scope = axes('Parent', ...
            uicontainer('Parent',gui.tab_panel), 'Color','Black');
        gui.axes_velocity_waterfall = axes('Parent', ...
            uicontainer('Parent',gui.tab_panel),'Color','Black');
        gui.tab_panel.TabTitles = {'Range Scope','Range Doppler',...
            'Range Waterfall','Velocity Scope','Velocity Waterfall'};
        gui.tab_panel.TabWidth = 100;
        gui.tab_panel.FontWeight = 'bold';
    end % createInterface
    
%-------------------------------------------------------------------------%
    function  waveformPlotUpdate(freq_low, freq_high)
    %BW_PLOT_UPDDATE update the waveform plot
    % INPUT:
    % freq_low, freq_high: frequency in MHz    
        
        set(gui.jhRangeSlider_BW, 'LowValue',freq_low, 'HighValue', freq_high);
        tickLabel{1} = javax.swing.JLabel(java.lang.String(num2str(freq_low/1000)));
        tickLabel{2} = javax.swing.JLabel(java.lang.String(num2str(freq_high/1000)));
        jht = java.util.Hashtable();
        jht.put(java.lang.Integer(freq_low), tickLabel{1});
        jht.put(java.lang.Integer(freq_high), tickLabel{2});
        set(gui.jhRangeSlider_BW, 'LabelTable', jht)
        if sdr.modulation_pre == ancortek.modOpt.FMCW_sawtooth || ...
                sdr.modulation == ancortek.modOpt.CW
            tmp = repmat(1:2,2,1);
            X = [0, tmp(:)'];
        elseif sdr.modulation_pre == ancortek.modOpt.FMCW_triangle
            X = 0:0.5:2;
        end
        Y = [repmat([freq_low, freq_high], 1, 2/1), freq_low];
        cla(gui.axes_waveformPlot)
        line(gui.axes_waveformPlot, X, Y, ...
             'Color', 'Green', 'LineWidth', 3);
        drawnow
        
        set(gui.bw_text, 'String', sprintf('%.3f -- %.3f GHz', freq_low/1e3, freq_high/1e3));
    end % waveformPlotUpdate

%-------------------------------------------------------------------------%
    function RangeSlider_BW_StateChangedCallback(~,~)
    %RANGESLIDER_BW_STATECHANGEDCALLBACK    

        waveformPlotUpdate(gui.jhRangeSlider_BW.low, gui.jhRangeSlider_BW.high);
    end % RangeSlider_BW_StateChangedCallback
    
%-------------------------------------------------------------------------%
    function RangeSlider_BW_MouseReleasedCallback(src, ~)
    %RANGESLIDER_BW_MOUSERELEASEDCALLBACK 
        
        BW_max = sdr.bw_max_GHz * 1e3; 
        
        % keep the maximum bandwidth within hardware limit
        if (src.high - src.low) > BW_max 
            if src.low < sdr.fStart/1e6 % Starting frequency slider was moved
                src.high = min(src.low + BW_max, src.Maximum);                
            else % Stopping frequency slider was moved
                src.low = max(src.high - BW_max, src.Minimum);                
            end
        end
        
        % Send instruction to radar
        sdr.bw_update(src.low*1e6, src.high*1e6);
        
        % default to tx1 when operating in CW mode
        if sdr.modulation == ancortek.modOpt.CW && sdr.nTx_max == 2
            gui.tx1_checkbox.Value = 1;
            gui.tx2_checkbox.Value = 0;
            gui.tx2_checkbox.Enable = 'off';
            gui.txViewer_popupmenu.String = {'1'};
            gui.txViewer_popupmenu.Value = 1;         
        else
            gui.tx2_checkbox.Enable = 'on';
        end
              
        activeTabsUpdate();
        axesUpdate();
    end % RangeSlider_BW_MouseReleasedCallback

%-------------------------------------------------------------------------%    
    function RangeSlider_BW_KeyPressedCallback(src, evnt) % (TODO)
    %RANGESLIDER_BW_KEYPRESSEDCALLBACK

    end % RangeSlider_BW_KeyPressedCallback

%-------------------------------------------------------------------------%
    function activeTabsUpdate()
        
        if gui.jhRangeSlider_BW.high == gui.jhRangeSlider_BW.low || ...
                sdr.modulation == ancortek.modOpt.CW
            setVal = find(ismember(gui.mod_popupmenu.String, 'CW'));
            gui.mod_popupmenu.Value = setVal;
            gui.tab_panel.TabEnables = {'off','off','off','on','off'};
            gui.tab_panel.Selection = 4;
        elseif sdr.modulation == ancortek.modOpt.FMCW_sawtooth
            setVal = find(ismember(gui.mod_popupmenu.String, 'FMCW Sawtooth'));
            gui.mod_popupmenu.Value = setVal;            
            gui.tab_panel.TabEnables = {'on','on','off','off','off'};
            gui.tab_panel.Selection = 1;
        elseif sdr.modulation == ancortek.modOpt.FMCW_triangle
            setVal = find(ismember(gui.mod_popupmenu.String, 'FMCW Triangle'));
            gui.mod_popupmenu.Value = setVal;              
            gui.tab_panel.TabEnables = {'off','off','off','off','off'};
            gui.tab_panel.Selection = 1;
        end
    end % activeTabsUpdate
    
%-------------------------------------------------------------------------%
    function onModelDetect(~, ~)
    %ONMODELDETECT Callback function for "modelDetect_pushButton"

        try 
            sdr.init;
            set(gui.msg_text, 'String', 'SDR-KIT connected', ...
                'ForegroundColor', 'green');
            set(gui.modelDetect_text, 'String', sdr.model_s);
            controlElementEnable('on');
            
            % Set default control elements
            if sdr.nTx_max == 2
                gui.tx1_checkbox.Enable = 'on';
                gui.tx2_checkbox.Enable = 'on';
            else
                gui.tx1_checkbox.Enable = 'off';
                gui.tx2_checkbox.Enable = 'off';                
            end
            
            if sdr.nRx_max == 4
                gui.rx1_checkbox.Enable = 'on';
                gui.rx2_checkbox.Enable = 'on';
                gui.rx3_checkbox.Enable = 'on';
                gui.rx4_checkbox.Enable = 'on';
            elseif sdr.nRx_max == 2
                gui.rx1_checkbox.Enable = 'on';
                gui.rx2_checkbox.Enable = 'on';
                gui.rx3_checkbox.Enable = 'off';
                gui.rx4_checkbox.Enable = 'off';
            else
                gui.rx1_checkbox.Enable = 'on';
                gui.rx2_checkbox.Enable = 'off';
                gui.rx3_checkbox.Enable = 'off';
                gui.rx4_checkbox.Enable = 'off';
            end            

            rxBitMask = dec2bin(double(sdr.activeRx), 4);
            txBitMask = dec2bin(double(sdr.activeTx), 2);
            
            rxViewerMenuUpdate(rxBitMask);
            txViewerMenuUpdate(txBitMask);
            nSampMenuUpdate();

            gui.jhRangeSlider_BW.maximum = sdr.fMax/1e6;
            gui.jhRangeSlider_BW.minimum = sdr.fMin/1e6;
            gui.axes_waveformPlot.YLim = [sdr.fMin/1e6, sdr.fMax/1e6];

            waveformPlotUpdate(sdr.fStart/1e6, sdr.fStop/1e6);
            activeTabsUpdate();
            axesUpdate();

        catch ME
            set(gui.msg_text, 'String', ME.message, 'ForegroundColor', 'red');
            set(gui.modelDetect_text, 'String', '');
            controlElementEnable('off');
        end
    end % onModelDetect
    
%-------------------------------------------------------------------------%
    function rxViewerMenuUpdate(rxBitMask)
    %RXVIEWERMENUUPDATE update rxViewer_popupmenu
    % rxBitMask can be either char array or array of 1s and 0s

        fieldName = {'rx4_checkbox'; 'rx3_checkbox'; 'rx2_checkbox'; 'rx1_checkbox'};
        cellForRxSelect = {};
        for ii=4:-1:1
            if ischar(rxBitMask(ii))
                rxBitMask(ii) = str2double(rxBitMask(ii));                
            end

           if rxBitMask(ii)
               cellForRxSelect{end+1} = num2str(abs(ii-5)); %#ok<AGROW>
               gui.(fieldName{ii}).Value = 1;
           else
               gui.(fieldName{ii}).Value = 0;
           end
        end

        gui.rxViewer_popupmenu.String = cellForRxSelect;
        gui.rxViewer_popupmenu.Value = 1;
    end % rxViewerMenuUpdate
    
%-------------------------------------------------------------------------%
    function txViewerMenuUpdate(txBitMask)
        %TXVIEWERMENUUPDATE updaate txViewer_popupmenu
        % txBitMask can be either char array or array of 1s and 0s
        
        fieldName = {'tx2_checkbox'; 'tx1_checkbox'};
        cellForTxViewer = {};
        for ii=2:-1:1
            if ischar(txBitMask(ii))
                txBitMask(ii) = str2double(txBitMask(ii));                
            end                    
            
            if txBitMask(ii)
                cellForTxViewer{end+1} = num2str(abs(ii-3)); %#ok<AGROW>
                gui.(fieldName{ii}).Value = 1;
            else
                gui.(fieldName{ii}).Value = 0;
            end
        end
        
        gui.txViewer_popupmenu.String = cellForTxViewer;
        gui.txViewer_popupmenu.Value = 1;
    end %  txViewerMenuUpdate

%-------------------------------------------------------------------------%
    function controlElementEnable(enabled)
    %CONTROLELEMENTENABLE
    % input:
    %     enabled: 'on' | 'off'    
            
        set(findall(gui.streamPanel, '-property', 'enable'), 'enable', enabled);
        set(findall(gui.chPanel, '-property', 'enable'), 'enable', enabled);
        set(findall(gui.paramPanel, '-property', 'enable'), 'enable', enabled);
        set(findall(gui.chViewPanel, '-property', 'enable'), 'enable', enabled);
        set(findall(gui.filterPanel, '-property', 'enable'), 'enable', enabled);
        set(findall(gui.displayCtrlPanel, '-property', 'enable'), 'enable', enabled);
        set(findall(gui.recordPanel, '-property', 'enable'), 'enable', enabled);
    
        if strcmp(enabled, 'on')
           gui.jhRangeSlider_BW.enabled = 1;
           gui.jhRangeSlider_DR.enabled = 1;
           gui.recordJHSpinner.enabled = 1;           
        else
            gui.jhRangeSlider_BW.enabled = 0;
            gui.jhRangeSlider_DR.enabled = 0;
            gui.recordJHSpinner.enabled = 0;
        end

        if strcmp(enabled, 'on')
            if sdr.nTx_max == 2
                gui.tx1_checkbox.Enable = 'on';
                gui.tx2_checkbox.Enable = 'on';
            else
                gui.tx1_checkbox.Enable = 'off';
                gui.tx2_checkbox.Enable = 'off';
            end
            
            if sdr.nRx_max == 4
                gui.rx1_checkbox.Enable = 'on';
                gui.rx2_checkbox.Enable = 'on';
                gui.rx3_checkbox.Enable = 'on';
                gui.rx4_checkbox.Enable = 'on';
            elseif sdr.nRx_max == 2
                gui.rx1_checkbox.Enable = 'on';
                gui.rx2_checkbox.Enable = 'on';
                gui.rx3_checkbox.Enable = 'off';
                gui.rx4_checkbox.Enable = 'off';
            else
                gui.rx1_checkbox.Enable = 'on';
                gui.rx2_checkbox.Enable = 'off';
                gui.rx3_checkbox.Enable = 'off';
                gui.rx4_checkbox.Enable = 'off';
            end
        end
    end % controElementEnable

%-------------------------------------------------------------------------%    
    function nSampMenuUpdate()
    %NSAMPMENUUPDATE nSamp_popupmenu selection update
        
    % curVal = gui.nSamp_popupmene.Value;        
        set(gui.nSamp_popupmenu, 'String', {num2str(sdr.nSamp_max),...
                            num2str(sdr.nSamp_max/2), ...
                            num2str(sdr.nSamp_max/4), ...
                            num2str(sdr.nSamp_max/8)});
        
    end % nSampMenuUpdate

%-------------------------------------------------------------------------%            
    function startStreamCallback(~, ~)
    
        if gui.startStream_togglebutton.Value == gui.startStream_togglebutton.Min
            gui.startStream_togglebutton.String = 'Start';
        else
            gui.startStream_togglebutton.String = 'Stop';
        end
    
        while  isvalid(gui.MainWindow) && ...
                (gui.startStream_togglebutton.Value == gui.startStream_togglebutton.Max)
                          
            try
                [Is, Qs] = sdr.get_IQCube(spParam.nPulses); % fast samples by receiving channels by slow samples
                
                if sdr.nTx == 2
                    % virtual array
                    Is_t1 = Is(:,:,1:2:end);
                    Is_t2 = Is(:,:,2:2:end);
                    Qs_t1 = Qs(:,:,1:2:end);
                    Qs_t2 = Qs(:,:,2:2:end);
                    Is = cat(2, Is_t1, Is_t2);
                    Qs = cat(2, Qs_t1, Qs_t2);
                end
                
                % current viewing channel
                chView = sdr.nRx*(gui.txViewer_popupmenu.Value - 1) + gui.rxViewer_popupmenu.Value;
                
                if sdr.modulation == ancortek.modOpt.FMCW_sawtooth || ...
                        sdr.modulation == ancortek.modOpt.FMCW_triangle
                    % Time-scope display
                    gui.hLine_time_scope(1).YData = Is(:, chView, 2);
                    gui.hLine_time_scope(2).YData = Qs(:, chView, 2);
                    drawnow  
                elseif sdr.modulation == ancortek.modOpt.CW
                    cwDat = complex(Is(:,chView,11:end), Qs(:,chView,11:end));
                    cwDat = cwDat(:);
                    cwDat = cwDat - mean(cwDat);
                    cwDat_dec = decimate(cwDat, spParam.decFactor);

                    % Time-scope display
                    gui.hLine_time_scope(1).YData = real(cwDat_dec);
                    gui.hLine_time_scope(2).YData = imag(cwDat_dec);
                    drawnow                    
                end
                
                switch gui.tab_panel.Selection
                  case 1 % Range Scope
                    % Range Processing
                    rngFFTOut = fftshift(fft(complex(Is, Qs), spParam.nfft_rng, 1), 1);
                    rngFFTOut = rngFFTOut(spParam.nfft_rng/2+1:end,:,:);
                      
                    if gui.staticRemoval_checkbox.Value 
                        rngProfile = -rngFFTOut(:, chView, 1) + 2* rngFFTOut(:, chView, 2) - ...
                            rngFFTOut(:, chView, 3);
                    else
                        rngProfile = rngFFTOut(:, chView, 1);
                    end                    
                
                    Range_psd = (1/spParam.df_rng).*abs(rngProfile).^2;
                    Range_psd(2:end-1) = 2*Range_psd(2:end-1);
                    Range_psd = 10*log10(Range_psd + eps); % power spectral density
                    [Range_psd_max, rng_psd_max_index] = max(Range_psd); % search for one peak
                    [Range_psd_min, ~] = min(Range_psd);
                    rng_max = spParam.rng_axis(rng_psd_max_index); % range estimate

                    gui.hLine_range_scope.YData = Range_psd;
                    gui.hLineV_range_scope.XData = [rng_max, rng_max];
                    gui.hLineV_range_scope.YData = [Range_psd_min Range_psd_max];
                    label = sprintf('Detected Range: %4.2f m ', rng_max);
                    gui.hText_range_scope.String = label;
                    gui.hText_range_scope.Position = [rng_max+1, Range_psd_max-1];
                    axis(gui.axes_range_scope, [0, sdr.rngMax, 0, max(Range_psd_max,1)]);
                    drawnow
                    
                  case 2 % Range Doppler
                    % Range Processing
                    rngFFTOut = fftshift(fft(complex(Is, Qs), spParam.nfft_rng, 1), 1);                    
                    rngFFTOut = rngFFTOut(spParam.nfft_rng/2+1:end,:,:);
                      
                    if gui.staticRemoval_checkbox.Value
                        rngFFTOut = bsxfun(@minus, rngFFTOut, mean(rngFFTOut, 3));
                    end

                    % Doppler processing 
                    doppFFTOut = fftshift(fft(rngFFTOut, spParam.nfft_vel,3),3);
                    RngDopp_psd_dB = squeeze(10*log10(abs(doppFFTOut(:, chView, :)).^2 * 1/(spParam.df_dopp * spParam.df_rng)));
                    RngDopp_psd_dB(isinf(RngDopp_psd_dB)) = 0;
                    gui.hRngDopp.CData = RngDopp_psd_dB;
                    drawnow;
                    
                    if gui.autoDynamicRng_checkbox.Value
                        AutoDRMaxVal = max(max(RngDopp_psd_dB));
                        AutoDRMaxVal = max(AutoDRMaxVal, 88);
                        
                        [row,col] = size(RngDopp_psd_dB);
                        AutoDRMinVal = sum(sum(RngDopp_psd_dB))/(row*col);
                        AutoDRMinVal = max(AutoDRMinVal, 40);
                        
                        gui.jhRangeSlider_DR.high = AutoDRMaxVal;
                        gui.jhRangeSlider_DR.low = AutoDRMinVal;      
                        set(gui.axes_range_doppler, 'CLim', [AutoDRMinVal, AutoDRMaxVal]);
                    end
                    
                  case 3 % Range waterfall (TODO)                    
                    
                  case 4 % Velocity scope                    
                    doppFFTOut = fftshift(fft(cwDat_dec, spParam.nfft_vel, 1), 1);
                    Velocity_psd = (1/spParam.df_dopp).*abs(doppFFTOut).^2;
                    Velocity_psd(2:end-1) = 2*Velocity_psd(2:end-1);
                    Velocity_psd = 10*log10(Velocity_psd + eps); % power spectral density
                    [vel_psd_max, vel_psd_max_index] = max(Velocity_psd); % search for one peak
                    vel_max = spParam.vel_axis(vel_psd_max_index); % velocity estimate

                    gui.hLine_velocity_scope.YData = Velocity_psd;
                    gui.hLineV_velocity_scope.XData = [vel_max, vel_max];
                    gui.hLineV_velocity_scope.YData = [0 vel_psd_max];
                    label = sprintf('Detected Range: %4.2f m/s ', vel_max);
                    gui.hText_velocity_scope.String = label;
                    gui.hText_velocity_scope.Position = [vel_max+1, vel_psd_max-1];
                    axis(gui.axes_velocity_scope, [-sdr.vMax_cw/spParam.decFactor, ...
                        sdr.vMax_cw/spParam.decFactor, 0, max(vel_psd_max,1)]);
                    drawnow

                  case 5 % Velocity waterfall (TODO)

                end % gui.tab_panel.Selection
                
            catch
                continue
            end
        end
    end % startStreamCallback
    
%-------------------------------------------------------------------------%            
    function TxCheckBox_Callback(src, ~)
    %TXCHECKBOX_CALLBACK A common callback for tx checkboxes
    %
    % In case Tx checkboxes are all clicked off    
        
        if sum([gui.tx1_checkbox.Value gui.tx2_checkbox.Value]) == 0
            src.Value = 1; 
        end
        
        if sdr.modulation == ancortek.modOpt.CW
           gui.tx1_checkbox.Value = 1;
           gui.tx2_checkbox.Value = 0;
        end
        
        txBitMask = [gui.tx2_checkbox.Value gui.tx1_checkbox.Value];
        txViewerMenuUpdate(txBitMask);
        
        % Send instruction to radar
        sdr.activeTx = ancortek.txOpt(bin2dec(num2str(txBitMask)));

    end %TxCheckBox_Callback
    
%-------------------------------------------------------------------------%            
    function RxCheckBox_Callback(src, ~) %#ok<*DEFNU>
    %RXCHECKBOX_CALLBACK A common callback for rx checkboxes
    %
    % Rx channel only works in pairs, Rx1 & Rx2 or Rx3 & Rx4.    
        
        value = get(src, 'Value'); % current checkbox value
        src = src.String; % checkbox source in string
        
        rxBitMask = [gui.rx4_checkbox.Value gui.rx3_checkbox.Value ...
                     gui.rx2_checkbox.Value gui.rx1_checkbox.Value];

        % prevent 0 rx case
        if sum(rxBitMask) == 0
           src.Value = 1; 
           return;
        end
        
    % correct the bit mask for 3-receiver cases
        if sum(rxBitMask)== 3
            if value % setting the ith bit
                rxBitMask = [1 1 1 1];
            else % clearing the ith bit
                switch src
                  case {'Rx 3', 'Rx 4'}
                    rxBitMask = [0 0 1 1];
                  case {'Rx 1','Rx 2'}
                    rxBitMask = [1 1 0 0];
                end
            end
        end
        
    % correct the bit mask for 2-receiver cases
        if isequal(rxBitMask, [0 1 0 1])
            switch src 
              case 'Rx 1'
                rxBitMask = [0 0 0 1];
              case 'Rx 3'
                rxBitMask = [0 1 0 0];
            end 
        elseif isequal(rxBitMask, [1 0 1 0])
            switch src 
              case 'Rx 2'
                rxBitMask = [0 0 1 1];
              case 'Rx 4'
                rxBitMask = [1 0 0 0];
            end
        elseif isequal(rxBitMask, [0 1 1 0])
            switch src
              case 'Rx 2'
                rxBitMask = [0 0 1 0];
              case 'Rx 3'
                rxBitMask = [0 1 0 0];
            end
        elseif isequal(rxBitMask, [1 0 0 1])
            switch src
              case 'Rx 1'
                rxBitMask = [0 0 0 1];
              case 'Rx 4'
                rxBitMask = [1 0 0 0];
            end
        end
        
        rxViewerMenuUpdate(rxBitMask)
        
        % Send instruction to radar
        sdr.activeRx = ancortek.rxOpt(bin2dec(num2str(rxBitMask)));
        
        nSampMenuUpdate();
        axesUpdate();
    end % RxCheckBox_Callback    

%-------------------------------------------------------------------------%            
    function nSamp_popupmenu_Callback(src, ~)
    %NSAMP_POPUPMENU_CALLBACK
    
        % Send instruction to radar
        sdr.samplesPerSweep = ancortek.sampOpt(src.Value - 1);
        
        spParam.fft_rng = 2*2^nextpow2(sdr.nSamp_up);
        axesUpdate();
    end %nSamp_popupmenu_Callback

%-------------------------------------------------------------------------%            
    function mod_popupmenu_Callback(src, ~)
        %MOD_POPUPMENU_CALLBACK
        
        curVal = src.Value;
        curString = src.String{curVal};
        
        if strcmp(curString, 'FMCW Sawtooth')
            % Send instruction to radar
            sdr.modulation = ancortek.modOpt.FMCW_sawtooth;
            if sdr.fStart + 100e6 <= sdr.fMax
                sdr.bw_update(sdr.fStart, sdr.fStart+100e6);
            else
                sdr.bw_update(sdr.fStop, sdr.fStop-100e6);
            end
            
        elseif strcmp(curString, 'FMCW Triangle')
            sdr.modulation = ancortek.modOpt.FMCW_triangle;
            if sdr.fStart + 100e6 <= sdr.fMax
                sdr.bw_update(sdr.fStart, sdr.fStart+100e6);
            else
                sdr.bw_update(sdr.fStop, sdr.fStop-100e6);
            end
            
        elseif strcmp(curString, 'CW')
            sdr.modulation = ancortek.modOpt.CW;          
            sdr.bw_update(sdr.fStart, sdr.fStart);          
        end
        
        % disable tx2 in cw
       if strcmp(curString, 'CW') && sdr.nTx_max == 2
           gui.tx2_checkbox.Enable = 'off';
           gui.tx2_checkbox.Value = 0;
           gui.txViewer_popupmenu.String = {'1'};
           gui.txViewer_popupmenu.Value = 1;        
       else
           gui.tx2_checkbox.Enable = 'on';
       end
        
        waveformPlotUpdate(sdr.fStart/1e6, sdr.fStop/1e6);
        activeTabsUpdate();
        axesUpdate();        
        
    end %mod_popupmenu_Callback
%-------------------------------------------------------------------------%
    function st_popupmenu_Callback(src, ~)
    %ST_POPUPMENU_CALLBACK 
        
        % Send instruction to radar
        sdr.sweeptime = ancortek.stOpt(src.Value - 1);

        nSampMenuUpdate();
        waveformPlotUpdate(sdr.fStart/1e6, sdr.fStop/1e6);
        spParam.fft_rng = 2*2^nextpow2(sdr.nSamp_up);
        axesUpdate();
    end % st_popupmenu_Callback
    
%-------------------------------------------------------------------------%
    function axesUpdate()
        %AXESUPDATE Update display axes when radar parameters are changed
               
        switch sdr.modulation
            case ancortek.modOpt.FMCW_sawtooth
                spParam.nfft_rng = max(1024, 2*sdr.nSamp);
                spParam.nfft_vel = max(1024, 2*spParam.nPulses);
                
                % Time-scope display
                cla(gui.axes_time_scope, 'reset');
                set(gui.axes_time_scope, 'Color','Black');
                
                spParam.rng_axis = linspace(0, sdr.rngMax, spParam.nfft_rng/2);
                spParam.vel_axis = linspace(-sdr.vMax, sdr.vMax, spParam.nfft_vel);
                spParam.df_rng = sdr.fs / spParam.nfft_rng;
                spParam.df_dopp = sdr.prf / spParam.nfft_vel;
                
                gui.hLine_time_scope = line(gui.axes_time_scope, ...
                    1:sdr.nSamp_up,  NaN(2, sdr.nSamp_up),...
                    'LineWidth', 3);
                
                gui.hLine_time_scope(1).Color = [1 1 0];
                gui.hLine_time_scope(2).Color = [0 1 0];
                axis(gui.axes_time_scope, [1 sdr.nSamp_up -inf inf]);
                xlabel(gui.axes_time_scope, 'Sampling number');
                ylabel(gui.axes_time_scope, 'Amplitude');
                legend(gui.axes_time_scope, {'I-Channel', 'Q-Channel'},...
                    'FontSize', 12, 'TextColor', 'k',...
                    'Color',[0.65,0.65,0.65]); % Create the legend
                set(gui.axes_time_scope, 'XGrid','off', 'YGrid', 'on',...
                    'GridColor', [0.23 0.44 0.34], 'GridLineStyle', '--',...
                    'GridAlpha', 0.8)
                
                % Range-scope display
                cla(gui.axes_range_scope, 'reset');
                set(gui.axes_range_scope, 'Color', 'Black');
                gui.hLine_range_scope = line(gui.axes_range_scope, ...
                    spParam.rng_axis, NaN(1, spParam.nfft_rng/2), 'LineWidth', 3);
                gui.hLine_range_scope.Color = [0 1 0];
                axis(gui.axes_range_scope, [0, sdr.rngMax, 0, 20]);
                xlabel(gui.axes_range_scope, 'Range (meters)');
                ylabel(gui.axes_range_scope, 'Power/frequency (dB/Hz)');
                hold(gui.axes_range_scope, 'on');
                grid(gui.axes_range_scope, 'on');
                set(gui.axes_range_scope, 'GridColor', [0.23 0.44 0.34],...
                    'GridLineStyle', '--', 'GridAlpha', 0.8)
                gui.hLineV_range_scope = line(gui.axes_range_scope, [0 0],[-1000 1000],...
                    'LineStyle', '--', 'Color', [1 1 0],...
                    'LineWidth', 3);
                gui.hText_range_scope = text(gui.axes_range_scope, 'Color', [1 1 0]);
                
                % Range Doppler
                cla(gui.axes_range_doppler, 'reset');
                gui.hRngDopp = imagesc(gui.axes_range_doppler, spParam.vel_axis, spParam.rng_axis, []);
                axis(gui.axes_range_doppler, 'xy');
                axis(gui.axes_range_doppler, [-sdr.vMax sdr.vMax 0 sdr.rngMax]);
                set(gui.axes_range_doppler, ...
                    'CLim', [gui.jhRangeSlider_DR.low, gui.jhRangeSlider_DR.high])
                xlabel(gui.axes_range_doppler, 'Velocity (m/s)');
                ylabel(gui.axes_range_doppler, 'Range (m)');
                legend(gui.axes_range_doppler, 'off');
                shading(gui.axes_range_doppler, 'interp');
                
            case ancortek.modOpt.CW
                datLen = (spParam.nPulses-10)*sdr.nSamp/spParam.decFactor; % discard the first 10 pulses
                spParam.nfft_vel = max(1024, 2*datLen);
                spParam.df_dopp = sdr.fs/spParam.decFactor/spParam.nfft_vel;
                spParam.vel_axis = linspace(-sdr.vMax_cw/spParam.decFactor,...
                    sdr.vMax_cw/spParam.decFactor, spParam.nfft_vel);
                
                % Time-scope display
                cla(gui.axes_time_scope, 'reset');
                set(gui.axes_time_scope, 'Color','Black');
                
                xTime = (1:datLen)*1e3*sdr.sTime/sdr.nSamp;
                gui.hLine_time_scope = line(gui.axes_time_scope, ...
                    xTime,  NaN(2, length(xTime)),...
                    'LineWidth', 3);
                
                gui.hLine_time_scope(1).Color = [1 1 0];
                gui.hLine_time_scope(2).Color = [0 1 0];
                axis(gui.axes_time_scope, [1 max(xTime) -inf inf]);
                xlabel(gui.axes_time_scope, 'Time (ms)');
                ylabel(gui.axes_time_scope, 'Amplitude');
                legend(gui.axes_time_scope, {'I-Channel', 'Q-Channel'},...
                    'FontSize', 12, 'TextColor', 'k',...
                    'Color',[0.65,0.65,0.65]); % Create the legend
                set(gui.axes_time_scope, 'XGrid','off', 'YGrid', 'on',...
                    'GridColor', [0.23 0.44 0.34], 'GridLineStyle', '--',...
                    'GridAlpha', 0.8)
                
                % Velocity scope
                cla(gui.axes_velocity_scope, 'reset');
                set(gui.axes_velocity_scope, 'Color', 'k');
                gui.hLine_velocity_scope = line(gui.axes_velocity_scope, ...
                    spParam.vel_axis, NaN(1, spParam.nfft_vel), 'LineWidth', 3);
                gui.hLine_velocity_scope.Color = [0 1 0];
                hold(gui.axes_velocity_scope, 'on');
                axis(gui.axes_velocity_scope, [-sdr.vMax_cw/spParam.decFactor,...
                    sdr.vMax_cw/spParam.decFactor, 0, inf]);
                xlabel(gui.axes_velocity_scope, 'Velocity (m/s)');
                ylabel(gui.axes_velocity_scope, 'Intensity (dB)');
                gui.hLineV_velocity_scope = line(gui.axes_velocity_scope, ...
                    [0 0 ],[0 20], 'LineStyle', '--', 'Color', [1 1 0], 'LineWidth', 3);
                gui.hText_velocity_scope = text(gui.axes_velocity_scope, 'Color', [1 1 0]);
                grid(gui.axes_velocity_scope, 'on');
                set(gui.axes_velocity_scope, 'GridColor', [0.23 0.44 0.34], 'GridLineStyle', '--', 'GridAlpha', 0.8)
                
            case ancortek.modOpt.FMCW_triangle
                
                % Time-scope display
                cla(gui.axes_time_scope, 'reset');
                set(gui.axes_time_scope, 'Color','Black');
                
                gui.hLine_time_scope = line(gui.axes_time_scope, ...
                    1:sdr.nSamp,  NaN(2, sdr.nSamp),...
                    'LineWidth', 3);
                
                gui.hLine_time_scope(1).Color = [1 1 0];
                gui.hLine_time_scope(2).Color = [0 1 0];
                axis(gui.axes_time_scope, [1 sdr.nSamp -inf inf]);
                xlabel(gui.axes_time_scope, 'Sampling number');
                ylabel(gui.axes_time_scope, 'Amplitude');
                legend(gui.axes_time_scope, {'I-Channel', 'Q-Channel'},...
                    'FontSize', 12, 'TextColor', 'k',...
                    'Color',[0.65,0.65,0.65]); % Create the legend
                set(gui.axes_time_scope, 'XGrid','off', 'YGrid', 'on',...
                    'GridColor', [0.23 0.44 0.34], 'GridLineStyle', '--',...
                    'GridAlpha', 0.8)
        end
        
        drawnow
        
    end % axesUpdate

%-------------------------------------------------------------------------%                
    function startRecording(~,~)
    %STARTRECORDING
        
        origMsg = gui.msg_text.String;
        set(gui.msg_text, 'String', 'Recording...', 'ForegroundColor', 'green');
        controlElementEnable('off');
        drawnow
        
    % Request data
        num_of_sweeps = gui.recordJHSpinner.Value / sdr.sTime / sdr.nTx;
        DATE = datestr(now);
        [Is, Qs] = sdr.get_IQCube(num_of_sweeps); % fast samples by receiving channels by slow samples
        
        if sdr.nTx == 2
        % virtual array
            Is_t1 = Is(:,:,1:2:end);
            Is_t2 = Is(:,:,2:2:end);
            Qs_t1 = Qs(:,:,1:2:end);
            Qs_t2 = Qs(:,:,2:2:end);
            Is = cat(2, Is_t1, Is_t2);
            Qs = cat(2, Qs_t1, Qs_t2);
        end

    % Prompt window
        DATE_vec = datevec(DATE);
        filename = sprintf('%2.f_%2.f_%2.f_%2.f_%2.f_%2.f', DATE_vec(1), DATE_vec(2), ...
                           DATE_vec(3), DATE_vec(4), DATE_vec(5), DATE_vec(6));
        [file, path] = uiputfile({'.mat'},'Save file name',filename);
        filename = [path, file];
        if file == 0
            set(gui.msg_text, 'String', origMsg, 'ForegroundColor', 'green');
            controlElementEnable('on');
            return
        end
        
        % convert the sdr object to a struct before saving. Saving the sdr
        % object directly will need to carry the SDRadar class definition
        % when loading
        sdrParam.model_s = sdr.model_s;
        sdrParam.rf_sn = sdr.rf_sn;
        sdrParam.pm_sn = sdr.pm_sn;
        sdrParam.nTx = sdr.nTx;
        sdrParam.nRx = sdr.nRx;
        sdrParam.fStart = sdr.fStart;
        sdrParam.fStop = sdr.fStop;
        sdrParam.sTime = sdr.sTime;
        sdrParam.Tramp_up = sdr.Tramp_up;
        sdrParam.Tramp_dn = sdr.Tramp_dn;
        sdrParam.nSamp = sdr.nSamp;
        sdrParam.nSamp_up = sdr.nSamp_up;
        sdrParam.modulation = cast(sdr.modulation,'uint16');
        sdrParam.activeTx = cast(sdr.activeTx,'uint16');
        sdrParam.activeRx = cast(sdr.activeRx,'uint16');
        
        if strcmp(file(end-3:end), '.mat')            
            save(filename, 'DATE','Is','Qs','sdrParam');
        end

        set(gui.msg_text, 'String', origMsg, 'ForegroundColor', 'green');
        controlElementEnable('on');
    end % startRecording
    
%-------------------------------------------------------------------------%                
    function RangeSlider_DR_MouseReleaseCallback(~,~)        
        axesUpdate();
    end % RangeSlider_DR_MouseReleaseCallback
    
%-------------------------------------------------------------------------%                
    function onExit(~,~)        
        delete(gui.MainWindow)
    end % onExit
%-------------------------------------------------------------------------%                
    % show the main window
    set(gui.MainWindow, 'Visible', 'on')    
    
    % store the gui structure with the mainWindow
    guidata(gui.MainWindow, gui);
end