/* Authors: Aaron Graham (aaron.graham@unb.ca, aarongraham9@gmail.com),
 *           Jean-Philippe Legault (jlegault@unb.ca, jeanphilippe.legault@gmail.com),
 *            Alexandrea Demmings (alexandrea.demmings@unb.ca, lxdemmings@gmail.com) and
 *             Dr. Kenneth B. Kent (ken@unb.ca)
 *             for the Reconfigurable Computing Research Lab at the
 *              Univerity of New Brunswick in Fredericton, New Brunswick, Canada
 */

#include <string>

#include "internal_bits.hpp"
#include "rtl_int.hpp"
#include "rtl_utils.hpp"

#define AMBIGUOUS_VALUE VNumber("'bx")

using namespace BitSpace;

class compare_bit {
  private:
    uint8_t result = 0x0;

  public:
    compare_bit(uint8_t set_to) { result = set_to; }

    bool is_unk() { return (!result); }

    bool is_gt() { return (result & (0x1)); }
    bool is_eq() { return (result & (0x2)); }
    bool is_lt() { return (result & (0x4)); }

    bool is_ne() { return (!is_eq()); }
    bool is_ge() { return (result & (0x3)); }
    bool is_le() { return (result & (0x6)); }
};

#define UNK_EVAL compare_bit(0x0)
#define GT_EVAL compare_bit(0x1)
#define EQ_EVAL compare_bit(0x2)
#define LT_EVAL compare_bit(0x4)

static compare_bit eval_op(VNumber& a_in, VNumber& b_in) {
    assert_Werr(a_in.size(),
                "empty 1st bit string");

    assert_Werr(b_in.size(),
                "empty 2nd bit string");

#ifndef RTL_ALLOW_UNKNOWN_COMPARE
    if (a_in.is_dont_care_string() || b_in.is_dont_care_string())
        return UNK_EVAL;
#endif

    bool neg_a = (a_in.is_negative());
    bool neg_b = (b_in.is_negative());

    if (neg_a && !neg_b) {
        return LT_EVAL;
    } else if (!neg_a && neg_b) {
        return GT_EVAL;
    }

    VNumber a;
    VNumber b;
    bool invert_result = (neg_a && neg_b);

    if (invert_result) {
        a = a_in.twos_complement();
        b = b_in.twos_complement();
    } else {
        a = a_in;
        b = b_in;
    }

    size_t std_length = std::max(a.size(), b.size());
    bit_value_t pad_a = a.get_padding_bit();
    bit_value_t pad_b = b.get_padding_bit();

    for (size_t i = std_length - 1; i < std_length; i--) {
        bit_value_t bit_a = pad_a;
        if (i < a.size()) {
            bit_a = a.get_bit_from_lsb(i);
        }

        bit_value_t bit_b = pad_b;
        if (i < b.size()) {
            bit_b = b.get_bit_from_lsb(i);
        }

        if (BitSpace::l_lt[bit_a][bit_b] == BitSpace::_1) {
            return (!invert_result) ? LT_EVAL : GT_EVAL;
        } else if (BitSpace::l_gt[bit_a][bit_b] == BitSpace::_1) {
            return (!invert_result) ? GT_EVAL : LT_EVAL;
        } else if (BitSpace::l_eq[bit_a][bit_b] == BitSpace::_1) {
            continue;
        } else {
            return UNK_EVAL;
        }
    }

    return EQ_EVAL;
}

static compare_bit eval_op(VNumber a, int64_t b) {
    VNumber bits_value = VNumber(std::to_string(std::abs(b)));
    if (b < 0)
        bits_value = bits_value.twos_complement();

    return eval_op(a, bits_value);
}

/**
 * Check if the Operation Should be Signed by Checking if Both Operands Are Signed:
 */
static bool is_signed_operation(VNumber& a, VNumber& b) {
    bool is_signed_operation = false;

    if ((true == a.is_signed()) && (true == b.is_signed())) {
        is_signed_operation = true;
    }

    return is_signed_operation;
}

/**
 * Addition operations
 */
static VNumber sum_op(VNumber& a, VNumber& b, const bit_value_t& initial_carry, bool is_twos_complement_subtraction) {
    assert_Werr(a.size(),
                "empty 1st bit string");

    assert_Werr(b.size(),
                "empty 2nd bit string");

    size_t std_length = std::max(a.size(), b.size());
    size_t new_length = ((true == is_twos_complement_subtraction) ? (std_length) : (std_length + 1));
    const bit_value_t pad_a = a.get_padding_bit();
    const bit_value_t pad_b = b.get_padding_bit();
    bool is_addition_signed_operation = is_signed_operation(a, b);

    //("pad_b: '" << (unsigned(pad_b)) << "'");

    bit_value_t previous_carry = initial_carry;
    VNumber result(new_length, _0, is_addition_signed_operation, a.is_defined_size() && b.is_defined_size());

    for (size_t i = 0; i < new_length; i++) {
        bit_value_t bit_a = pad_a;
        if (i < a.size()) {
            bit_a = a.get_bit_from_lsb(i);
        }

        bit_value_t bit_b = pad_b;
        if (i < b.size()) {
            bit_b = b.get_bit_from_lsb(i);
        }

        result.set_bit_from_lsb(i, l_sum[previous_carry][bit_a][bit_b]);
        previous_carry = l_carry[previous_carry][bit_a][bit_b];
    }

    return result;
}

static VNumber shift_op(VNumber& a, int64_t b, bool sign_shift) {
    VNumber to_return;

    if (b == 0) {
        to_return = a;
    }
    //if b is negative then shift right
    else if (b < 0) {
        size_t u_b = static_cast<size_t>(-b);
        bit_value_t pad = (sign_shift) ? a.get_padding_bit() : BitSpace::_0;
        to_return = VNumber(a.size(), pad, sign_shift, a.is_defined_size());
        for (size_t i = 0; i < (a.size() - u_b); i++) {
            to_return.set_bit_from_lsb(i, a.get_bit_from_lsb(i + u_b));
        }
    } else {
        size_t u_b = static_cast<size_t>(b);
        bit_value_t pad = BitSpace::_0;
        to_return = VNumber((a.size() + u_b), pad, sign_shift, a.is_defined_size());
        for (size_t i = 0; i < a.size(); i++) {
            to_return.set_bit_from_lsb(i + u_b, a.get_bit_from_lsb(i));
        }
    }
    return to_return;
}

bool V_TRUE(VNumber& a) {
    return a.is_true();
}

bool V_FALSE(VNumber& a) {
    return a.is_false();
}

bool V_UNK(VNumber& a) {
    return a.is_dont_care_string();
}

bool V_IS_X(VNumber& a) {
    return a.is_x();
}

bool V_IS_Z(VNumber& a) {
    return a.is_z();
}

bool V_IS_SIGNED(VNumber& a) {
    return a.is_signed();
}

bool V_IS_UNSIGNED(VNumber& a) {
    return !a.is_signed();
}

std::string V_STRING(VNumber& a) {
    return a.to_printable();
}

/***
 *                    __          __   __   ___  __       ___    __       
 *    |  | |\ |  /\  |__) \ /    /  \ |__) |__  |__)  /\   |  | /  \ |\ | 
 *    \__/ | \| /~~\ |  \  |     \__/ |    |___ |  \ /~~\  |  | \__/ | \| 
 *                                                                        
 */

VNumber V_BITWISE_NOT(VNumber& a) {
    return a.invert();
}

VNumber V_LOGICAL_NOT(VNumber& a) {
    if (a.is_dont_care_string())
        return AMBIGUOUS_VALUE;

    VNumber ored = a.bitwise_reduce(l_or);
    VNumber noted = ored.invert();
    return noted;
}

VNumber V_ADD(VNumber& a) {
    VNumber result(a);
    return result;
}

VNumber V_MINUS(VNumber& a) {
    return a.twos_complement();
}

VNumber V_UNSIGNED(VNumber& a) {
    return a.to_unsigned();
}

VNumber V_SIGNED(VNumber& a) {
    return a.to_signed();
}

VNumber V_BITWISE_AND(VNumber& a) {
    VNumber to_return = a.bitwise_reduce(l_and);
    return to_return;
}

VNumber V_BITWISE_OR(VNumber& a) {
    VNumber to_return = a.bitwise_reduce(l_or);
    return to_return;
}

VNumber V_BITWISE_XOR(VNumber& a) {
    VNumber to_return = a.bitwise_reduce(l_xor);
    return to_return;
}

VNumber V_BITWISE_NAND(VNumber& a) {
    VNumber to_return = a.bitwise_reduce(l_and).invert();
    return to_return;
}

VNumber V_BITWISE_NOR(VNumber& a) {
    VNumber to_return = a.bitwise_reduce(l_or).invert();
    return to_return;
}

VNumber V_BITWISE_XNOR(VNumber& a) {
    VNumber to_return = a.bitwise_reduce(l_xor).invert();
    return to_return;
}

/***
 *     __               __          __   __   ___  __       ___    __       
 *    |__) | |\ |  /\  |__) \ /    /  \ |__) |__  |__)  /\   |  | /  \ |\ | 
 *    |__) | | \| /~~\ |  \  |     \__/ |    |___ |  \ /~~\  |  | \__/ | \| 
 *                                                                          
 */

VNumber V_REPLICATE(VNumber& a, VNumber& n_times) {
    assert_Werr(!n_times.is_dont_care_string(),
                "Cannot use undefined number for the replication count");

    return a.replicate(n_times.get_value());
}

VNumber V_CONCAT(std::vector<VNumber> concat_list) {
    assert_Werr(!concat_list.empty(),
                "Concat List cannot be empty");

    VNumber init = concat_list[0];
    for (size_t i = 1; i < concat_list.size(); i++) {
        init = init.insert_at_lsb(concat_list[i]);
    }
    return init;
}

VNumber V_BITWISE_AND(VNumber& a, VNumber& b) {
    return a.bitwise(b, l_and);
}

VNumber V_BITWISE_OR(VNumber& a, VNumber& b) {
    return a.bitwise(b, l_or);
}

VNumber V_BITWISE_XOR(VNumber& a, VNumber& b) {
    return a.bitwise(b, l_xor);
}

VNumber V_BITWISE_NAND(VNumber& a, VNumber& b) {
    return a.bitwise(b, l_nand);
}

VNumber V_BITWISE_NOR(VNumber& a, VNumber& b) {
    return a.bitwise(b, l_nor);
}

VNumber V_BITWISE_XNOR(VNumber& a, VNumber& b) {
    return a.bitwise(b, l_xnor);
}

/**
 * Logical Operations
 */

VNumber V_CASE_EQUAL(VNumber& a, VNumber& b) {
    VNumber longEval = a.bitwise(b, l_case_eq);
    VNumber eq = V_BITWISE_AND(longEval);
    return eq;
}

VNumber V_CASE_NOT_EQUAL(VNumber& a, VNumber& b) {
    VNumber eq = V_CASE_EQUAL(a, b);
    VNumber neq = V_LOGICAL_NOT(eq);
    return neq;
}

VNumber V_LOGICAL_AND(VNumber& a, VNumber& b) {
    if (a.is_dont_care_string() || b.is_dont_care_string())
        return AMBIGUOUS_VALUE;
    VNumber reduxA = a.bitwise_reduce(l_or);
    VNumber reduxB = b.bitwise_reduce(l_or);

    VNumber to_return = reduxA.bitwise(reduxB, l_and);

    return to_return;
}

VNumber V_LOGICAL_OR(VNumber& a, VNumber& b) {
    if (a.is_dont_care_string() || b.is_dont_care_string())
        return AMBIGUOUS_VALUE;
    VNumber reduxA = a.bitwise_reduce(l_or);
    VNumber reduxB = b.bitwise_reduce(l_or);

    VNumber to_return = reduxA.bitwise(reduxB, l_or);

    return to_return;
}

VNumber V_LT(VNumber& a, VNumber& b) {
    compare_bit cmp = eval_op(a, b);
    BitSpace::bit_value_t result = cmp.is_unk() ? BitSpace::_x : cmp.is_lt() ? BitSpace::_1 : BitSpace::_0;
    VNumber to_return(1, result, false, true);
    return to_return;
}

VNumber V_GT(VNumber& a, VNumber& b) {
    compare_bit cmp = eval_op(a, b);
    BitSpace::bit_value_t result = cmp.is_unk() ? BitSpace::_x : cmp.is_gt() ? BitSpace::_1 : BitSpace::_0;
    VNumber to_return(1, result, false, true);
    return to_return;
}

VNumber V_EQUAL(VNumber& a, VNumber& b) {
    compare_bit cmp = eval_op(a, b);
    BitSpace::bit_value_t result = cmp.is_unk() ? BitSpace::_x : cmp.is_eq() ? BitSpace::_1 : BitSpace::_0;
    VNumber to_return(1, result, false, true);
    return to_return;
}

VNumber V_GE(VNumber& a, VNumber& b) {
    compare_bit cmp = eval_op(a, b);
    BitSpace::bit_value_t result = cmp.is_unk() ? BitSpace::_x : cmp.is_ge() ? BitSpace::_1 : BitSpace::_0;
    VNumber to_return(1, result, false, true);
    return to_return;
}

VNumber V_LE(VNumber& a, VNumber& b) {
    compare_bit cmp = eval_op(a, b);
    BitSpace::bit_value_t result = cmp.is_unk() ? BitSpace::_x : cmp.is_le() ? BitSpace::_1 : BitSpace::_0;
    VNumber to_return(1, result, false, true);
    return to_return;
}

VNumber V_NOT_EQUAL(VNumber& a, VNumber& b) {
    compare_bit cmp = eval_op(a, b);
    BitSpace::bit_value_t result = cmp.is_unk() ? BitSpace::_x : cmp.is_ne() ? BitSpace::_1 : BitSpace::_0;
    VNumber to_return(1, result, false, true);
    return to_return;
}

VNumber V_SIGNED_SHIFT_LEFT(VNumber& a, VNumber& b) {
    if (b.is_dont_care_string())
        return AMBIGUOUS_VALUE;

    return shift_op(a, b.get_value(), a.is_signed());
}

VNumber V_SHIFT_LEFT(VNumber& a, VNumber& b) {
    if (b.is_dont_care_string())
        return AMBIGUOUS_VALUE;

    return shift_op(a, b.get_value(), false);
}

VNumber V_SIGNED_SHIFT_RIGHT(VNumber& a, VNumber& b) {
    if (b.is_dont_care_string())
        return AMBIGUOUS_VALUE;

    return shift_op(a, -1 * b.get_value(), a.is_signed());
}

VNumber V_SHIFT_RIGHT(VNumber& a, VNumber& b) {
    if (b.is_dont_care_string())
        return AMBIGUOUS_VALUE;

    return shift_op(a, -1 * b.get_value(), false);
}

VNumber V_ADD(VNumber& a, VNumber& b) {
    return sum_op(a, b, _0, /* is_twos_complement_subtraction */ false);
}

VNumber V_MINUS(VNumber& a, VNumber& b) {
    size_t std_length = std::max(a.size(), b.size());
    VNumber padded_a(a, std_length);
    VNumber padded_b(b, std_length);

    VNumber complement = V_MINUS(padded_b);
    if (padded_b.is_negative() && complement.is_negative()) {
        /* special case: 2's comp is identical to original, must pad */
        complement = VNumber(padded_b, padded_b.size() + 1);
        complement = V_MINUS(complement);
    }

    return sum_op(padded_a, complement, _0, /* is_twos_complement_subtraction */ true);
}

VNumber V_MULTIPLY(VNumber& a_in, VNumber& b_in) {
    if (a_in.is_dont_care_string() || b_in.is_dont_care_string()) {
        return AMBIGUOUS_VALUE;
    }

    VNumber a;
    VNumber b;

    bool is_multiply_signed_operation = is_signed_operation(a_in, b_in);
    bool neg_a = a_in.is_negative();
    bool neg_b = b_in.is_negative();

    if (neg_a) {
        a = V_MINUS(a_in);

        if (a.is_negative()) {
            /* special case: 2's comp is identical to original, must pad */
            a = VNumber(a_in, a_in.size() + 1);
            a = V_MINUS(a);
        }
    } else {
        a = a_in;
    }

    if (neg_b) {
        b = V_MINUS(b_in);

        if (b.is_negative()) {
            /* special case: 2's comp is identical to original, must pad */
            b = VNumber(b_in, b_in.size() + 1);
            b = V_MINUS(b);
        }
    } else {
        b = b_in;
    }

    bool invert_result = ((!neg_a && neg_b) || (neg_a && !neg_b));

    VNumber result("0");
    VNumber b_copy = b;

    for (size_t i = 0; i < a.size(); i++) {
        bit_value_t bit_a = a.get_bit_from_lsb(i);

        if (bit_a == _1) {
            result = V_ADD(result, b_copy);
        }

        b_copy = shift_op(b_copy, 1, is_multiply_signed_operation);
    }

    if (invert_result) {
        result = V_MINUS(result);
    }

    return result;
}

/*
 * From Table 5-6 "Power operator rules" of IEEE Standard 1364-2005:
 *  "Verilog Hardware Description Language"; on Page 46 (PDF Page 76):
 *
 * Table 5-6 — Power operator rules:
 *
 * |-----------------------------------------------------------------------------|
 * | |  \ op1 is -> |               |                  |      |   |              |
 * | \/   \         | negative < –1 | –1               | zero | 1 | positive > 1 |
 * | op2 is \       |               |                  |      |   |              |
 * |-----------------------------------------------------------------------------|
 * |                |               |                  |      |   |              |
 * | Positive       | op1 ** op2    | op2 is odd -> –1 | 0    | 1 | op1 ** op2   |
 * |                |               | op2 is even -> 1 |      |   |              |
 * |                |               |                  |      |   |              |
 * |-----------------------------------------------------------------------------|
 * |                |               |                  |      |   |              |
 * | Zero           | 1             | 1                | 1    | 1 | 1            |
 * |                |               |                  |      |   |              |
 * |-----------------------------------------------------------------------------|
 * |                |               |                  |      |   |              |
 * | Negative       | 0             | op2 is odd -> –1 | 'bx  | 1 | 0            |
 * |                |               | op2 is even -> 1 |      |   |              |
 * |                |               |                  |      |   |              |
 * |-----------------------------------------------------------------------------|
 */
VNumber V_POWER(VNumber& a, VNumber& b) {
    if (a.is_dont_care_string() || b.is_dont_care_string()) {
        return AMBIGUOUS_VALUE;
    }

    compare_bit res_a = eval_op(a, 0);
    short val_a = (res_a.is_eq()) ? 0 : (res_a.is_lt()) ? (eval_op(a, -1).is_lt()) ? -2 : -1 :
                                                        /* GREATHER_THAN */ (eval_op(a, 1).is_gt()) ? 2 : 1;

    compare_bit res_b = eval_op(b, 0);
    short val_b = (res_b.is_eq()) ? 0 : (res_b.is_lt()) ? -1 :
                                                        /* GREATHER_THAN */ 1;

    // Compute: Case Where 'val_a <= -2' or 'val_a >= 2'; As-Per the Spec:
    if (val_b > 0 && (val_a < -1 || val_a > 1)) {
        VNumber result("2'sb01");
        VNumber one = VNumber("2'sb01");
        VNumber tmp_b = b;

        while (eval_op(tmp_b, 0).is_gt()) {
            VNumber tmp_b_comp = V_MINUS(tmp_b, one);
            if (tmp_b_comp.is_negative() && tmp_b.is_negative()) {
                /* special case: 2's comp is identical to original, must pad */
                tmp_b_comp = VNumber(tmp_b, tmp_b.size() + 1);
                tmp_b_comp = V_MINUS(tmp_b_comp);
            }
            tmp_b = tmp_b_comp;

            result = V_MULTIPLY(result, a);
        }

        return result;
    } else if (val_b == 0 || val_a == 1) {
        return VNumber("2'sb01");
    } else if (val_b == -1 && val_a == 0) {
        return AMBIGUOUS_VALUE;
    } else if (val_a == -1) {
        // Even:
        if (BitSpace::_0 == b.get_bit_from_lsb(0)) {
            return VNumber("2'sb01");
        }
        // Odd:
        else {
            return VNumber("2'sb11");
        }
    } else {
        return VNumber("2'sb00");
    }
}

/////////////////////////////
VNumber V_DIV(VNumber& a_in, VNumber& b_in) {
    if (a_in.is_dont_care_string() || b_in.is_dont_care_string() || eval_op(b_in, 0).is_eq())
        return AMBIGUOUS_VALUE;

    VNumber result("0");

    bool is_division_signed_operation = is_signed_operation(a_in, b_in);

    bool neg_a = a_in.is_negative();
    bool neg_b = b_in.is_negative();

    VNumber a = neg_a ? V_MINUS(a_in) : a_in;
    VNumber b = neg_b ? V_MINUS(b_in) : b_in;

    if (neg_a && a.is_negative()) {
        /* special case: 2's comp is identical to original, must pad */
        a = VNumber(a_in, a_in.size() + 1);
        a = V_MINUS(a);
    }

    if (neg_b && b.is_negative()) {
        /* special case: 2's comp is identical to original, must pad */
        b = VNumber(b_in, b_in.size() + 1);
        b = V_MINUS(b);
    }

    while (eval_op(a, b).is_ge()) {
        VNumber count("1");
        VNumber tmp = b;

        // initialize our variables
        VNumber sub_with = tmp;
        VNumber count_sub_with = count;
        while (eval_op(tmp, a).is_le()) {
            sub_with = tmp;
            count_sub_with = count;
            count = shift_op(count, 1, is_division_signed_operation);
            tmp = shift_op(tmp, 1, is_division_signed_operation);
        }
        a = V_MINUS(a, sub_with);
        result = V_ADD(result, count_sub_with);
    }

    return (neg_a != neg_b) ? V_MINUS(result) : result;
}

VNumber V_MOD(VNumber& a_in, VNumber& b_in) {
    if (a_in.is_dont_care_string() || b_in.is_dont_care_string() || eval_op(b_in, 0).is_eq())
        return AMBIGUOUS_VALUE;

    bool neg_a = a_in.is_negative();
    bool neg_b = b_in.is_negative();

    VNumber a = neg_a ? V_MINUS(a_in) : a_in;
    VNumber b = neg_b ? V_MINUS(b_in) : b_in;

    if (neg_a && a.is_negative()) {
        /* special case: 2's comp is identical to original, must pad */
        a = VNumber(a_in, a_in.size() + 1);
        a = V_MINUS(a);
    }

    if (neg_b && b.is_negative()) {
        /* special case: 2's comp is identical to original, must pad */
        b = VNumber(b_in, b_in.size() + 1);
        b = V_MINUS(b);
    }

    bool is_modulo_signed_operation = is_signed_operation(a, b);

    while (eval_op(a, b).is_ge()) {
        VNumber tmp = b;
        VNumber sub_with = tmp;

        while (eval_op(tmp, a).is_le()) {
            sub_with = tmp;
            tmp = shift_op(tmp, 1, is_modulo_signed_operation);
        }
        a = V_MINUS(a, sub_with);
    }

    return (neg_a) ? V_MINUS(a) : a;
}

/***
 *    ___  ___  __             __          __   __   ___  __       ___    __       
 *     |  |__  |__) |\ |  /\  |__) \ /    /  \ |__) |__  |__)  /\   |  | /  \ |\ | 
 *     |  |___ |  \ | \| /~~\ |  \  |     \__/ |    |___ |  \ /~~\  |  | \__/ | \| 
 *                                                                                 
 */
VNumber V_TERNARY(VNumber& a_in, VNumber& b_in, VNumber& c_in) {
    /*	if a evaluates properly	*/
    compare_bit eval = eval_op(V_LOGICAL_NOT(a_in), 0);

    return (eval.is_unk()) ? b_in.bitwise(c_in, l_ternary) : (eval.is_eq()) ? VNumber(b_in) : VNumber(c_in);
}
