/***************************************************************************
 *   Copyright (C) 2006-2007 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 "disassembler.h"

#include <tqregexp.h>

#include "devices/base/device_group.h"
#include "devices/pic/pic/pic_memory.h"
#include "coff_object.h"

//-----------------------------------------------------------------------------
TQString SourceLine::comment(PURL::SourceFamily family, const TQString &text)
{
  switch (family.type()) {
  case PURL::SourceFamily::Asm:   return "; " + text;
  case PURL::SourceFamily::C:     return "/* " + text + " */";
  case PURL::SourceFamily::JAL:   return "-- " + text;
  case PURL::SourceFamily::Cpp:   return "// " + text;
  case PURL::SourceFamily::Basic: return "' " + text;
  case PURL::SourceFamily::Nb_Types: break;
  }
  Q_ASSERT(false);
  return TQString();
}

namespace SourceLine
{
class LineData {
public:
  LineData() : group(-1) {}
  TQString text, comment;
  int group;
};
} // namespace

TQStringList SourceLine::lines(PURL::SourceFamily family, const List &list, uint nbSpaces)
{
  TQValueList<LineData> lines;
  TQValueList<uint> groupCommentColumn;
  groupCommentColumn.append(0);
  List::const_iterator it;
  for (it=list.begin(); it!=list.end(); ++it) {
    LineData data;
    switch((*it).type) {
    case Indented:
      data.text = repeat(" ", nbSpaces);
    case NotIndented:
      if ( (*it).code.isEmpty() && !(*it).comment.isEmpty() ) data.text += comment(family, (*it).comment);
      else {
        data.text += (*it).code;
        data.comment = (*it).comment;
        data.group = groupCommentColumn.count() - 1;
        groupCommentColumn[data.group] = qMax(groupCommentColumn[data.group], uint(data.text.length()));
      }
      break;
    case Separator:
      data.text = comment(family, "-----------------------------------------------------------------------");
      groupCommentColumn.append(0);
      break;
    case Empty:
      break;
    case Title:
      data.text = comment(family, (*it).comment);
      break;
    }
    lines += data;
  }
  TQStringList slines;
  TQValueList<LineData>::const_iterator lit;
  for (lit=lines.begin(); lit!=lines.end(); ++lit) {
    if ( (*lit).group==-1 || (*lit).comment.isEmpty() ) slines += (*lit).text;
    else {
      uint col = groupCommentColumn[(*lit).group] + 1;
      slines += (*lit).text.leftJustify(col, ' ') + comment(family, (*lit).comment);
    }
  }
  return slines;
}

TQString SourceLine::text(PURL::SourceFamily family, const List &list, uint nbSpaces)
{
  return lines(family, list, nbSpaces).join("\n") + "\n";
}

TQString SourceLine::transformConfigName(const Pic::Data &data, uint wordIndex, const TQString &name)
{
  if ( !data.is18Family() ) return name;
  bool ok;
  (void)fromHexLabel(name, &ok);
  if (ok) return name;
  TQString s = name + '_';
  if ( data.name()=="18C601" || data.name()=="18C801" || data.name().startsWith("18F" ) )
    s += TQString::number(wordIndex/2+1) + (wordIndex%2==0 ? 'L' : 'H');
  else s += TQString::number(wordIndex);
  return s;
}

TQStringList SourceLine::ignoredConfigNames(const Pic::Data &data, uint wordIndex)
{
  TQStringList cnames;
  const TQStringList &names = data.config()._words[wordIndex].ignoredCNames;
  for (uint i=0; i<uint(names.count()); i++) cnames += transformConfigName(data, wordIndex, names[i]);
  return cnames;
}

TQStringList SourceLine::extraConfigNames(const Pic::Data &data, uint wordIndex, const Pic::Config::Value &value)
{
  TQStringList cnames;
  const TQStringList &names = value.configNames[Pic::ConfigNameType::Extra];
  for (uint i=0; i<uint(names.count()); i++) cnames += transformConfigName(data, wordIndex, names[i]);
  return cnames;
}

TQStringList SourceLine::configNames(Pic::ConfigNameType type, const Pic::Memory &memory, uint word, bool &ok)
{
  ok = true;
  const Pic::Data &data = memory.device();
  const Pic::Config &config = data.config();
  BitValue v = memory.normalizedWord(Pic::MemoryRangeType::Config, word);
  const Pic::Config::Word &cword = config._words[word];
  TQStringList cnames;
  for (uint k=0; k<uint(cword.masks.count()); k++) {
    const Pic::Config::Mask &cmask = cword.masks[k];
    if ( cmask.value.isInside(cword.pmask) ) continue; // protected bits
    for (int l=cmask.values.count()-1; l>=0; l--) {
      const Pic::Config::Value &cvalue = cmask.values[l];
      if ( !cvalue.value.isInside(v) ) continue;
      TQStringList vcnames = cvalue.configNames[type];
      if ( vcnames.isEmpty() && type!=Pic::ConfigNameType::Default ) vcnames = cvalue.configNames[Pic::ConfigNameType::Default];
      for (uint i=0; i<uint(vcnames.count()); i++) {
        if ( vcnames[i].isEmpty() ) ok = false;
        else cnames += transformConfigName(data, word, vcnames[i]);
      }
      break;
    }
  }
  return cnames;
}

//-----------------------------------------------------------------------------
TQString GPUtils::toDeviceName(const TQString &device)
{
  if ( device.startsWith("PS") ) return device.lower();
  return "p" + device.lower();
}

SourceLine::List GPUtils::includeLines(const Device::Data &data)
{
  SourceLine::List lines;
  TQString include = toDeviceName(data.name());
  if ( data.name()=="12CR509A" ) include = "p12c509a";
  else if ( TQRegExp("16CR?5.?[A-C]?").exactMatch(data.name()) ) include = "p16c5x";
  else if ( TQRegExp("16F5.?").exactMatch(data.name()) ) include = "p16f5x";
  else if ( data.name()=="16CR620A" ) include = "p16c620a";
  lines.appendIndentedCode("#include <" + include + ".inc>");
  return lines;
}

SourceLine::List GPUtils::generateConfigLines(const Pic::Memory &memory, bool &ok)
{
  SourceLine::List lines;
  const Pic::Data &data = memory.device();
  const Pic::Config &config = data.config();
  for (uint i=0; i<data.nbWords(Pic::MemoryRangeType::Config); i++) {
    const Pic::Config::Word &cword = config._words[i];
    TQStringList cnames = SourceLine::configNames(Pic::ConfigNameType::Default, memory, i, ok);
    if ( cnames.isEmpty() ) continue;
    TQString code = "__CONFIG ";
    if ( !cword.name.isEmpty() ) code += "_" + cword.name + ", ";
    code += cnames.join(" & ");
    lines.appendIndentedCode(code);
  }
  return lines;
}

SourceLine::List GPUtils::disassemble(const Pic::Memory &memory)
{
  SourceLine::List lines;
  const Pic::Data &data = memory.device();

  // includes
  lines += includeLines(data);
  lines.appendNotIndentedCode("processor " + toDeviceName(data.name()));

  // memory config
  bool isDefault = true;
  for (uint k=0; k<data.nbWords(Pic::MemoryRangeType::Config); k++) {
    BitValue op = memory.normalizedWord(Pic::MemoryRangeType::Config, k);
    BitValue mask = data.config()._words[k].usedMask();
    if ( !mask.isInside(op) ) isDefault = false; // this is not completely correct but otherwise empty config is written...
  }
  if ( !isDefault ) {
    lines.appendEmpty();
    lines.appendSeparator();
    bool ok;
    lines += generateConfigLines(memory, ok);
  }

  // user ids
  TQString tmp;
  for (uint k=0; k<data.nbWords(Pic::MemoryRangeType::UserId); k++) {
    BitValue op = memory.normalizedWord(Pic::MemoryRangeType::UserId, k);
    BitValue mask = data.userIdRecommendedMask();
    if ( mask.isInside(op) ) continue;
    if ( data.is18Family() ) {
      Address ad = data.range(Pic::MemoryRangeType::UserId).start + data.range(Pic::MemoryRangeType::UserId).hexFileOffset + k*data.addressIncrement(Pic::MemoryRangeType::UserId);
      lines.appendIndentedCode("__IDLOCS " + toHexLabel(ad, data.nbCharsAddress()) + ", " + toHexLabel(op, data.nbCharsWord(Pic::MemoryRangeType::UserId)));
    } else tmp += toHex(op.nybble(0), 1);
  }
  if ( !tmp.isEmpty() ) {
    lines.appendEmpty();
    lines.appendSeparator();
    lines.appendIndentedCode("__IDLOCS 0x" + tmp);
  }

  // memory code
  lines.appendEmpty();
  lines.appendSeparator();
  lines.appendTitle("code memory");
  bool first = true, newOrg = true;
  uint nb = data.nbWords(Pic::MemoryRangeType::Code);
  for (uint k=0; k<nb; k++) {
    BitValue op = memory.normalizedWord(Pic::MemoryRangeType::Code, k);
    BitValue mask = data.mask(Pic::MemoryRangeType::Code);
    if ( mask.isInside(op) ) newOrg = true;
    else {
      if (newOrg) {
        if ( !first ) tmp += '\n';
        first = false;
        Address org = data.range(Pic::MemoryRangeType::Code).start + data.range(Pic::MemoryRangeType::Code).hexFileOffset + k*data.addressIncrement(Pic::MemoryRangeType::Code);
        lines.appendNotIndentedCode("org " + toHexLabel(org, data.nbCharsAddress()));
        newOrg = false;
      }
      char buffer[512];
      buffer[0] = 0;
      BitValue op2 = ((k+1)<nb ? memory.word(Pic::MemoryRangeType::Code, k+1) : 0);
      uint n = Coff::disassemble(op.toUInt(), op2.toUInt(), k, data.architecture(), buffer, 512);
      lines.appendIndentedCode(TQString(buffer));
      if ( n==2 ) k++;
    }
  }

  // eeprom data
  lines.appendEmpty();
  lines.appendSeparator();
  lines.appendTitle("eeprom memory");
  newOrg = true;
  nb = data.nbWords(Pic::MemoryRangeType::Eeprom);
  for (uint k=0; k<nb; k++) {
    BitValue op = memory.normalizedWord(Pic::MemoryRangeType::Eeprom, k);
    BitValue mask = data.mask(Pic::MemoryRangeType::Eeprom);
    if ( mask.isInside(op) ) newOrg = true;
    else {
      if (newOrg) {
        Address org = data.range(Pic::MemoryRangeType::Eeprom).start + data.range(Pic::MemoryRangeType::Eeprom).hexFileOffset + k*data.addressIncrement(Pic::MemoryRangeType::Eeprom);
        lines.appendNotIndentedCode("org " + toHexLabel(org, data.nbCharsAddress()));
        newOrg = false;
      }
      lines.appendIndentedCode("de " + toHexLabel(op, data.nbCharsWord(Pic::MemoryRangeType::Eeprom)));
    }
  }

  lines.appendNotIndentedCode("end");
  return lines;
}

//-----------------------------------------------------------------------------
SourceLine::List Tool::SourceGenerator::templateSourceFile(PURL::ToolType type, const Device::Data &data, bool &ok) const
{
  SourceLine::List lines;
  lines.appendSeparator();
  lines.appendTitle(i18n("Template source file generated by piklab"));
  lines += includeLines(type, data);
  lines.appendEmpty();
  lines.appendSeparator();
  lines.appendTitle(i18n("Configuration bits: adapt to your setup and needs"));
  Device::Memory *memory = data.group().createMemory(data);
  lines += configLines(type, *memory, ok);
  delete memory;
  lines.appendEmpty();
  lines += sourceFileContent(type, data, ok);
  return lines;
}
