#!/usr/bin/env python3

import sys
import math
import json
import argparse
import os.path
import logging
from logging.handlers import RotatingFileHandler

DEFAULT_CONFIG = "/var/www/revpi/pictory/projects/_config.rsc"
DEFAULT_CONFIG_OLD = "/var/www/pictory/projects/_config.rsc"

def current_offset(io):
    return int(io[3])

def next_offset(io):
    current_offset = int(io[3])
    length = math.ceil(int(io[2]) / 8) # length is min. 1
    offset = current_offset + length
    if io[7] != '':
        bit = int(io[7])
        offset += int(bit / 8)
    return offset

def fix_memory(io, delta):
    io[3] = str(int(io[3]) - delta)
    return io

def write_configuration(config, filename):
    with open(filename, "w") as f:
        json.dump(config, f)

# Validate & Correct the pictory configuration file
def main():
    parser = argparse.ArgumentParser(
        description='Fix wrong offset of ModbusRTUMaster in Pictory config file')
    parser.add_argument('-m', '--modify', action='store_true', dest='modify_config',
        default=False, help='modify configuration file', required=False)
    parser.add_argument('-l', '--log', metavar='log-file',
        help='path to log file', required=False, default=None)
    parser.add_argument('-c', '--config', metavar='config-file',
        help='path to pictory configuration file', required=False, default=None)

    args = parser.parse_args()

    logger = logging.getLogger('configchecker')
    logger.setLevel(logging.DEBUG)

    if args.log is not None:
        handler = RotatingFileHandler(args.log, 'a', 90000, backupCount=5)
        formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
    else:
        handler = logging.StreamHandler()
        formatter = logging.Formatter('%(levelname)s:%(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)

    # try default configuration files
    if args.config is None:
        # new path since Buster
        if os.path.isfile(DEFAULT_CONFIG):
            args.config = DEFAULT_CONFIG
        # old path Stretch and older
        elif os.path.isfile(DEFAULT_CONFIG_OLD):
            args.config = DEFAULT_CONFIG_OLD
        else:
            logger.error("Could not automatically detect the default pictory " \
             "configuration file. Please specify a file with the argument '-c'")
            sys.exit(2)

        logger.warning(f"No configuration file specified. Using '{args.config}' as default")
    elif not os.path.isfile(args.config):
        logger.error(f"Configuration file '{args.config}' does not exist")
        sys.exit(2)

    logger.info(f"Opening file '{args.config}' to check")
    try:
        with open(args.config, 'r') as f:
            data = json.load(f)
    except ValueError:
        logger.error("Configuration file is not in JSON format")
        sys.exit(3)
    except PermissionError:
        logger.critical(f"Permission denied when opening file '{args.config}'")
        sys.exit(1)

    num_changes = 0
    for device in data.get("Devices", []):
        changed = False
        logger.info(f"Checking device '{device['id']}' ...")

        io_sort = lambda e: current_offset(e)

        offset_calculated = 0
        for io_type in ["inp", "out", "mem"]:
            for memory in sorted(device[io_type].values(), key=io_sort):
                offset = current_offset(memory)

                if offset > offset_calculated:
                    delta = offset - offset_calculated

                    logger.info(f"  Found offset in {io_type} configuration " \
                        f"'{memory[0]}': {delta} bytes")

                    memory = fix_memory(memory, delta)
                    num_changes += 1
                    changed = True

                offset_calculated = next_offset(memory)

        if not changed:
            logger.info("  Pass")

    if num_changes:
        if args.modify_config:
            try:
                logger.info(f"Writing changes to configuration file '{args.config}'...")
                write_configuration(data, args.config)
            except IOError as e:
                logger.error("Could not write configuration file. Please make "\
                    "sure that you have write permissions to this file.")
                sys.exit(5)
        else:
            logger.info("The configuration file has not been modified. To apply" \
                f" {num_changes} modifications run again with argument '-m'")

    sys.exit(0)

if __name__ == '__main__':
    main()
