classdef Amplifier
    %UNTITLED Summary of this class goes here
    %   Detailed explanation goes here

    properties
        amp_mode
        gain_mode
        nase_mode

        amplification_db       
        noifig

        fsimu
    end

    methods
        function obj = Amplifier(options)
            %UNTITLED Construct an instance of this class
            %   Detailed explanation goes here
            arguments
                options.amp_mode amp_mode   = amp_mode.ideal_no_noise
                options.gain_mode gain_mode = gain_mode.output_power
                options.nase_mode nase_mode = nase_mode.pass_ase

                options.amplification_db double = 0
                
                options.noifig double = 0;
            end

            fn = fieldnames(options);
            for n = 1:numel(fn)
                obj.(fn{n}) = options.(fn{n});
            end

        end

        function signalclass_out = process(obj,signalclass_in)

            % actual processing of the signal
            signalclass_in = obj.process_(signalclass_in);

            % append to logbook
            lbdesc = ['Amp '];
            signalclass_in  = signalclass_in.logbookentry(lbdesc);

            % write to output
            signalclass_out = signalclass_in;

        end

        function [X_out] = process_(obj,X_in)


            %calc gain for output power mode, amp mode and saturated mode
            a_lin = obj.calculateGain(X_in.signal);

            %apply amplification
            X_in.signal = a_lin * X_in.signal;
            
            % now, optical ase noise:
            if class(X_in) == "Opticalsignal"

                if obj.amp_mode == amp_mode.ideal_no_noise

                    %don't add/ remove noise, but scale it accordingly
                    X_in.nase = obj.onlyAmplifyAse(X_in.nase, a_lin);

                elseif obj.amp_mode == amp_mode.edfa_increase_nase

                    %calculate ASE-noise
                    X_in.nase = obj.increaseAse(X_in.nase, a_lin, obj.noifig , X_in.lambda );

                elseif obj.amp_mode == amp_mode.edfa_replace_nase

                    %calculate ASE-noise
                    X_in.nase = obj.replaceAse(X_in.nase, a_lin, obj.noifig , X_in.lambda );
                    
                end

                if obj.nase_mode == nase_mode.generate_ase
                    
                    nase        = X_in.nase;
                    fs          = X_in.fs;
                    dimension   = size(X_in.signal);

                    nase_numeric = obj.generateAseNoise(nase, fs, dimension);

                    [X_in.signal, osnr] = obj.applyAseToSignal(X_in.signal, nase_numeric);

                    X_in.nase   = 0;

                elseif obj.nase_mode == nase_mode.pass_ase
                    
                    X_in.nase = X_in.nase;

                end

            end

            X_out = X_in;



        end

        function a_lin = calculateGain(obj,xin)

            if obj.gain_mode == gain_mode.output_power

                %get linear gain for output power mode
                pow_in = mean(abs(xin.^2)) ; % lin input power
                pow_out = 10^(obj.amplification_db/10 - 3) ; % dBm to lin
                a_lin = sqrt(pow_out/pow_in) ;

            elseif obj.gain_mode == gain_mode.gain

                %get linear gain for classic gain mode
                a_lin=10^(obj.amplification_db/20);

            end


        end
        
        function nase_amped = onlyAmplifyAse(~,nase_old, a_lin)
            nase_amped = a_lin^2 * nase_old;
        end

        function nase_new = calculateAseFromThisAmp(~, a_lin, noisefig, lambda)
            h = Constant.Planck;
            c = Constant.LightSpeed;
            nase_new = 0.5* 10^(noisefig/10) * h*c/lambda * (a_lin^2-1) ;
        end

        function ase_increased = increaseAse(obj,nase_old, a_lin, noisefig, lambda)           
            
            nase_fromThisAmp = obj.calculateAseFromThisAmp(a_lin, noisefig, lambda);
            
            nase_old = a_lin^2 * nase_old;

            ase_increased = nase_old + nase_fromThisAmp;
        end

        function nase_new = replaceAse(~,a_lin, noisefig, lambda_nm)

            nase_fromThisAmp = obj.calculateAseFromThisAmp(a_lin, noisefig, lambda_nm);
            
            nase_new = nase_fromThisAmp;
        end

        function nase_numeric = generateAseNoise(~, nase, fs, dimension)
            rng(2023);
            nase_numeric = (randn(dimension) + 1i*randn(dimension))*sqrt(nase/2*fs) ;   
        end

        function [noisy_sig, osnr] = applyAseToSignal(~, opticalsignal, nase_numeric)
            noisy_sig = opticalsignal + nase_numeric ;
            osnr = pow2db(mean(abs(opticalsignal.^2))/mean(abs(nase_numeric.^2)));
            % disp(osnr);
        end
    end
end

