/***************************************************************************
 *   Copyright (C) 2006 Nicolas Hadacek <hadacek@kde.org>                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/
#include "pic_register_view.h"

#include <tqlayout.h>
#include <tqlabel.h>
#include <tqpushbutton.h>
#include <tqcheckbox.h>
#include <tqcombobox.h>
#include <tqpopupmenu.h>

#include <klocale.h>
#include <kiconloader.h>

#include "libgui/main_global.h"
#include "devices/gui/hex_word_editor.h"
#include "common/gui/misc_gui.h"
#include "devices/pic/base/pic.h"
#include "progs/base/generic_prog.h"
#include "progs/base/generic_debug.h"
#include "progs/base/prog_group.h"
#include "libgui/gui_debug_manager.h"
#include "coff/base/text_coff.h"

//-----------------------------------------------------------------------------
Pic::BankWidget::BankWidget(uint i, TQWidget *parent)
  : TQFrame(parent, "bank_widget"), _bindex(i), _bankCombo(0)
{
  setFrameStyle(WinPanel | Sunken);
  TQGridLayout *top = new TQGridLayout(this, 1, 1, 5, 0);
  top->setColSpacing(1, 4);
  TQFont f("courier", font().pointSize());

  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  const Pic::RegistersData &rdata = data.registersData();
  bool debugging = Main::programmerGroup().isDebugger();
  uint row = 0;
  if ( rdata.nbBanks!=1 ) {
    if ( data.is18Family() ) {
      if ( (i/2)==0 ) {
        TQString title = ((i%2)==0 ? i18n("Access Bank (low)") : i18n("Access Bank (high)"));
        TQLabel *label = new TQLabel(title, this);
        label->setAlignment(AlignCenter);
        top->addMultiCellWidget(label, row,row, 0,6, AlignHCenter);
      } else {
        _bankCombo = new TQComboBox(this);
        for (uint k=1; k<2*rdata.nbBanks-1; k++) {
	  _bankCombo->insertItem((k%2)==0 ? i18n("Bank %1 (low)").arg(k/2) : i18n("Bank %1 (high)").arg(k/2));
	}
        if ( _bindex==3 ) _bankCombo->setCurrentItem(1);
        connect(_bankCombo, TQT_SIGNAL(activated(int)), TQT_SLOT(bankChanged()));
        top->addMultiCellWidget(_bankCombo, row,row, 0,6, AlignHCenter);
      }
    } else {
      TQLabel *label = new TQLabel(i18n("Bank %1").arg(i), this);
      label->setAlignment(AlignCenter);
      top->addMultiCellWidget(label, row,row, 0,6, AlignHCenter);
    }
    row++;
    top->setRowSpacing(row, 5);
    row++;
  }

  KIconLoader loader;
  TQPixmap readIcon = loader.loadIcon("viewmag", KIcon::Small);
  TQPixmap editIcon = loader.loadIcon("edit", KIcon::Small);
  uint nb;
  if ( !data.is18Family() ) nb = rdata.nbRegistersPerBank();
  else nb = kMax(rdata.accessBankSplit, rdata.nbRegistersPerBank() - rdata.accessBankSplit);
  _registers.resize(nb);
  for (uint k=0; k<nb; k++) {
    _registers[k].alabel = new TQLabel(this);
    _registers[k].alabel->setFont(f);
    top->addWidget(_registers[k].alabel, row, 0);
    if (debugging) {
      _registers[k].button = new PopupButton(this);
      _registers[k].button->appendItem(i18n("Read"), readIcon, ReadId);
      _registers[k].button->appendItem(i18n("Edit"), editIcon, EditId);
      _registers[k].button->appendItem(i18n("Watch"), WatchId);
      connect(_registers[k].button, TQT_SIGNAL(activated(int)), TQT_SLOT(buttonActivated(int)));
      top->addWidget(_registers[k].button, row, 2);
      _registers[k].edit = new Register::LineEdit(this);
      connect(_registers[k].edit, TQT_SIGNAL(modified()), TQT_SLOT(write()));
      _registers[k].edit->setFont(f);
      top->addWidget(_registers[k].edit, row, 6);
    } else {
      _registers[k].label = new TQLabel(this);
      top->addWidget(_registers[k].label, row, 2);
    }
    row++;
  }

  if (debugging) {
    top->setColSpacing(3, 5);
    top->setColSpacing(5, 5);
  }
  top->setRowStretch(row, 1);

  updateRegisterAddresses();
}

void Pic::BankWidget::bankChanged()
{
  updateRegisterAddresses();
  updateView();
}

uint Pic::BankWidget::bank() const
{
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  if ( !data.is18Family() ) return _bindex;
  if ( _bindex==0 ) return 0;
  const Pic::RegistersData &rdata = data.registersData();
  if ( _bindex==1 ) return rdata.nbBanks - 1;
  return (_bankCombo->currentItem()+1)/2;
}

uint Pic::BankWidget::nbRegisters() const
{
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  const Pic::RegistersData &rdata = data.registersData();
  if ( !data.is18Family() ) return rdata.nbRegistersPerBank();
  if ( _bindex==0 || (_bankCombo && _bankCombo->currentItem()==2*int(rdata.nbBanks)-3) ) return rdata.accessBankSplit;
  if ( _bindex==1 || (_bankCombo && _bankCombo->currentItem()==0) ) return rdata.nbRegistersPerBank() - rdata.accessBankSplit;
  return rdata.nbRegistersPerBank() / 2;
}

uint Pic::BankWidget::indexOffset() const
{
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  const Pic::RegistersData &rdata = data.registersData();
  uint offset = bank() * rdata.nbRegistersPerBank();
  if ( !data.is18Family() ) return offset;
  if ( _bindex==0 || (_bankCombo && (_bankCombo->currentItem()%2)==1) ) return offset;
  if ( _bindex==1 || (_bankCombo && _bankCombo->currentItem()==0) ) return offset + rdata.accessBankSplit;
  return offset + rdata.nbRegistersPerBank()/2;
}

void Pic::BankWidget::updateRegisterAddresses()
{
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  const Pic::RegistersData &rdata = data.registersData();
  uint nbChars = rdata.nbCharsAddress();
  uint nb = nbRegisters();
  uint offset = indexOffset();
  for (uint k=0; k<_registers.count(); k++) {
    if ( k<nb ) {
      _registers[k].alabel->show();
      _registers[k].address = rdata.addressFromIndex(offset + k);
      _registers[k].alabel->setText(toHexLabel(_registers[k].address, nbChars) + ":");
    } else _registers[k].alabel->hide();
  }
}

void Pic::BankWidget::buttonActivated(int id)
{
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  const Pic::RegistersData &rdata = data.registersData();
  for (uint i=0; i<_registers.count(); i++) {
    if ( sender()!=_registers[i].button ) continue;
    Register::TypeData rtd(_registers[i].address, rdata.nbChars());
    switch (id) {
      case ReadId: Debugger::manager->readRegister(rtd); break;
      case EditId:
        _registers[i].edit->selectAll();
        _registers[i].edit->setFocus();
        break;
      case WatchId: {
        bool isWatched = Register::list().isWatched(rtd);
        Debugger::manager->setRegisterWatched(rtd, !isWatched);
        break;
      }
    }
    break;
  }
}

void Pic::BankWidget::write()
{
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  const Pic::RegistersData &rdata = data.registersData();
  for (uint i=0; i<_registers.count(); i++) {
    if ( sender()!=_registers[i].edit ) continue;
    Register::TypeData rtd(_registers[i].address, rdata.nbChars());
    Debugger::manager->writeRegister(rtd, _registers[i].edit->value());
    break;
  }
}

void Pic::BankWidget::updateView()
{
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  const Pic::RegistersData &rdata = data.registersData();
  bool active = ( Main::programmerState()==Programmer::Halted );
  const Coff::Object *coff = Debugger::manager->coff();
  uint nb = nbRegisters();
  for (uint i=0; i<_registers.count(); i++) {
    uint address = _registers[i].address;
    Device::RegisterProperties rp = rdata.properties(address);
    TQString label = rdata.label(address);
    Register::TypeData rtd(address, rdata.nbChars());
    bool isWatched = Register::list().isWatched(rtd);
    if (coff) {
      TQString name = coff->variableName(address);
      if ( !name.isEmpty() ) label = "<" + name + ">";
    }
    if (_registers[i].button) {
      if ( i<nb ) {
	_registers[i].button->show();
	_registers[i].button->setText(label);
	if (isWatched) {
	  TQFont f = _registers[i].button->font();
	  f.setBold(true);
	  _registers[i].button->setFont(f);
	} else _registers[i].button->unsetFont();
	_registers[i].button->popup()->setItemEnabled(ReadId, active && (rp & Device::Readable));
	_registers[i].button->popup()->setItemEnabled(EditId, active);
	_registers[i].button->popup()->changeItem(WatchId, isWatched ? i18n("Stop Watching") : i18n("Watch"));
	_registers[i].button->popup()->setItemEnabled(WatchId, rp & Device::Readable);
      } else _registers[i].button->hide();
    }
    if (_registers[i].label) {
      if ( i<nb ) {
	_registers[i].label->show();
	_registers[i].label->setText(label);
      } else _registers[i].label->hide();
    }
    if (_registers[i].edit) {
      if ( i<nb ) {
	_registers[i].edit->show();
	_registers[i].edit->setEnabled(active);
	BitValue value = Register::list().value(rtd);
	if ( value!=Register::list().oldValue(rtd) ) _registers[i].edit->setColor(red);
	else _registers[i].edit->unsetColor();
	_registers[i].edit->setValue(NumberBase::Hex, value, rdata.nbChars());
      } else _registers[i].edit->hide();
    }
  }
}

//-----------------------------------------------------------------------------
Pic::RegisterView::RegisterView(TQWidget *parent)
  : Register::View(parent, "pic_register_view"),
    _readAllButton(0), _clearAllButton(0)
{
  TQVBoxLayout *vbox = new TQVBoxLayout(this, 10, 10);
  TQHBoxLayout *hbox = new TQHBoxLayout(vbox);

  bool debugging = Main::programmerGroup().isDebugger();
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  uint nb = data.registersData().nbBanks;
  if ( debugging && nb!=0 ) {
    TQWidget *w = new TQWidget(this);
    hbox->addWidget(w);
    TQGridLayout *grid = new TQGridLayout(w, 1, 1, 0, 10);
    _readAllButton = new TQPushButton(i18n("Read All"), w);
    connect(_readAllButton, TQT_SIGNAL(clicked()), Debugger::manager, TQT_SLOT(readAllRegisters()));
    grid->addWidget(_readAllButton, 0, 0);
    _clearAllButton = new TQPushButton(i18n("Clear all watching"), w);
    connect(_clearAllButton, TQT_SIGNAL(clicked()), TQT_SLOT(stopWatchAllRegisters()));
    grid->addWidget(_clearAllButton, 0, 1);
    grid->setColStretch(2, 1);
  }

  TQHBoxLayout *hbox2 = 0;
  if ( nb==0 ) {
    TQLabel *label = new TQLabel(i18n("Registers information not available."), this);
    vbox->addWidget(label);
  } else {
    hbox = new TQHBoxLayout(vbox);
    hbox2 = new TQHBoxLayout(hbox);
    hbox->addStretch(1);
    if ( data.is18Family() ) {
      nb = 2;
      for (uint k=1; k<data.registersData().nbBanks-1; k++) {
        if ( !data.registersData().isBankUsed(k) ) continue;
        nb += 2;
        break;
      }
    }
    _banks.resize(nb);
    for (uint i=0; i<nb; i++) {
      _banks[i] = new BankWidget(i, this);
      _banks[i]->show();
      hbox2->addWidget(_banks[i]);
    }
  }
  vbox->addStretch(1);
}

void Pic::RegisterView::updateView()
{
  if (_readAllButton) _readAllButton->setEnabled(Main::programmerState()==Programmer::Halted);
  for (uint i=0; i<_banks.count(); i++) if (_banks[i]) _banks[i]->updateView();
}

void Pic::RegisterView::stopWatchAllRegisters()
{
  Debugger::manager->stopWatchAll();
}

//----------------------------------------------------------------------------
Pic::RegisterListViewItem::RegisterListViewItem(const Register::TypeData &data, KListViewItem *parent)
  : Register::ListViewItem(data, parent)
{}

uint Pic::RegisterListViewItem::nbCharsAddress() const
{
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  return data.registersData().nbCharsAddress();
}

TQString Pic::RegisterListViewItem::label() const
{
  if ( _data.type()!=Register::Regular ) return _data.name();
  const Coff::Object *coff = Debugger::manager->coff();
  if (coff) {
    TQString name = coff->variableName(_data.address());
    if ( !name.isEmpty() ) return "<" + name + ">";
  }
  const Pic::Data &data = static_cast<const Pic::Data &>(*Main::deviceData());
  return data.registersData().label(_data.address());
}
