classdef (StrictDefaults)RBDSDataLinkDecoder < matlab.System
  %RBDSDataLinkDecoder Decoder of RDS/RBDS data link layer (layer #2)
  %   RBDSDataLinkDecoder receives bits and detects offset words to identify
  %   group start and attain synchronization. It then performs error
  %   correction and re-establishes synchronization if needed.
  
  % Copyright 2016-2020 The MathWorks, Inc.
  
  %#codegen
  
  properties(Access = private)
    pIsSynchronized
    pGroupStartIdx
    pBuffer
    pNumBlockErrors
    pPIcode
  end
  
	
  properties(Nontunable)
    RunInGenCode = false
  end
  
  methods
    function obj = RBDSDataLinkDecoder(varargin)
      setProperties(obj, nargin, varargin{:});
    end
  end
  
  methods(Access=protected)
  
    function [enabled, iw1, iw2, iw3, iw4] = stepImpl(obj, bits)
      
      N = 26; % codeword length
      K = 16; % message length
      
      % init outputs for the cases that they are not assigned
      enabled = false;
      iw1 = zeros(1, K);
      iw2 = zeros(1, K);
      iw3 = zeros(1, K);
      iw4 = zeros(1, K);
      
      % take care of pBuffering
      extendedpBuffer = [obj.pBuffer; double(bits)]; % search for entire group within extendedpBuffer
      obj.pBuffer = extendedpBuffer(1+end-4*N:end); % only for updating state
      
      if obj.pIsSynchronized
        % slide next group start for this step:
        obj.pGroupStartIdx = mod(obj.pGroupStartIdx - length(bits), 4*N);
        
        if obj.pGroupStartIdx < length(bits)
          % group is fully contained in the pBuffer, perform error detection:
          forStart  = obj.pGroupStartIdx;
          forEnd    = obj.pGroupStartIdx;
        else
          % group is not fully contained in the pBuffer, no need to perform ECC now
          forStart = 0;
          forEnd   = -1;
        end
        
      else % need to do exhaustive search for synchronization
        forStart  = 0;
        forEnd    = length(bits)-1;
      end
      
      % Offset words from RBDS standard:
      OffsetWordA   = [zeros(1, K) 0 0 1 1 1 1 1 1 0 0]';
      OffsetWordB   = [zeros(1, K) 0 1 1 0 0 1 1 0 0 0]';
      OffsetWordC   = [zeros(1, K) 0 1 0 1 1 0 1 0 0 0]';
      OffsetWordCp  = [zeros(1, K) 1 1 0 1 0 1 0 0 0 0]';
      OffsetWordD   = [zeros(1, K) 0 1 1 0 1 1 0 1 0 0]';
      
      for idx = forStart:forEnd
        
        % Split group to the 4 blocks:
        block1 = extendedpBuffer(1     + idx : N   + idx);
        block2 = extendedpBuffer(1+N   + idx : N*2 + idx);
        block3 = extendedpBuffer(1+N*2 + idx : N*3 + idx);
        block4 = extendedpBuffer(1+N*3 + idx : N*4 + idx);

        % Add offset word in each block and detect errors:
        if ~obj.RunInGenCode
          found1  = RBDSErrorDetection(block1, OffsetWordA);
          found2  = RBDSErrorDetection(block2, OffsetWordB);
          found3  = RBDSErrorDetection(block3, OffsetWordC);
          found3p = RBDSErrorDetection(block3, OffsetWordCp);
          found4  = RBDSErrorDetection(block4, OffsetWordD);
        else
          found1  = RBDSErrorDetection_mex(block1, OffsetWordA);
          found2  = RBDSErrorDetection_mex(block2, OffsetWordB);
          found3  = RBDSErrorDetection_mex(block3, OffsetWordC);
          found3p = RBDSErrorDetection_mex(block3, OffsetWordCp);
          found4  = RBDSErrorDetection_mex(block4, OffsetWordD);
        end

        numFound = found1 + found2 + (found3 || found3p) + found4;
        
        if numFound >= 4
          % The standard requires at least 2 preambles to be found.
          % Requiring 4 preambles prevents bit errors from being forwarded
          % to Layer 3, so the displayed text is cleaner. In this case,
          % error correction is not really needed, but it is nevertheless
          % provided below, to allow modifications in the threshold of
          % found preambles.
          if ~obj.RunInGenCode
            iw1 = RBDSErrorCorrection(block1', OffsetWordA');
            iw2 = RBDSErrorCorrection(block2', OffsetWordB');
            iw3 = RBDSErrorCorrection(block3', OffsetWordC');
            iw4 = RBDSErrorCorrection(block4', OffsetWordD');
          else
            iw1 = RBDSErrorCorrection_mex(block1', OffsetWordA');
            iw2 = RBDSErrorCorrection_mex(block2', OffsetWordB');
            iw3 = RBDSErrorCorrection_mex(block3', OffsetWordC');
            iw4 = RBDSErrorCorrection_mex(block4', OffsetWordD');
          end
          enabled = true;
          
          % establish synchronization
          obj.pIsSynchronized = true;
          obj.pNumBlockErrors = 0;
          obj.pGroupStartIdx = idx + 4*N; % set the next start
          
          % cache PI code, to be used for detection of 1-bit synchronization slips
          if found1
            obj.pPIcode = iw1'; % iw1 is PI code for all group types
          end
          return;
          
        elseif obj.pIsSynchronized
          % possible loss of synchronization
          
          % detect synchronization slips of 1 bit, using the PI code
          iw1shiftLeft = block1(2:17);
          if all(iw1shiftLeft == obj.pPIcode)
            obj.pGroupStartIdx = obj.pGroupStartIdx + 1;
            obj.pNumBlockErrors = 0;
            return;
          end
          
          if idx > 0
            iw1shiftRight = [extendedpBuffer(idx); block1(1:15)];
            if all(iw1shiftRight == obj.pPIcode)
              obj.pGroupStartIdx = obj.pGroupStartIdx - 1;
              obj.pNumBlockErrors = 0;
              return;
            end
          end
          
          % otherwise look for 50 failed attempts before declaring sync loss
          obj.pNumBlockErrors = obj.pNumBlockErrors + 1;
          if obj.pNumBlockErrors >= 25 % 25 block threshold
            % declare loss of synchronization. need to start exhaustive
            % search again
            obj.pIsSynchronized = false;
          end
        end
      end
    end
    
    function resetImpl(obj)
      obj.pIsSynchronized = false;
      obj.pBuffer         = zeros(26*4, 1);
      obj.pGroupStartIdx  = inf;
      obj.pNumBlockErrors = 0;
      obj.pPIcode         = -ones(16, 1);
    end
  end
  
  methods(Access=protected)
    
    % Methods for System block:
    function num = getNumInputsImpl(~)
      num = 1; % bits
    end
    
    function varargout = getInputNamesImpl(~)
      varargout{1} = getString(message('comm_demos:RBDS:Bits'));
    end

    function num = getNumOutputsImpl(~)
      num = 5;
    end
    
    function varargout = getOutputDataTypeImpl(obj)
      varargout{1} = 'logical';
      for out = 2:obj.getNumOutputsImpl
        varargout{out} = 'double';
      end
    end
    
    function varargout = isOutputFixedSizeImpl(obj)
      for out = 1:obj.getNumOutputsImpl
        varargout{out} = true;
      end
    end
    
    function varargout = getOutputSizeImpl(obj)
      varargout{1} = 1;
      for out = 2:obj.getNumOutputsImpl
        varargout{out} = [1 16];
      end
    end
    
    function varargout = isOutputComplexImpl(obj)
      for out = 1:obj.getNumOutputsImpl
        varargout{out} = false;
      end
    end
    
    function icon = getIconImpl(~)
      % icon drawing
      icon = getString(message('comm_demos:RBDS:Layer2'));
    end  
  end
  
  methods(Static, Access = protected)
    
    function groups = getPropertyGroupsImpl
      groups = matlab.system.display.Section();
    end
  end
  
end

