# This Device Class is published under the terms of the MIT License.
# Required Third Party Libraries, which are included in the Device Class
# package for convenience purposes, may have a different license. You can
# find those in the corresponding folders or contact the maintainer.
#
# MIT License
# 
# Copyright (c) 2018 Axel Fischer (sweep-me.net)
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


# SweepMe! device class
# Type: Spectrometer
# Device: Ocean Optics USB4000

import time
import os
import sys
import numpy as np

from FolderManager import addFolderToPATH
addFolderToPATH()
           
import seabreeze.spectrometers as sb

from EmptyDeviceClass import EmptyDevice

class Device(EmptyDevice):

    def __init__(self):

        EmptyDevice.__init__(self)
        
        self.shortname = "USBxxxx"
        
        self.variables = ["Wavelength", "Intensity", "Integration time", "Saturation"]
        self.units = ["nm", "W/nm", "s", "%"]
        self.plottype = [True, True, True, True]
        self.savetype = [True, True, True, True]

        self.calibrationfolder = self.get_folder("CALIBRATIONS")
        

    def get_GUIparameter(self, parameter = {}):
        self.integration_time = parameter["IntegrationTime"]        
        self.integration_time_automatic_max = float(parameter["IntegrationTimeMax"])
        self.sweep_mode = parameter["SweepMode"]
        self.automatic = parameter["IntegrationTimeAutomatic"]
        self.average = parameter["Average"]
        self.device_type = parameter["Port"]
        self.calibration = parameter["Calibration"]
        self.trigger_type = parameter["Trigger"]
        self.trigger_delay = float(parameter["TriggerDelay"])
        self.skip_initial_spec = int(parameter.get("SkipInitialSpectra", 20))
        
    def set_GUIparameter(self):
        GUIparameter = {
                        "Trigger": ["Internal", "External Rising", "External Falling"],
                        "SweepMode": ["None", "Integration time [s]"],
                        "IntegrationTime": 0.1,
                        "Average": 1,          
                        "SkipInitialSpectra": 20,
                        }
                        
        
        return GUIparameter
        
    def find_Ports(self):
    
        ports = [str(spec) for spec in sb.list_devices()]
    
        if isinstance(ports, bool):
            ports = []

        # returns a list of ports                             
        return ports
        
                   
    def get_CalibrationFile_properties(self, port):
        # returns two string
        # 1 file ending
        # 2 string in file_name

        serialno = str(port[str(port).find(":")+1:-1])
        
        if serialno == "":
            serialno = "noserialnumber"
        
        if "USB2+H06605" in serialno:
            self.cal_ending = ".labsphere_cal"
            print("Labsphere Spectrometer")
            return [".labsphere_cal", ""]
            
        else:
            self.cal_ending = ".IrradCal"
            return [".IrradCal", serialno]

       # USB4F05021_040309.IrradCal
     
                   
    def connect(self):
                    
        devices = sb.list_devices()
        
        for i in devices:
            if str(i) == self.device_type:
            
                if self.device_type in self.device_communication:
                    self.spectrometer = self.device_communication[self.device_type]
                else:
                    self.spectrometer = sb.Spectrometer(i)
                    self.device_communication[self.device_type] = self.spectrometer
        try:
            self.spectrometer
        except AttributeError:
            self.stopMeasurement = "Cannot connect to spectrometer."
                        
        #print self.spectrometer.model
        #print self.spectrometer.pixels

    def disconnect(self):
    
        if self.device_type in self.device_communication:
            try:
                self.spectrometer.close()
            except:
                pass
            finally:
                del self.device_communication[self.device_type]
                
        else:
            pass

    def initialize(self):
        
        self.integration_time = float(self.integration_time)
        self.integration_time_max = 65.0
        
        if self.spectrometer.model ==  "USB4000":
            self.integration_time_max = 65.0
            
        if self.spectrometer.model ==  "USB2000PLUS":
            self.integration_time_max = 65.0
            
        if self.spectrometer.model ==  "USB2000":
            self.integration_time_max = 30.0
        
        try:
            self.integration_time_limits = self.spectrometer.integration_time_micros_limits
            self.integration_time_max = self.integration_time_limits[1]/1e6
            self.integration_time_min = self.integration_time_limits[0]/1e6
        except:
            self.integration_time_min = self.spectrometer.minimum_integration_time_micros/1e6 # integration time in s

        if self.automatic:
            if self.integration_time_automatic_max > self.integration_time_max:
                self.integration_time_automatic_max = self.integration_time_max
                                          
        self.set_Integration_time()
        
        # self.spectrometer.Averaging = self.average
        self.wavelengths = self.read_Wavelengths()
        
        if "USB" in self.calibration:
            self.cal_ending = ".IrradCal"
        else:
            self.cal_ending = ".labsphere_cal"
        
        
        if self.calibration != "":
            calibration_file = self.calibrationfolder + os.sep + self.calibration
            
            if not calibration_file.endswith(self.cal_ending): #(".IrradCal"):
                calibration_file += self.cal_ending #".labsphere_cal"#".IrradCal"
            
            IrradCal=np.loadtxt(calibration_file,skiprows=9) # Ocean Optics file
            self.Calibration_array = IrradCal[:,1]
        else:
            self.Calibration_array = np.ones(self.spectrometer.pixels)
       
        if self.spectrometer.pixels != len(self.Calibration_array):
            self.stopMeasurement = "Check your calibration file. Number of pixels is not equal to the pixels your spectrometer has."
        
        
        self.max_intensity = self.spectrometer.max_intensity    # max intensity
        self.nonlinear_correction = self.spectrometer._nc       # nonlinear correction factors
        self.dark_pixel = self.spectrometer._dp                 # index of dark pixels
        self.pixels = self.spectrometer.pixels                  # total pixels
        self.pixel_range = np.ones(self.pixels, dtype=bool) 
        self.hot_pixel = [1]
        
        self.pixel_range[self.dark_pixel] = False
        self.pixel_range[self.hot_pixel] = False
        ### Debugging
        print(f"spectrometer object: {type(self.spectrometer)}")
        self.spectrometer._dev.f.spectrometer.get_intensities() # flush last spectrum
        
        
    def deinitialize(self):
        pass
        
    def read_Wavelengths(self):
        # must return a list of all wavelengths at which the spectrum is measured
        self.wavelengths = self.spectrometer.wavelengths()
        
        return self.wavelengths
        
    def start(self):
        # do some preliminary stuff to prepare the measurement
        pass
        
    def apply(self):
    
        if self.sweep_mode == "Integration time [s]":
    
            try:
                self.integration_time = float(self.value)
            except:
                self.messageBox("Reference spectrum not taken. No support for changing integration time.")
                                              
            self.set_Integration_time()
        
    def trigger(self):
        pass

    def measure(self):
        pass        
                
    def call(self):
        
        #print("features:", self.spectrometer.features)
        self.average = int(self.average)
        self.spectrum = np.zeros(len(self.wavelengths))
        self.sat = []
        
        #for i in np.arange(int(self.skip_initial_spec[-1])):
        #    self.spectrometer.intensities()
        
        # skipping 20 spectra with 5ms integration time; should be stable afte 10 -> 20 to be certain
        self.temp_integration_time = self.integration_time
        self.integration_time = 0.005
        self.set_Integration_time()
        
        for i in range(0, int(self.skip_initial_spec)):
            self.spectrometer._dev.f.spectrometer.get_intensities()
        
        self.integration_time = self.temp_integration_time
        self.set_Integration_time()
        
        for i in np.arange(self.average):
            # self.spec = self.spectrometer._dev.f.spectrometer.get_intensities()
            self.spec = self.spectrometer.intensities()
            self.sat.append(100*np.max(self.spec[self.pixel_range])/self.max_intensity)     # calculate saturation
            self.spectrum += self.spec
            
        self.spectrum /= self.average
        
        # nonlinarity correction
        if self.nonlinear_correction is not None:
            self.spectrum /= np.polyval(self.nonlinear_correction, self.spectrum)
        
        # dark current correction with dark pixels
        # self.dark_current = np.loadtxt(self.get_folder("SELF")[:-7]+"dark_current/dark_current.txt", skiprows = 3)[:,1]
        # self.dark_current_factor = np.mean(self.spectrum[self.dark_pixel]/self.dark_current[self.dark_pixel])
        # print("Dark Current Factor:", self.dark_current_factor)
        # if len(self.dark_pixel) > 0:
            # self.dark_value = self.spectrum[self.dark_pixel]
            # if np.abs(np.sum(self.dark_value)) is not np.inf:
                #pass#self.spectrum -= self.dark_current*self.dark_current_factor#np.mean(self.dark_value)
        
        # Saturation for average measurement
        if max(self.sat) > (100*(self.max_intensity-1)/self.max_intensity):
            self.saturation = 100.0
        else:
            self.saturation = np.mean(np.array(self.sat))
        
        # calibrate spectrum (integration time, intensity)
        self.spectrum = self.spectrum*self.Calibration_array/self.integration_time
        print("Saturation:", self.saturation)
        
        self.spectrum[np.invert(self.pixel_range)] *= 0
        
        return [self.wavelengths, self.spectrum, self.integration_time, self.saturation]

        
    def read_Integration_time(self):
        pass
        
    def set_Integration_time(self): 
    
        if self.integration_time < self.integration_time_min:
            self.integration_time = self.integration_time_min
            
        if self.integration_time > self.integration_time_max:
            self.integration_time = self.integration_time_max

        self.spectrometer.integration_time_micros(self.integration_time*1e6)
        time.sleep(self.integration_time*1)

