import numpy as np
import itertools as it
from abp import clifford
from abp import build_tables
from abp import qi
import pytest


def identify_pauli(m):
    """ Given a signed Pauli matrix, name it. """
    for sign in (+1, -1):
        for pauli_label, pauli in zip("xyz", qi.paulis):
            if np.allclose(sign * pauli, m):
                return sign, pauli_label


def test_find_clifford():
    """ Test that slightly suspicious function """
    assert build_tables.find_clifford(qi.id, clifford.unitaries) == 0
    assert build_tables.find_clifford(qi.px, clifford.unitaries) == 1



def get_action(u):
    """ What does this unitary operator do to the Paulis? """
    return [identify_pauli(u.dot(p.dot(qi.hermitian_conjugate(u)))) for p in qi.paulis]


def format_action(action):
    return "".join("{}{}".format("+" if s >= 0 else "-", p) for s, p in action)


def test_we_have_24_matrices():
    """ Check that we have 24 unique actions on the Bloch sphere """
    actions = set(tuple(get_action(u)) for u in clifford.unitaries)
    assert len(set(actions)) == 24


def test_we_have_all_useful_gates():
    """ Check that all the interesting gates are included up to a global phase """
    for name, u in list(qi.by_name.items()):
        build_tables.find_clifford(u, clifford.unitaries)


def test_group():
    """ Test we are really in a group """
    matches = set()
    for a, b in it.combinations(clifford.unitaries, 2):
        i = build_tables.find_clifford(a.dot(b), clifford.unitaries)
        matches.add(i)
    assert len(matches) == 24


def test_conjugation_table():
    """ Check that the table of Hermitian conjugates is okay """
    assert len(set(clifford.conjugation_table)) == 24


def test_cz_table_makes_sense():
    """ Test the CZ table is symmetric """
    hadamard = clifford.hadamard
    assert all(clifford.cz_table[0, 0, 0] == [1, 0, 0])
    assert all(clifford.cz_table[1, 0, 0] == [0, 0, 0])
    assert all(
        clifford.cz_table[0, hadamard, hadamard] == [0, hadamard, hadamard])


def test_commuters():
    """ Test that commutation is good """
    assert len(build_tables.get_commuters(clifford.unitaries)) == 4


def test_conjugation():
    """ Test that clifford.conugate() agrees with graphsim.LocCliffOp.conjugate """
    try:
        from anders_briegel import graphsim
    except ImportError:
        pytest.skip("Original C++ is not available, skipping test")
        

    for operation_index, transform_index in it.product(list(range(4)), list(range(24))):
        transform = graphsim.LocCliffOp(transform_index)
        operation = graphsim.LocCliffOp(operation_index)

        phase = operation.conjugate(transform).ph
        phase = [1, 0, -1][phase]
        new_operation = operation.op

        NEW_OPERATION, PHASE = clifford.conjugate(
            operation_index, transform_index)
        assert new_operation == NEW_OPERATION
        assert PHASE == phase


def test_cz_table():
    """ Does the CZ code work good? """
    state_table = build_tables.get_state_table(clifford.unitaries)

    rows = it.product([0, 1], it.combinations_with_replacement(list(range(24)), 2))

    for bond, (c1, c2) in rows:

        # Pick the input state
        input_state = state_table[bond, c1, c2]

        # Go and compute the output
        computed_output = np.dot(qi.cz, input_state)
        computed_output = qi.normalize_global_phase(computed_output)

        # Now look up the answer in the table
        bondp, c1p, c2p = clifford.cz_table[bond, c1, c2]
        table_output = state_table[bondp, c1p, c2p]

        assert np.allclose(computed_output, table_output)

