classdef PAMmapper
    %PAMMAPPER Summary of this class goes here
    %   Detailed explanation goes here

    properties
        M
        unipolar
        thresholds
    end

    methods
        function obj = PAMmapper(M, unipolar)
            %PAMMAPPER Construct an instance of this class
            %   Detailed explanation goes here
            obj.M = M;
            obj.unipolar = unipolar;
            
            obj.thresholds = obj.get_demodulation_thresholds();


        end

        function out = map(obj,signal_in)
            
            if isa(signal_in,'Signal')
                signal_in.signal = obj.map_(signal_in.signal); 
                signal_in = signal_in.normalize("mode","rms");
                signal_in  = signal_in.logbookentry();
                out = signal_in;
            else
                out = signal_in;
            end
            
        end

        function signalclass_out = demap(obj,signalclass_in)
            signalclass_in.signal = obj.demap_(signalclass_in.signal); 
            signalclass_in  = signalclass_in.logbookentry();
            signalclass_out = signalclass_in;
        end

        function pam_sig = map_(obj,bitpattern)

            switch obj.M
                case 1
                    % 2-ASK: BPSK / OOK
                    pam_sig=bitpattern(:,1);

                    if obj.unipolar==0
                        pam_sig=2*pam_sig-1;
                    end

                case 4
                    % 4-ASK:
                    pam_sig=2*bitpattern(:,1)+(bitpattern(:,1)==bitpattern(:,2));

                    if obj.unipolar==0
                        pam_sig=2*pam_sig-3;
                    end

                    pam_sig = pam_sig/sqrt(5);

                case 6
                  
                    m = 1;
                    if size(bitpattern,2)>size(bitpattern,1)
                        bitpattern = bitpattern'; %vector aufrecht stellen
                    end
                    % LUT based mapping
                    for k = 1:5:fix(length(bitpattern)/5)*5
                        pam_sig(m:m+1,1) = obj.thresholds(bin2dec(int2str(bitpattern(k:k+4)'))+1,:);
                        m = m+2;
                    end

                    pam_sig = pam_sig/sqrt(10);

                case 8
                    % 8-ASK:
                    x1 = bitpattern(:,1);
                    x2 = (bitpattern(:,1)==bitpattern(:,3));
                    x3 = x2~=bitpattern(:,2);

                    pam_sig = 4*x1 + 2*x2 + x3;

                    if obj.unipolar==0
                        pam_sig=2*pam_sig-7;
                    end

                    pam_sig = pam_sig/sqrt(21);
                case 16
                    % 16-ASK:
                    x1 = bitpattern(:,1);
                    x2 = (bitpattern(:,1)==bitpattern(:,4));
                    x3 = x2~=bitpattern(:,3);
                    x4 = x3~=bitpattern(:,2);

                    pam_sig = 8*x1 + 4*x2 + 2*x3 + x4;

                    if obj.unipolar==0
                        pam_sig=2*pam_sig-15;
                    end

            end
        end

        function thres =  get_demodulation_thresholds(obj)
            %simply get the obj.thresholdseshold values for PAM

            %28.03.2023 - Silas Oett. - Extracted from digi_demod.m
            %

            switch obj.M

                case 2
                    % 2-ASK

                    if obj.unipolar
                        thres=0.5;
                    else %bi polar
                        thres=0;
                    end

                case 4
                    % 4-ASK
                    if obj.unipolar==0
                        thres=[-2,0,2];
                    elseif obj.unipolar==1
                        thres=[0.5,1.5,2.5];
                    end

                    thres = thres .* 1/sqrt(5);

                case 6 %PAM 6 

                    thres = [-3 5;-1 5;-3 -5;-1 -5;-5 3;-5 1;-5 -3;-5 -1;-1 3;-1 1;-1 -3;-1 -1;-3 3;-3 1;-3 -3;-3 -1;3 5;1 5;3 -5;1 -5;5 3;5 1;5 -3;5 -1;1 3;1 1;1 -3;1 -1;3 3;3 1;3 -3;3 -1];
                
                case 8
                    % 8-ASK
                    if obj.unipolar==0
                        thres=-6:2:6;
                    elseif obj.unipolar==1
                        thres=0.5:6.5;
                    end
                    
                    thres=thres./sqrt(21);

                case 16
                    % 16-ASK
                    if obj.unipolar==0 && scale_mode==1
                        thres=-14:2:14;
                    elseif obj.unipolar==1 && scale_mode==1
                        thres=0.5:14.5;
                    end

            end

        end

        function [data_out] = demap_(obj,data_in)
            data_in= data_in';
            if obj.M ~= 6
                
                % create output
                if ~isempty(obj.thresholds)
                    a = squeeze(repmat(real(data_in),[1 1 length(obj.thresholds)])); %Eingangssignal in 3 spalten
                    b = squeeze(repmat(reshape(obj.thresholds(:).',[1 1 length(obj.thresholds)]),[1 length(data_in) 1])); %Threshold in 3 Spalten
                    comp_real = a > b; %check for each symbol/ sampling if it exeeds the obj.thresholdseshold 1, 2 or 3
    
                    comp_real=repmat(real(data_in),[1 1 length(obj.thresholds)]) > repmat(reshape(obj.thresholds(:).',[1 1 length(obj.thresholds)]),[1 length(data_in) 1]);
    
                else
                    comp_real=[];
                end
    
                s1=size(comp_real,1);
                s2=size(comp_real,2);
            end

            switch obj.M

                case 2
                    % 2-ASK

                    data_out=comp_real(:,:,1);

                case 4
                    % 4-ASK

                    data_out=[comp_real(:,:,2); ones(s1,s2) - comp_real(:,:,1) + comp_real(:,:,3)];

                case 6

                    data_in = data_in/(sqrt(mean(abs(data_in).^2)));
                    data_in = data_in*sqrt(10);

                    if size(data_in,2) > 1
                        data_in = data_in.';
                    end
  
                    if length(data_in)/2 ~= round(length(data_in)/2)
                        data_in = [data_in;0];
                    end
        
                    m = 1;
                    for n = 1:2:length(data_in)
                        dist = sqrt((data_in(n)-obj.thresholds(:,1)).^2+(data_in(n+1)-obj.thresholds(:,2)).^2);
                        [~,dd_idx] = min(dist);
        %                 dec_out(n:n+1) = LUT(dd_idx,:);
                        data_out(m:m+4) = bitget(dd_idx-1,5:-1:1);
                        m = m+5;
                    end


                case 8
                    % 8-ASK
                    data_out=[comp_real(:,:,4);
                        comp_real(:,:,1)-comp_real(:,:,3)+comp_real(:,:,5)-comp_real(:,:,7);
                        1-comp_real(:,:,2)+comp_real(:,:,6)];

                case 16
                    % 16-ASK
                    data_out=[comp_real(:,:,8);
                        comp_real(:,:,1)-comp_real(:,:,3)+comp_real(:,:,5)-comp_real(:,:,7)+comp_real(:,:,9)-comp_real(:,:,11)+comp_real(:,:,13)-comp_real(:,:,15);
                        comp_real(:,:,2)-comp_real(:,:,6)+comp_real(:,:,10)-comp_real(:,:,14);
                        1-comp_real(:,:,4)+comp_real(:,:,12)];
            end

            data_out = data_out';


        end

        function [data_out] = decide_pamlevel(obj,data_in,options)
            arguments
                obj
                data_in Signal
                options.symbol_levels = []
            end
            
            %A) normally return the preproduct of the decision
            a = squeeze(repmat(real(data_in.signal),[1 1 length(obj.thresholds)])); %Eingangssignal in 3 spalten
            b = squeeze(repmat(reshape(obj.thresholds(:).',[1 1 length(obj.thresholds)]),[1 length(data_in.signal) 1])); %Threshold in 3 Spalten
            comp_real = a > b; %check for each symbol/ sampling if it exeeds the obj.thresholdseshold 1, 2 or 3
            
            data_out = data_in;
            data_out.signal = sum(comp_real,2);
            
            %Option: return the actual level values/ just map onto given
            %symbol levels
            if ~isempty(options.symbol_levels)
                data_out.signal = options.symbol_levels(data_out.signal+1);
            end
            
        end

        function [out] = separate_pamlevels(obj,data_in)
            
            %A) normally return the preproduct of the decision
            a = squeeze(repmat(real(data_in.signal),[1 1 length(obj.thresholds)])); %Eingangssignal in 3 spalten
            b = squeeze(repmat(reshape(obj.thresholds(:).',[1 1 length(obj.thresholds)]),[1 length(data_in.signal) 1])); %Threshold in 3 Spalten
            comp_real = a > b; %check for each symbol/ sampling if it exeeds the obj.thresholdseshold 1, 2 or 3
            comp_real_sum = sum(comp_real,2); 

            out = NaN(length(data_in),length(obj.thresholds)+1);

            for idx = 1:length(data_in)
                out(idx,comp_real_sum(idx)+1) = data_in.signal(idx);
            end

        end

    end

end

