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

        Aeff = self.optical_Aeff()
        self.Aeff = {"g": Aeff[0],
                     "r": Aeff[1],
                     "i": Aeff[2]}

        Aeff_optspec = self.optical_spec_Aeff()
        self.Aeff_optspec = Aeff_optspec
        
    def __repr__(self):
        return "OptChannel"
        
    @property
    def name(self):
        return "OPTCHAN"

    @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("Invalid configuration. Must be 'CBE' or 'REQ'.")
        
    @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("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 {"g": 22.2,
                    "r": 22.2,
                    "i": 22.2}
        elif self.conf == "REQ-FAVG":
            return {"g": 430.1,
                    "r": 430.1,
                    "i": 430.1}
        elif self.conf == "REQ":
            return {"g": 34.8,
                    "r": 34.8,
                    "i": 34.8}
        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 {"g": 0.714,
                    "r": 0.714,
                    "i": 0.714}
        elif self.conf == "REQ-FAVG":
            return {"g": 0.714,
                    "r": 0.714,
                    "i": 0.714}
        elif self.conf == "REQ":
            return {"g": 0.714,
                    "r": 0.714,
                    "i": 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: "OPTCHAN"}, axis="columns")
        
    @property
    def dichroic_throughput(self):
    	"""Dichroic Throughput"""
    	f = fits.open(config.DATADIR + "Optical_Dichroic.fits")
    	g = fits.open(config.DATADIR + "UV_Dichroic.fits")
    	return f[1].data["Reflectance"] * g[1].data["Transmission"]
    	
    @property
    def aft_optics_throughput(self):
    	"""Throughput of the 8 aft optics (AR coatings)"""
    	f = fits.open(config.DATADIR + "Aft_Optical_Throughput.fits")
    	return f[1].data["Throughput"]

    @property
    def optical_prisms_throughput(self):
        """Throughput of the prisms.  Only a guesstimate right now"""
        optprism_throughput = 0.5
        optical_prism_throughput = np.full(config.WAV.shape,optprism_throughput)
        return optical_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 optical_spec_Aeff(self):
        """ Effective area of optical spec mode"""
        Aeff_optspec = 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.optical_prisms_throughput * \
                       self.optical_detector_qe

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

    
    def optical_Aeff(self):
        """Effective area for each filter"""
        
        # g-band
        f = fits.open(config.DATADIR + "LSST_g_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_g = 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
        
        #r-band
        f = fits.open(config.DATADIR + "LSST_r_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_r = 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
                  
        #i-band
        f = fits.open(config.DATADIR + "LSST_i_filter.fits")
        filter_throughput = f[1].data["Throughput"]
        Aeff_i = 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_g, Aeff_r, Aeff_i)
        elif self.conf == "REQ-FAVG":
            return (Aeff_g*0.85, Aeff_r*0.85, Aeff_i*0.85)
        elif self.conf == "REQ":
            return (Aeff_g*0.85, Aeff_r*0.85, Aeff_i*0.85)
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")
		    	
    @property
    def zero_point(self):
        """Photometric zeropoint"""
        if self.conf == "CBE":
            return {"g": 22.214,
                "r": 22.034,
                "i": 21.314}
        elif self.conf == "REQ-FAVG":
            return {"g": 22.038,
                "r": 21.858,
                "i": 21.138}
        elif self.conf == "REQ":
            return {"g": 22.038,
                "r": 21.858,
                "i": 21.138}
        else:
            raise ValueError("Invalid configuration. Must be 'CBE' or 'REQ'.")
