# -*- coding: utf-8 -*-

"""Test cases for PyBEL-CX."""

import json
import unittest

from pybel.constants import (
    ANNOTATIONS, CITATION, CITATION_REFERENCE, CITATION_TYPE, EVIDENCE, OBJECT,
    RELATION, SUBJECT,
)

__all__ = [
    'TestCase',
]


def _edge_to_tuple(u, v, data):
    """Convert an edge to tuple.

    :param tuple u: The source BEL node
    :param tuple v: The target BEL node
    :param dict data: The edge's data dictionary
    :return: A tuple that can be hashed representing this edge. Makes no promises to its structure.
    """
    citation = data.get(CITATION)
    if citation is None:
        citation_hashable = None
    else:
        citation_hashable = (citation[CITATION_TYPE], citation[CITATION_REFERENCE])

    evidence_hashable = data.get(EVIDENCE)

    annotations = data.get(ANNOTATIONS)
    if annotations is None:
        annotations_hashable = None
    else:
        annotations_hashable = tuple(
            (key, tuple(sorted(values)))
            for key, values in sorted(annotations.items())
        )

    subject = data.get(SUBJECT)
    if subject is None:
        subject_hashable = None
    else:
        subject_hashable = json.dumps(subject, ensure_ascii=True, sort_keys=True, indent=0)

    obj = data.get(OBJECT)
    if obj is None:
        object_hashable = None
    else:
        object_hashable = json.dumps(obj, ensure_ascii=True, sort_keys=True, indent=0)

    return (
        u,
        v,
        data[RELATION],
        citation_hashable,
        evidence_hashable,
        annotations_hashable,
        subject_hashable,
        object_hashable,
    )


def _hash_edge(u, v, data):
    """Convert an edge tuple to a hash.

    :param tuple u: The source BEL node
    :param tuple v: The target BEL node
    :param dict data: The edge's data dictionary
    :return: A hashed version of the edge tuple using md5 hash of the binary pickle dump of u, v, and the json dump of d
    :rtype: str
    """
    return hash(_edge_to_tuple(u, v, data))


def _get_edge_dict(graph):
    return {
        _hash_edge(u, v, data): (u, v, data)
        for u, v, k, data in graph.edges(keys=True, data=True)
    }


class TestCase(unittest.TestCase):
    """Extension to base :class:`unittest.TestCase` with :class:`pybel.BELGraph` comparison."""

    def assert_graph_equal(self, g1, g2):
        """Assert two BEL graphs are the same.

        :param pybel.BELGraph g1:
        :param pybel.BELGraph g2:
        """
        self.assertEqual(g1.graph, g2.graph, msg='Metadata were not the same')

        # self.assertEqual(g1.number_of_nodes(), g2.number_of_nodes())
        self.assertEqual(set(g1), set(g2), msg='Nodes were not the same')

        # self.assertEqual(g1.number_of_edges(), g2.number_of_edges())
        self.assertEqual(set(g1.edges()), set(g2.edges()))

        g1_edge_hashes = _get_edge_dict(g1)
        g2_edge_hashes = _get_edge_dict(g2)

        g1k = g1_edge_hashes.keys()
        g2k = g2_edge_hashes.keys()

        g1ng2 = g1k - g2k
        g2ng1 = g2k - g1k

        if g1ng2 and not g2ng1:
            for k in g1ng2:
                print(k[:6], g1_edge_hashes[k])
            self.fail()

        elif not g1ng2 and g2ng1:
            for k in g2ng1:
                print(k[:6], g2_edge_hashes[k])
            self.fail()

        elif g1ng2 and g2ng1:
            print('in g1 but not g2:')
            for k in g1ng2:
                print(k[:6], g1_edge_hashes[k])

            print('in g2 but not g1:')
            for k in g2ng1:
                print(k[:6], g2_edge_hashes[k])
            self.fail()
