'''
Copyright 2020 Cisco Systems, Inc. and its affiliates.
 
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
 
    http://www.apache.org/licenses/LICENSE-2.0
 
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License


Program to generate Python enum declarations
from an Entity-Relationship Markup Language (ERML) file.

Usage: genpyenums.py [OPTIONS]

  Generate Python enum declarations from an Entity-Relationship Markup
  Language (ERML) file

Options:
  --input TEXT    Input Entity-Relationship Markup Language file (default is
                  standard input, also represented by a dash "-")
  --output TEXT   Output schema definition file (default is standard output,
                  also represented by a dash "-")
  --overwrite     If specified, overwrite the output file if it already exists
  --logging TEXT  Set logging to the specified level: NOTSET, DEBUG, INFO,
                  WARNING, ERROR, CRITICAL
  --help          Show this message and exit.
'''

'''
TODO:
syntax for retired/obsolete enum values
'''

import sys
import os.path
from loguru import logger
import click
import yaml
import jsonschema
import datetime
from json_schema_erml import json_schema_erml
import json
from util import i


@logger.catch
def genpyenums(er_yaml, input, output_object):
    '''
    Generally-callable entry point to 
    generate Python enum declarations from an Entity-Relationship Markup Language (ERML) file
    '''
    logger.debug('Entering genpyenums()')
    logger.debug('Before validating YAML via jsonschema.validate()')
    try:
        jsonschema.validate(instance=er_yaml, schema=json_schema_erml)
    except jsonschema.exceptions.ValidationError as ex:
        print(f'\nERROR: Invalid YAML (schema) for Entity-Relationship Markup Language input file.\n'
              f'ERROR DETAILS:\n{ex}\n', file=sys.stderr)
        sys.exit(1)
    logger.debug('After jsonschema.validate()')

    print(f"'''", file=output_object)
    print(f'Enum definitions generated by Zepster', file=output_object)
    print(f'Source: {"stdin" if input == "-" else input}', file=output_object)
    print(f'Generated: {datetime.datetime.utcnow().isoformat()}', file=output_object)
    print(f"'''\n", file=output_object)
    print('from enum import Enum, unique\n\n', file=output_object)

    for enum_outer in er_yaml['enums']:
        enum = enum_outer['enum']
        logger.debug(f'enum_outer={enum_outer}')
        enum_name = enum['name']
        logger.debug(f'enum_name={enum_name}')
        print('@unique', file=output_object)
        print(f'class {enum_name.capitalize()}(Enum):', file=output_object)
        if 'description' in enum or 'note' in enum:
            print(f"{i(2)}'''", file=output_object)
            if 'description' in enum:
                print(f'{i(2)}Description:', file=output_object)
                enum_description = enum['description']
                for line in enum_description.splitlines():
                    print(f'{i(2)}{line}', file=output_object)
            if 'note' in enum:
                print(f'{i(2)}Note:', file=output_object)
                enum_note = enum['note']
                for line in enum_note.splitlines():
                    print(f'{i(2)}{line}', file=output_object)
            print(f"{i(2)}'''", file=output_object)
    
        for ordinal, enum_value_or_more in enumerate(enum['values']):
            logger.debug(f'{i(2)}enum_value_or_more={enum_value_or_more} type={type(enum_value_or_more)}')
            if type(enum_value_or_more) == type(''):
                logger.debug(f'{i(2)}Type is string')
                enum_value = enum_value_or_more
            elif type(enum_value_or_more) == type({}):
                logger.debug(f'{i(2)}Type is dictionary')
                enum_value = enum_value_or_more['value']
                if 'description' in enum_value_or_more:
                    print(f'{i(2)}# Description:', file=output_object)
                    enum_value_description = enum_value_or_more['description']
                    for line in enum_value_description.splitlines():
                        print(f'{i(2)}# {line}', file=output_object)
                if 'note' in enum_value_or_more:
                    print(f'{i(2)}# Note:', file=output_object)
                    enum_value_note = enum_value_or_more['note']
                    for line in enum_value_note.splitlines():
                        print(f'{i(2)}# {line}', file=output_object)
            else:
                raise ValueError(f'Enum value did not match expected type of string or '
                                 f'dicitonary for enum table "{enum_name}". '
                                 f'Value is {enum_value_or_more}')
            # escape to prevent Python injection
            print(f'{i(2)}{enum_value} = {ordinal+1}', file=output_object)
        print('\n', file=output_object)
    logger.debug('Leaving genpyenums()')


@click.command()
@click.option(
    '--input',
    default='-',
    help='Input Entity-Relationship Markup Language file (default is standard input, also represented by a dash "-")',
)
@click.option(
    '--output',
    default='-',
    help='Output schema definition file (default is standard output, also represented by a dash "-")',
)
@click.option(
    '--overwrite',
    is_flag=True,
    default=False,
    help='If specified, overwrite the output file if it already exists',
)
@click.option(
    '--logging',
    type=str,
    default='WARNING',
    help='Set logging to the specified level: NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL',
)
@logger.catch
def main(input, output, overwrite, logging):
    '''
    Generate Python enum declarations from an Entity-Relationship Markup Language (ERML) file
    '''

    if logging != 'WARNING':
        # Remove default logger to reset logging level from the previously-set level of WARNING to 
        # something else per https://github.com/Delgan/loguru/issues/51
        logger.remove(loguru_handler_id)
        logger.add(sys.stderr, level=logging)

    logger.debug('Entering main()')
    logger.info(f'click version is {click.__version__}')
    logger.debug(
        f'parameters: input={input} output={output} overwrite={overwrite} logging={logging}'
    )

    close_input_object = False
    close_output_object = False

    if output == '-':
        output_object = sys.stdout
    else:
        if overwrite == False and os.path.exists(output):
            print(f'Error: Specified output file already exists: {output}', file=sys.stderr)
            sys.exit(1)

        try:
            output_object = open(output, 'w')
            close_output_object = True
        except IOError as ex:
            print(f'ERROR: Unable to write to the specified output file {output}.\n'
                  f'Details: {ex}', file=sys.stderr)
            sys.exit(1)

    if input == '-':
        input_object = sys.stdin
    else:
        if os.path.exists(input):
            try:
                input_object = open(input, 'r')
                close_input_object = True
            except IOError as ex:
                print(f'ERROR: Unable to read the specified input file {input}.\n'
                      f'Details: {ex}', file=sys.stderr)
                sys.exit(1)
        else:
            print(f'Error: Specified input file does not exist: {input}', file=sys.stderr)
            sys.exit(1)

    logger.debug('Before reading YAML via yaml.safe_load()')
    try:
        er_yaml = yaml.safe_load(input_object)
    except (yaml.scanner.ScannerError, yaml.parser.ParserError) as ex:
        print(f'\nERROR: Invalid YAML (syntax) for Entity-Relationship Markup Language input file.\n'
              f'ERROR DETAILS:\n{ex}\n', file=sys.stderr)
        sys.exit(1)
    logger.debug('After yaml.safe_load()')

    genpyenums(er_yaml, input, output_object)

    if close_input_object:
        input_object.close()
    if close_output_object:
        output_object.close()
    logger.debug('Leaving main()')
    

if __name__ == "__main__":
    try:
        # Remove default logger to reset logging level from the default of DEBUG to something else
        # per https://github.com/Delgan/loguru/issues/51
        logger.remove(0)
        global loguru_handler_id
        loguru_handler_id = logger.add(sys.stderr, level='WARNING')

        main()
    finally:
        logger.info(f'exiting {__name__}')
