'''
xiapi_dng

ctypes wrapper around the xiapi_dng_store native library. Provides DNG file
saving for XI_IMG data captured through ximea.xiapi.

Requires the XIMEA "DNG Store" native library to be installed:
- Windows: xiapi_dng_store.dll, found via the XIMEA_SP_PATH env var (set by
    the XIMEA Software Package installer) + DNG Store\\binX64 or binX32
- Linux: /opt/XIMEA/lib/libxiapi_dng_store.so
- macOS: /Library/Frameworks/m3api.framework/Versions/B/Libraries/libxiapi_dng_store.dylib
'''

import os
import platform
import sys
from ctypes import c_uint, c_int, c_char, c_float, c_void_p, create_string_buffer, CDLL
from ctypes import Structure, byref

_OS = platform.system()

if _OS == 'Windows':
    _sp_path = os.environ.get('XIMEA_SP_PATH')
    if _sp_path is None:
        raise RuntimeError('XIMEA_SP_PATH environment variable is not set. '
                            'Install the XIMEA Software Package or set it to the SP install directory.')
    _arch_subdir = 'binX64' if sys.maxsize > 2**32 else 'binX32'
    _dng_dir = os.path.join(_sp_path, 'DNG Store', _arch_subdir)
    os.add_dll_directory(_dng_dir)
    _device = CDLL('xiapi_dng_store.dll')
elif _OS == 'Darwin':
    _device = CDLL('/Library/Frameworks/m3api.framework/Versions/B/Libraries/libxiapi_dng_store.dylib')
else:
    _device = CDLL('/opt/XIMEA/lib/libxiapi_dng_store.so')


class XI_DNG_METADATA(Structure):
    '''
    Structure containing information about DNG metadata.
    '''
    _fields_ = [
        ("cfa", c_uint),                    # bayer matrix type
        ("maxPixelValue", c_int),           # maximum pixel value, important for 16-bit image formats (i.e. 255 for 8-bit)
        ("cameraModelName", c_char * 256),  # camera model name, encoded in UTF8
        ("cameraSerialNumber", c_char * 256),
        ("cameraUserId", c_char * 256),     # camera ID given by the user, encoded in UTF8
        ("autoExposureOn", c_int),          # 1: on, 0: off, -1: N/A
        ("autoWhiteBalanceOn", c_int),      # 1: on, 0: off, -1: N/A
        ("whiteBalanceR", c_float),         # white balance coefficients
        ("whiteBalanceG", c_float),
        ("whiteBalanceB", c_float),
        ("lensAperture", c_float),
        ("lensFocalLength", c_float),
        ("acqTimeYear", c_int),             # acquisition time
        ("acqTimeMonth", c_int),
        ("acqTimeDay", c_int),
        ("acqTimeHour", c_int),
        ("acqTimeMinute", c_int),
        ("acqTimeSecond", c_int),
    ]


class Metadata(XI_DNG_METADATA):
    def __init__(self, camera=None, handle=None):
        if camera is not None and handle is not None:
            raise ValueError('only one of both must be set: camera or handle')
        if camera is not None:
            self.fill_from_camera(camera)
        elif handle is not None:
            self.fill_from_handle(handle)
        else:
            self.init()

    def init(self):
        init_metadata_struct(self)

    def fill_from_camera(self, camera):
        fill_metadata_from_camera_params(camera.handle, self)

    def fill_from_handle(self, handle):
        fill_metadata_from_camera_params(handle, self)


def store_image(filename, img, metadata):
    '''
    Save img (ximea.xiapi.Image instance) to filename in DNG format, using
    metadata (xiapi_dng.Metadata instance).
    '''
    f_name = create_string_buffer(bytes(filename, 'UTF-8'))  # only python3.x
    stat = _device.xidngStore(f_name, byref(img), byref(metadata))
    if stat != 0:
        raise RuntimeError('xidngStore failed with error %i' % stat)


def init_metadata_struct(metadata):
    stat = _device.xidngInitMetadataStruct(byref(metadata))
    if stat != 0:
        raise RuntimeError('xidngInitMetadataStruct failed with error %i' % stat)


def fill_metadata_from_camera_params(camera_handle, metadata):
    stat = _device.xidngFillMetadataFromCameraParams(camera_handle, byref(metadata))
    if stat != 0:
        raise RuntimeError('xidngFillMetadataFromCameraParams failed with error %i' % stat)
