""" 
Validate information files

"""
# Standard library modules
import json
import pprint
import os.path
import sys
import pkg_resources

# Non-standard modules
import jsonschema
import jsonref
import yaml

root_symbol='#'
VALID_FORMATS=['JSON','YAML']
VALID_TYPES=['campaign','network','instrumentation','instrument_components',
             'response','filter']
################################################################################ 

def list_valid_types():
    """
    Returns a list of valid information file types
    """
    return VALID_TYPES

################################################################################ 

def get_information_file_type(filename):
    """
    Determines the type of a file, assuming that the filename is "*.{TYPE}.{SOMETHING}
    """

    the_type = filename.split('.')[-2].split('/')[-1].lower()
    if the_type in VALID_TYPES:
        return the_type
    print(f'Unknown type: {the_type}')
    sys.exit(1)
################################################################################ 
def validate(filename,format=None, type=None,verbose=False, quiet=False):
    """
    Validates a YAML or JSON file against schema
    type: "network", "instrumentation","response", "instrument_components","filter"
    format: "JSON" or "YAML"
    
    if type and/or format are not provided, tries to figure them out from the
    filename, which should be "*{TYPE}.{FORMAT}
    """

    if quiet:
        verbose=False
        
    if not type:
        type = get_information_file_type(filename)

#     with open(filename,'r') as f:
#         if format=='YAML':
#             instance=yaml.safe_load(f)
#         else:
#             instance=json.load(f)
    instance=read_json_yaml(filename,format=format)
    
    SCHEMA_FILE=pkg_resources.resource_filename('obsinfo',f'data/schemas/{type}.schema.json')    
    base_path=os.path.dirname(SCHEMA_FILE)
    base_uri=f'file://{base_path}/' 
    with open(SCHEMA_FILE,'r') as f:
        try:
            schema=jsonref.loads(f.read(),base_uri=base_uri,jsonschema=True)
        except json.decoder.JSONDecodeError as e:
            print(f"JSONDecodeError: Error loading JSON schema file: {SCHEMA_FILE}")
            print(str(e))
            return
        except:
            print(f"Error loading JSON schema file: {SCHEMA_FILE}")
            print(sys.exc_info()[1])
            return        
        
    # Lazily report all errors in the instance
    # ASSUMES SCHEMA IS DRAFT-04 (I couldn't get it to work otherwise)
    try:
        if verbose:
            print(f'instance = {filename}')
        elif not quiet:
            print(f'instance = {filename} ... ',end='')

        if verbose:
            print(f'schema =   {os.path.basename(SCHEMA_FILE)}')
            print('\tTesting schema ...',end='')
            
        v = jsonschema.Draft4Validator(schema)
        
        if verbose:
            print('OK')
            print( '\tTesting instance ...',end='')
        if not v.is_valid(instance):
            if quiet:
                # IF HAVE TO PRINT ERROR MESSAGE, PRINT INTRO TOO
                print(f'instance = {filename}')                
            else:
                print('')
            for error in sorted(v.iter_errors(instance), key=str):
                print('\t\t',end='')
                for elem in error.path:
                    print(f"['{elem}']",end='')
                print(f': {error.message}')
            print('\tFAILED')
        else:
            if not quiet:
                print('OK')
    except jsonschema.ValidationError as e:
        if quiet:
            # IF HAVE TO PRINT ERROR MESSAGE, PRINT INTRO TOO
            print(f'instance = {filename}')                
        else:
            print('')
        print('\t'+e.message)

    return 
    
################################################################################ 

def get_information_file_format(filename):
    """
    Determines if the information file is in JSON or YAML format
    
    Assumes that the filename is "*.{FORMAT}
    """

    format = filename.split('.')[-1].upper()
    if format in VALID_FORMATS:
        return format
    print('Unknown format: {format}')
    sys.exit(1)
                
##################################################
def read_json_yaml(filename,format=None,debug=False):
    """ Reads a JSON or YAML file """
    if not format:
        format=get_information_file_format(filename)

    with open(filename,'r') as f:
        if format=='YAML':
            try:
                element=yaml.safe_load(f)
            except:
                print(f"Error loading YAML file: {filename}")
                print(sys.exc_info()[1])
                return
        else:
            try:
                element=json.load(filename)
            except JSONDecodeError as e:
                print(f"JSONDecodeError: Error loading JSON file: {filename}")
                print(str(e))
                return
            except:
                print(f"Error loading JSON file: {filename}")
                print(sys.exc_info()[1])
                return

    return element
        
##################################################
def load_information_file(reference,source_file=None,
                            root_symbol=root_symbol,debug=False):
    """
    Loads all (or part) of an information file
    
    input:
        reference (str): path to the element (filename &/or internal element path)
        source_file (str): full path of referring file (if any)
    output:
        element: the requested element
        base_file: the path of this file
        
    root_symbol is interpreted as the file's root level
     - If it is at the beginning of the reference, the element is searched for
        in source_file.
     - If it is in the middle of the reference, the element is searched for within the
        filename preceding it. 
     - If it is at the end (or absent), then the entire file is loaded 
     
    Based on JSON Pointers
       
    """
    
    # Figure out filename, absolute path and path inside file
    filename=None
    if root_symbol in reference:
        if reference.count(root_symbol) > 1:
            raise RuntimeError('More than one occurence of "{}" in file reference "{}"'.format(
                    root_symbol,reference))
        if reference[0] == root_symbol:
            filename=''
            internal_path=reference[1:]
        elif reference[-1] == root_symbol:
            filename=reference[0:-1]
            internal_path=''
        else:     
            A=reference.split(root_symbol)
            filename=A[0]
            internal_path=A[1]
    else:
        filename=reference
        internal_path=''            
    if debug:
        print('LOAD_INFORMATION_FILE(): reference={}, source_file={}'.format(reference, source_file))        
    if source_file:
        if os.path.isfile(source_file):
            current_path=os.path.dirname(source_file)
        else:
            current_path=source_file
        filename=os.path.join(current_path,filename)
    else:
        current_path=os.getcwd()
    if debug:
        print('LOAD_INFORMATION_FILE(): filename={}, internal_path={}'.format(filename,internal_path))

    # MAKE SURE THAT IT CONFORMS TO SCHEMA
    validate(filename,quiet=True)

    # READ IN FILE
    element=read_json_yaml(filename)
        
    # BREAK OUT THE REQUESTED PART
    if internal_path:
        for key in internal_path.split('/') :
            if not key in element:
                raise RuntimeError("Internal path {} not found in file {}".format(\
                        internal_path,filename))
            else:
                element=element[key]

    # RETURN RESULT
    if debug:
        print("LOAD_YAML(): ",type(element))        
    return element, os.path.abspath(os.path.dirname(filename))   
   
################################################################################ 
def _validate_script(argv=None):
    """
    Validate an obsinfo information file

    Validates a file named *.{TYPE}.json or *.{TYPE}.yaml against the 
    obsinfo schema.{TYPE}.json file.

    {TYPE} can be campaign, network, instrumentation, instrument_components or
    response
    """
    from argparse import ArgumentParser

    parser = ArgumentParser( prog='obsinfo-validate', description=__doc__)
    parser.add_argument( 'info_file', help='Information file')
    parser.add_argument( '-t', '--type', choices=VALID_TYPES, default=None,
        help='Forces information file type (overrides interpreting from filename)')
    parser.add_argument( '-f', '--format', choices=VALID_FORMATS, default=None,
        help='Forces information file format (overrides interpreting from filename)')
    parser.add_argument( '-v', '--verbose',action="store_true",
        help='increase output verbosiy')
    args = parser.parse_args()
            
    validate(args.info_file,format=args.format,type=args.type,verbose=args.verbose)

