from threading import Thread
from datetime import datetime
import time, numpy as np
import cv2
from uvctypes import *
from multiprocessing  import Queue

class Thermal(object):
    def __init__(self, width, height, framerate, frame_width, frame_height, log=None):
        self.__log = self.__log if log is None else log
        self.__isCaptured = False
        self.__frame = None
        self.__frame2c = None
        self.isOpened = False
        self.isStopping = False
        self.isNotSleeping=True
        self.width = width
        self.height = height
        self.frame_width = frame_width
        self.frame_height = frame_height
        self.framerate = framerate
        # uvc thermal init
        BUF_SIZE = 8
        self.q = Queue(BUF_SIZE)
        self.PTR_PY_FRAME_CALLBACK = CFUNCTYPE(None, POINTER(uvc_frame), c_void_p)(self.py_frame_callback)
        self.devh = None
        self.scale = 0.0092
        self.__thread = Thread(target=self.__job)
    def start(self):
        self.__isCaptured = False
        self.__frame = None
        self.__frame2c = None
        self.isOpened = True
        self.isStopping = False
        self.__thread.start()
    def restart(self):
        self.__isCaptured = False
        self.__frame = None
        self.__frame2c = None
        self.isOpened = True
        self.isStopping = False
        del self.__thread
        self.__thread = Thread(target=self.__job)
        self.__thread.start()
    def stop(self):
        self.__isCaptured = False
        self.__frame = None
        self.__frame2c = None
        self.isStopping = True
        libuvc.uvc_stop_streaming(self.devh)
    def capture(self):
        return self.__isCaptured, self.__frame, self.__frame2c
    def doFFC(self):
        perform_manual_ffc(self.devh)
    def update_scale(self,raw_temp,c):
        self.scale = c/raw_temp
    def py_frame_callback(self,frame, userptr):
        array_pointer = cast(frame.contents.data, POINTER(c_uint16 * (frame.contents.width * frame.contents.height)))
        data = np.frombuffer(
            array_pointer.contents, dtype=np.dtype(np.uint16)).reshape(frame.contents.height, frame.contents.width)
        if frame.contents.data_bytes != (2 * frame.contents.width * frame.contents.height):
            return
        if not self.q.full():
            self.q.put(data)        
    def startStream(self):        
        ctx = POINTER(uvc_context)()
        dev = POINTER(uvc_device)()
        self.devh = POINTER(uvc_device_handle)()
        ctrl = uvc_stream_ctrl()

        res = libuvc.uvc_init(byref(ctx), 0)
        if res < 0:
            print("uvc_init error")
            #exit(1)

        try:
            res = libuvc.uvc_find_device(ctx, byref(dev), PT_USB_VID, PT_USB_PID, 0)
            if res < 0:
                print("uvc_find_device error")
                exit(1)

            try:
                res = libuvc.uvc_open(dev, byref(self.devh))
                if res < 0:
                    print("uvc_open error")
                    exit(1)

                print("device opened!")
                print_device_info(self.devh)
                print_device_formats(self.devh)

                frame_formats = uvc_get_frame_formats_by_guid(self.devh, VS_FMT_GUID_Y16)
                if len(frame_formats) == 0:
                    print("device does not support Y16")
                    exit(1)

                libuvc.uvc_get_stream_ctrl_format_size(self.devh, byref(ctrl), UVC_FRAME_FORMAT_Y16,
                frame_formats[0].wWidth, frame_formats[0].wHeight, int(1e7 / frame_formats[0].dwDefaultFrameInterval)
                )

                res = libuvc.uvc_start_streaming(self.devh, byref(ctrl), self.PTR_PY_FRAME_CALLBACK, None, 0)
                if res < 0:
                    print("uvc_start_streaming failed: {0}".format(res))
                    exit(1)
                print("done starting stream, displaying settings")
                print_shutter_info(self.devh)
                print("resetting settings to default")
                set_auto_ffc(self.devh)
                set_gain_high(self.devh)
                set_radiometry_control(self.devh)
#                 print_flux_linear_parameters(self.devh)
#                 set_rbfo(self.devh)
#                 print_rbfo(self.devh)
                #set_tlinear_auto_resolution(self.devh)
#                 set_tlinear_control(self.devh)
#                 set_tshutter_control(self.devh)
                print("current settings")
                print_shutter_info(self.devh)
            except:
                #libuvc.uvc_unref_device(dev)
                print('Failed to Open Device')
        except:
            #libuvc.uvc_exit(ctx)
            print('Failed to Find Device')
            exit(1)                
    def __job(self):
        duration = 300 * self.framerate # restart / per one hour
        self.startStream()
        self.__log("Opened: {0}, Stopping: {1}, Duration: {2}".format(
            self.isOpened,
            self.isStopping,
            duration))
        perform_manual_ffc(self.devh)
        while self.isOpened and not self.isStopping:
            if(duration < 0):
                perform_manual_ffc(self.devh)
                duration = 300 * self.framerate
                time.sleep(1/self.framerate)
            a = self.q.get(True, 500)
            b = a.copy()                # 原始資料
            c = b.copy()
#             c_min = c.min()
#             c_max = c.max()
#             c_std = c.std()
#             c_var = c.var()
#             c_mean = c.mean()
#             self.__log("frame {0}, Tmax={1:.1f}, Tmin={2:.1f}, Tmean={3:.1f}, Tstd={4:.1f}, Tvar={5:.1f}, Tmax-Tmin={6:.1f}".format(
#                     duration,
#                     float(c_max),
#                     float(c_min),
#                     float(c_mean),
#                     float(c_std),
#                     float(c_var),
#                     float(c_max-c_min)
#                 ))
            self.__frame = cv2.resize(self.raw_to_8bit(b),(self.frame_width,self.frame_height), interpolation=cv2.INTER_AREA)
            self.__frame2c = c
            self.__isCaptured = True
            duration = duration - 1
            time.sleep(1 / self.framerate)
        self.__log("thermal stop")
        self.__frame = None
        self.__frame2c = None
        self.__isCaptured = False
    def __k2c(self, value):
        return (value - 27315) / 100.0
    def __k2f(self, value):
        return 1.8 * k2c(value) + 32.0
    def __s2c(self, value):
        return self.scale*value
    def __log(self, message):
        print(message)
    def raw_to_8bit(self,data):
        cv2.normalize(data, data, 0, 65535, cv2.NORM_MINMAX)
        np.right_shift(data, 8, data)
        return cv2.cvtColor(np.uint8(data), cv2.COLOR_GRAY2RGB)

