from itertools import repeat
from Aspidites._vendor.pyrsistent import pvector, pset

import time

def run_big_iterator_initialization():
    """
    The results are comparable to those of doing it with a list since most of the
    code is shared.
    """
    before = time.time()
    iterator = (x for x in range(1000000))
    print("Big iterator: " + str(time.time() - before))

    before = time.time()
    seq = pvector(iterator)
    print("Big vector from iterator: " + str(time.time() - before))


def run_big_list_initialization():
    """
    Some example results, these are some of the fastest I've seen, it tends to vary...

    == CPython ==
    Big list: 0.174705028534
    Big vector: 0.353514909744

    == PyPy ==
    Slicing solution PyPy:
    Big list: 0.0481958389282
    Big vector from list: 0.166031122208

    """
    before = time.time()
    l = [x for x in range(1000000)]
    print("Big list from list comprehension: " + str(time.time() - before))

    before = time.time()
    seq = pvector(l)
    print("Big vector from list: " + str(time.time() - before))


def run_slicing_performance():
    """
    Again, the fastest, large variations exist...

    == CPython ==
    List slicing: 0.00921297073364
    Pvec slicing: 0.225503921509

    == PyPy ==
    List slicing: 0.00399494171143
    Pvec slicing: 0.102802991867

    """

    list = [x for x in range(1000000)]

    before = time.time()
    sublist = list[533:744444]
    print("List slicing: " + str(time.time() - before))

    vec = pvector(list)
    before = time.time()
    subvec = vec[533:744444]
    print("Pvec slicing: " + str(time.time() - before))


def run_create_many_small_vectors():
    """
    == PyPy ==
    Single element: 0.149819135666
    Double elements: 0.350324869156
    Ten elements: 0.338271856308

    === CPython ===
    Single element: 3.62240195274
    Double elements: 8.04715490341
    Ten elements: 5.73595809937
    """
    iterations = range(100000)

    before = time.time()
    single = [2]
    for _ in iterations:
        vec = pvector(single)
    print("Many small Single element: " + str(time.time() - before))


    before = time.time()
    double = [2, 3]
    for _ in iterations:
        vec = pvector(double)
    print("Many small Double elements: " + str(time.time() - before))

    before = time.time()
    ten = range(10)
    for _ in iterations:
        vec = pvector(ten)
    print("Many small Ten elements: " + str(time.time() - before))

    before = time.time()
    x = range(32)
    for _ in iterations:
        vec = pvector(x)
    print("Many small 32 elements: " + str(time.time() - before))

    before = time.time()
    x = range(33)
    for _ in iterations:
        vec = pvector(x)
    print("Many small 33 elements: " + str(time.time() - before))

def run_set_performance():
    """
    == PyPy ==
    Big set from list: 0.0152490139008
    Big pset from list: 1.62447595596
    Random access set: 0.0192308425903
    Random access pset: 2.18643188477

    === CPython ===
    Big set from list: 0.0202131271362
    Big pset from list: 2.87654399872
    Random access set: 0.0950989723206
    Random access pset: 11.2261350155
    """
    l = [x for x in range(100000)]

    before = time.time()
    s1 = set(l)
    print("Big set from list: " + str(time.time() - before))

    before = time.time()
    s2 = pset(l, pre_size=2*len(l))
    print("Big pset from list: " + str(time.time() - before))

    before = time.time()
    random_access(s1)
    print("Random access set: " + str(time.time() - before))

    before = time.time()
    random_access(s2)
    print("Random access pset: " + str(time.time() - before))

def run_vector_random_access_performance():
    def random_access(o):
        result = 0
        for x in range(10000):
            for y in testdata:
                result = o[y]

        return result

    testdata = [0, 4, 55, 10000, 98763, -2, 30000, 42004, 37289, 100, 2, 999999]
    l = range(1000000)
    
    before = time.time()
    random_access(l)
    print("Random access large list: " + str(time.time() - before))

    v = pvector(l)
    before = time.time()
    random_access(v)
    print("Random access large vector: " + str(time.time() - before))

    testdata = [0, 4, 17, -2, 3, 7, 8, 11, 1, 13, 18, 10]
    l = range(20)
    
    before = time.time()
    random_access(l)
    print("Random access small list: " + str(time.time() - before))

    v = pvector(l)
    before = time.time()
    random_access(v)
    print("Random access small vector: " + str(time.time() - before))
    

def run_string_from_objects():
    p = pvector(range(1000000))

    before = time.time()
    s1 = str(p)
    print("Str 1: " + str(time.time() - before))

    before = time.time()
    s2 = str(p)
    print("Str 2: " + str(time.time() - before))

def run_to_list():
    p = pvector(range(1000000))

    try:
        before = time.time()
        l1 = p.tolist()
        print("Tolist: " + str(time.time() - before))
    except:
        print("Tolist not implemented")

    before = time.time()
    l2 = list(p)
    print("Iterator: " + str(time.time() - before))

    try:
        before = time.time()
        l1 = p._totuple()
        print("Totuple: " + str(time.time() - before))
    except:
        print("Totuple not implemented")

def run_len():
    # This is quite close to the python function call overhead baseline since the function
    # itself hardly does anything. That is the only reason this test is interesting.
    v = pvector()
    r = 1000000

    before = time.time()
    for _ in range(r):
        len(v)
    len_duration = time.time() - before
    print("Len: %s s, per call %s s" % (len_duration, len_duration / r))

    before = time.time()
    for _ in range(r):
        pass
    empty_duration = time.time() - before
    print("Empty loop: %s s, per call %s s" % (empty_duration, empty_duration / r))
    print("Len estimate: %s, per call: %s" % (len_duration - empty_duration, (len_duration - empty_duration) / r))

def random_access(s):
    testdata = [0, 4, 55, 10000, 98763, -2, 30000, 42004, 37289, 100, 2, 999999]
    result = False
    for x in range(10000):
        for y in testdata:
            result = y in s

    return result

def run_multiple_random_inserts():
    from Aspidites._vendor.pyrsistent import _pvector as _pvector

    indices = [2, 405, 56, 5067, 15063, 7045, 19999, 10022, 6000, 4023]
    for x in range(4):
        indices.extend([i+318 for i in indices])

    print("Number of accesses: %s" % len(indices))
    print("Number of elements in vector: %s" % max(indices))

    original = _pvector(range(max(indices) + 1))
    original2 = _pvector(range(max(indices) + 1))

    # Using ordinary set
    start = time.time()
    new = original
    for r in range(10000):
        for i in indices:
            new = new.set(i, 0)
    print("Done simple, time=%s s, iterations=%s" % (time.time() - start, 10000 * len(indices)))
    assert original == original2

    # Using setter view
    start = time.time()
    evolver = original.evolver()
    for r in range(10000):
        for i in indices:
            evolver[i] = 0
    new2 = evolver.persistent()
    print("Done evolver, time=%s s, iterations=%s" % (time.time() - start, 10000 * len(indices)))

    assert original == original2

    def interleave(it1, it2):
        for i in zip(it1, it2):
            for j in i:
                yield j

    # Using mset
    start = time.time()
    new3 = original
    args = list(interleave(indices, repeat(0)))
    for _ in range(10000):
        new3 = new3.mset(*args)
    print("Done mset, time=%s s, iterations=%s" % (time.time() - start, 10000 * len(args)/2))

    assert list(new) == list(new2)
    assert list(new2) == list(new3)

def run_multiple_inserts_in_pmap():
    from Aspidites._vendor.pyrsistent import pmap

    COUNT = 100000
    def test_range():
        prime = 317
        return range(0, prime*COUNT, prime)

    elements = {x: x for x in test_range()}

    # Using ordinary set
    start = time.time()
    m1 = pmap(elements)
    print("Done initializing, time=%s s, count=%s" % (time.time() - start, COUNT))


    start = time.time()
    m2 = pmap()
    for x in test_range():
        m2 = m2.set(x, x)
    print("Done setting, time=%s s, count=%s" % (time.time() - start, COUNT))

    assert m1 == m2

    start = time.time()
    m3 = pmap()
    e3 = m3.evolver()
    for x in test_range():
        e3[x] = x
    m3 = e3.persistent()
    print("Done evolving, time=%s s, count=%s" % (time.time() - start, COUNT))

    assert m3 == m2

    start = time.time()
    m4 = pmap()
    m4 = m4.update(elements)
    m4 = m4.update(elements)
    print("Done updating, time=%s s, count=%s" % (time.time() - start, COUNT))

    assert m4 == m3

    start = time.time()
    m5 = pmap()
    m5 = m5.update_with(lambda l, r: r, elements)
    m5 = m5.update_with(lambda l, r: r, elements)
    print("Done updating with, time=%s s, count=%s" % (time.time() - start, COUNT))

    assert m5 == m4


if __name__ == "__main__":
    pass
#    run_multiple_inserts_in_pmap()
#    run_multiple_random_inserts()
#    run_big_list_initialization()
#    run_big_iterator_initialization()
#    run_slicing_performance()
#    run_create_many_small_vectors()
#    run_set_performance()
#    run_string_from_objects()
#    run_to_list()
#    run_len()
#    run_vector_random_access_performance()
