
#include <QColor>
#include <QList>
#include <assert.h>

#include <iostream>

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

static int colums_count = 8;

EventModel::EventModel(HistoryBase *hist, QObject *parent)
  : QAbstractItemModel(parent)
  , _hist(hist)
{
  _cache._date = 0;
}

bool EventModel::eventInfo(HistoryBase::event_info_s &i, const QModelIndex &index) const
{
  if (!index.isValid())
    return false;
  bool r = eventInfo(index.row());
  if (r)
    i = _cache;
  return r;
}

QVariant EventModel::data(const QModelIndex &index, int role) const
{
  if (!index.isValid())
    return QVariant();

  // FIXME add cache

  switch (role)
    {
    case Qt::DisplayRole: {
      QString s;
      if (!eventInfo(index.row()))
	return QVariant();

      switch (index.column())
	{
	case 0:
	  s.sprintf("%u", _cache._date);
	  return QVariant(s);
	case 1:
	  return QVariant(HistoryBase::EventTypeName(_cache._type));
	case 2:
	  switch (_cache._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:
	      s.sprintf("%08llx", _cache._addr);
	      return QVariant(s);
	    default:
	      s.sprintf("%08llx to", _cache._addr);
	      return QVariant(s);
	    case ISS_MCEVENT_REG_CHANGE:
	      return QVariant(_hist->regName(_cache._cpu_id, _cache._addr));
	    }
	  return QVariant(s);
	case 3:
	  switch (_cache._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:
	    case ISS_MCEVENT_REG_CHANGE:
	      s.sprintf("%08llx", _cache._data);
	      return QVariant(s);
	    default:
	      s.sprintf("%08llx", _cache._addr + _cache._size);
	      return QVariant(s);
	    }
	case 4:
	  s.sprintf("%llu", _cache._size);
	  return QVariant(s);
	case 5:
	  s.sprintf("%u", _cache._cpu_id);
	  return QVariant(s);
	case 6:
	  s.sprintf("%llx", _cache._pc);
	  return QVariant(s);
	case 7:
	  s.sprintf("%llu", _cache._cycle);
	  return QVariant(s);
	}
    }

    case Qt::BackgroundRole: {
      QColor c;
      if (!eventInfo(index.row()))
	return QVariant(Qt::gray);

      switch (_cache._type)
	{
	case ISS_MCEVENT_READ_START:
	case ISS_MCEVENT_WRITE_START:
	case ISS_MCEVENT_SC_START:
	case ISS_MCEVENT_LL_START:
	  c = QColor(255, 255, 220);
	  break;

	case ISS_MCEVENT_READ_DONE:
	  c = QColor(220, 255, 220);
	  break;

	case ISS_MCEVENT_WRITE_DONE:
	case ISS_MCEVENT_SC_DONE:
	  c = QColor(255, 220, 220);
	  break;

	case ISS_MCEVENT_SC_FAILED:
	  c = Qt::red;
	  break;

	case ISS_MCEVENT_REG_CHANGE:
	  c = QColor(220, 220, 255);
	  break;

	default:
	  c = Qt::white;
	  break;
	}
      return QVariant(c);
    }

    case Qt::ForegroundRole: {
      QColor c;
      c = Qt::black;
      return QVariant(c);
    }

    default:
      return QVariant();
    }
}

Qt::ItemFlags EventModel::flags(const QModelIndex &index) const
{
  return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QVariant EventModel::headerData(int section, Qt::Orientation orientation, int role) const
{
  if (role != Qt::DisplayRole)
    return QVariant();

  const char * fields[] = { "Event #", "Type", "Address", "Data", "Size", "Cpu", "PC", "Cycle" };

  switch (orientation) {
  case Qt::Horizontal:
    return QVariant(fields[section]);
  default:
    return QVariant();
  }
}

int EventModel::columnCount(const QModelIndex &parent) const
{
  if (parent.isValid())
    return 0;
  return colums_count;
}

QModelIndex EventModel::index(int row, int column, const QModelIndex &parent) const
{
  if (column < colums_count)
    return createIndex(row, column, 0);
  else
    return QModelIndex();
}

QModelIndex EventModel::parent(const QModelIndex &index) const
{
  return QModelIndex();
}

/****************************************************/

EventModelAll::EventModelAll(HistoryBase *hist, QObject *parent)
  : EventModel(hist, parent)
{
  _first = _hist->eventLast() - _hist->eventCount() + 1;
  _count = _hist->eventCount();
}

int EventModelAll::rowCount(const QModelIndex &parent) const
{
  if (parent.isValid())
    return 0;
  return _count;
}

bool EventModelAll::eventInfo(int row) const
{
  HistoryBase::history_date_t date = row + _first;
  if (_cache._date != date && !_hist->eventInfo(_cache, date))
    return false;

  return true;
}

QModelIndex EventModelAll::indexFromDate(HistoryBase::history_date_t date) const
{
  if (date >= _first && date < _first + _count)
    return createIndex(date - _first, 0, 0);
  return QModelIndex();
}

void EventModelAll::update()
{
  HistoryBase::history_date_t nfirst = _hist->eventLast() - _hist->eventCount() + 1;
  HistoryBase::history_date_t ncount = _hist->eventCount();
  HistoryBase::history_date_t c = std::min(nfirst - _first, _count);

  if (nfirst > _first) {
    beginRemoveRows(QModelIndex(), 0, c);
    endRemoveRows();
  }

  if (ncount > _count) {
    beginInsertRows(QModelIndex(), _count - c, ncount - c);
    endInsertRows();
  }

  _first = nfirst;
  _count = ncount;
}

/****************************************************/

EventModelMem::EventModelMem(HistoryBase *hist, uint64_t addr, QObject *parent)
  : EventModel(hist, parent)
  , _addr(addr)
{
  _last = 0;

  update();
}

int EventModelMem::rowCount(const QModelIndex &parent) const
{
  if (parent.isValid())
    return 0;

  return _info.size();
}

bool EventModelMem::eventInfo(int row) const
{
  if (row >= _info.size())
    return false;
  _cache = _info[row];
  return true;
}

QModelIndex EventModelMem::indexFromDate(HistoryBase::history_date_t date) const
{
  for (int i = 0; i < _info.size(); i++)
    if (_info[i]._date = date)
      return createIndex(i, 0, 0);
  return QModelIndex();
}

void EventModelMem::update()
{
  /* get most recent event on cell */
  HistoryBase::event_info_s i;
  if (!_hist->memEventInfo(i, _addr, -1))
    return;

  /* no new event since last query */
  if (i._date == _last)
    return;

  HistoryBase::history_date_t last = i._date;

  int s = _info.size();
  _info.insert(s, i);

  while (i._prev != _last) {

    assert(i._prev != 0);

    if (!_hist->eventInfo(i, i._prev)) {
      /* missing entries between _info and existing history */
      beginRemoveRows(QModelIndex(), 0, s);
      _info.erase(_info.begin(), _info.begin() + s);
      endRemoveRows();
      break;
    }

    beginInsertRows(QModelIndex(), s, s);
    _info.insert(s, i);
    endInsertRows();
  }

  _last = last;
}

/****************************************************/

EventModelReg::EventModelReg(HistoryBase *hist, unsigned cpu, unsigned reg, QObject *parent)
  : EventModel(hist, parent)
  , _cpu(cpu)
  , _reg(reg)
{
  _last = 0;

  update();
}

int EventModelReg::rowCount(const QModelIndex &parent) const
{
  if (parent.isValid())
    return 0;
  return _info.size();
}

bool EventModelReg::eventInfo(int row) const
{
  if (row >= _info.size())
    return false;
  _cache = _info[row];
  return true;
}

QModelIndex EventModelReg::indexFromDate(HistoryBase::history_date_t date) const
{
  for (int i = 0; i < _info.size(); i++)
    if (_info[i]._date = date)
      return createIndex(i, 0, 0);
  return QModelIndex();
}

void EventModelReg::update()
{
  /* get most recent event on cell */
  HistoryBase::event_info_s i;
  if (!_hist->regEventInfo(i, _cpu, _reg, -1))
    return;

  /* no new event since last query */
  if (i._date == _last)
    return;

  HistoryBase::history_date_t last = i._date;

  int s = _info.size();
  _info.insert(s, i);

  while (i._prev != _last) {

    assert(i._prev != 0);

    if (!_hist->eventInfo(i, i._prev)) {
      /* missing entries between _info and existing history */
      beginRemoveRows(QModelIndex(), 0, s);
      _info.erase(_info.begin(), _info.begin() + s);
      endRemoveRows();
      break;
    }

    beginInsertRows(QModelIndex(), s, s);
    _info.insert(s, i);
    endInsertRows();
  }

  _last = last;
}



