# Standard library
import configparser  # noqa: E402
import logging  # noqa: E402
import os  # noqa
from glob import glob
from importlib.metadata import PackageNotFoundError, version  # noqa

# Third-party
import numpy as np  # noqa
import pandas as pd  # noqa
import astropy.io.fits as fits
import astropy.units as u
import astropy.constants as const
from astropy.modeling.models import Gaussian1D

# Local
from . import config

def get_version():
    try:
        return version("luxetc")
    except PackageNotFoundError:
        return "unknown"


__version__ = get_version()

from .hardware import Hardware  # noqa: E402, F401
from .uvchannel import UVChannel	# noqa: E402, F401
from .optchannel import OptChannel 	# noqa: E402, F401
from .nirchannel import NIRChannel	# noqa: E402, F401
from .orbit import Orbit  # noqa: E402, F401
from . import spec_etc_utils
#from .phoenix import SED  # noqa: E402, F401
#from .utils import *  # noqa: E402, F401, F403


class LuxETC(object):
    """Holds information and methods for the full Lux system."""

    def __init__(self, conf="REQ"):
    	
    	# Load all subclasses
        self.Orbit = Orbit()
        self.Hardware = Hardware()
        self.UVChannel = UVChannel(conf=conf)
        self.OptChannel = OptChannel(conf=conf)
        self.NIRChannel = NIRChannel(conf=conf)
        
        # Setup configuration
        if conf=="CBE":
            print("You are using Current Best Estimates (CBE) of system performance. Please make sure this is intended (Lux must achieve science objects at the requirements level).")
        elif conf=="REQ":
            print("You are using Requirements-level performace (REQ). This is default behavior.")
        elif conf=="REQ-FAVG":
            print("You are using Requirements-level performace (REQ) with FOV-averaged aperture fractions. This is not recommended, but is available for testing purposes.")
        else:
            raise ValueError("No such configuration setting: %s" % conf)
        self.conf = conf
            
    def __repr__(self):
        return "Lux Satellite"

    def _repr_html_(self):
        return "Lux Satellite"
        
    def zodi_counts(self, filt, zodi_level="avg"):
        """Calculates zodiacal light rate (per pixel) for a given filter""" 
        
        f = fits.open(config.DATADIR + "zodi_avg.fits")
        z = f[1].data["Photon Flux"] / u.s / u.cm / u.cm / u.AA / u.arcsec / u.arcsec
        if (zodi_level=="high"):
            z *= 1.73
        elif (zodi_level=="low"):
            z /= 1.73
        	
        if filt in config.LUXFILTS["UVCHAN"]:
            zfl = z * self.UVChannel.Aeff[filt] * self.UVChannel.pixel_scale**2
        elif filt in config.LUXFILTS["OPTCHAN"]:
            zfl = z * self.OptChannel.Aeff[filt] * self.OptChannel.pixel_scale**2
        elif filt in config.LUXFILTS["NIRCHAN"]:
            zfl = z * self.NIRChannel.Aeff[filt] * self.NIRChannel.pixel_scale**2
        else:
        	raise ValueError("No such filter in set: %s" % filt)
            
        return np.trapezoid(zfl, x=config.WAV.to('AA')) * u.electron * u.pixel**2
        
    def airglow_counts(self, filt, airglow_level="avg"):
        """Calculates airglow rate (per pixel) for a given filter"""
        
        ag = np.zeros(len(config.WAV)) * u.erg / u.s / u.cm / u.cm / u.AA / u.arcsec / u.arcsec
        if airglow_level=="low":
            ag[147] = 1.5e-18 * u.erg / u.s / u.cm / u.cm / u.AA / u.arcsec / u.arcsec
        elif airglow_level=="avg":
            ag[147] = 1.5e-16 * u.erg / u.s / u.cm / u.cm / u.AA / u.arcsec / u.arcsec
        elif airglow_level=="high":
            ag[147] = 3.0e-16 * u.erg / u.s / u.cm / u.cm / u.AA / u.arcsec / u.arcsec
           
        E = const.h * const.c / config.WAV.to('AA')
        
        if filt in config.LUXFILTS["UVCHAN"]:
            photon_spectrum = ag * self.UVChannel.Aeff[filt] * self.UVChannel.pixel_scale**2 / E.to('erg')
            return np.trapezoid(photon_spectrum, x=config.WAV.to('AA')) * u.pixel * u.pixel * u.electron
        elif filt in config.LUXFILTS["OPTCHAN"]:
            photon_spectrum = ag * self.OptChannel.Aeff[filt] * self.OptChannel.pixel_scale**2 / E.to('erg')
            return np.trapezoid(photon_spectrum, x=config.WAV.to('AA')) * u.pixel * u.pixel * u.electron
        elif filt in config.LUXFILTS["NIRCHAN"]:
            photon_spectrum = ag * self.NIRChannel.Aeff[filt] * self.NIRChannel.pixel_scale**2 / E.to('erg')
            return np.trapezoid(photon_spectrum, x=config.WAV.to('AA')) * u.pixel * u.pixel * u.electron
        else:
        	raise ValueError("No such filter in set: %s" % filt)
        
    def source_counts(self, source, filt):
        """Calculates source count rate (per pixel) for a given filter"""
        
        E = const.h * const.c / config.WAV.to('AA')
        
        if filt in config.LUXFILTS["UVCHAN"]:
            photon_spectrum = source * self.UVChannel.Aeff[filt] / E.to('erg')
            return self.UVChannel.aperture_fraction[filt] * \
                    np.trapezoid(photon_spectrum, x=config.WAV.to('AA')) * u.electron
        elif filt in config.LUXFILTS["OPTCHAN"]:
            photon_spectrum = source * self.OptChannel.Aeff[filt] / E.to('erg')
            return self.OptChannel.aperture_fraction[filt] * \
                    np.trapezoid(photon_spectrum, x=config.WAV.to('AA')) * u.electron  
        elif filt in config.LUXFILTS["NIRCHAN"]:
            photon_spectrum = source * self.NIRChannel.Aeff[filt] / E.to('erg')
            return self.NIRChannel.aperture_fraction[filt] * \
                    np.trapezoid(photon_spectrum, x=config.WAV.to('AA')) * u.electron        
        else:
        	raise ValueError("No such filter in set: %s" % filt)

    def get_snr(self, source, texps={}, zodi_level="avg", airglow_level="avg"):
        """Calculate SNR given source, filter, and texp"""
        
        SNR = {}
        
        # Loop through filters
        for filt, texp in texps.items():
        
            # Source counts
            src_cts = self.source_counts(source, filt) * texp * u.s
            
            # Zodi noise
            zodi_noise = self.zodi_counts(filt, zodi_level) * texp * u.s
            
            # Airglow noise
            aglow_noise = self.airglow_counts(filt, airglow_level) * texp * u.s
            
            if filt in config.LUXFILTS["UVCHAN"]:
                # Dark noise
                dark_noise = self.UVChannel.background_dark_rate * texp * u.s
            
                # Number of frames
                Nframes = texp * u.s / self.UVChannel.integration_time
            
                temp = src_cts / np.sqrt(src_cts + self.UVChannel.aperture_area[filt] * \
                             (zodi_noise + dark_noise + aglow_noise + 
                             Nframes * self.UVChannel.background_read_noise**2 / u.electron))
                SNR[filt] = temp.value
                
            elif filt in config.LUXFILTS["OPTCHAN"]:
                # Dark noise
                dark_noise = self.OptChannel.background_dark_rate * texp * u.s
            
                # Number of frames
                Nframes = texp * u.s / self.OptChannel.integration_time
            
                temp = src_cts / np.sqrt(src_cts + self.OptChannel.aperture_area[filt] * \
                             (zodi_noise + dark_noise + aglow_noise +
                             Nframes * self.OptChannel.background_read_noise**2 / u.electron))
                SNR[filt] = temp.value
                
            elif filt in config.LUXFILTS["NIRCHAN"]:
                # Dark noise
                dark_noise = self.NIRChannel.background_dark_rate * texp * u.s
            
                # Number of frames
                Nframes = texp * u.s / self.NIRChannel.integration_time
            
                temp = src_cts / np.sqrt(src_cts + self.NIRChannel.aperture_area[filt] * \
                             (zodi_noise + dark_noise + aglow_noise +
                             Nframes * self.NIRChannel.background_read_noise**2 / u.electron))
                SNR[filt] = temp.value
                
            else:
                raise ValueError("No such filter in set: %s" % filt)

        return SNR
        
    def get_texp(self, source, snrs={}, zodi_level="avg", airglow_level="avg"):
        """Calculate required exposure times given source, filter, and SNR"""
        
        texps = {}
        
        # Loop through filters
        for filt, SNR in snrs.items():
        
            # Source rate
            Rsrc = self.source_counts(source, filt)
            
            # Zodi rate
            Rzodi = self.zodi_counts(filt, zodi_level)
            
            # Airglow rate
            Raglow = self.airglow_counts(filt, airglow_level)
            
            if filt in config.LUXFILTS["UVCHAN"]:
                Npix = self.UVChannel.aperture_area[filt]
                Rdark = self.UVChannel.background_dark_rate
                RN = self.UVChannel.background_read_noise
                Tframe = self.UVChannel.integration_time
            elif filt in config.LUXFILTS["OPTCHAN"]:
                Npix = self.OptChannel.aperture_area[filt]
                Rdark = self.OptChannel.background_dark_rate
                RN = self.OptChannel.background_read_noise
                Tframe = self.OptChannel.integration_time
            elif filt in config.LUXFILTS["NIRCHAN"]:
                Npix = self.NIRChannel.aperture_area[filt]
                Rdark = self.NIRChannel.background_dark_rate
                RN = self.NIRChannel.background_read_noise
                Tframe = self.NIRChannel.integration_time
            else:
                raise ValueError("No such filter in set: %s" % filt)

            temp1 = SNR**2 * (Rsrc + Npix * Rzodi + Npix * Raglow + Npix * Rdark + Npix * RN**2 / Tframe / u.electron) / Rsrc**2    
            texps[filt] = temp1.value
            
        return texps         
        
    def get_limmag(self, filts={}, zodi_level="avg", airglow_level="avg"):
        """Calculate limiting magnitude for given filter, texp, and SNR"""
        
        limmags = {}
        
        # Loop through filters
        for filt, (Texp, SNR) in filts.items():
        
            # Zodi rate
            Rzodi = self.zodi_counts(filt, zodi_level)
            
            # Airglow rate
            Raglow = self.airglow_counts(filt, airglow_level)
            
            if filt in config.LUXFILTS["UVCHAN"]:
                Npix = self.UVChannel.aperture_area[filt]
                Rdark = self.UVChannel.background_dark_rate
                RN = self.UVChannel.background_read_noise
                Tframe = self.UVChannel.integration_time
                ZP = self.UVChannel.zero_point[filt]
            elif filt in config.LUXFILTS["OPTCHAN"]:
                Npix = self.OptChannel.aperture_area[filt]
                Rdark = self.OptChannel.background_dark_rate
                RN = self.OptChannel.background_read_noise
                Tframe = self.OptChannel.integration_time
                ZP = self.OptChannel.zero_point[filt]
            elif filt in config.LUXFILTS["NIRCHAN"]:
                Npix = self.NIRChannel.aperture_area[filt]
                Rdark = self.NIRChannel.background_dark_rate
                RN = self.NIRChannel.background_read_noise
                Tframe = self.NIRChannel.integration_time
                ZP = self.NIRChannel.zero_point[filt]
            else:
                raise ValueError("No such filter in set: %s" % filt)
               
            a = Texp / (SNR**2)
            b = -1
            ct = -Npix * (Rzodi + Raglow + Rdark + RN**2 / Tframe / u.electron)
            c = ct.value    
            
            Rsrc = (-b + np.sqrt(b**2 - 4. * a * c)) / 2. / a
            limmags[filt] = -2.5 * np.log10(Rsrc) + ZP   
               
        return limmags   
  
        
        
