/*++
Copyright (c) 2014 Microsoft Corporation

Module Name:

    tbv.cpp

Abstract:

    ternary bit-vector utilities.

Author:

    Nikolaj Bjorner (nbjorner) 2014-09-15

Revision History:


--*/

#include "muz/rel/tbv.h"
#include "util/hashtable.h"
#include "ast/ast_util.h"


static bool s_debug_alloc = false;

void tbv_manager::debug_alloc() {
    s_debug_alloc = true;
}

tbv_manager::~tbv_manager() {    
    DEBUG_CODE(
        for (auto t : allocated_tbvs)
            TRACE("doc", tout << "dangling: " << t << "\n";););
}

void tbv_manager::reset() {
    m.reset();
}
tbv* tbv_manager::allocate() {
    tbv* r = static_cast<tbv*>(m.allocate());
    m.reset(*r);
    DEBUG_CODE(
        if (s_debug_alloc) {
            TRACE("doc", tout << allocated_tbvs.size() << " " << r << "\n";);
        }
        allocated_tbvs.insert(r);
        );
    return r;
}
tbv* tbv_manager::allocate1() {
    tbv* v = allocate();
    fill1(*v);
    return v;
}
tbv* tbv_manager::allocate0() {
    tbv* v = allocate();
    fill0(*v);
    return v;
}
tbv* tbv_manager::allocateX() {
    tbv* v = allocate();
    fillX(*v);
    return v;
}
tbv* tbv_manager::allocate(tbv const& bv) {
    tbv* r = allocate();
    copy(*r, bv);
    return r;
}
tbv* tbv_manager::allocate(uint64_t val) {
    tbv* v = allocate0();
    for (unsigned bit = std::min(64u, num_tbits()); bit-- > 0;) {
        if (val & (1ULL << bit)) {                        
            set(*v, bit, BIT_1);
        } else {
            set(*v, bit, BIT_0);
        }
    }
    return v;
}

tbv* tbv_manager::allocate(uint64_t val, unsigned hi, unsigned lo) {
    tbv* v = allocateX();
    SASSERT(64 >= num_tbits() && num_tbits() > hi && hi >= lo);
    set(*v, val, hi, lo);
    return v;
}
tbv* tbv_manager::allocate(tbv const& bv, unsigned const* permutation) {
    tbv* r = allocate();
    unsigned sz = num_tbits();
    for (unsigned i = 0; i < sz; ++i) {
        set(*r, permutation[i], bv[i]);
    }
    return r;
}
tbv* tbv_manager::allocate(char const* bv) {
    tbv* result = allocateX();
    unsigned i = 0, sz = num_tbits();
    while(*bv && i < sz) {
        if (*bv == '0') set(*result, i++, BIT_0);
        else if (*bv == '1') set(*result, i++, BIT_1);
        else if (*bv == '*') i++;
        else if (*bv == 'x') i++;
        else if (i == 0 && (*bv == ' ' || *bv == '\t')) ;
        else break;
        ++bv;
    }
    return result;
}

tbv* tbv_manager::project(bit_vector const& to_delete, tbv const& src) {
    tbv* r = allocate();
    unsigned i, j;
    unsigned n = to_delete.size();
    for (i = 0, j = 0; i < n; ++i) {
        if (!to_delete.get(i)) {
            set(*r, j, src[i]);
            ++j;
        }
    }
    SASSERT(num_tbits() == j);
    return r;
}

void tbv_manager::set(tbv& dst, unsigned index, tbit value) {
    SASSERT(index < num_tbits());
    m.set(dst, 2*index,   (value & 2) != 0);
    m.set(dst, 2*index+1, (value & 1) != 0);    
}


void tbv_manager::set(tbv& dst, uint64_t val, unsigned hi, unsigned lo) {
    SASSERT(lo <= hi && hi < num_tbits());
    for (unsigned i = 0; i < hi - lo + 1; ++i) {
        set(dst, lo + i, (val & (1ULL << i))?BIT_1:BIT_0);
    }
}
void tbv_manager::set(tbv& dst, rational const& r, unsigned hi, unsigned lo) {
    SASSERT(lo <= hi && hi < num_tbits());
    if (r.is_uint64()) {
        set(dst, r.get_uint64(), hi, lo);
        return;
    }
    for (unsigned i = 0; i < hi - lo + 1; ++i) {
        if (bitwise_and(r, rational::power_of_two(i)).is_zero())
            set(dst, lo + i, BIT_0);
        else
            set(dst, lo + i, BIT_1);
    }
}

void tbv_manager::set(tbv& dst, tbv const& other, unsigned hi, unsigned lo) {
    dst.set(other, 2*hi+1, 2*lo);
}


tbv* tbv_manager::allocate(rational const& r) {
    if (r.is_uint64()) {
        return allocate(r.get_uint64());
    }
    tbv* v = allocate0();
    for (unsigned bit = num_tbits(); bit > 0; ) {
        --bit;
        if (bitwise_and(r, rational::power_of_two(bit)).is_zero()) {
            set(*v, bit, BIT_0);
        } else {
            set(*v, bit, BIT_1);
        }
    }            
    return v;
}
void tbv_manager::deallocate(tbv* bv) {
#if 0
    DEBUG_CODE(
        if (!allocated_tbvs.contains(bv)) {
            std::cout << "double deallocate: " << bv << "\n";
            UNREACHABLE();
        }
        if (s_debug_alloc) {
            TRACE("doc", tout << "deallocate: " << bv << "\n";);
        }
        allocated_tbvs.erase(bv););
#endif
    m.deallocate(bv);
}    
void tbv_manager::copy(tbv& dst, tbv const& src) const {
    m.copy(dst, src);
}    
tbv& tbv_manager::fill0(tbv& bv) const { 
    // 10101010 = 2 + 8 + 32 + 128
    memset(bv.m_data, 2 + 8 + 32 + 128, m.num_bytes());
    return bv; 
}
tbv& tbv_manager::fill1(tbv& bv) const {  
    // 01010101 = 1 + 4 + 16 + 64
    memset(bv.m_data, 1 + 4 + 16 + 64, m.num_bytes());
    return bv; 
}
tbv& tbv_manager::fillX(tbv& bv) const { 
    m.fill1(bv); 
    return bv; 
}

tbv& tbv_manager::set_or(tbv& dst,  tbv const& src) const {
    m.set_or(dst, src); 
    return dst;
}
bool tbv_manager::set_and(tbv& dst,  tbv const& src) const {
    m.set_and(dst, src); 
    return is_well_formed(dst);
}

bool tbv_manager::is_well_formed(tbv const& dst) const {
    unsigned nw = m.num_words();
    unsigned w;
    for (unsigned i = 0; i + 1 < nw; ++i) {
        w = dst.get_word(i);
        w = w | (w << 1) | 0x55555555;
        if (w != 0xFFFFFFFF) return false;
    }
    if (nw > 0) {        
        w = m.last_word(dst);
        w = w | (w << 1) | 0x55555555 | ~m.get_mask();
        if (w != 0xFFFFFFFF) return false;
    }
    return true;
}

void tbv_manager::complement(tbv const& src, ptr_vector<tbv>& result) {
    tbv* r;
    unsigned n = num_tbits();
    for (unsigned i = 0; i < n; ++i) {
        switch (src.get(i)) {
        case BIT_0:
            r = allocate(src);
            set(*r, i, BIT_1);
            result.push_back(r);
            break;
        case BIT_1:
            r = allocate(src);
            set(*r, i, BIT_0);
            result.push_back(r);
            break;
        default:
            break;
        }
    }
}

bool tbv_manager::equals(tbv const& a, tbv const& b) const {
    return m.equals(a, b);
}
unsigned tbv_manager::hash(tbv const& src) const {
    return m.hash(src);
}
bool tbv_manager::contains(tbv const& a, tbv const& b) const {
    return m.contains(a, b);
}

bool tbv_manager::contains(tbv const& a, unsigned_vector const& colsa,
                           tbv const& b, unsigned_vector const& colsb) const {
    for (unsigned i = 0; i < colsa.size(); ++i) {
        tbit bit_a = a[colsa[i]];
        if (bit_a == BIT_x) continue;
        if (bit_a != b[colsb[i]]) return false;
    }
    return true;
}

bool tbv_manager::intersect(tbv const& a, tbv const& b, tbv& result) {
    copy(result, a);
    return set_and(result, b);
}

std::ostream& tbv_manager::display(std::ostream& out, tbv const& b, unsigned hi, unsigned lo) const {
    SASSERT(lo <= hi && hi < num_tbits());
    for (unsigned i = hi+1; i-- > lo; ) {
        switch (b.get(i)) {
        case BIT_0:
            out << '0';
            break;
        case BIT_1:
            out << '1';
            break;
        case BIT_x:
            out << 'x';
            break;
        case BIT_z:
            out << 'z';
            break;
        default:
            UNREACHABLE();
        }
    }
    return out;
}

std::ostream& tbv_manager::display(std::ostream& out, tbv const& b) const {
    if (num_tbits() == 0) return out << "[]";
    return display(out, b, num_tbits()-1, 0);
}

expr_ref tbv_manager::to_formula(ast_manager& m, tbv const& src) {
    expr_ref result(m);
    expr_ref_vector conj(m);
    for (unsigned i = 0; i < num_tbits(); ++i) {
        switch (src[i]) {
        case BIT_0:
            conj.push_back(m.mk_not(m.mk_const(symbol(i), m.mk_bool_sort())));
            break;
        case BIT_1:
            conj.push_back(m.mk_const(symbol(i), m.mk_bool_sort()));
            break;
        default:
            break;
        }
    }
    result = mk_and(m, conj.size(), conj.data());
    return result;
}

expr_ref tbv_manager::mk_var(ast_manager& m, unsigned i) {
    return expr_ref(m.mk_const(symbol(i), m.mk_bool_sort()), m);
}
