##  @package E2Configs
#   Load config files for Multi-Camera Application.
#
#   There are 4 required and 1 optional config files:
#   ip_addresses.json    - The list of cameras, identified by their ip addresses
#   camera_settings.json - The saved camera settings from the last run.
#   camera_ranges.json   - The legal ranges of settings.
#   button_mapping.json  - Mapping from buttons to their functions.
#   focus*.json          - (optional) Mapping from camera IP to focus position setting.
#
#   Another file (testing only):
#   nolens               - Flag that camera has no lens.
#
#   @author     Mark Zvilius
#   @date       23-Feb-2020
#   @copyright  Virtual Reality Technology Underwater Limited (VRTUL)
#
import json
from glob import glob
from typing import Dict

CAMERA_LIST_FILE = 'config/ip_addresses.json'
CAMERA_SETTINGS_FILE = 'config/camera_settings.json'
CAMERA_RANGES_FILE = 'config/camera_ranges.json'
BUTTON_MAPPING_FILE = 'config/button_mapping.json'
CAMERA_NOLENS_FILE = 'config/nolens'
FOCUS_FILE_PATTERN = 'config/focus*.json'

# Camera IP addresses from CAMERA_LIST_FILE. The first is the master for recording.
camera_ips = []

# Saved camera settings.
camera_settings = {}

# Allowed ranges for camera settings.
camera_ranges = {}

# Button mappings.
button_map = {}

# Camera focus position settings: key is IP address.
camera_focus = {}

##
#   Load from config files.
#
#   Any failure throws an exception.
#
def load():
    global camera_ips, camera_settings, camera_ranges, button_map, camera_focus

    with open(CAMERA_LIST_FILE, 'r') as fp:
        camera_ips = json.load(fp)
        
    with open(CAMERA_SETTINGS_FILE, 'r') as fp:
        camera_settings = json.load(fp)

    with open(CAMERA_RANGES_FILE, 'r') as fp:
        camera_ranges = json.load(fp)

    with open(BUTTON_MAPPING_FILE, 'r') as fp:
        button_map = json.load(fp)
    
    camera_focus = load_focus_config(FOCUS_FILE_PATTERN)

##
#   Load the focus-position config file, if it exists.
#
#   The focus-position config is optional. If it doesn't exist, the focus-position
#   will not be set on the cameras.
#
#   There should be only one focus-position config file. If there are multiple, this
#   function will use the first one in the list returned by glob().
#
#   @param  pattern     File path pattern.
#   @return Dictionary of IP:focus_position.
#
def load_focus_config(pattern: str) -> Dict[str,int]:
    files = glob(pattern)
    if len(files) == 0:
        return {}
    with open(files[0], 'r') as fp:
        focus_dict = json.load(fp)
    return focus_dict


def update_settings(keyword, value):
    """Update camera_settings and write to file.

    keyword -- from E2Protocol.KEY_xxx
    value -- new value

    TODO: Need to figure out error handling.
    """
    print('%s -> %s' % (keyword,value))         # DEBUG
    camera_settings[keyword] = value
    with open(CAMERA_SETTINGS_FILE, 'w') as fp:
        json.dump(camera_settings, fp)


def get_index_of_current_value(keyword):
    """Look up index of current setting value in the range for that setting.

    keyword -- from E2Protocol.KEY_xxx
    returns -- (index,errmsg) index if successful
                              -1 keyword not in settings
                              -2 keyword not in ranges

    Notes:
    If the current value from settings is not in camera_ranges, default
    to using the first (index 0) value in the range.
    """
    if not keyword in camera_settings:
        errmsg = 'keyword %s not in settings' % (keyword)
        status = -1
        print(errmsg)               # DEBUG
        print(camera_settings)
        return (status,errmsg)

    value = camera_settings[keyword]        # current value
    
    if not keyword in camera_ranges:
        errmsg = 'keyword %s not in camera_ranges' % (keyword)
        status = -2
        print(errmsg)               # DEBUG
        print(camera_ranges)
        return (status,errmsg)

    range = camera_ranges[keyword]      # ordered list of possible values

    # Get the index of the current value. (See note above.)
    try:
        idx = range.index(value)
    except ValueError:
        idx = 0
        print('current value %s not in range %s' % (value,range))   # DEBUG
    return (idx,"")


def setting_up(keyword):
    """Increase setting, up to top (end) of list.

    Saves new setting value in camera_settings, and persists to the file.

    keyword -- from E2Protocol.KEY_xxx
    returns -- (value,errmsg) value as string if ok
                              None and errmsg if failed
    """
    (idx,errmsg) = get_index_of_current_value(keyword)
    if idx < 0:
        return (None,errmsg)

    # Increase the index without wrapping.
    range = camera_ranges[keyword]
    new_idx = min( idx+1, len(range)-1 )

    # Get the new value, update the settings, and write to file.
    new_value = range[new_idx]
    update_settings(keyword, new_value)
    return (new_value, "")
    

def setting_dn(keyword):
    """Decrease setting, down to bottom (beginning) of list.

    Saves new setting value in camera_settings, and persists to the file.

    keyword -- from E2Protocol.KEY_xxx
    returns -- (value,errmsg) value as string if ok
                              None and errmsg if failed
    """
    (idx,errmsg) = get_index_of_current_value(keyword)
    if idx < 0:
        return (None,errmsg)

    # Decrease the index without wrapping.
    range = camera_ranges[keyword]
    new_idx = max( idx-1, 0 )

    # Get the new value, update the settings, and write to file.
    new_value = range[new_idx]
    update_settings(keyword, new_value)
    return (new_value, "")


def setting_wrap(keyword):
    """Increase setting, wrap from end of list back to beginning.

    Saves new setting value in camera_settings, and persists to the file.

    keyword -- from E2Protocol.KEY_xxx
    returns -- (value,errmsg) value as string if ok
                              None and errmsg if failed
    """
    (idx,errmsg) = get_index_of_current_value(keyword)
    if idx < 0:
        return (None,errmsg)

    # Increase the index with wrapping.
    range = camera_ranges[keyword]
    new_idx = idx+1
    if new_idx == len(range):
        new_idx = 0

    # Get the new value, update the settings, and write to file.
    new_value = range[new_idx]
    update_settings(keyword, new_value)
    return (new_value, "")
    

def print_settings():
    """Print current camera settings."""
    for k,v in camera_settings.items():
        print('%s -> %s' % (k,v))


def check_no_lens_config():
    """Check if no lens is configured (testing only).

    Existence of CAMERA_NOLENS_FILE flags that the camera has no lens.
    This is just for Mark Z who at the time of this writing has no lens
    on his test camera.

    returns -- if configured for no lens
    """
    nolens = True
    fp = None
    try:
        fp = open(CAMERA_NOLENS_FILE)
    except FileNotFoundError:
        nolens = False
    finally:
        if fp: fp.close()
    return nolens

##  Unit tests.
if __name__ == "__main__":
    foc = load_focus_config(FOCUS_FILE_PATTERN)
    if len(foc) == 0:
        print('no focus*.json')
        exit(0)
    
    for ip,foc in foc.items():
        print(f'{ip} -> {foc}')
