classdef bleDialog < wirelessWaveformGenerator.WaveformConfigurationDialog
    %

    %   Copyright 2018-2023 The MathWorks, Inc.

    properties (Dependent)
        DataRate      % PHY mode
        Sps           % Samples per Symbol
        ChannelIndex  % Channel index
        AccessAddress % Access address
        EnableDFPacket% Enable/disable direction finding packet
        DFPacketType  % Direction finding packet type
        Whiten        % Data whiten
        ModulationIndexBLE % GFSK modulation index
        PulseLength   % Pulse length
    end

    properties (Hidden)
        TitleString = getString(message('bluetooth:waveformGeneratorApp:BLEPHYTitle'))
        configFcn = @struct
        configGenFcn = @struct
        configGenVar = 'cfg'

        DataRateType = 'popupmenu'
        DataRateDropDown = {'2 Mbps', '1 Mbps', '500 Kbps', '125 Kbps'} % Values displayed to user
        mappedDataRate = {'LE2M', 'LE1M', 'LE500K', 'LE125K'}; % Values accepted by bleWaveformGenerator
        DataRateLabel
        DataRateGUI

        SpsType = 'edit'
        SpsGUI
        SpsLabel

        ChannelIndexType = 'edit'
        ChannelIndexGUI
        ChannelIndexLabel

        AccessAddressType = 'edit'
        AccessAddressGUI
        AccessAddressLabel

        EnableDFPacketType = 'checkbox'
        EnableDFPacketLabel
        EnableDFPacketGUI

        DFPacketTypeType = 'popupmenu'
        DFPacketTypeDropDown = {'Connectionless CTE', 'Connection CTE'} % Values displayed to user
        DFPacketTypeLabel
        DFPacketTypeGUI

        WhitenType = 'checkbox';
        WhitenLabel
        WhitenGUI

        ModulationIndexBLEType = 'edit'
        ModulationIndexBLEGUI
        ModulationIndexBLELabel

        PulseLengthType = 'popupmenu'
        PulseLengthDropDown = {'1','2','3','4'}
        PulseLengthGUI
        PulseLengthLabel
    end

    methods
        function obj = bleDialog(parent)
            obj@wirelessWaveformGenerator.WaveformConfigurationDialog(parent); % call base constructor

            obj.SpsGUI.(obj.Callback)             = @(a, b) spsChanged(obj, []);
            obj.ChannelIndexGUI.(obj.Callback)    = @(a, b) channelIndexChanged(obj, []);
            obj.AccessAddressGUI.(obj.Callback)   = @(a, b) accessAddressChanged(obj, []);
            obj.ModulationIndexBLEGUI.(obj.Callback) = @(a, b) ModulationIndexBLEChanged(obj, []);
            obj.PulseLengthGUI.(obj.Callback)     = @(a, b) PulseLengthChanged(obj, []);
            obj.EnableDFPacketGUI.(obj.Callback)     = @(a, b) EnableDFPacketChanged(obj, []);
        end

        function restoreDefaults(obj)
            obj.DataRate = 'LE1M';
            obj.Sps = 8;
            obj.ChannelIndex = 37;
            obj.AccessAddress = '[0 1 1 0 1 0 1 1 0 1 1 1 1 1 0 1 1 0 0 1 0 0 0 1 0 1 1 1 0 0 0 1]''';
            obj.EnableDFPacket = false;
            obj.DFPacketType = 'Connectionless CTE';
            obj.Whiten = true;
            obj.ModulationIndexBLE = 0.5;
            obj.PulseLength = 1;
        end

        function config = getConfigurationForSave(obj)
            config.waveform = getConfigurationForSave@wirelessWaveformGenerator.WaveformConfigurationDialog(obj);
            config.generation = getConfigurationForSave(obj.Parent.GenerationDialog);
        end

        function waveform = generateWaveform(obj)
            generationDialog = obj.Parent.GenerationDialog;
            userDefined = strcmp(generationDialog.InputSource, 'User-defined');
            if userDefined
                MPDU = generationDialog.MPDU();
            else
                MPDU = generationDialog.MPDU(generationDialog.NumBits);
                MPDU = MPDU{:};
            end
            [whitenStatus, DFPacketTypeVal] = mapGUIToAPI(obj);

            waveform = bleWaveformGenerator(MPDU, ...
                'Mode',              obj.DataRate, ...
                'SamplesPerSymbol',  obj.Sps, ...
                'ChannelIndex',      obj.ChannelIndex, ...
                'AccessAddress',     obj.AccessAddress,...
                "DFPacketType",      DFPacketTypeVal,...
                "ModulationIndex",   obj.ModulationIndexBLE,...
                "PulseLength",       obj.PulseLength,...
                "WhitenStatus",      whitenStatus);
        end
        function addGenerationCode(obj, sw)
            [WS, DFPacketTypeVal] = mapGUIToAPI(obj);
            WS = ['''' WS ''''];
            DFPacketTypeVal = ['''' DFPacketTypeVal ''''];
            addcr(sw, ['waveform = bleWaveformGenerator(in, ''Mode'', '''                     obj.DataRate ''', ...']);
            addcr(sw, ['''SamplesPerSymbol'', ' 'sps' ', ...']);
            addcr(sw, ['''ChannelIndex'', '     obj.ChannelIndexGUI.(obj.EditValue) ', ...']);
            addcr(sw, ['''AccessAddress'', '    obj.AccessAddressGUI.(obj.EditValue) ', ...']);
            addcr(sw, ['''DFPacketType'', ' DFPacketTypeVal ', ...']);
            addcr(sw, ['''WhitenStatus'', '     WS ', ...']);
            addcr(sw, ['''PulseLength'', ' obj.PulseLengthGUI.(obj.EditValue) ', ...']);
            addcr(sw, ['''ModulationIndex'', '    obj.ModulationIndexBLEGUI.(obj.EditValue) ');']);
        end
        function addConfigCode(obj, sw)
            % Waveform generation is function (bleWaveformGenerator) with no object configuration.
            % Nothing to do here other than supporting variables
            addcr(sw, ['sps = ' num2str(obj.Sps) ';']);
            addcr(sw, ['symbolRate = ' num2str(getSymbolRate(obj)) ';']);
        end
        function [WS, DFPacketTypeVal] = mapGUIToAPI(obj)
            WS = uiservices.logicalToOnOff(obj.Whiten);
            if(~obj.EnableDFPacket)
                DFPacketTypeVal = 'Disabled';
            else
                DFPacketTypeVal = regexprep(obj.DFPacketType,' ','');
            end
        end
        function addInputCode(obj, sw)
            addInputCode(obj.Parent.GenerationDialog, sw);
            addcr(sw, '');
        end

        function sr = getSampleRate(obj)
            % Set the symbol rate, as per the standard
            sr = obj.Sps * getSymbolRate(obj);
        end

        function str = getSampleRateStr(~)
            str = 'sps * symbolRate';
        end

        function sps = getSamplesPerSymbol(obj)
            sps = obj.Sps;
        end

        function symbolRate = getSymbolRate(obj)
            switch obj.DataRate
                case 'LE2M'
                    symbolRate = 2e6;
                case 'LE1M'
                    symbolRate = 1e6;
                case 'LE500K'
                    symbolRate = 500e3;
                case 'LE125K'
                    symbolRate = 125e3;
            end
        end

        function spsChanged(obj, ~)
            try
                val = obj.Sps;
                validateattributes(val, {'numeric'}, {'positive', 'integer', 'scalar', 'real'}, '', 'Sps');
            catch e
                obj.errorFromException(e);
            end
        end
        function n = get.Sps(obj)
            n = getEditVal(obj, 'Sps');
        end
        function set.Sps(obj, val)
            setEditVal(obj, 'Sps', val);
        end

        function r = get.DataRate(obj)
            r =  obj.mappedDataRate{contains(obj.DataRateDropDown, obj.DataRateGUI.Value)};
        end
        function set.DataRate(obj, val)
            obj.DataRateGUI.Value = obj.DataRateGUI.Items{contains(obj.mappedDataRate, val)};
        end

        function channelIndexChanged(obj, ~)
            try
                val = obj.ChannelIndex;
                validateattributes(val, {'numeric'}, {'nonnegative', 'integer', 'scalar', 'real', '<=', 39}, '', 'Channel Index');
            catch e
                obj.errorFromException(e);
            end
        end
        function n = get.ChannelIndex(obj)
            n = getEditVal(obj, 'ChannelIndex');
        end
        function set.ChannelIndex(obj, val)
            setEditVal(obj, 'ChannelIndex', val);
        end

        function accessAddressChanged(obj, ~)
            try
                val = obj.AccessAddress;
                validateattributes(val, {'numeric'}, {'binary', 'numel', 32}, '', 'Access Address');
            catch e
                obj.errorFromException(e);
            end
        end
        function n = get.AccessAddress(obj)
            n = getEditVal(obj, 'AccessAddress');
            n = n(:); % support row vectors too
        end
        function set.AccessAddress(obj, val)
            setEditVal(obj, 'AccessAddress', val);
        end

        function r = get.DFPacketType(obj)
            r =  getDropdownVal(obj,'DFPacketType');
        end
        function set.DFPacketType(obj, val)
            setDropdownNumVal(obj, 'DFPacketType', val);
        end
        function n = get.ModulationIndexBLE(obj)
            n = getEditVal(obj, 'ModulationIndexBLE');
        end
        function set.ModulationIndexBLE(obj, val)
            setEditVal(obj, 'ModulationIndexBLE', val);
        end
        function ModulationIndexBLEChanged(obj, ~)
            try
                val = obj.ModulationIndexBLE;
                validateattributes(val,{'double'},{'nonnan','real',...
                    'scalar','>=',0.45,'<=',0.55},mfilename,'Modulation Index');
            catch e
                obj.errorFromException(e);
            end
        end
        function n = get.Whiten(obj)
            n = getCheckboxVal(obj, 'Whiten');
        end
        function set.Whiten(obj, val)
            setCheckboxVal(obj, 'Whiten', val);
        end

        % EnableDFPacket status
        function n = get.EnableDFPacket(obj)
            n = getCheckboxVal(obj, 'EnableDFPacket');
        end
        function set.EnableDFPacket(obj, val)
            setCheckboxVal(obj, 'EnableDFPacket', val);
            EnableDFPacketChanged(obj)
        end
        function EnableDFPacketChanged(obj, ~)
            EnableDFPacketVal = obj.EnableDFPacket;
            set([obj.DFPacketTypeLabel obj.DFPacketTypeGUI], 'Visible', EnableDFPacketVal)
            layoutUIControls(obj);
        end

        function n = get.PulseLength(obj)
            n = getEditVal(obj, 'PulseLength');
        end
        function set.PulseLength(obj, val)
            setEditVal(obj, 'PulseLength', val);
        end
        function PulseLengthChanged(obj, ~)
            try
                val = obj.PulseLength;
                validateattributes(val,{'numeric'},{'real','integer','positive',...
                    'scalar','<=',4}, mfilename,'Pulse Length');
            catch e
                obj.errorFromException(e);
            end
        end

        function str = getCatalogPrefix(~)
            str = 'bluetooth:waveformGeneratorApp:';
        end

        function c = getSourceClass(~)
            c = 'wirelessAppContainer.sources.BitSourcePNLengthDialog';
        end

        function cellDialogs = getDialogsPerColumn(obj)
            cellDialogs{1} = {obj obj.Parent.GenerationDialog};
        end

        function [blockName, maskTitleName, waveNameText] = getMaskTextWaveName(obj)
            blockName = obj.Parent.WaveformGenerator.pCurrentExtensionType;
            maskTitleName = [obj.Parent.WaveformGenerator.pCurrentExtensionType ' Waveform Generator'];
            waveNameText = 'Bluetooth low energy';
        end

        function b = timeScopeEnabled(~)
            b = false; % eye diagram is shown by default
        end

        function b = offersConstellation(~)
            b = true;
        end
        function b = constellationEnabled(~)
            b = true;
        end
        function b = showConstellationTrajectory(~)
            b = true;
        end

        function b = offersEyeDiagram(~)
            b = true;
        end
        function b = eyeEnabled(~)
            b = true;
        end

        % Set Help button to doc page of Bluetooth LE Waveform Generator
        function helpCallback(~)
            helpview('bluetooth', 'BluetoothLEWaveformGenerator');
        end
    end
end