#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

import pytest;
from pytest import mark;
from pytest import fixture;
from pytest import lazy_fixture;
from unittest import TestCase;
from unittest.mock import patch;

from src.thirdparty.types import *;
from src.models.graphs import *;
from src.algorithms.tarjan import *;

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FIXTURES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

@fixture(scope='module')
def graph1() -> Graph:
    return Graph(
        nodes=[1,2,3,4],
        edges=[(1,2), (2,4), (4,2)],
    );

@fixture(scope='module')
def graph2() -> Graph:
    return Graph(
        nodes=[1,2,3,4,5,6,7],
        edges=[(1,2), (1,3), (2,3), (3,4), (4,5), (5,2), (5,6), (5,7), (6,7)],
    );

@fixture(scope='module')
def graph3() -> Graph:
    return Graph(
        nodes=[1,2,3,4,5,6,7,8],
        edges=[
            (1,2),
            (1,3),
            (2,4),
            (2,5),
            (3,5),
            (3,6),
            (3,8),
            (4,5),
            (4,7),
            (5,1),
            (5,8),
            (6,8),
            (7,8),
            (8,6),
        ],
    );

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Test Tarjan-Algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

@mark.parametrize(
    ('G', 'expected'),
    [
        (lazy_fixture('graph1'), [[1], [3], [2,4]]),
        (lazy_fixture('graph2'), [[1], [6], [7], [2,3,4,5]]),
        (lazy_fixture('graph3'), [[1,2,3,4,5], [7], [6,8]]),
    ],
)
@mark.usefixtures('test')
def test_tarjan(test, G, expected):
    components = tarjan_algorithm(G, False);
    assert_components_eq(test, components, expected);

@patch(f'{__name__}.tarjan_algorithm', lambda *_: [])
@mark.parametrize(('G', 'expected'), [ (lazy_fixture('graph1'), [[1], [3], [2,4]])])
@mark.usefixtures('test')
def test_failable_tarjan(test, G, expected):
    with pytest.raises(AssertionError):
        test_tarjan(test, G, expected);

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUXILIARY METHODS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

def assert_components_eq(test: TestCase, components1: list[list[Any]], components2: list[list[Any]]):
    result = check_components_eq(test, components1, components2);
    test.assertTrue(result);

def check_components_eq(test: TestCase, components1: list[list[Any]], components2: list[list[Any]]) -> bool:
    if len(components1) != len(components2):
        return False;
    for component1 in components1:
        found = False;
        for component2 in components2:
            try:
                test.assertCountEqual(component1, component2);
                found = True;
                break;
            except:
                continue;
        if not found:
            return False;
    return True;