""" Switch between continuous mode and all-LED one-shot mode
Based on synchr_cont_ledring and synchronous_ledring
Camera trigger should come from LEDring, '&' for continuous and '!' for the single LEDs
For now this uses the LED to trigger continuous, haven't quite gotten that sorted yet
Mainly to test switching between the two modes
Enter to switch between 2 modes, esc to exit?
IH 6/6/23
6/12/23 implement asynchronously - following asynchr_cont_ledring
6/13/23 'save' version: closeAllWindows still freezes but can get by without it;
   implement saving as per syncrh_dualmodesave_ledring
6/16/23 'gains' version: adjusts gains
"""

import threading
import sys
import cv2
from typing import Optional
from vimba import *

import serial
import time

inttimes = [10, 10, 10, 10, 20, 30, 50, 60]
gains = [0, 2, 4, 6, 0, 0, 0, 0]
i = 0 # instantiate here

ENTER_KEY_CODE = 13
ESCAPE_KEY_CODE = 27

path = 'c:\MultiSpec\Images\Test'

# copied but modified to not print help text
def abort(reason: str, return_code: int = 1):
    print(reason + '\n')

    sys.exit(return_code)


# copied from sample code; this function allows a camera id to be passed, e.g. by user argument
# but not including the code to read a camera ID as user argument
# since we'll only have one camera and easier to autodetect
def get_camera(camera_id: Optional[str]) -> Camera:
    with Vimba.get_instance() as vimba:
        if camera_id:
            try:
                return vimba.get_camera_by_id(camera_id)

            except VimbaCameraError:
                abort('Failed to access Camera \'{}\'. Abort.'.format(camera_id))

        else:
            cams = vimba.get_all_cameras()
            if not cams:
                abort('No Cameras accessible. Abort.')

            return cams[0]
        
# below modified from aysnchronous_grab_opencv
# set up for trigger on external signal
# also set up to output trigger_ready pin
def setup_camera(cam: Camera):
    with cam:

        # turns out I would like to knwo if one of these fails
        # so take out of the try-except statements
        cam.ExposureMode.set('TriggerWidth')
        cam.LineSelector.set('Line0')
        cam.LineMode.set('Input')
        cam.TriggerSelector.set('FrameStart')
        cam.TriggerActivation.set('LevelHigh')
        cam.TriggerSource.set('Line0')
        cam.TriggerMode.set('On')

        # set up trigger ready output
        cam.LineSelector.set('Line1')
        cam.LineMode.set('Output')
        #cam.LineSource.set('FrameTriggerWait')
        #cam.LineSource.set('Off')
        cam.LineSource.set('ExposureActive')

        cam.LineSelector.set('Line2')
        cam.LineMode.set('Output')
        cam.LineSource.set('FrameTriggerWait')

        cam.GainAuto.set('Off')
  

        # Query available, open_cv compatible pixel formats
        # monochrome formats only for our camera
        cv_fmts = intersect_pixel_formats(cam.get_pixel_formats(), OPENCV_PIXEL_FORMATS)
        mono_fmts = intersect_pixel_formats(cv_fmts, MONO_PIXEL_FORMATS)

        if mono_fmts:
            cam.set_pixel_format(mono_fmts[0])

        else:
            abort('Camera does not support a OpenCV compatible format natively. Abort.')


class Handler:
    def __init__(self, lednum, inttime_ms):
        self.shutdown_event = threading.Event()
        self.lednum = lednum
        self.inttime_ms = inttime_ms
        # can't get it to work passing variables to the __call__ function
        # seems the call to that function is buried within the vimba api
        # just want to have these available to label the frame
        self.key = 1 
        # need to be able to get key (received while streaming) from handler object to instruct program what to do

    def __call__(self, cam: Camera, frame: Frame):

        self.key = cv2.waitKey(1)
        if self.key == ENTER_KEY_CODE or self.key == ESCAPE_KEY_CODE:
            self.shutdown_event.set()
            return

        elif frame.get_status() == FrameStatus.Complete:
            #print('{} acquired {}'.format(cam, frame), flush=True)

            im = frame.as_opencv_image()
            #(h, w) = im.shape[:2] # 4608, 5328
            #print('{0},{1}\n\r'.format(str(h),str(w)))
            imS = cv2.resize(im,(1152,1328)) # values based on our image size and screen resolution
            #msg = 'Stream from \'{}\', LED{}, integration {} ms.'
            msg = 'Camera stream'
            #cv2.imshow(msg.format(cam.get_name(), self.lednum, self.inttime_ms), imS)
            cv2.imshow(msg, imS)

        cam.queue_frame(frame)


def main():
    # cam_id = parse_args() - remove this
    #print(cv2.__version__)

    ENTER_KEY_CODE = 13
    ESC_KEY_CODE = 27

    with Vimba.get_instance():
        with get_camera(None) as cam:
            setup_camera(cam)
            i_cont = 2 # LED for continuous mode
            handler = Handler(lednum=i_cont, inttime_ms=inttimes[i_cont])
            #time.sleep(0.1)

            cam.Gain.set(gains[i_cont])

            with serial.Serial('COM3', baudrate=115200, timeout=1) as ser:
                key = -1 # instantiate and starting value

                while(key != ESC_KEY_CODE):

                    # start in continuous mode
                    strtosend = '&{0},{1}\n\r'.format(str(i_cont),str(inttimes[i_cont]))
                    ser.write(strtosend.encode())

                    try:
                        # need this to be ready to stream again
                        handler.shutdown_event.clear()

                        # Start Streaming with a custom a buffer of 10 Frames (defaults to 5)
                        cam.start_streaming(handler=handler, buffer_count=10)
                        handler.shutdown_event.wait()
                        key = handler.key # works only if we intercept it early, otherwise another frame sneaks in and resets handler/key...

                    finally:
                        cam.stop_streaming()
                        ser.write(b'\n')

                    #cv2.destroyAllWindows()
                    #for i in range (1,5):
                    #    cv2.waitKey(1)

                    # destroyWindow / destroyAllWindows keeps freezing after asynchronous
                    # then the 8-shot mode can't exit properly either, possibly because the windows can't get focus to register waitkey
                    # so maybe esc during streaming is the only way we can get out
                    if key == ESC_KEY_CODE: # check here as well for the escape key
                        break

                    #key = -1 # it should get overwritten in next section anyways, but why not

                    # 8-shot mode - this is still synchronous
                    # let's label files with date/time
                    t = time.time()
                    timestamp = time.strftime('%Y%m%d_%H%M%S',time.localtime(t))

                    for i in range(0,8): # goes in steps of 1, apparently
                        cam.Gain.set(gains[i])

                        strtosend = '!{0},{1}\n\r'.format(str(i),str(inttimes[i]))
                        ser.write(strtosend.encode())

                        # Acquire one frame
                        for frame in cam.get_frame_generator(limit=1, timeout_ms=2000):
                            print('Got {}'.format(frame), flush=True)

                            # no imshow - because I can't close windows, it freezes
                            #msg = 'Frames from LED{}, integration {}ms'
                            #cv2.imshow(msg.format(str(i),str(inttimes[i])), frame.as_opencv_image())
                            #key = cv2.waitKey(1) 

                            # go directly to saving
                            fn = '{0}\{1}_LED{2}.png'.format(path,timestamp,str(i))
                            #image = iio.imwrite(fn,frame.as_numpy_ndarray(),extension=".png")
                            # imageio raises ValueError: Can't write images with one color channel.
                            # somehow nobody else on the internet seems to have this problem??
                            cv2.imwrite(fn,frame.as_opencv_image())

                            # 6/15/23 - let's see if we can do it in the same window, just give it hte same name
                            imS = cv2.resize(frame.as_opencv_image(),(1152,1328)) # values based on our image size and screen resolution
                            msg = 'Camera stream'
                            cv2.imshow(msg, imS)
                            key = cv2.waitKey(1) 

                            #cv2.destroyAllWindows()

                        # there's only 8, but might as well let user break out of this too
                        # 6/13/23 there is no way to exit 8-shot mode because the 
                        #    asynchronous window steals focus and freezes when try to destroy
                        if key == ENTER_KEY_CODE or key == ESC_KEY_CODE:
                            cv2.destroyAllWindows()
                            break
                        #time.sleep(0.1)
                    
                    #print('Reached end of 8-shot for loop; key is {}\n\r'.format(str(key)))
                    # destroyAllWindows freezes...
                    #cv2.destroyAllWindows()
                    #for i in range (1,5):
                    #    cv2.waitKey(1)

                    



if __name__ == '__main__':
    main()