from hashlib import md5
import array
import re


# Codecs we use.
def encode_torsion(divisors):
    return ''.join(chr(x) for x in divisors).encode('utf8')


def decode_torsion(utf8):
    return [ord(x) for x in utf8.decode('utf8')]


def encode_matrices(matrices):
    """
    Convert a list of 2x2 integer matrices into a sequence of bytes.
    """
    # The tricky thing here is converting signed integers to bytes.
    return bytes(array.array('b', sum(sum(matrices, []), [])).tostring())
    # NOTE: tostring is deprecated in python3, but for now
    # it does the same thing as tobytes.


def decode_matrices(byteseq):
    """
    Convert a sequence of 4n bytes into a list of n 2x2 integer matrices.
    """
    m = array.array('b')
    m.fromstring(byteseq)
    return [ [ list(m[n:n+2]), list(m[n+2:n+4]) ]
             for n in range(0, len(m), 4) ]


# Some hash functions for manifolds:

def old_basic_hash(mfld, digits=6):
    return '%%%df' % digits % mfld.volume() + " " + repr(mfld.homology())


def basic_hash(mfld, digits=6):
    if mfld.solution_type() != 'contains degenerate tetrahedra':
        volume = '%%%df' % digits % mfld.volume()
    else:
        volume = 'degenerate'
    return volume + " " + repr(mfld.homology())


def cover_type(mfld):
    return re.findall("~reg~|~irr~|~cyc~", mfld.name())[-1][1:-1]


def cover_hash(mfld, degrees):
    return [ repr(sorted(
        [(cover_type(C), C.homology())
             for C in mfld.covers(degree, method='snappea')]
        )) for degree in degrees ]


def old_combined_hash(mfld):
    hash = str(" &and& ".join([old_basic_hash(mfld)] +
                              cover_hash(mfld, (2, 3))))
    return hash.encode('utf8')


def combined_hash(mfld):
    hash = str(" &and& ".join([basic_hash(mfld)] +
                              cover_hash(mfld, (2, 3))))
    return hash.encode('utf8')


# This one is the hash used in the first version of the database.
def old_db_hash(mfld):
    return md5(old_combined_hash(mfld)).hexdigest()


# This one is used now.
def db_hash(mfld):
    return md5(combined_hash(mfld)).hexdigest()
