import numpy as np
import scipy.constants as Constant
import amp_mode 
import gain_mode
import nase_mode

class Amplifier:
    def __init__(self, options=None):
        self.amp_mode = options.amp_mode if options and hasattr(options, 'amp_mode') else amp_mode.ideal_no_noise
        self.gain_mode = options.gain_mode if options and hasattr(options, 'gain_mode') else gain_mode.output_power
        self.nase_mode = options.nase_mode if options and hasattr(options, 'nase_mode') else nase_mode.pass_ase
        self.amplification_db = options.amplification_db if options and hasattr(options, 'amplification_db') else 0
        self.noifig = options.noifig if options and hasattr(options, 'noifig') else 0
        self.fsimu = options.fsimu if options and hasattr(options, 'fsimu') else None

    def process(self, signalclass_in):
        signalclass_in = self.process_(signalclass_in)
        lbdesc = 'Amp '
        signalclass_in = signalclass_in.logbookentry(lbdesc)
        signalclass_out = signalclass_in
        return signalclass_out

    def process_(self, X_in):
        a_lin = self.calculateGain(X_in.signal)
        X_in.signal = a_lin * X_in.signal

        if isinstance(X_in, Opticalsignal):
            if self.amp_mode == amp_mode.ideal_no_noise:
                X_in.nase = self.onlyAmplifyAse(X_in.nase, a_lin)
            elif self.amp_mode == amp_mode.edfa_increase_nase:
                X_in.nase = self.increaseAse(X_in.nase, a_lin, self.noifig, X_in.lambda_)
            elif self.amp_mode == amp_mode.edfa_replace_nase:
                X_in.nase = self.replaceAse(X_in.nase, a_lin, self.noifig, X_in.lambda_)

            if self.nase_mode == nase_mode.generate_ase:
                nase = X_in.nase
                fs = X_in.fs
                dimension = X_in.signal.shape
                nase_numeric = self.generateAseNoise(nase, fs, dimension)
                X_in.signal, osnr = self.applyAseToSignal(X_in.signal, nase_numeric)
                X_in.nase = 0
            elif self.nase_mode == nase_mode.pass_ase:
                X_in.nase = X_in.nase

        X_out = X_in
        return X_out

    def calculateGain(self, xin):
        if self.gain_mode == gain_mode.output_power:
            pow_in = np.mean(np.abs(xin) ** 2)
            pow_out = 10 ** (self.amplification_db / 10 - 3)
            a_lin = np.sqrt(pow_out / pow_in)
        elif self.gain_mode == gain_mode.gain:
            a_lin = 10 ** (self.amplification_db / 20)
        return a_lin

    def onlyAmplifyAse(self, nase_old, a_lin):
        nase_amped = a_lin ** 2 * nase_old
        return nase_amped

    def calculateAseFromThisAmp(self, a_lin, noisefig, lambda_):
        h = Constant.Planck
        c = Constant.speed_of_light
        nase_new = 0.5 * 10 ** (noisefig / 10) * h * c / lambda_ * (a_lin ** 2 - 1)
        return nase_new

    def increaseAse(self, nase_old, a_lin, noisefig, lambda_):
        nase_fromThisAmp = self.calculateAseFromThisAmp(a_lin, noisefig, lambda_)
        nase_old = a_lin ** 2 * nase_old
        ase_increased = nase_old + nase_fromThisAmp
        return ase_increased

    def replaceAse(self, nase_old, a_lin, noisefig, lambda_):
        nase_fromThisAmp = self.calculateAseFromThisAmp(a_lin, noisefig, lambda_)
        nase_new = nase_fromThisAmp
        return nase_new

    def generateAseNoise(self, nase, fs, dimension):
        nase_numeric = (np.random.randn(*dimension) + 1j * np.random.randn(*dimension)) * np.sqrt(nase / 2 * fs)
        return nase_numeric

    def applyAseToSignal(self, opticalsignal, nase_numeric):
        noisy_sig = opticalsignal + nase_numeric
        osnr = pow(10 * np.log10(np.mean(np.abs(opticalsignal) ** 2) / np.mean(np.abs(nase_numeric) ** 2)))
        print(osnr)
        return noisy_sig, osnr
