# 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

# 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 .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):
    	
    	self.Orbit = Orbit()
    	self.Hardware = Hardware()
    	#self.UVChannel = UVChannel()
    	self.OptChannel = OptChannel()
    	self.NIRChannel = NIRChannel()
        
    def __repr__(self):
        return "Lux Satellite"

    def _repr_html_(self):
        return "Lux Satellite"
        
    def zodi_counts(self, filt, zodi_level):
        """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=="g" or filt=="r" or filt=="i"):
            zfl = z * self.OptChannel.Aeff[filt] * self.OptChannel.pixel_scale**2
        elif (filt =="z" or filt=="y" or filt=="j"):
            zfl = z * self.NIRChannel.Aeff[filt] * self.NIRChannel.pixel_scale**2
            
        return np.trapezoid(zfl, x=config.WAV.to('AA')) * u.electron * u.pixel**2
        
    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=="g" or filt=="r" or filt=="i"):
            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=="z" or filt=="y" or filt=="j"):
            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        

    def get_snr(self, source, texps={}, zodi_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
            
            if (filt=="g" or filt=="r" or filt=="i"):
                # 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 + 
                             Nframes * self.OptChannel.background_read_noise**2 / u.electron))
                SNR[filt] = temp.value
            
            elif (filt=="z" or filt=="y" or filt=="j"):
                # 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 + 
                             Nframes * self.NIRChannel.background_read_noise**2 / u.electron))
                SNR[filt] = temp.value

        return SNR
        
    def get_texp(self, source, snrs={}, zodi_level="avg"):
        """Calculate required exposure times given source, filter, and SNR"""
        
        texps = {}
        
        # Loop through filters
        for filt, snr in snrs.items():
        
            # Source rate
            src_rate = self.source_counts(source, filt)
            
            # Zodi rate
            zodi_rate = self.zodi_counts(filt, zodi_level)
            
            if (filt=="g" or filt=="r" or filt=="i"):
                temp1 = snr**2 * (src_rate + self.OptChannel.aperture_area[filt] * \
                        (zodi_rate + self.OptChannel.background_dark_rate))
                temp2 = 4 * snr**2 * src_rate**2 * self.OptChannel.aperture_area[filt] * \
                        self.OptChannel.background_read_noise**2 / u.electron**2
                temp3 = (temp1 + np.sqrt(temp1**2 + temp2)) / 2. / src_rate**2
                texps[filt] = temp3.value
            elif (filt=="z" or filt=="y" or filt=="j"):
                temp1 = snr**2 * (src_rate + self.NIRChannel.aperture_area[filt] * \
                        (zodi_rate + self.NIRChannel.background_dark_rate))
                temp2 = 4 * snr**2 * src_rate**2 * self.NIRChannel.aperture_area[filt] * \
                        self.NIRChannel.background_read_noise**2 / u.electron**2
                temp3 = (temp1 + np.sqrt(temp1**2 + temp2)) / 2. / src_rate**2
                texps[filt] = temp3.value
            
        return texps         
        
    def get_limmag(self, filts={}, zodi_level="avg"):
        """Calculate limiting magnitude for given filter, texp, and SNR"""
        
        limmags = {}
        
        # Loop through filters
        for filt, (texp, snr) in filts.items():
        
            # Zodi rate
            zodi_rate = self.zodi_counts(filt, zodi_level)
            
            if (filt=="g" or filt=="r" or filt=="i"):
                src_cts = 0.5 * (snr**2 + np.sqrt(snr**4 + \
                           4. * snr**2 * self.OptChannel.aperture_area[filt] * \
                           texp * u.s * (zodi_rate + self.OptChannel.background_dark_rate + \
                           self.OptChannel.background_read_noise**2 / u.electron / texp / u.s) / u.electron))
            
                limmags[filt] = -2.5 * np.log10(src_cts.value/texp) + self.OptChannel.zero_point[filt]
                
            elif (filt=="z" or filt=="y" or filt=="j"):
                src_cts = 0.5 * (snr**2 + np.sqrt(snr**4 + \
                           4. * snr**2 * self.NIRChannel.aperture_area[filt] * \
                           texp * u.s * (zodi_rate + self.NIRChannel.background_dark_rate + \
                           self.NIRChannel.background_read_noise**2 / u.electron / texp / u.s) / u.electron))
            
                limmags[filt] = -2.5 * np.log10(src_cts.value/texp) + self.NIRChannel.zero_point[filt]
                
        return limmags   
  
        
        