"""Holds metadata and methods on Lux UV Channel"""

# Standard library
from dataclasses import dataclass

# Third-party
import astropy.units as u
import astropy.io.fits as fits
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# Local
from . import config
from . import hardware
#from . import PANDORASTYLE

@dataclass
class UVChannel():
    """
    Holds information on the Lux UV Channel
    """
    
    def __init__(self, conf="REQ"):

        # Set configuration
        self.conf = conf
        
        # Set common telescope properties
        self.hardware = hardware.Hardware()

        # Set effective area
        Aeff = self.uv_Aeff()
        self.Aeff = {"uv2": Aeff[0],
                     "uv1": Aeff[1],
                     "u": Aeff[2]}

        Aeff_uvspec = self.uv_spec_Aeff()
        self.Aeff_uvspec = Aeff_uvspec

        
    def __repr__(self):
        return "UVChannel"
        
    @property
    def name(self):
        return "UVCHAN"

    @property
    def shape(self):
        """Shape of the detector in pixels"""
        return (4096, 4096)
        ### Need to verify what portion of the detector is actually read out

    @property
    def pixel_scale(self):
        """Pixel scale of the detector"""
        return 1.08 * u.arcsec / u.pixel

    @property
    def pixel_size(self):
        """Size of a pixel"""
        return 9.0 * u.um / u.pixel

    @property
    def bits_per_pixel(self):
        """Number of bits per pixel"""
        return 16 * u.bit / u.pixel

    @property
    def naxis1(self):
        """WCS's are COLUMN major, so naxis1 is the number of columns"""
        return self.shape[1] * u.pixel

    @property
    def naxis2(self):
        """WCS's are COLUMN major, so naxis2 is the number of rows"""
        return self.shape[0] * u.pixel

    @property
    def background_dark_rate(self):
        """Detector background dark current rate"""
        if self.conf == "CBE":
            return 0.04 * u.electron / u.second
        elif self.conf == "REQ-FAVG":
            return 0.10 * u.electron / u.second
        elif self.conf == "REQ":
            return 0.10 * u.electron / u.second
        else:
            raise ValueError("No such configuration setting: %s" % self.conf)

    @property
    def background_read_noise(self):
        """Detector background read noise"""
        if self.conf == "CBE":
            return 2.3 * u.electron
        elif self.conf == "REQ-FAVG":
            return 3.3 * u.electron
        elif self.conf == "REQ":
            return 3.3 * u.electron
        else:
            raise ValueError("No such configuration setting: %s" % self.conf)
		
    @property
    def integration_time(self):
        """Integration time"""
        return 60.0 * u.second
        
    @property
    def aperture_area(self):
        """Area to sum for aperture photometry"""
        if self.conf == "CBE":
            return {"uv2": 34.2,
                    "uv1": 34.2,
                    "u": 34.2}
        elif self.conf == "REQ-FAVG":
            return {"uv2": 430.1,
                    "uv1": 430.1,
                    "u": 430.1}
        elif self.conf == "REQ":
            return {"uv2": 51.2,
                    "uv1": 51.2,
                    "u": 51.2}
        else:
            raise ValueError("No such configuration setting: %s" % self.conf)
        
    @property
    def aperture_fraction(self):
        """Fraction of PSF included in aperture_area"""
        if self.conf == "CBE":
            return {"uv2": 0.709,
                    "uv1": 0.709,
                    "u": 0.709}
        elif self.conf == "REQ-FAVG":
            return {"uv2": 0.709,
                    "uv1": 0.709,
                    "u": 0.709}
        elif self.conf == "REQ":
            return {"uv2": 0.709,
                    "uv1": 0.709,
                    "u": 0.709}
        else:
            raise ValueError("No such configuration setting: %s" % self.conf)

    @property
    def info(self):
        return pd.DataFrame(
            {
                "Detector Size": f"({self.naxis1.value.astype(int)}, {self.naxis2.value.astype(int)})",
                "Pixel Scale": f"{self.pixel_scale.value} {self.pixel_scale.unit.to_string('latex')}",
                "Pixel Size": f"{self.pixel_size.value} {self.pixel_size.unit.to_string('latex')}",
                "Read Noise": f"{self.background_read_noise.value} {self.background_read_noise.unit.to_string('latex')}",
                "Dark Noise": f"{self.background_dark_rate.value} {self.background_dark_rate.unit.to_string('latex')}",
                "Integration Time": f"{self.integration_time.value} {self.integration_time.unit.to_string('latex')}",
            },
            index=[0],
        ).T.rename({0: "UVCHAN"}, axis="columns")
        
    @property
    def dichroic_throughput(self):
    	"""Dichroic Throughput"""
    	f = fits.open(config.DATADIR + "UV_Dichroic.fits")
    	return f[1].data["Reflectance"]
    	
    @property
    def aft_optics_throughput(self):
    	"""Throughput of the 6 aft optics (AR coatings)"""
    	f = fits.open(config.DATADIR + "Aft_UV_Throughput.fits")
    	return f[1].data["Throughput"]

    @property
    def uv_prisms_throughput(self):
        """Throughput of the prisms.  Only a guesstimate right now"""
        uvprism_throughput = 0.5
        uv_prism_throughput = np.full(config.WAV.shape,uvprism_throughput)
        return uv_prism_throughput
    
    @property
    def optical_detector_qe(self):
    	"""Quantum Efficiency of Optical Detector"""
    	f = fits.open(config.DATADIR + "Optical_Detector_QE.fits")
    	return f[1].data["QE"]
    	
    def uv_spec_Aeff(self):
        """ Effective area of optical spec mode"""
        Aeff_uvspec = 0.25 * np.pi * \
                       np.power(self.hardware.primary_mirror_effective_diameter,2) * \
                       self.hardware.fore_throughput * \
                       self.dichroic_throughput * \
                       self.aft_optics_throughput * \
                       self.uv_prisms_throughput * \
                       self.optical_detector_qe

        if self.conf == "CBE":
            return Aeff_uvspec
        elif self.conf == "REQ":
            return Aeff_uvspec
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")

    def uv_Aeff(self):
        """Effective area for each filter"""
        
        # u-band
        f = fits.open(config.DATADIR + "LSST_u_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_u = 0.25 * np.pi * \
                  np.power(self.hardware.primary_mirror_effective_diameter,2) * \
                  self.hardware.fore_throughput * \
                  self.dichroic_throughput * \
                  self.aft_optics_throughput * \
                  filter_throughput * \
                  self.optical_detector_qe
        
        #uv1-band
        f = fits.open(config.DATADIR + "UV1_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_uv1 = 0.25 * np.pi * \
                  np.power(self.hardware.primary_mirror_effective_diameter,2) * \
                  self.hardware.fore_throughput * \
                  self.dichroic_throughput * \
                  self.aft_optics_throughput * \
                  filter_throughput * \
                  self.optical_detector_qe
                  
        #uv2-band (UVEX)
        f = fits.open(config.DATADIR + "UVEX_NUV_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_uv2 = 0.25 * np.pi * \
                  np.power(self.hardware.primary_mirror_effective_diameter,2) * \
                  self.hardware.fore_throughput * \
                  self.dichroic_throughput * \
                  self.aft_optics_throughput * \
                  filter_throughput * \
                  self.optical_detector_qe
                  
        if self.conf == "CBE":
            return (Aeff_uv2, Aeff_uv1, Aeff_u)
        elif self.conf == "REQ":
            return (Aeff_uv2*0.75, Aeff_uv1*0.75, Aeff_u*0.75)
        elif self.conf == "REQ-FAVG":
            return (Aeff_uv2*0.75, Aeff_uv1*0.75, Aeff_u*0.75)
        else:
            raise ValueError("No such configuration setting: %s" % self.conf)
		    	
    @property
    def zero_point(self):
        """Photometric zeropoint"""
        if self.conf == "CBE":
            return {"u": 21.193,
                    "uv1": 20.312,
                    "uv2": 20.296}
        elif self.conf == "REQ-FAVG":
            return {"u": 20.881,
                    "uv1": 20.000,
                    "uv2": 19.984}
        elif self.conf == "REQ":
            return {"u": 20.881,
                    "uv1": 20.000,
                    "uv2": 19.984}
        else:
            raise ValueError("No such configuration setting: %s" % self.conf)
