import os
path = os.path
from random import randrange

from myhdl import (block, Signal, intbv, delay, instance, StopSimulation)
from myhdl.conversion import verify
import pytest

NRTESTS = 10


@block
def binaryOps(
    Bitand,
# #               Bitor,
# #               Bitxor,
# #               FloorDiv,
    LeftShift,
    Modulo,
    Mul,
# #               Pow,
    RightShift,
    Sub,
    Sum, Sum1, Sum2, Sum3,
    EQ,
    NE,
    LT,
    GT,
    LE,
    GE,
    BoolAnd,
    BoolOr,
    left, right, aBit):

    @instance
    def comb():
        while 1:
            yield left, right, aBit
    # #         Bitand.next = left & right
    # #         Bitor.next = left | right
    # #         Bitxor.next = left ^ right
    # #         if right != 0:
    # #             FloorDiv.next = left // right
            # Keep left shifts smaller than 2** 31 for VHDL's to_integer
            LeftShift.next = 0
            if left < 256 and right < 22 and right >= 0:
                LeftShift.next = left << right
    # #         if right != 0:
    # #             Modulo.next = left % right
            Mul.next = left * right
    # #         # Icarus doesn't support ** yet
    # #         #if left < 256 and right < 22:
    # #         #    Pow.next = left ** right
    # #         Pow.next = 0
    # #         if right >= -0:
    # #            RightShift.next = left >> right
                # # RightShift.next = left
            Sub.next = left - right
            Sum.next = left + right
            Sum1.next = left + right[2:]
            Sum2.next = left + right[1]
            Sum3.next = left + aBit
            EQ.next = left == right
            NE.next = left != right
            LT.next = left < right
            GT.next = left > right
            LE.next = left <= right
            GE.next = left >= right
            BoolAnd.next = bool(left) and bool(right)
            BoolOr.next = bool(left) or bool(right)

    return comb


@block
def binaryBench(Ll, Ml, Lr, Mr):

    seqL = []
    seqR = []
    for __ in range(NRTESTS):
        seqL.append(randrange(Ll, Ml))
        seqR.append(randrange(Lr, Mr))
    for j, k in ((Ll, Lr), (Ml - 1, Mr - 1), (Ll, Mr - 1), (Ml - 1, Lr)):
        seqL.append(j)
        seqR.append(k)
    seqL = tuple(seqL)
    seqR = tuple(seqR)

    aBit = Signal(bool(0))
    left = Signal(intbv(Ll, min=Ll, max=Ml))
    right = Signal(intbv(Lr, min=Lr, max=Mr))
    M = 2 ** 14

    Bitand = Signal(intbv(0, min=-2 ** 17, max=2 ** 17))
# #        Bitand_v = Signal(intbv(0, min=-2**17, max=2**17))
# #         Bitor = Signal(intbv(0)[max(m, n):])
# #         Bitor_v = Signal(intbv(0)[max(m, n):])
# #         Bitxor = Signal(intbv(0)[max(m, n):])
# #         Bitxor_v = Signal(intbv(0)[max(m, n):])
# #         FloorDiv = Signal(intbv(0)[m:])
# #         FloorDiv_v = Signal(intbv(0)[m:])
    LeftShift = Signal(intbv(0, min=-2 ** 64, max=2 ** 64))
    Modulo = Signal(intbv(0)[M:])
    Mul = Signal(intbv(0, min=-2 ** 17, max=2 ** 17))
# #         Pow = Signal(intbv(0)[64:])
    RightShift = Signal(intbv(0, min=-M, max=M))
    Sub = Signal(intbv(min=-M, max=M))
    Sum, Sum1, Sum2, Sum3 = [Signal(intbv(min=-M, max=M)) for __ in range(4)]
    EQ, NE, LT, GT, LE, GE = [Signal(bool()) for __ in range(6)]
    BoolAnd, BoolOr = [Signal(bool()) for __ in range(2)]

    binops = binaryOps(
        Bitand,
# #                            Bitor,
# #                            Bitxor,
# #                            FloorDiv,
        LeftShift,
        Modulo,
        Mul,
   # #                            Pow,
        RightShift,
        Sub,
        Sum, Sum1, Sum2, Sum3,
        EQ,
        NE,
        LT,
        GT,
        LE,
        GE,
        BoolAnd,
        BoolOr,
        left, right, aBit)

    @instance
    def stimulus():
        for i in range(len(seqL)):
            left.next = seqL[i]
            right.next = seqR[i]
            yield delay(10)

    @instance
    def check():
        while 1:
            yield left, right
            aBit.next = not aBit
            yield delay(1)

                # print "%s %s %s %s" % (left, right, Mul, Mul_v)
                # print "%s %s %s %s" % (left, right, bin(Mul), bin(Mul_v))
                # print "%s %s %s %s" % (left, right, Sum, Sum_v)
                # print "%s %s %s %s" % (left, right, bin(Sum), bin(Sum_v))
# #                 print left
# #                 print right
# #                 print bin(left)
# #                 print bin(right)
# #                 print bin(Bitand)
# #                 print bin(Bitand_v)
# #                 print Bitand
# #                 print Bitand_v
# #                self.assertEqual(Bitand, Bitand_v)
                # w = len(Bitand)
                # self.assertEqual(bin(Bitand, w), bin(Bitand_v,w ))
# #                 self.assertEqual(Bitor, Bitor_v)
# #                 self.assertEqual(Bitxor, Bitxor_v)
# # ##                 self.assertEqual(FloorDiv, FloorDiv_v)
            print(LeftShift)
            # print Modulo
            print(Mul)
                # self.assertEqual(Pow, Pow_v)
            print(RightShift)
            print(Sub)
            print(Sum)
            print(Sum1)
            print(Sum2)
            print(Sum3)
            print(int(EQ))
            print(int(NE))
            print(int(LT))
            print(int(GT))
            print(int(LE))
            print(int(GE))
            print(int(BoolAnd))
            print(int(BoolOr))

    return binops, stimulus, check


@pytest.mark.filterwarnings("ignore:Signal is not driven")
def testBinaryOps():
    for Ll, Ml, Lr, Mr in (
                            (-254, 236, 0, 4),
                            (-128, 128, -128, 128),
                            (-53, 25, -23, 123),
                            (-23, 145, -66, 12),
                            (23, 34, -34, -16),
                            (-54, -20, 45, 73),
                            (-25, -12, -123, -66),
                           ):
        assert binaryBench(Ll, Ml, Lr, Mr).verify_convert() == 0


@block
def unaryOps(
             BoolNot,
             Invert,
             UnaryAdd,
             UnarySub,
             arg):

    @instance
    def comb():
        while 1:
            yield arg
            # BoolNot.next = not arg
            Invert.next = ~arg
            # UnaryAdd.next = +arg
            UnarySub.next = - -arg

    return comb


@block
def unaryBench(m):

    M = 2 ** m
    seqM = tuple([i for i in range(-M, M)])

    arg = Signal(intbv(0, min=-M, max=+M))
    BoolNot = Signal(bool(0))
    Invert = Signal(intbv(0, min=-M, max=+M))
    UnaryAdd = Signal(intbv(0, min=-M, max=+M))
    UnarySub = Signal(intbv(0, min=-M, max=+M))

    unaryops = unaryOps(
                         BoolNot,
                         Invert,
                         UnaryAdd,
                         UnarySub,
                         arg)

    @instance
    def stimulus():
        for i in range(len(seqM)):
            arg.next = seqM[i]
            yield delay(10)
        raise StopSimulation

    @instance
    def check():
        while 1:
            yield arg
            yield delay(1)
            # print BoolNot
            print(Invert)
            # print UnaryAdd
            print(UnarySub)

    return unaryops, stimulus, check


def testUnaryOps():
    for m in (4, 7):
        assert unaryBench(m).verify_convert() == 0


@block
def augmOps(
# #               Bitand,
# #               Bitor,
# #               Bitxor,
# #               FloorDiv,
              LeftShift,
# #               Modulo,
              Mul,
              RightShift,
              Sub,
              Sum,
              left, right):

    M = 2 ** 17
    N = 2 ** 64

    @instance
    def comb():
        var = intbv(0, min=-M, max=+M)
        var2 = intbv(0, min=-N, max=+N)
        while 1:
            yield left, right
    # #         var[:] = left
    # #         var &= right
    # #         Bitand.next = var
    # #         var[:] = left
    # #         var |= right
    # #         Bitor.next = var
    # #         var[:] = left
    # #         var ^= left
    # #         Bitxor.next = var
    # #         if right != 0:
    # #             var[:] = left
    # #             var //= right
    # #             FloorDiv.next = var
            LeftShift.next = 0
            if left < 256 and right < 22 and right >= 0:
                var2[:] = left
                var2 <<= right
                LeftShift.next = var2
    # #         if right != 0:
    # #             var[:] = left
    # #             var %= right
    # #             Modulo.next = var
            var[:] = left
            var *= right
            Mul.next = var

            var[:] = left
            RightShift.next = 0
            if right >= 0:
                var >>= right
                RightShift.next = var

            var[:] = left
            var -= right
            Sub.next = var
            var[:] = left
            var += right
            Sum.next = var

    return comb


@block
def augmBench(Ll, Ml, Lr, Mr):

    M = 2 ** 17

    seqL = []
    seqR = []
    for i in range(NRTESTS):
        seqL.append(randrange(Ll, Ml))
        seqR.append(randrange(Lr, Mr))
    for j, k in ((Ll, Lr), (Ml - 1, Mr - 1), (Ll, Mr - 1), (Ml - 1, Lr)):
        seqL.append(j)
        seqR.append(k)
    seqL = tuple(seqL)
    seqR = tuple(seqR)

    left = Signal(intbv(Ll, min=Ll, max=Ml))
    right = Signal(intbv(Lr, min=Lr, max=Mr))

# #         Bitand = Signal(intbv(0)[max(m, n):])
# #         Bitor = Signal(intbv(0)[max(m, n):])
# #         Bitxor = Signal(intbv(0)[max(m, n):])

# #         FloorDiv = Signal(intbv(0)[m:])
    LeftShift = Signal(intbv(0, min=-2 ** 64, max=2 ** 64))
# #         Modulo = Signal(intbv(0)[m:])

    Mul = Signal(intbv(0, min=-M, max=+M))

    RightShift = Signal(intbv(0, min=-M, max=+M))

    Sub = Signal(intbv(0, min=-M, max=+M))
    Sum = Signal(intbv(0, min=-M, max=+M))

    augmops = augmOps(
# #                            Bitand,
# #                            Bitor,
# #                            Bitxor,
# #                            FloorDiv,
        LeftShift,
# #                            Modulo,
        Mul,
        RightShift,
        Sub,
        Sum,
        left, right)

    @instance
    def stimulus():
        for i in range(len(seqL)):
            left.next = seqL[i]
            right.next = seqR[i]
            yield delay(10)

    @instance
    def check():
        while 1:
            yield left, right
            yield delay(1)
                # print "%s %s %s %s" % (left, right, Or, Or_v)
# #                 self.assertEqual(Bitand, Bitand_v)
# #                 self.assertEqual(Bitor, Bitor_v)
# #                 self.assertEqual(Bitxor, Bitxor_v)
# #                 self.assertEqual(FloorDiv, FloorDiv_v)
            print(LeftShift)
# #                 self.assertEqual(Modulo, Modulo_v)
            print(Mul)
            print(RightShift)
            print(Sub)
            print(Sum)

    return augmops, stimulus, check


def checkAugmOps(Ll, Ml, Lr, Mr):
    assert verify(augmBench, Ll, Ml, Lr, Mr) == 0


def testAugmOps():
    for Ll, Ml, Lr, Mr in (
                            (-254, 236, 0, 4),
                            (-128, 128, -128, 128),
                            (-53, 25, -23, 123),
                            (-23, 145, -66, 12),
                            (23, 34, -34, -16),
                            (-54, -20, 45, 73),
                            (-25, -12, -123, -66),
                           ):
        assert augmBench(Ll, Ml, Lr, Mr).verify_convert() == 0


@block
def expressions(a, b, clk):

    c = Signal(intbv(0, min=0, max=47))

    @instance
    def comb():

        d = intbv(0, min=-23, max=43)
        d[:] = -17

        c.next = 5
        yield clk.posedge
        a.next = c + 1
        b.next = c + 1
        yield clk.posedge
        a.next = c + -10
        b.next = c + -1
        yield clk.posedge
        a.next = c < -10
        b.next = c < -1
        yield clk.posedge
        a.next = d + c
        b.next = d >= c
        yield clk.posedge
# #         a.next = d & c
# #         b.next = c + (d & c)
        yield clk.posedge
        a.next = d + -c
        b.next = c + (-d)
        yield clk.posedge
        a.next = -d
        yield clk.posedge
        a.next = -c
        yield clk.posedge
        c.next = 46
        yield clk.posedge
        a.next = ~d + 1
        b.next = ~c + 1
        yield clk.posedge
        a.next = ~c + 1
        b.next = ~d + 1

        yield clk.posedge
        raise StopSimulation

    return comb


@block
def expressionsBench():

    a = Signal(intbv(0, min=-34, max=47))
    b = Signal(intbv(0, min=0, max=47))
    clk = Signal(bool())

    expr = expressions(a, b, clk)

    @instance
    def check():
        while 1:
            yield clk.posedge
            yield delay(1)
            print(int(a))
            print(int(b))

    @instance
    def clkgen():
        while True:
            yield delay(10)
            clk.next = not clk

    return expr, check, clkgen


def testExpressions():
    assert expressionsBench().verify_convert() == 0

