# Path: augur/util_support/node_data.py
# class DuplicatedNonDictAttributeError(Exception):
#     def __init__(self, key):
#         self.key = key
# 
# Path: augur/util_support/node_data.py
# class NodeData:
#     """Data structure representing a set of Augur nodes"""
# 
#     def __init__(self):
#         self.attrs = {}
# 
#     def update(self, new_attrs):
#         for k, v in new_attrs.items():
#             self.deep_add_or_update(self.attrs, k, v)
# 
#     def deep_add_or_update(self, d, key, value):
#         """
#         Recursively merge in new attributes using the following rules:
# 
#             if key is new:
#                 add it
#             if key is old, but the old value is not a dict:
#                 if new value is also not a dict:
#                     overwrite it
#                 if trying to overwrite a non-dict with a dict:
#                     raise exception
#             if key old and the old value is a dict:
#                 if new value is also a dict:
#                     recursively merge
#                 if trying to overwrite a dict with a non-dict:
#                     raise exception
#         """
# 
#         # TODO Python 3.9: Use the new dictionary union operator (https://www.python.org/dev/peps/pep-0584/)
# 
#         if key not in d or (
#             not isinstance(d[key], dict) and not isinstance(value, dict)
#         ):
#             d[key] = value
#             return
# 
#         if not isinstance(d[key], dict) or not isinstance(value, dict):
#             raise DuplicatedNonDictAttributeError(key)
# 
#         for sub_k, sub_v in value.items():
#             self.deep_add_or_update(d[key], sub_k, sub_v)
# 
# Path: augur/util_support/node_data_file.py
# class NodeDataFile:
#     def __init__(self, fname):
#         self.fname = fname
# 
#         with open(fname, encoding="utf-8") as jfile:
#             self.attrs = json.load(jfile)
# 
#         self.validate()
# 
#     @property
#     def annotations(self):
#         return self.attrs.get("annotations")
# 
#     @property
#     def nodes(self):
#         return self.attrs.get("nodes")
# 
#     @property
#     def generated_by(self):
#         return self.attrs.get("generated_by")
# 
#     @property
#     def is_generated_by_incompatible_augur(self):
#         if not isinstance(self.generated_by, dict):
#             # If it's not a dict created by augur, we can't reliably classify it as incompatible
#             return False
# 
#         generated_by_augur = self.generated_by.get("program") == "augur"
#         compatible_version = is_augur_version_compatible(
#             self.generated_by.get("version")
#         )
# 
#         return generated_by_augur and not compatible_version
# 
#     def items(self):
#         filtered_attrs = {
#             key: value for key, value in self.attrs.items() if key not in FILTERED_ATTRS
#         }
# 
#         return filtered_attrs.items()
# 
#     def validate(self):
#         if self.annotations:
#             try:
#                 validate_json(
#                     self.annotations,
#                     load_json_schema("schema-annotations.json"),
#                     self.fname,
#                 )
#             except ValidateError as err:
#                 raise RuntimeError(
#                     f"{self.fname} contains an `annotations` attribute of an invalid JSON format. Was it "
#                     "produced by different version of augur the one you are currently using "
#                     f" ({__version__})? Please check the program that produced that JSON file."
#                 ) from err
# 
#         if not isinstance(self.nodes, dict):
#             raise RuntimeError(
#                 f"`nodes` value in {self.fname} is not a dictionary. Please check the formatting of this JSON!"
#             )
# 
#         if self.is_generated_by_incompatible_augur:
#             raise RuntimeError(
#                 f"Augur version incompatibility detected: the JSON {self.fname} was generated by "
#                 f"{self.generated_by}, which is incompatible with the current augur version "
#                 f"({__version__}). We suggest you rerun the pipeline using the current version of "
#                 "augur."
#             )


import Bio.Phylo
import sys
from augur.util_support.node_data import DuplicatedNonDictAttributeError
from augur.util_support.node_data import NodeData
from augur.util_support.node_data_file import NodeDataFile

class NodeDataReader:
    """
    Parses one or more node data files and combines them into a single data structure.

    The format of Node data files is described in `docs/usage/json_format.md`. The `generated_by` attribute is
    not included in the resulting data structure.

    If a tree file is specified, it is used to verify the node names.
    """

    def __init__(self, filenames, tree_file=None):
        if not isinstance(filenames, list):
            filenames = [filenames]
        self.filenames = filenames
        self.tree_file = tree_file

    def read(self):
        node_data = self.build_node_data()

        self.check_against_tree_file(node_data)

        return node_data

    def build_node_data(self):
        node_data = NodeData()

        for node_data_file in self.node_data_files:
            try:
                node_data.update(node_data_file)

# Path: augur/util_support/node_data.py
# class DuplicatedNonDictAttributeError(Exception):
#     def __init__(self, key):
#         self.key = key
# 
# Path: augur/util_support/node_data.py
# class NodeData:
#     """Data structure representing a set of Augur nodes"""
# 
#     def __init__(self):
#         self.attrs = {}
# 
#     def update(self, new_attrs):
#         for k, v in new_attrs.items():
#             self.deep_add_or_update(self.attrs, k, v)
# 
#     def deep_add_or_update(self, d, key, value):
#         """
#         Recursively merge in new attributes using the following rules:
# 
#             if key is new:
#                 add it
#             if key is old, but the old value is not a dict:
#                 if new value is also not a dict:
#                     overwrite it
#                 if trying to overwrite a non-dict with a dict:
#                     raise exception
#             if key old and the old value is a dict:
#                 if new value is also a dict:
#                     recursively merge
#                 if trying to overwrite a dict with a non-dict:
#                     raise exception
#         """
# 
#         # TODO Python 3.9: Use the new dictionary union operator (https://www.python.org/dev/peps/pep-0584/)
# 
#         if key not in d or (
#             not isinstance(d[key], dict) and not isinstance(value, dict)
#         ):
#             d[key] = value
#             return
# 
#         if not isinstance(d[key], dict) or not isinstance(value, dict):
#             raise DuplicatedNonDictAttributeError(key)
# 
#         for sub_k, sub_v in value.items():
#             self.deep_add_or_update(d[key], sub_k, sub_v)
# 
# Path: augur/util_support/node_data_file.py
# class NodeDataFile:
#     def __init__(self, fname):
#         self.fname = fname
# 
#         with open(fname, encoding="utf-8") as jfile:
#             self.attrs = json.load(jfile)
# 
#         self.validate()
# 
#     @property
#     def annotations(self):
#         return self.attrs.get("annotations")
# 
#     @property
#     def nodes(self):
#         return self.attrs.get("nodes")
# 
#     @property
#     def generated_by(self):
#         return self.attrs.get("generated_by")
# 
#     @property
#     def is_generated_by_incompatible_augur(self):
#         if not isinstance(self.generated_by, dict):
#             # If it's not a dict created by augur, we can't reliably classify it as incompatible
#             return False
# 
#         generated_by_augur = self.generated_by.get("program") == "augur"
#         compatible_version = is_augur_version_compatible(
#             self.generated_by.get("version")
#         )
# 
#         return generated_by_augur and not compatible_version
# 
#     def items(self):
#         filtered_attrs = {
#             key: value for key, value in self.attrs.items() if key not in FILTERED_ATTRS
#         }
# 
#         return filtered_attrs.items()
# 
#     def validate(self):
#         if self.annotations:
#             try:
#                 validate_json(
#                     self.annotations,
#                     load_json_schema("schema-annotations.json"),
#                     self.fname,
#                 )
#             except ValidateError as err:
#                 raise RuntimeError(
#                     f"{self.fname} contains an `annotations` attribute of an invalid JSON format. Was it "
#                     "produced by different version of augur the one you are currently using "
#                     f" ({__version__})? Please check the program that produced that JSON file."
#                 ) from err
# 
#         if not isinstance(self.nodes, dict):
#             raise RuntimeError(
#                 f"`nodes` value in {self.fname} is not a dictionary. Please check the formatting of this JSON!"
#             )
# 
#         if self.is_generated_by_incompatible_augur:
#             raise RuntimeError(
#                 f"Augur version incompatibility detected: the JSON {self.fname} was generated by "
#                 f"{self.generated_by}, which is incompatible with the current augur version "
#                 f"({__version__}). We suggest you rerun the pipeline using the current version of "
#                 "augur."
#             )


# Path: augur/util_support/node_data_reader.py
import Bio.Phylo
import sys
from augur.util_support.node_data import DuplicatedNonDictAttributeError
from augur.util_support.node_data import NodeData
from augur.util_support.node_data_file import NodeDataFile


class NodeDataReader:
    """
    Parses one or more node data files and combines them into a single data structure.

    The format of Node data files is described in `docs/usage/json_format.md`. The `generated_by` attribute is
    not included in the resulting data structure.

    If a tree file is specified, it is used to verify the node names.
    """

    def __init__(self, filenames, tree_file=None):
        if not isinstance(filenames, list):
            filenames = [filenames]
        self.filenames = filenames
        self.tree_file = tree_file

    def read(self):
        node_data = self.build_node_data()

        self.check_against_tree_file(node_data)

        return node_data

    def build_node_data(self):
        node_data = NodeData()

        for node_data_file in self.node_data_files:
            try:
                node_data.update(node_data_file)

            except DuplicatedNonDictAttributeError as e: