classdef bluetoothDialog < wirelessWaveformGenerator.WaveformConfigurationDialog
  %

  % Copyright 2022-2023 The MathWorks, Inc.
  properties (Dependent)
    Mode
    PacketType
    DeviceAddress
    LogicalTransportAddress
    HeaderControlBits
    ModulationIndex
    SamplesPerSymbol
    Whiten
    WhitenInitialization
    LLID
    FlowIndicator
  end

  properties (Hidden)
    TitleString = getString(message('bluetooth:waveformGeneratorApp:BluetoothPHYTitle'))
    configFcn = @bluetoothWaveformConfig
    configGenFcn = @bluetoothWaveformConfig
    configGenVar = 'bluetoothCfg'

    ModeType = 'popupmenu'
    ModeDropDown = {'BR', 'EDR2M', 'EDR3M'}
    ModeLabel
    ModeGUI

    PacketTypeType = 'popupmenu'
    PacketTypeDropDown = {'ID','NULL','POLL','FHS','DM1',... 
                             'HV1','HV2','HV3','DV',...
                             'EV3','EV4','EV5',...
                             'DH1','AUX1','DM3','DH3','DM5','DH5'};
    PacketTypeDropDownBR = {'ID','NULL','POLL','FHS','DM1',... 
                             'HV1','HV2','HV3','DV',...
                             'EV3','EV4','EV5',...
                             'DH1','AUX1','DM3','DH3','DM5','DH5'};
    PacketTypeDropDownEDR2M = {'ID','NULL','POLL','FHS','DM1','2-EV3',...
                '2-EV5','2-DH1','AUX1','2-DH3','2-DH5'};
    PacketTypeDropDownEDR3M = {'ID','NULL','POLL','FHS','DM1','3-EV3','3-EV5',...
                         '3-DH1','AUX1','3-DH3','3-DH5'};
    PacketTypeLabel
    PacketTypeGUI

    DeviceAddressType = 'edit'
    DeviceAddressGUI
    DeviceAddressLabel

    LogicalTransportAddressType = 'edit'
    LogicalTransportAddressGUI
    LogicalTransportAddressLabel

    HeaderControlBitsType = 'edit'
    HeaderControlBitsGUI
    HeaderControlBitsLabel

    ModulationIndexType = 'edit'
    ModulationIndexGUI
    ModulationIndexLabel

    SamplesPerSymbolType = 'edit'
    SamplesPerSymbolGUI
    SamplesPerSymbolLabel

    WhitenType = 'checkbox';
    WhitenLabel
    WhitenGUI

    WhitenInitializationType = 'edit'
    WhitenInitializationGUI
    WhitenInitializationLabel

    LLIDType = 'edit'
    LLIDGUI
    LLIDLabel

    FlowIndicatorType = 'checkbox'
    FlowIndicatorGUI
    FlowIndicatorLabel

    PN9
    PN15
  end

  methods
    function obj = bluetoothDialog(parent)
      obj@wirelessWaveformGenerator.WaveformConfigurationDialog(parent);
      obj.ModeGUI.(obj.Callback)                    = @(a,b) ModeChanged(obj, []);
      obj.PacketTypeGUI.(obj.Callback)              = @(a,b) PacketTypeChanged(obj, []);
      obj.DeviceAddressGUI.(obj.Callback)           = @(a,b) DeviceAddressChanged(obj, []);
      obj.LogicalTransportAddressGUI.(obj.Callback) = @(a,b) LogicalTransportAddressChanged(obj, []);
      obj.HeaderControlBitsGUI.(obj.Callback)       = @(a,b) HeaderControlBitsChanged(obj, []);
      obj.ModulationIndexGUI.(obj.Callback)         = @(a,b) ModulationIndexChanged(obj, []);
      obj.SamplesPerSymbolGUI.(obj.Callback)        = @(a,b) SamplesPerSymbolChanged(obj, []);
      obj.WhitenGUI.(obj.Callback)                  = @(a,b) WhitenStatusChanged(obj, []);
      obj.WhitenInitializationGUI.(obj.Callback)    = @(a,b) WhitenInitializationChanged(obj, []);
      obj.LLIDGUI.(obj.Callback)                    = @(a,b) LLIDChanged(obj, []);
    end

    function setupDialog(obj)
        PacketTypeChanged(obj);
        generatorClass = obj.getSourceClass();
        genDialog = obj.Parent.DialogsMap(generatorClass);
        genDialog.setupDialog();
        genDialog.layoutUIControls();
    end

    function restoreDefaults(obj)
      obj.Mode                    = 'BR';
      obj.PacketType              = 'FHS';
      obj.DeviceAddress           = '0123456789AB';
      obj.LogicalTransportAddress = [0;0;1];
      obj.HeaderControlBits       = [1;1;1];
      obj.ModulationIndex         = 0.32;
      obj.SamplesPerSymbol        = 8;
      obj.Whiten                  = true;
      obj.WhitenInitialization    = [1;1;1;1;1;1;1];
      obj.LLID                    = [1;1];
      obj.FlowIndicator           = true;
    end
 
    function config = getConfigurationForSave(obj)
      config.waveform = obj.getConfiguration;
      config.generation = getConfigurationForSave(obj.Parent.GenerationDialog);
    end

    function [waveform,customConstellationOutputs] = generateWaveform(obj)
      bluetoothCfg = obj.getConfiguration;
      config1 = obj.Parent.GenerationDialog;
      if strcmp(config1.InputSource, 'User-defined')
        bits = config1.MPDU();
      else
        bits = config1.MPDU(config1.NumBits*8);
        bits = bits{:};
      end
      waveform = bluetoothWaveformGenerator(bits,bluetoothCfg);
      payloadLength = getPayloadLength(bluetoothCfg);
      filterSpan = 8*any(strcmp(bluetoothCfg.Mode,{'EDR2M','EDR3M'})); % To meet the spectral mask requirements
      packetDuration = bluetoothPacketDuration(bluetoothCfg.Mode,bluetoothCfg.PacketType,payloadLength);
      waveform = waveform(1:(packetDuration+filterSpan)*bluetoothCfg.SamplesPerSymbol);
      if strcmp(bluetoothCfg.Mode,'BR')
        customConstellationOutputs = {waveform};
      else
        if ~strcmp(bluetoothCfg.PacketType,'ID')
            accessCodeHeader = 126*obj.SamplesPerSymbol;
        else
            accessCodeHeader = length(waveform);
        end
        guard = 5*bluetoothCfg.SamplesPerSymbol;
        waveformGFSk = waveform(1:accessCodeHeader);
        if ~any(strcmp(bluetoothCfg.PacketType,{'ID','NULL','POLL'}))
            waveformDPSK = waveform(accessCodeHeader+guard+4*bluetoothCfg.SamplesPerSymbol+1:end-5*bluetoothCfg.SamplesPerSymbol);
            customConstellationOutputs = {waveformGFSk,waveformDPSK};
        else
            customConstellationOutputs = {waveformGFSk};
        end
      end
    end
    function config = applyConfiguration(obj, config)
        applyConfiguration@wirelessWaveformGenerator.WaveformConfigurationDialog(obj, config);
        obj.Whiten = uiservices.onOffToLogical(config.WhitenStatus);
    end

    function addGenerationCode(obj, sw)
      addcr(sw, ['waveform = bluetoothWaveformGenerator(in, ' obj.configGenVar ');']);
      addcr(sw, ['payloadLength = getPayloadLength(' obj.configGenVar ');']);
      addcr(sw, char("filterSpan = 8*any(strcmp(bluetoothCfg.Mode,{'EDR2M','EDR3M'}));"));
      addcr(sw, char("packetDuration = bluetoothPacketDuration(bluetoothCfg.Mode,bluetoothCfg.PacketType,payloadLength);"));
      addcr(sw, char("waveform = waveform(1:(packetDuration+filterSpan)*bluetoothCfg.SamplesPerSymbol);"));
    end

    function addConfigCode(obj, sw)
        addConfigCode@wirelessWaveformGenerator.WaveformConfigurationDialog(obj, sw);
        c = obj.Parent.GenerationDialog;
        addcr(sw, [obj.configGenVar '.PayloadLength = ' num2str(c.NumBits) ';']);
        if obj.Whiten
            WS = 'On';
        else
            WS = 'Off';
        end
        WS = ['''' WS ''''];
        addcr(sw,[obj.configGenVar '.WhitenStatus = ' WS ';']);
        val = obj.DeviceAddress;
        val = ['''' val ''''];
        addcr(sw,[obj.configGenVar '.DeviceAddress = ' val ';']);
    end
    function addInputCode(obj, sw)
        c = obj.Parent.GenerationDialog;
        addInputCode(c, sw);
        addcr(sw, '');
    end
    function props = props2ExcludeFromConfig(~)
      props = {'Whiten'};
    end
    function props = props2ExcludeFromConfigGeneration(~)
        props = {'Whiten','DeviceAddress'};
    end

    function cfg = getConfiguration(obj)
        cfg = getConfiguration@wirelessWaveformGenerator.WaveformConfigurationDialog(obj);
        cfg.WhitenStatus = uiservices.logicalToOnOff(obj.Whiten);
        config1 = obj.Parent.GenerationDialog;
        cfg.PayloadLength = config1.NumBits*strcmp(config1.NumBitsLabel.Visible,'on')...
          +config1.FixedPayloadLength*strcmp(config1.FixedPayloadLengthLabel.Visible,'on');
    end

    % Mode
    function r = get.Mode(obj)
      r =  getDropdownVal(obj,'Mode');
    end
    function set.Mode(obj, val) 
      setDropdownNumVal(obj, 'Mode', val);
      ModeChanged(obj)
    end
    function ModeChanged(obj, ~)
        if strcmp(obj.Mode,'BR')
            obj.PacketTypeGUI.(obj.DropdownValues) =  obj.PacketTypeDropDownBR;
        elseif strcmp(obj.Mode,'EDR2M')
            obj.PacketTypeGUI.(obj.DropdownValues) = obj.PacketTypeDropDownEDR2M;
        else
            obj.PacketTypeGUI.(obj.DropdownValues) = obj.PacketTypeDropDownEDR3M;
        end
        PacketTypeChanged(obj);
        layoutUIControls(obj);
    end

    % Packet type
    function r = get.PacketType(obj)
      r =  getDropdownVal(obj,'PacketType');
    end
    function set.PacketType(obj, val) 
      setDropdownNumVal(obj, 'PacketType', val);
      PacketTypeChanged(obj);
    end
    function PacketTypeChanged(obj, ~)
        try
            val = obj.PacketType;
            valPL = any(strcmp(val,{'DM1','DH1','DM3','DH3',...
                'DM5','DH5','DV','EV3','EV4','EV5','2-EV3','3-EV3','2-EV5',...
                '3-EV5','2-DH1','3-DH1','2-DH3','3-DH3','2-DH5','3-DH5','AUX1'}));
            valB = ~any(strcmp(val,{'ID','NULL','POLL'}));
            if ~isKey(obj.Parent.DialogsMap, 'wirelessWaveformGenerator.bluetoothDialog')
                return
            end
            generationDialog = obj.Parent.GenerationDialog;
            if isempty(generationDialog)
                return
            end
            if strcmp(generationDialog.InputSource,'User-defined')
                valBSIB = ~any(strcmp(val,{'ID','NULL','POLL'}));
            else
                valBSIB = false;
            end
            bluetoothCfg = obj.getConfiguration;
            bluetoothCfg.PayloadLength = generationDialog.NumBits;
            PLValue = getPayloadLength(bluetoothCfg);
            bluetoothCfg.PayloadLength = PLValue;
            generationDialog.FixedPayloadLength = PLValue;
            set([generationDialog.NumBitsLabel generationDialog.NumBitsGUI], 'Visible', valPL)
            set([generationDialog.FixedPayloadLengthLabel generationDialog.FixedPayloadLengthGUI], 'Visible', ~valPL)
            set([generationDialog.InputSourceLabel generationDialog.InputSourceGUI], 'Visible', valB)
            set([generationDialog.InputValueLabel generationDialog.InputValueGUI], 'Visible', valBSIB)
            layoutUIControls(generationDialog);
            vaLLIDFI = any(strcmp(val,{'DM1','DH1','DM3','DH3',...
                'DM5','DH5','AUX1','DV','2-DH1','3-DH1','2-DH3','3-DH3',...
                '2-DH5','3-DH5'}));
            set([obj.LLIDLabel obj.LLIDGUI obj.FlowIndicatorLabel obj.FlowIndicatorGUI], 'Visible', vaLLIDFI)
            layoutUIControls(obj);
        catch e
            obj.errorFromException(e);
        end
    end

    % Device address
    function n = get.DeviceAddress(obj)
      n = getEditStr(obj, 'DeviceAddress');
    end
    function set.DeviceAddress(obj, val) 
      setEditStr(obj, 'DeviceAddress', val);
    end
    function DeviceAddressChanged(obj, ~)
      try
        val = char(obj.DeviceAddress);
        ble.internal.validateHex(val,12,'DeviceAddress');
      catch e
        obj.errorFromException(e);
      end
    end

    % Logical transport address
    function n = get.LogicalTransportAddress(obj)
      n = getEditVal(obj, 'LogicalTransportAddress');

    end
    function set.LogicalTransportAddress(obj, val) 
      setEditVal(obj, 'LogicalTransportAddress', val);
    end
    function LogicalTransportAddressChanged(obj, ~)
      try
        val = obj.LogicalTransportAddress;
        validateattributes(val,{'double'},{'nonnan','finite',...
            'column','binary','size',[3,1]},mfilename,'LogicalTransportAddress');
      catch e
        obj.errorFromException(e);
      end
    end

    % Header control bits
    function n = get.HeaderControlBits(obj)
      n = getEditVal(obj, 'HeaderControlBits');
    end
    function set.HeaderControlBits(obj, val) 
      setEditVal(obj, 'HeaderControlBits', val);
    end
    function HeaderControlBitsChanged(obj, ~)
      try
        val = obj.HeaderControlBits;
        validateattributes(val,{'double'},{'nonnan','finite',...
            'column','binary','size',[3,1]},mfilename,'HeaderControlBits');
      catch e
        obj.errorFromException(e);
      end
    end

    % Modulation index
    function n = get.ModulationIndex(obj)
      n = getEditVal(obj, 'ModulationIndex');
    end
    function set.ModulationIndex(obj, val) 
      setEditVal(obj, 'ModulationIndex', val);
    end
    function ModulationIndexChanged(obj, ~)
      try
        val = obj.ModulationIndex;
        validateattributes(val,{'double'},{'nonnan','real',...
                'scalar','>=',0.28,'<=',0.35},mfilename,'ModulationIndex');
      catch e
        obj.errorFromException(e);
      end
    end

    % Samples per symbol
    function n = get.SamplesPerSymbol(obj)
      n = getEditVal(obj, 'SamplesPerSymbol');
    end
    function set.SamplesPerSymbol(obj, val) 
      setEditVal(obj, 'SamplesPerSymbol', val);
    end
    function SamplesPerSymbolChanged(obj, ~)
      try
        val = obj.SamplesPerSymbol;
        validateattributes(val,{'double'},{'nonnan','real','integer','positive',...
                'scalar'},mfilename,'SamplesPerSymbol');
      catch e
        obj.errorFromException(e);
      end
    end

    % Whiten status
    function n = get.Whiten(obj)
      n = getCheckboxVal(obj, 'Whiten');
    end
    function set.Whiten(obj, val) 
      setCheckboxVal(obj, 'Whiten', val);
      WhitenStatusChanged(obj)
    end
    function WhitenStatusChanged(obj, ~)
        whitenInit = obj.Whiten;
        set([obj.WhitenInitializationLabel obj.WhitenInitializationGUI], 'Visible', whitenInit)
        layoutUIControls(obj);
    end

    % Whiten initialization
    function n = get.WhitenInitialization(obj)
      n = getEditVal(obj, 'WhitenInitialization');
    end
    function set.WhitenInitialization(obj, val) 
      setEditVal(obj, 'WhitenInitialization', val);
    end
    function WhitenInitializationChanged(obj, ~)
      try
        val = obj.WhitenInitialization;
        validateattributes(val,{'double'},{'nonnan','finite','column',...
            'binary','size',[7,1]},mfilename,'WhitenInitialization');
      catch e
        obj.errorFromException(e);
      end
    end

    % LLID
    function n = get.LLID(obj)
      n = getEditVal(obj, 'LLID');
    end
    function set.LLID(obj, val) 
      setEditVal(obj, 'LLID', val);
    end
    function LLIDChanged(obj, ~)
      try
        val = obj.LLID;
        validateattributes(val,{'double'},{'nonnan','finite',...
            'column','binary','size',[2,1]},mfilename,'LLID');
      catch e
        obj.errorFromException(e);
      end
    end

    % Flow indicator
    function n = get.FlowIndicator(obj)
      n = getCheckboxVal(obj, 'FlowIndicator');
    end
    function set.FlowIndicator(obj, val) 
      setCheckboxVal(obj, 'FlowIndicator', val);
    end

    function sr = getSampleRate(obj)
      symbolRate = 1e6;
      sr = obj.SamplesPerSymbol * symbolRate;
    end
    function sps = getSamplesPerSymbol(obj)
      sps = obj.SamplesPerSymbol;
    end
    function str = getCatalogPrefix(~)
      str = 'bluetooth:waveformGeneratorApp:';
    end

    function b = offersConstellation(~)
      b = true;
    end
    function b = constellationEnabled(~)
      b = true;
    end
    function b = overridesConstellatonPlot(~)
      b = true;
    end
    function b = constellationNames(~)
        b = {'GFSK', 'DPSK'};
    end
    function [blockName, maskTitleName, waveNameText] = getMaskTextWaveName(obj)
        blockName = [strrep(obj.Parent.WaveformGenerator.pCurrentExtensionType,'/','//')]; % Symbol '/' misinterpreted by Simulink;
        maskTitleName = [obj.Parent.WaveformGenerator.pCurrentExtensionType ' Waveform Generator'];
        waveNameText = 'Bluetooth BR/EDR';
    end
    function c = getSourceClass(~)
        c = 'wirelessWaveformGenerator.bluetoothSourceDialog';
    end
    function b = mayHaveEmptyTimePeriods(~)
      b = true;
    end
    function cellDialogs = getDialogsPerColumn(obj)
        cellDialogs{1} = {obj obj.Parent.GenerationDialog};
    end

    % Set Help button to doc page of Bluetooth BR/EDR Waveform Generator
    function helpCallback(~)
        helpview('bluetooth', 'BluetoothBREDRWaveformGenerator');
    end
   end
end