
#include <assert.h>
#include <stdint.h>

#include "memchecker_events.h"
#include "history.hh"

#include <QMutexLocker>

#include <iostream>
#include <cstdlib>

const char * HistoryBase::EventTypeName(unsigned e)
{
  switch (e)
    {
    case ISS_MCEVENT_RESET:
      return "Reset";
    case ISS_MCEVENT_PRELOAD:
      return "Preload";
    case ISS_MCEVENT_READ_START:
      return "Load (wait)";
    case ISS_MCEVENT_READ_DONE:
      return "Load";
    case ISS_MCEVENT_WRITE_START:
      return "Store (wait)";
    case ISS_MCEVENT_WRITE_DONE:
      return "Store";
    case ISS_MCEVENT_LL_START:
      return "LL (wait)";
    case ISS_MCEVENT_SC_START:
      return "SC (wait)";
    case ISS_MCEVENT_SC_DONE:
      return "SC (ok)";
    case ISS_MCEVENT_SC_FAILED:
      return "SC (failed)";
    case ISS_MCEVENT_MEM_DECLARE:
      return "Mem declare";
    case ISS_MCEVENT_TYPE_GLOBAL:
      return "Global";
    case ISS_MCEVENT_TYPE_GLOBAL_RO:
      return "Global Ro";
    case ISS_MCEVENT_TYPE_ALLOC:
      return "Alloc";
    case ISS_MCEVENT_TYPE_FREE:
      return "Free";
    case ISS_MCEVENT_TYPE_SPECIAL:
      return "Special";
    case ISS_MCEVENT_LOCK_ENABLE:
      return "Lock";
    case ISS_MCEVENT_LOCK_DISABLE:
      return "NoLock";
    case ISS_MCEVENT_STACK_ENABLE:
      return "Stack";
    case ISS_MCEVENT_STACK_DISABLE:
      return "NoStack";
    case ISS_MCEVENT_VALUE_INVALID:
      return "Invalid";
    case ISS_MCEVENT_VALUE_UNDEFINED:
      return "Valid/Undef";
    case ISS_MCEVENT_VALUE_INIT:
      return "Init";
    case ISS_MCEVENT_REG_CHANGE:
      return "Reg change";
    }

  return "Unknown";
}

template <typename Addr, typename Data>
History<Addr, Data>::History()
{
    _hist_size = 1000000;
    _hist_first = 1;
}

template <typename Addr, typename Data>
void History<Addr, Data>::init()
{
    QMutexLocker l(&_mutex);

    _hist_first = 1;
    
    for (typename region_map_t::iterator i = _mem_map.begin(); i != _mem_map.end(); i++)
        delete i->second;

    for (typename regs_map_t::iterator i = _regs_map.begin(); i != _regs_map.end(); i++)
        delete i->second;

    _mem_map.clear();
    _regs_map.clear();
    _hist.clear();

    emit initEvent();
}


template <typename Addr, typename Data>
HistoryBase::history_date_t History<Addr, Data>::eventCount() const
{
    return _hist.size();
}

template <typename Addr, typename Data>
HistoryBase::history_date_t History<Addr, Data>::eventLast() const
{
    return _hist_first + _hist.size() - 1;
}

template <typename Addr, typename Data>
bool History<Addr, Data>::eventInfo(event_info_s &i, uint64_t date) const
{
    if (date < _hist_first || date >= _hist_first + _hist.size())
        return false;

    const history_entry_s &h = _hist[date - _hist_first];

    i._type = h._type;
    i._be = h._be;
    switch (h._type)
        {
        case ISS_MCEVENT_READ_START:
        case ISS_MCEVENT_READ_DONE:
        case ISS_MCEVENT_WRITE_START:
        case ISS_MCEVENT_WRITE_DONE:
        case ISS_MCEVENT_LL_START:
        case ISS_MCEVENT_LL_DONE:
        case ISS_MCEVENT_SC_START:
        case ISS_MCEVENT_SC_DONE:
        case ISS_MCEVENT_SC_FAILED:
            i._addr = h._addr + (h._be ? __builtin_ctz(h._be) : 0);
            i._size = h._be ? sizeof(int) * 8 - __builtin_clz(h._be) - __builtin_ctz(h._be) : 0;
            i._data = (h._data >> (8 * (i._addr % sizeof(Data)))) & ((1ULL << (8 * i._size)) - 1);
            break;
        default:
            i._addr = h._addr;
            i._size = h._size;
            i._data = 0;
            break;
        case ISS_MCEVENT_REG_CHANGE:
            i._addr = h._reg_id;
            i._size = sizeof(Data);
            i._data = h._data;
            break;
        }
    i._cpu_id = h._cpu_id;
    i._date = date;
    i._prev = h._prev;

    i._cycle = 0;
    i._pc = -1;

    typename regs_map_t::const_iterator ri = _regs_map.find(h._cpu_id);
    if (ri != _regs_map.end()) {
        const reg_bank_s *r = ri->second;

        uint64_t pc_diff = 0;
        for (history_date_t q = date - _hist_first - 1; q != (history_date_t)-1; q--) {
            const history_entry_s *f = &_hist[q];
            if (f->_type == ISS_MCEVENT_REG_CHANGE && f->_reg_id == r->_pc_reg) {
                i._pc = pc_diff + f->_data;
                break;
            } else if (f->_cpu_id == h._cpu_id) {
                pc_diff += f->_pc_inc;
            }
        }
    }

    return true;
}

template <typename Addr, typename Data>
void History<Addr, Data>::dropHist()
{
    while (_hist.size() >= _hist_size) {
        history_entry_s &v = _hist.front();

        typename region_map_t::iterator ri = _mem_map.upper_bound(v._addr);
        if (ri == _mem_map.begin())
            std::abort();
        ri--;
        region_s *r = ri->second;
        Addr raddr = ri->first;

        typename regs_map_t::const_iterator ci = _regs_map.find(v._cpu_id);
        if (ci == _regs_map.end())
            std::abort();
        reg_bank_s *c = ci->second;

        switch (v._type) {
        case ISS_MCEVENT_WRITE_DONE:
        case ISS_MCEVENT_SC_DONE: {
            for (Addr va = v._addr; va < v._addr + sizeof(Data); va++)
                if ((v._be >> (va % sizeof(Data))) & 1)
                    r->_data.at(va - raddr) = v._data >> (va % sizeof(Data));
            break;
        }

        case ISS_MCEVENT_TYPE_GLOBAL:
        case ISS_MCEVENT_TYPE_GLOBAL_RO:
        case ISS_MCEVENT_TYPE_ALLOC:
        case ISS_MCEVENT_TYPE_FREE:
        case ISS_MCEVENT_TYPE_SPECIAL:
            for (Addr va = v._addr; va < v._addr + v._size; va++) {
                uint16_t &t = r->_type.at(va - raddr);
                t = (t & ~ISS_MCEVENT_TYPE_MASK) | v._type;
            }
            break;

        case ISS_MCEVENT_LOCK_ENABLE:
        case ISS_MCEVENT_LOCK_DISABLE:
            for (Addr va = v._addr; va < v._addr + v._size; va++) {
                uint16_t &t = r->_type.at(va - raddr);
                t = (t & ~ISS_MCEVENT_LOCK_MASK) | v._type;
            }
            break;

        case ISS_MCEVENT_STACK_ENABLE:
        case ISS_MCEVENT_STACK_DISABLE:
            for (Addr va = v._addr; va < v._addr + v._size; va++) {
                uint16_t &t = r->_type.at(va - raddr);
                t = (t & ~ISS_MCEVENT_STACK_MASK) | v._type;
            }
            break;

        case ISS_MCEVENT_VALUE_INVALID:
        case ISS_MCEVENT_VALUE_UNDEFINED:
        case ISS_MCEVENT_VALUE_INIT:
            for (Addr va = v._addr; va < v._addr + v._size; va++) {
                uint16_t &t = r->_type.at(va - raddr);
                t = (t & ~ISS_MCEVENT_VALUE_MASK) | v._type;
            }
            break;

        case ISS_MCEVENT_REG_CHANGE:
            c->_data[v._reg_id] = v._data;
            break;
        }

        c->_data[c->_pc_reg] += v._pc_inc;

        _hist.pop_front();
        _hist_first++;
    }
}

/*********************************************************************** mem */

template <typename Addr, typename Data>
bool History<Addr, Data>::memEventInfo(event_info_s &i, uint64_t addr, uint64_t date) const
{
    /* get region for addr */
    typename region_map_t::const_iterator ri = _mem_map.upper_bound(addr);
    if (ri == _mem_map.begin())
        return false;
    ri--;
    const region_s *r = ri->second;
    Addr raddr = ri->first;    

    if (raddr + r->_hist.size() <= addr)
        return false;

    /* get last event date for this cell */
    history_date_t d = r->_hist[addr - raddr];

    /* check if the requested history entry still exists */
    while (1) {
        if (d < _hist_first)
            return false;

        if (d <= date)
            break;

        d = _hist[d - _hist_first]._prev;
    }

    return eventInfo(i, d);
}

template <typename Addr, typename Data>
void History<Addr, Data>::memEvent(Addr raddr, Addr start, Addr size,
                                   region_s *r, history_entry_s &e)
{
    history_date_t d = 0, o = -1;

    e._pc_inc = 0;

    for (Addr a = start - raddr; a < start - raddr + size; a++) {
        if (((e._be >> (a % sizeof(Data))) & 1) == 0)
            continue;

        if (r->_hist.at(a) != o) {
            //dropHist();

            d = _hist_first + _hist.size();
            o = e._prev = r->_hist.at(a);
            _hist.push_back(e);
        }

        r->_hist.at(a) = d;
    }

    if (d != 0)
        emit newEvent(d, e._type, start, size);
}

template <typename Addr, typename Data>
void History<Addr, Data>::memAccess(unsigned type, Addr addr, Data data, unsigned be, unsigned cpu, uint64_t cycles)
{
    QMutexLocker l(&_mutex);
    unsigned int size = sizeof(Data);

    switch (type) {
    case ISS_MCEVENT_READ_START:
    case ISS_MCEVENT_LL_START:
    case ISS_MCEVENT_READ_DONE:
    case ISS_MCEVENT_LL_DONE:
        be = (1 << sizeof(Data)) - 1;
        break;

    case ISS_MCEVENT_WRITE_START:
    case ISS_MCEVENT_WRITE_DONE:
    case ISS_MCEVENT_SC_START:
    case ISS_MCEVENT_SC_DONE:
    case ISS_MCEVENT_SC_FAILED:
        if (!be)
            return;
        break;

    default:
        assert(!"bad event type for the History::memAccess function.");
    }

    typename region_map_t::iterator ri = _mem_map.upper_bound(addr);
    if (ri == _mem_map.begin())
        return;
    ri--;
    region_s *r = ri->second;
    Addr raddr = ri->first;

    if (raddr + r->_hist.size() < addr + size)
        return;

    unsigned pair_type;

    switch (type) {
    case ISS_MCEVENT_READ_START:
    case ISS_MCEVENT_LL_START:
    case ISS_MCEVENT_WRITE_START:
    case ISS_MCEVENT_SC_START: {
        /* new event */
        history_entry_s e;
        e._type = type;
        e._cpu_id = cpu;
        e._addr = addr;
        e._data = data;
        e._be = be;
        memEvent(raddr, addr, size, r, e);
        return;
    }

    case ISS_MCEVENT_READ_DONE:
        pair_type = ISS_MCEVENT_READ_START;
        break;
    case ISS_MCEVENT_LL_DONE:
        pair_type = ISS_MCEVENT_LL_START;
        break;
    case ISS_MCEVENT_WRITE_DONE:
        pair_type = ISS_MCEVENT_WRITE_START;
        break;
    case ISS_MCEVENT_SC_DONE:
    case ISS_MCEVENT_SC_FAILED:
        pair_type = ISS_MCEVENT_SC_START;
        break;
    }

    /* replace by associated DONE event */
    bool done = false;
    for (history_date_t q = _hist.size() - 1; q != (history_date_t)-1; q--) {
        history_entry_s &f = _hist[q];

        if (f._cpu_id == cpu && f._type == pair_type && f._addr == addr) {
            f._type = type;
            f._data = data;
            done = true;
        } else if (done) {
            break;
        }
    }
}

template <typename Addr, typename Data>
void History<Addr, Data>::memState(unsigned type, Addr addr, Addr size, unsigned cpu, uint64_t cycles)
{
    QMutexLocker l(&_mutex);

    region_s *r;
    Addr raddr;

    if (type == ISS_MCEVENT_MEM_DECLARE) {
        std::cerr << std::hex << " region at " << addr << " size " << size << "\n";

        r = new region_s(size);
        _mem_map.insert(typename region_map_t::value_type(addr, r));
        raddr = addr;

        for (Addr a = 0; a < size; a++)
            r->_type[a] = ISS_MCEVENT_VALUE_UNDEFINED;

    } else {
        // std::cerr << std::hex << EventTypeName(type) << " at " << addr << " size " << size << "\n";

        typename region_map_t::iterator ri = _mem_map.upper_bound(addr);
        if (ri == _mem_map.begin())
            return;
        ri--;
        r = ri->second;
        raddr = ri->first;

        if (raddr + r->_hist.size() < addr + size)
            return;
    }

    history_entry_s e;

    e._type = type;
    e._cpu_id = cpu;
    e._addr = addr;
    e._size = size;
    e._be = -1;

    memEvent(raddr, addr, size, r, e);
}

template <typename Addr, typename Data>
void History<Addr, Data>::setData(Addr addr, uint8_t *data, size_t size)
{
    QMutexLocker l(&_mutex);

    typename region_map_t::iterator ri = _mem_map.upper_bound(addr);
    if (ri == _mem_map.begin())
        return;
    ri--;
    region_s *r = ri->second;
    Addr raddr = ri->first;    

    for (Addr a = addr; a < std::min(addr + size, raddr + r->_data.size()); a++) {
        r->_data[a - raddr] = data[a - addr];
        r->_type[a - raddr] = ISS_MCEVENT_VALUE_INIT;
    }
}

template <typename Addr, typename Data>
bool History<Addr, Data>::memInfo(mem_info_s &i, uint64_t addr, uint64_t date) const
{
    QMutexLocker l(&_mutex);

    if (_mem_map.empty())
        return false;

    /* get region for addr */
    typename region_map_t::const_iterator ri = _mem_map.upper_bound(addr);
    if (ri == _mem_map.begin())
        return false;
    ri--;
    const region_s *r = ri->second;
    Addr raddr = ri->first;    

    if (raddr + r->_hist.size() <= addr)
        return false;

    history_date_t d = r->_hist[addr - raddr];

    unsigned tmask = 0;
    i._type = 0;
    bool data_done = false;

    while (1) {

        if (d < _hist_first) {

            if (d > date)
                return false;

            i._type |= r->_type[addr - raddr] & ~tmask;
            if (!data_done) {
                i._data = r->_data[addr - raddr];
            }
            return true;

        } else {
            const history_entry_s &h = _hist[d - _hist_first];

            if (d <= date) {

                if ((h._type & ISS_MCEVENT_TYPE_MASK) && !(tmask & ISS_MCEVENT_TYPE_MASK)) {
                    i._type |= h._type;
                    tmask |= ISS_MCEVENT_TYPE_MASK;

                } else if ((h._type & ISS_MCEVENT_LOCK_MASK) && !(tmask & ISS_MCEVENT_LOCK_MASK)) {
                    i._type |= h._type;
                    tmask |= ISS_MCEVENT_LOCK_MASK;

                } else if ((h._type & ISS_MCEVENT_STACK_MASK) && !(tmask & ISS_MCEVENT_STACK_MASK)) {
                    i._type |= h._type;
                    tmask |= ISS_MCEVENT_STACK_MASK;

                } else if ((h._type & ISS_MCEVENT_VALUE_MASK) && (!(tmask & ISS_MCEVENT_VALUE_MASK) ||
                                  (h._type & ISS_MCEVENT_VALUE_INVALID && (i._type & ISS_MCEVENT_VALUE_INIT))) ) {
                    i._type &= ~ISS_MCEVENT_VALUE_MASK;
                    i._type |= h._type;
                    tmask |= ISS_MCEVENT_VALUE_MASK;
                }

#warning add a special flag to show bad accesses as well
#warning may use loads too to get cell state ?

                if (h._type == ISS_MCEVENT_WRITE_DONE || h._type == ISS_MCEVENT_SC_DONE) {
                    if (!data_done) {
                        i._data = h._data >> ((addr % sizeof(Data)) * 8);
                        data_done = true;
                    }

                    if (!(tmask & ISS_MCEVENT_VALUE_INVALID)) {
                        i._type = (i._type & ~ISS_MCEVENT_VALUE_MASK) | ISS_MCEVENT_VALUE_INIT;
                        tmask |= ISS_MCEVENT_VALUE_MASK;
                    }
                }
            }

            d = h._prev;
        }

    }

}

/*********************************************************************** regs */

template <typename Addr, typename Data>
unsigned History<Addr, Data>::cpuCount() const
{
    return _regs_map.size();
}

template <typename Addr, typename Data>
unsigned History<Addr, Data>::regCount(unsigned int cpu) const
{
    typename regs_map_t::const_iterator ri = _regs_map.find(cpu);
    if (ri == _regs_map.end())
        return 0;
    reg_bank_s *r = ri->second;
    return r->_hist.size();
}

template <typename Addr, typename Data>
void History<Addr, Data>::cpuReset(unsigned int cpu, unsigned int regs_count,
                                   unsigned pc_reg, unsigned sp_reg,
                                   const char reg_names[][8])
{
    QMutexLocker l(&_mutex);

    if (cpu >= 16)
        return;

    reg_bank_s *r = new reg_bank_s();
    _regs_map.insert(typename regs_map_t::value_type(cpu, r));
    r->_names.resize(regs_count);
    r->_hist.resize(regs_count);
    r->_data.resize(regs_count);
    // FIXME setup initial register values

    r->_last_cycle = 0;
    r->_pc_reg = pc_reg;
    r->_sp_reg = sp_reg;

    for (int i = 0; i < regs_count; i++)
        {
            int l;
            for (l = 0; reg_names[i][l] && l < 8; l++)
                ;
            r->_names[i] = std::string(reg_names[i], l);
        }

    emit resetEvent(cpu);
}

template <typename Addr, typename Data>
bool History<Addr, Data>::regEventInfo(event_info_s &i, unsigned cpu, unsigned reg, uint64_t date) const
{
    /* get region for addr */
    typename regs_map_t::const_iterator ri = _regs_map.find(cpu);
    if (ri == _regs_map.end())
        return false;
    const reg_bank_s *r = ri->second;

    if (reg >= r->_hist.size())
        return false;

    /* get last event date for this cell */
    history_date_t d = r->_hist[reg];

    /* check if the requested history entry still exists */
    while (1) {
        if (d < _hist_first)
            return false;

        if (d <= date)
            break;

        d = _hist[d - _hist_first]._prev;
    }

    return eventInfo(i, d);
}

template <typename Addr, typename Data>
void History<Addr, Data>::regWrite(unsigned id, Data data, unsigned cpu, uint64_t cycles)
{
    QMutexLocker l(&_mutex);

    if (cpu >= 16)
        return;

    typename regs_map_t::iterator ri = _regs_map.find(cpu);
    if (ri == _regs_map.end())
        return;
    reg_bank_s *r = ri->second;

    history_entry_s e;
    e._type = ISS_MCEVENT_REG_CHANGE;
    e._reg_id = id;
    e._cpu_id = cpu;
    e._pc_inc = 0;
    e._cycles_diff = cycles - r->_last_cycle;
    r->_last_cycle = cycles;
    e._data = data;

    history_date_t d = _hist_first + _hist.size();

    if (id == r->_pc_reg) {
        Data pc_diff = data - r->_last_pc;
        r->_last_pc = data;

        if (pc_diff < 16) {
            for (history_date_t q = _hist.size() - 1; q != (history_date_t)-1; q--)
                {
                    history_entry_s *f = &_hist[q];

                    if (f->_cpu_id != cpu)
                        continue;

                    if (16 - f->_pc_inc > pc_diff) {
                        f->_pc_inc += pc_diff;
                        return;
                    } else {
                        goto done;
                    }
                }
        }
    }

 done:;
    e._prev = r->_hist.at(id);
    _hist.push_back(e);
    r->_hist.at(id) = d;
}

template <typename Addr, typename Data>
QString History<Addr, Data>::regName(unsigned int cpu, unsigned int reg) const
{
    if (cpu >= 16)
        return QString();

    typename regs_map_t::const_iterator ri = _regs_map.find(cpu);
    if (ri == _regs_map.end())
        return QString();
    reg_bank_s *r = ri->second;
    return QString::fromStdString(r->_names.at(reg));
}

template <typename Addr, typename Data>
bool History<Addr, Data>::regInfo(reg_info_s &i, unsigned int cpu, unsigned int reg, uint64_t date) const
{
    QMutexLocker l(&_mutex);

    typename regs_map_t::const_iterator ci = _regs_map.find(cpu);
    if (ci == _regs_map.end())
        return false;
    reg_bank_s *c = ci->second;

    if (reg >= c->_hist.size())
        return false;

    i._name = c->_names[reg];

    date = std::min(date, _hist_first + _hist.size() - 1);
    history_date_t d = c->_hist[reg];

    while (1) {
        if (d < _hist_first) {
            i._data = c->_data[reg];
            d = _hist_first;
            break;
        }

        const history_entry_s &e = _hist[d - _hist_first];

        assert(e._reg_id == reg);
        assert(e._cpu_id == cpu);

        if (d <= date) {
            i._data = e._data;
            break;
        }

        d = e._prev;
    }

    /* accumulate pc diffs */
    if (reg == c->_pc_reg) {
        while (d < date) {
            const history_entry_s &e = _hist[d++ - _hist_first];
            if (e._cpu_id == cpu)
                i._data += e._pc_inc;
        }
    }

    return true;
}

template class History<uint32_t, uint32_t>;

