"""Holds metadata and methods on Lux NIR 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 NIRChannel():
    """
    Holds information on the Lux NIR Channel
    """
    
    def __init__(self, conf="CBE"):
        
        # Set configuration
        self.conf = conf
        
        # Set common telescope properties
        self.hardware = hardware.Hardware()

        Aeff = self.nir_Aeff()
        self.Aeff = {"z": Aeff[0],
                     "y": Aeff[1],
                     "j": Aeff[2]}

        Aeff_nirspec = self.nir_spec_Aeff()
        self.Aeff_nirspec = Aeff_nirspec

        
    def __repr__(self):
        return "NIRChannel"
        
    @property
    def name(self):
        return "NIRCHAN"

    @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.20 * u.arcsec / u.pixel

    @property
    def pixel_size(self):
        """Size of a pixel"""
        return 10.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.016 * u.electron / u.second
        elif self.conf == "REQ-FAVG":
            return 0.05 * u.electron / u.second
        elif self.conf == "REQ":
            return 0.05 * u.electron / u.second
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")
        
    @property
    def background_read_noise(self):
        """Detector background read noise"""
        if self.conf == "CBE":
            return 5.5 * u.electron
        elif self.conf == "REQ-FAVG":
            return 7.5 * u.electron
        elif self.conf == "REQ":
            return 7.5 * u.electron
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")
		
    @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 {"z": 17.9,
                    "y": 17.9,
                    "j": 17.9}
        elif self.conf == "REQ-FAVG":
            return {"z": 353.0,
                    "y": 353.0,
                    "j": 353.0}
        elif self.conf == "REQ":
            return {"z": 28.2,
                    "y": 28.2,
                    "j": 28.2}
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")
        
    @property
    def aperture_fraction(self):
        """Fraction of PSF included in aperture_area"""
        if self.conf == "CBE":
            return {"z": 0.714,
                    "y": 0.714,
                    "j": 0.714}
        elif self.conf == "REQ-FAVG":
            return {"z": 0.714,
                    "y": 0.714,
                    "j": 0.714}
        elif self.conf == "REQ":
            return {"z": 0.714,
                    "y": 0.714,
                    "j": 0.714}
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")

    @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: "NIRCHAN"}, axis="columns")
        
    @property
    def dichroic_throughput(self):
        """Dichroic Throughput"""
        f = fits.open(config.DATADIR + "UV_Dichroic.fits")
        g = fits.open(config.DATADIR + "Optical_Dichroic.fits")
        return f[1].data["Transmission"] * g[1].data["Transmission"]

    @property
    def aft_optics_throughput(self):
        """Throughput of the 8 aft optics (AR coatings)"""
        f = fits.open(config.DATADIR + "Aft_NIR_Throughput.fits")
        return f[1].data["Throughput"]

    @property
    def nir_prisms_throughput(self):
        """Throughput of the prisms.  Only a guesstimate right now"""
        nirprism_throughput = 0.5
        nir_prism_throughput = np.full(config.WAV.shape,nirprism_throughput)
        return nir_prism_throughput
    
    @property
    def nir_detector_qe(self):
        """Quantum Efficiency of Optical Detector"""
        f = fits.open(config.DATADIR + "NIR_Detector_QE.fits")
        return f[1].data["QE"]
    	
    def nir_spec_Aeff(self):
        """ Effective area of optical spec mode"""
        Aeff_nirspec = 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.nir_prisms_throughput * \
                       self.nir_detector_qe

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

    def nir_Aeff(self):
        """Effective area for each filter"""
        
        # z-band
        f = fits.open(config.DATADIR + "LSST_z_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_z = 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.nir_detector_qe
        
        # y-band
        f = fits.open(config.DATADIR + "Roman_y_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_y = 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.nir_detector_qe
                  
        # j-band
        f = fits.open(config.DATADIR + "Roman_j_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_j = 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.nir_detector_qe
                  
        if self.conf == "CBE":
            return (Aeff_z, Aeff_y, Aeff_j)
        elif self.conf == "REQ-FAVG":
            return (Aeff_z*0.85, Aeff_y*0.85, Aeff_j*0.85)
        elif self.conf == "REQ":
            return (Aeff_z*0.85, Aeff_y*0.85, Aeff_j*0.85)
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")

    @property
    def zero_point(self):
        """Photometric zeropoint"""
        if self.conf == "CBE":
            return {"z": 21.193,
                "y": 22.245,
                "j": 22.140}
        elif self.conf == "REQ-FAVG":
            return {"z": 21.017,
                "y": 22.069,
                "j": 21.964}
        elif self.conf == "REQ":
            return {"z": 21.017,
                "y": 22.069,
                "j": 21.964}
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")

        
