"""
*************************************************************************
// Example program using OpenCV library
//      python >3.7 - OpenCV 4.5
// @file	e8.py
// @author Luis M. Jimenez
// @date 2022
//
// @brief Course: Computer Vision (1782)
// Dept. of Systems Engineering and Automation
// Automation, Robotics and Computer Vision Lab (ARVC)
// http://arvc.umh.es
// University Miguel Hernandez
//
// @note Description:
//	- This example captures images from a camera, shows them in a window and...
//  - Enables a handler for mouse events
//  - Saves window image on leftmouse+Shift click
//  - Saves window image on Space bar or F5 keystroke
//  - Calibrate camera
//  - Save calibration data to JSON
//
*************************************************************************
"""

# Import libraries
import cv2 as cv
import numpy as np
import argparse
import os
import json


# -----------------------------------------
# Functions
# -----------------------------------------
"""
//----------------------------------------------------------------------
// Mouse events handler  for image window
//----------------------------------------------------------------------
// event:	event type sent to the handler ->  cv.EVENT_MOUSEMOVE,
//          cv.EVENT_LBUTTONDOWN, cv.EVENT_LBUTTONUP, cv.EVENT_LBUTTONDBLCLK,
//          cv.EVENT_RBUTTONDOWN, cv.EVENT_RBUTTONUP, cv.EEVENT_RBUTTONDBLCLK,
//          cv.EVENT_MBUTTONDOWN, cv.EVENT_MBUTTONUP, cv.EEVENT_MBUTTONDBLCLK,
//          cv.EVENT_MOUSEWHEEL, cv.EVENT_MOUSEHWHEEL
// x:	X-coordinate position of the mouse in window
// y:	Y-coordinate position of the mouse in window
// flags: aditional flags sent to the handler ->
//          cv.EVENT_FLAG_SHIFTKEY, cv.EVENT_FLAG_CTRLKEY, cv.EVENT_FLAG_ALTKEY,
//          cv.EVENT_FLAG_LBUTTON, cv.EVENT_FLAG_RBUTTON, cv.EVENT_FLAG_MBUTTON, 
// param: set in cv.SetMouseCallback
//----------------------------------------------------------------------
"""
def onMouse(event, x, y, flags, param):
    global ID_FILE, capture  # global variables used in the mouse handler
    # print(f"{event=}, {x=}, {y=}, {flags=}, {param=}")

    # on click left mouse button and SHIFT key, saves image
    if event==cv.EVENT_LBUTTONDOWN and (flags & cv.EVENT_FLAG_SHIFTKEY):
        filename = f"Image{ID_FILE}.jpg"
        print(f"Saving image window in file: {filename}")
        cv.imwrite(filename, capture)   # save window image
        ID_FILE += 1


# -----------------------------------------
# Global variables
# -----------------------------------------
WINDOW_CAMERA1 = '(W1) Camera 1'   # window id
CAMERA_ID = 0	                   # default camera
KEY_F5 = 7602176                   # F5 unicode key code
ID_FILE = 1                        # filename id

# check command line parameters (camera id)
parser = argparse.ArgumentParser(description='OpenCV example: captures images from a camera')
parser.add_argument('-c', dest='cameraID', type=int, default=CAMERA_ID, metavar='id', help='camera id')
CAMERA_ID = parser.parse_args().cameraID

# -----------------------------------------
# Put here the code to Initialize objets
# -----------------------------------------

# Open camera object
camera = cv.VideoCapture(CAMERA_ID)
if not camera.isOpened():
    print("you need to connect a camera, sorry.")
    exit()

# Getting camera resolution
cameraWidth = int(camera.get(cv.CAP_PROP_FRAME_WIDTH))
cameraHeight = int(camera.get(cv.CAP_PROP_FRAME_HEIGHT))

# Creating visualization windows
cv.namedWindow(WINDOW_CAMERA1, cv.WINDOW_AUTOSIZE)

# Setting Mouse Handler
cv.setMouseCallback(WINDOW_CAMERA1, onMouse)

print(f"Capturing images from camera {CAMERA_ID} ({cameraWidth},{cameraHeight})")
print("...Hit F5 or Space bar to capture and save the image")
print("...Hit q/Q/Esc to exit.")

# -----------------------------------------
# Main Loop
# while there are images ...
# -----------------------------------------
while True:
    # Capture frame-by-frame
    ret, capture = camera.read()

    # if frame is read correctly ret is True
    if not ret:
        print("Can't receive frame (stream end?). Exiting ...")
        break
    # -----------------------------------------
    # Put your image processing code here
    # -----------------------------------------


    # -----------------------------------------
    # Put your visualization code here
    # -----------------------------------------
    cv.imshow(WINDOW_CAMERA1, capture)     # Display the resulting frame

    # check keystroke to exit (image window must be on focus)
    key = cv.pollKey()
    if key == ord('q') or key == ord('Q') or key == 27:
        break
    elif key == KEY_F5 or key == ord(' '):
        filename = f"Image{ID_FILE}.jpg"
        print(f"Saving image window in file: {filename}")
        cv.imwrite(filename, capture)   # save window image
        ID_FILE += 1

# End while (main loop)
# -----------------------------------------

# Calibrate camera with stored image files (calibration pattern)

# Calibration pattern inner corners (width Y-axis, height X-axis)
patternSize = (8, 6)
squareSize = 27.0       # square pattern side in mm

# 3D object points coordinates (x,y,z)
objp3D = np.zeros((patternSize[1], patternSize[0], 3), np.float32)
for x in range(patternSize[1]):
    for y in range(patternSize[0]):
        objp3D[x,y] = (x*squareSize, y*squareSize, 0)

objp3D = objp3D.reshape(-1, 3)   #  transform in a  row vector of (x,y,z) tuples

objpoints = []      # 3D point in real world space
imgpoints = []      # 2D points in image plane
num_patterns = 0    # number of detected patterns. We need at lest 3

# reading directory files
valid_extension = ('jpg', 'jpeg', 'png', 'tiff', 'tif', 'bmp', 'pgm')
directoryList = sorted(os.listdir("."))
for file in directoryList:
    # Filter non image files
    if not file.endswith(valid_extension):
        continue

    print(f"Processing image file: {file}")
    image = cv.imread(file)
    gray_image = cv.cvtColor(image, cv.COLOR_BGR2GRAY)  # transforms to gray level

    # Calibration pattern inner corners (cols Y-axis, rows X-axis)
    patternWasFound, corners = cv.findChessboardCorners(gray_image, patternSize)

    if patternWasFound:
        num_patterns += 1
        # Iterative algorithm termination criteria
        termCriteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
        corners = cv.cornerSubPix(gray_image, corners, winSize=(11,11), zeroZone=(-1,-1), criteria=termCriteria)

        # add points to image/3Dobjet lists
        objpoints.append(objp3D)
        imgpoints.append(corners)

        # Draw and display the corners
        cv.drawChessboardCorners(image, patternSize, corners, patternWasFound)

        cv.imshow(WINDOW_CAMERA1, image)     # Display the resulting frame
        key = cv.waitKey(1000)              # update image and wait 1 second

# end for file in  os.listdir("."):

if num_patterns >= 3:
    # imageSize:  (width, height)
    imageSize=gray_image.shape[::-1]  # store size as (cols,rows), shape returns (rows,cols))

    termCriteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    
    rms, cameraMatrix, distCoeffs, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, imageSize=imageSize,
                                                      cameraMatrix=None, distCoeffs=None, flags=0, criteria=termCriteria)
    print(f"RMS reprojection error: {rms}")

    # store calibration data in a JSON file
    with open('camera_calib.json', 'w') as file:
        json.dump({'camera_matrix': cameraMatrix.tolist(),
                   'distortion_coefficients': distCoeffs.tolist(),
                   'nx': imageSize[0],  # width  (x-axis)
                   'ny': imageSize[1],  # height (y-axis)
                   'rms': rms }, 
                   file, indent=4)

    # Reading calibration data from file
    try:
        with open('camera_calib.json') as file:
            data = json.load(file)

        if 'camera_matrix' in data:
            camera_matrix = np.array(data['camera_matrix'])
        if 'distortion_coefficients' in data:
            distortion_coefficients = np.array(data['distortion_coefficients'])

        print('Camera matrix:', camera_matrix)
        print('distortion_coefficients:', distortion_coefficients)

    except:
        print("File not valid")


# -----------------------------------------
# free windows and camera resources
# -----------------------------------------
cv.destroyAllWindows()
if camera.isOpened():  camera.release()
