/***************************************************************************
 *   Copyright (C) 2005-2007 Nicolas Hadacek <hadacek@kde.org>             *
 *   Copyright (C) 2003-2004 Alain Gibaud <alain.gibaud@free.fr>           *
 *                                                                         *
 *   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_memory_editor.h"

#include <tqframe.h>
#include <tqgroupbox.h>
#include <tqlabel.h>
#include <tqscrollbar.h>
#include <tqgrid.h>
#include <tqhbox.h>
#include <tqtooltip.h>
#include <tqregexp.h>
#include <tqcolor.h>
#include <tqlayout.h>
#include <tqpixmap.h>

#include <tdelocale.h>
#include <kpushbutton.h>
#include <tdeaction.h>

#include "common/common/misc.h"
#include "pic_config_editor.h"
#include "libgui/toplevel.h"
#include "libgui/main_global.h"
#include "devices/pic/prog/pic_prog.h"
#include "libgui/global_config.h"
#include "pic_hex_view.h"

//-----------------------------------------------------------------------------
Pic::MemoryEditorLegend::Data::Data(const TQString &text, TQWidget *parent)
{
  button = new PopupButton(text, parent);
  TDEActionCollection *ac = 0;
  TDEAction *a = new TDEAction(i18n("Go to start"), "go-top", 0, parent, TQ_SLOT(gotoStart()), ac);
  actions.append(a);
  button->appendAction(a);
  a = new TDEAction(i18n("Go to end"), "go-bottom", 0, parent, TQ_SLOT(gotoEnd()), ac);
  actions.append(a);
  button->appendAction(a);
  label = new TQLabel(parent);
}

void Pic::MemoryEditorLegend::Data::setProtected(bool on)
{
  if (on) label->setPaletteBackgroundColor(MemoryEditorLegend::protectedColor());
  else label->unsetPalette();
}

bool Pic::MemoryEditorLegend::Data::hasAction(const TDEAction *action) const
{
  for (uint i=0; i<actions.count(); i++) if ( actions[i]==action ) return true;
  return false;
}

//-----------------------------------------------------------------------------
const char * const Pic::MemoryEditorLegend::BLOCK_COLORS[Protection::MAX_NB_BLOCKS] = {
  "#88FF88", "#88FFFF", "#FFFF88", "#FF88FF", "#0088FF", "#88FF00", "#00FF88", "#FF8800"
};

Pic::MemoryEditorLegend::MemoryEditorLegend(MemoryRangeType type, Memory &memory, TQWidget *parent)
  : MemoryEditor(type, memory, parent, "memory_displayer_legend")
{
  TQGridLayout *grid = new TQGridLayout(_top);

  TQWidget *w = new TQWidget(this);
  w->setFixedWidth(20);
  w->setPaletteBackgroundColor(protectedColor());
  grid->addWidget(w, 0, 0);
  const Protection &protection = device().config().protection();
  TQString s = (protection.family()==Protection::CodeGuard ? i18n("High Security") : i18n("Code protection"));
  TQLabel *label = new TQLabel(s, this);
  grid->addMultiCellWidget(label, 0,0, 1,2);
  grid->addRowSpacing(1, 10);
  uint row = 2;

  if ( type==MemoryRangeType::Code && protection.hasBootBlock() ) {
    w = new TQWidget(this);
    w->setFixedWidth(20);
    w->setPaletteBackgroundColor(bootColor());
    grid->addWidget(w, row, 0);
    _boot = Data(protection.bootLabel(), this);
    grid->addWidget(_boot.button, row, 1);
    grid->addWidget(_boot.label, row, 2);
    row++;
  }

  uint nb = (type==MemoryRangeType::Code ? protection.nbBlocks() : 0);
  for (uint i=0; i<nb; i++) {
    w = new TQWidget(this);
    w->setFixedWidth(20);
    w->setPaletteBackgroundColor(blockColor(i));
    grid->addWidget(w, row, 0);
    _blocks.append(Data(protection.blockLabel(i), this));
    grid->addWidget(_blocks[i].button, row, 1);
    grid->addWidget(_blocks[i].label, row, 2);
    row++;
  }
}

void Pic::MemoryEditorLegend::updateDisplay()
{
  const Protection &protection = device().config().protection();
  Protection::Type ptype = (protection.family()==Protection::CodeGuard ? Protection::HighSecurity : Protection::ProgramProtected);
  uint nbChars = 2 * device().nbBytesAddress();
  if (_boot.label) {
    AddressRange r = memory().bootRange();
    if ( r.isEmpty() ) _boot.label->setText(i18n("not present"));
    else _boot.label->setText(TQString("[%1:%2]").arg(toHex(r.start, nbChars)).arg(toHex(r.end, nbChars)));
    _boot.button->setEnabled(!r.isEmpty());
    _boot.setProtected(memory().isBootProtected(ptype));
  }
  for (uint i=0; i<_blocks.count(); i++) {
    AddressRange r = memory().blockRange(i);
    if ( r.isEmpty() ) _blocks[i].label->setText(i18n("not present"));
    else _blocks[i].label->setText(TQString("[%1:%2]").arg(toHex(r.start, nbChars)).arg(toHex(r.end, nbChars)));
    _blocks[i].button->setEnabled(!r.isEmpty());
    _blocks[i].setProtected(memory().isBlockProtected(ptype, i));
  }
}

void Pic::MemoryEditorLegend::gotoStart()
{
  Address start = device().range(type()).start;
  const TDEAction *action = static_cast<const TDEAction *>(sender());
  if ( _boot.hasAction(action) ) {
    AddressRange r = memory().bootRange();
    emit setStartWord(r.start - start);
    return;
  }
  for (uint i=0; i<_blocks.count(); i++) {
    if ( _blocks[i].hasAction(action) ) {
      AddressRange r = memory().blockRange(i);
      emit setStartWord(r.start - start);
      return;
    }
  }
  Q_ASSERT(false);
}

void Pic::MemoryEditorLegend::gotoEnd()
{
  Address start = device().range(type()).start;
  const TDEAction *action = static_cast<const TDEAction *>(sender());
  if ( _boot.hasAction(action) ) {
    AddressRange r = memory().bootRange();
    emit setEndWord(r.end - start);
    return;
  }
  for (uint i=0; i<_blocks.count(); i++) {
    if ( _blocks[i].hasAction(action) ) {
      AddressRange r = memory().blockRange(i);
      emit setEndWord(r.end - start);
      return;
    }
  }
  Q_ASSERT(false);
}


//-----------------------------------------------------------------------------
Pic::HexWordEditor::HexWordEditor(MemoryRangeType type, Memory &memory, TQWidget *parent)
  : Device::HexWordEditor(memory, memory.device().nbCharsWord(type), parent),
    MemoryCaster(type, memory)
{}

void Pic::HexWordEditor::setWord(BitValue value)
{
  if ( type()==MemoryRangeType::Config ) {
    const Config::Word &cword = device().config()._words[_offset];
    value |= cword.usedMask().complementInMask(device().mask(MemoryRangeType::Config));
  }
  memory().setWord(type(), _offset, value);
}

//-----------------------------------------------------------------------------
Pic::MemoryRangeEditor::MemoryRangeEditor(MemoryRangeType type, Memory &memory,
                                          uint nbLines, uint nbCols,
                                          uint wordOffset, int nbWords, TQWidget *parent)
  : Device::MemoryRangeEditor(memory, nbLines, nbCols, wordOffset, nbWords, parent, "pic_memory_range_editor"),
    MemoryCaster(type, memory), _legend(0)
{
  if ( type==MemoryRangeType::Code ) _blockRanges.resize(memory.device().config().protection().nbBlocks());
}

void Pic::MemoryRangeEditor::addLegend(TQVBoxLayout *vbox)
{
  if ( type()==MemoryRangeType::Code || type()==MemoryRangeType::Eeprom ) {
    _legend = new MemoryEditorLegend(type(), memory(), this);
    connect(_legend, TQ_SIGNAL(setStartWord(int)), TQ_SLOT(setStartWord(int)));
    connect(_legend, TQ_SIGNAL(setEndWord(int)), TQ_SLOT(setEndWord(int)));
    vbox->addWidget(_legend);
  }
}

bool Pic::MemoryRangeEditor::isRangeReadOnly() const
{
  return ( (type().data().properties & ReadOnly) || type()==MemoryRangeType::CalBackup );
}

void Pic::MemoryRangeEditor::updateDisplay()
{
  const Protection &protection = device().config().protection();
  if ( type()==MemoryRangeType::Code ) {
    if ( protection.hasBootBlock() ) _bootRange = memory().bootRange();
    for (uint k=0; k<_blockRanges.count(); k++)
      _blockRanges[k] = memory().blockRange(k);
  }
  Protection::Type ptype = (protection.family()==Protection::CodeGuard ? Protection::HighSecurity : Protection::ProgramProtected);
  _codeProtected = memory().protectedRanges(ptype, type());
  Device::MemoryRangeEditor::updateDisplay();
  if (_legend) _legend->updateDisplay();
}

void Pic::MemoryRangeEditor::updateAddressColor(uint i, Address address)
{
  if ( _codeProtected.contains(address) )
    _addresses[i]->setPaletteBackgroundColor(MemoryEditorLegend::protectedColor());
  else _addresses[i]->unsetPalette();
  _blocks[i]->unsetPalette();
  if ( type()==MemoryRangeType::Code ) {
    if ( _bootRange.contains(address) ) _blocks[i]->setPaletteBackgroundColor(MemoryEditorLegend::bootColor());
    else for (uint k=0; k<_blockRanges.count(); k++) {
      if ( !_blockRanges[k].contains(address) ) continue;
      _blocks[i]->setPaletteBackgroundColor(MemoryEditorLegend::blockColor(k));
      break;
    }
  }
}

Device::HexWordEditor *Pic::MemoryRangeEditor::createHexWordEditor(TQWidget *parent)
{
  return new HexWordEditor(type(), memory(), parent);
}

//-----------------------------------------------------------------------------
Pic::MemoryTypeEditor::MemoryTypeEditor(const HexView *hexview, MemoryRangeType type, Memory &memory, TQWidget *parent, const char *name)
  : Device::MemoryTypeEditor(hexview, memory, parent, name), MemoryCaster(type, memory)
{}

void Pic::MemoryTypeEditor::init(bool first)
{
  Device::MemoryTypeEditor::init(first);
  _title->setText(type().label());

  uint nbChars = device().nbCharsWord(type());
  TQString add;
  if ( type()==MemoryRangeType::UserId ) add = i18n(" - recommended mask: %1").arg(toHexLabel(device().userIdRecommendedMask(), nbChars));
  if ( type()==MemoryRangeType::Cal && _hexview ) add = i18n(" - not programmed by default");
  TQString comment = i18n("%1-bit words - mask: %2")
                   .arg(device().nbBitsWord(type())).arg(toHexLabel(device().mask(type()), nbChars));
  _comment->setText(comment + add);
}

bool Pic::MemoryTypeEditor::internalDoAction(Device::Action action)
{
  Programmer::PicBase *prog = static_cast<Programmer::PicBase *>(Main::programmer());
  switch (action) {
    case Device::Clear: memory().clear(type()); return true;
    case Device::Zero:  memory().fill(type(), 0);  return true;
    case Device::ChecksumCheck : memory().checksumCheckFill(); return true;
    case Device::Reload: {
      const Memory *omemory = static_cast<const Memory *>(originalMemory());
      Q_ASSERT(omemory);
      memory().copyFrom(type(), *omemory); return true;
    }
    case Device::Program:
      prog->programSingle(type(), memory());
      return false;
    case Device::Verify:
      prog->verifySingle(type(), memory());
      return false;
    case Device::Read:
      return prog->readSingle(type(), memory());
    case Device::Erase:
      prog->eraseSingle(type());
      return false;
    case Device::BlankCheck:
      prog->blankCheckSingle(type());
      return false;
    case Device::Nb_Actions: break;
  }
  Q_ASSERT(false);
  return false;
}

//-----------------------------------------------------------------------------
Pic::MemoryTypeRangeEditor::MemoryTypeRangeEditor(const HexView *hexview, MemoryRangeType type, Memory &memory, TQWidget *parent)
  : MemoryTypeEditor(hexview, type, memory, parent, "pic_memory_type_range_editor"), _mre(0)
{}

void Pic::MemoryTypeRangeEditor::init(bool first)
{
  MemoryTypeEditor::init(first);
  uint nbLines = 0;
  if ( type()==MemoryRangeType::Code ) nbLines = 16;
  else if ( type()==MemoryRangeType::Eeprom ) nbLines = 8;
  else nbLines = (device().nbWords(type())/8>1 ? 2 : 1);
  _mre = new MemoryRangeEditor(type(), memory(), nbLines, 8, 0, -1, this);
  addEditor(_mre);
  _top->addWidget(_mre);
  _mre->init();
}

//-----------------------------------------------------------------------------
Pic::MemoryUserIdEditor::MemoryUserIdEditor(const HexView *hexview, Memory &memory, TQWidget *parent)
  : MemoryTypeRangeEditor(hexview, MemoryRangeType::UserId, memory, parent), _saveReadOnly(false)
{}

void Pic::MemoryUserIdEditor::init(bool first)
{
  MemoryTypeRangeEditor::init(first);
  _setToChecksum = new TDEToggleAction(i18n("Set to unprotected checksum"), 0, 0,
    this, TQ_SLOT(toggleSetToChecksum()), Main::toplevel().actionCollection());
  addAction(_setToChecksum);
  if ( readConfigEntry(BaseGlobalConfig::UserIdSetToChecksum).toBool() && memory().isClear(MemoryRangeType::UserId) ) {
    _setToChecksum->activate();
    toggleSetToChecksum();
  }
}

void Pic::MemoryUserIdEditor::toggleSetToChecksum()
{
  if ( _setToChecksum->isChecked() ) {
    _mre->setComment(i18n("Set to unprotected checksum"));
    emit modified();
  } else _mre->setComment(TQString());
  setReadOnly(_saveReadOnly);
}

void Pic::MemoryUserIdEditor::updateDisplay()
{
  if ( _setToChecksum->isChecked() ) memory().setUserIdToUnprotectedChecksum();
  MemoryTypeRangeEditor::updateDisplay();
}

void Pic::MemoryUserIdEditor::setReadOnly(bool readOnly)
{
  _saveReadOnly = readOnly;
  MemoryTypeRangeEditor::setReadOnly(readOnly || _setToChecksum->isChecked());
}

//-----------------------------------------------------------------------------
Pic::MemoryCalibrationEditor::MemoryCalibrationEditor(const HexView *hexview, Memory &memory, TQWidget *parent)
  : MemoryTypeEditor(hexview, MemoryRangeType::Cal, memory, parent, "pic_memory_calibration_editor")
{}

void Pic::MemoryCalibrationEditor::init(bool first)
{
  MemoryTypeEditor::init(first);
  MemoryRangeEditor *mre = new MemoryRangeEditor(MemoryRangeType::Cal, memory(), 1, 8, 0, -1, this);
  addEditor(mre);
  _top->addWidget(mre);
  mre->init();
  if ( device().isReadable(MemoryRangeType::CalBackup) ) {
    mre = new MemoryRangeEditor(MemoryRangeType::CalBackup, memory(), 1, 8, 0, -1, this);
    addEditor(mre);
    _top->addWidget(mre);
    mre->init();
    mre->setComment(i18n("(backup)"));
  }
}

bool Pic::MemoryCalibrationEditor::hasAction(Device::Action action) const
{
  return ( action==Device::Read || action==Device::Verify || action==Device::Program );
}

bool Pic::MemoryCalibrationEditor::internalDoAction(Device::Action action)
{
  Programmer::PicBase *prog = static_cast<Programmer::PicBase *>(Main::programmer());
  switch (action) {
    case Device::Reload: {
      const Memory *omemory = static_cast<const Memory *>(originalMemory());
      Q_ASSERT(omemory);
      memory().copyFrom(MemoryRangeType::Cal, *omemory);
      memory().copyFrom(MemoryRangeType::CalBackup, *omemory);
      return true;
    }
    case Device::Program:
      if ( prog->programCalibration(memory().arrayForWriting(Pic::MemoryRangeType::Cal)) )
        return prog->readSingle(MemoryRangeType::Cal, memory());
      return false;
    case Device::Verify:
      prog->verifySingle(MemoryRangeType::Cal, memory());
      return false;
    case Device::Read:
      return prog->readSingle(MemoryRangeType::Cal, memory());
    case Device::Clear:
    case Device::Zero:
    case Device::ChecksumCheck:
    case Device::Erase:
    case Device::BlankCheck:
    case Device::Nb_Actions: break;
  }
  Q_ASSERT(false);
  return false;
}

#include "pic_memory_editor.moc"
