/***************************************************************************
 *   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 "text_coff.h"

#include "common/global/pfile.h"

namespace Coff
{
class CodeData {
public:
  TQString address, opcode, disasm1, disasm2;
};
class LineData {
public:
  TQValueVector<CodeData> codes;
  TQString lineNumber, lineText;
};
class FileData {
public:
  PURL::Url url;
  bool read;
  TQValueVector<LineData> lines;
};
}

Coff::TextObject::TextObject(const Device::Data *device, const PURL::Url &url)
  : Object(device, url), _initialized(true)
{}

bool Coff::TextObject::parse(Log::Base &log)
{
  bool ok = Object::parse(log);
  _initialized = !ok;
  return ok;
}

PURL::Url Coff::TextObject::urlForFilename(const TQString &filename) const
{
  PURL::Url rurl = PURL::Url::fromPathOrUrl(filename);
  return rurl.toAbsolute(url().directory());
}

const Coff::Section *Coff::TextObject::section(const CodeLine &cline) const
{
  if ( cline.section().instructions().contains(cline.address()) ) return &cline.section();
  // possible for coff generated by picc...
  for (uint i=0; i<uint(_sections.count()); i++) {
    if ( _sections[i]->type()!=SectionType::Code ) continue;
    if ( _sections[i]->instructions().contains(cline.address()) ) return _sections[i];
  }
  return 0;
}

void Coff::TextObject::init() const
{
  if (_initialized) return;
  _initialized = true;

  // open and read files
  TQMap<TQString, FileData> fd;
  for (uint i=0; i<uint(_sections.count()); i++) {
    if ( _sections[i]->type()!=SectionType::Code ) continue;
    for (uint k=0; k<uint(_sections[i]->lines().count()); k++) {
      TQString filename = _sections[i]->lines()[k]->filename();
      if ( filename.isEmpty() || fd.contains(filename) ) continue;
      _filenames.append(filename);
      FileData fdata;
      fdata.url = urlForFilename(filename);
      Log::StringView sview;
      PURL::File file(fdata.url, sview);
      fdata.read = file.openForRead();
      if (fdata.read) {
        TQStringList lines = file.readLines();
        fdata.lines.resize(lines.count());
        for (uint i=0; i<uint(lines.count()); i++)
          fdata.lines[i].lineText = lines[i];
      } else fdata.lines.resize(nbLines(filename));
      fd[filename] = fdata;
    }
  }

  // create strings (for later justification)
  const uint addressWidth = _device->nbCharsAddress();
  uint opcodeWidth = 0, disasm1Width = 0, disasm2Width = 0, lineNumberWidth = 0, lineTextWidth = 0;
  TQMap<TQString, FileData>::iterator it;
  for (it=fd.begin(); it!=fd.end(); ++it) {
    for (uint i=0; i<uint(it.data().lines.count()); i++) {
      LineData &ldata = it.data().lines[i];
      TQValueVector<const CodeLine *> lines = findCodeLines(it.key(), i);
      ldata.codes.resize(lines.count());
      for (uint k=0; k<uint(lines.count()); k++) {
        Address address = lines[k]->address();
        ldata.codes[k].address = toHex(address, addressWidth).upper();
        const Section *sec = section(*lines[k]);
        if (sec) {
          ldata.codes[k].opcode = "0x" + sec->instructions()[address].opcode.upper();
          //tqDebug("%s: %s", ldata.codes[k].address.latin1(), ldata.codes[k].opcode.latin1());
          opcodeWidth = qMax(opcodeWidth, uint(ldata.codes[k].opcode.length()));
          TQString s = sec->instructions()[address].disasm;
          int j = s.find('\t');
          if ( j!=-1 ) {
            ldata.codes[k].disasm2 = s.mid(j+1);
            disasm2Width = qMax(disasm2Width, uint(ldata.codes[k].disasm2.length()));
          }
          ldata.codes[k].disasm1 = (j==-1 ? s : s.mid(0, j));
          disasm1Width = qMax(disasm1Width, uint(ldata.codes[k].disasm1.length()));
        }
      }
      ldata.lineNumber = TQString::number(i+1);
      lineNumberWidth = qMax(lineNumberWidth, uint(ldata.lineNumber.length()));
      lineTextWidth = qMax(lineTextWidth, uint(ldata.lineText.length()));
    }
  }
  uint asmWidth = addressWidth + 4 + opcodeWidth + 4 + disasm1Width + 2 + disasm2Width;
  uint totalWidth = asmWidth + 4 + lineNumberWidth + 2 + lineTextWidth;

  // create text
  for (it = fd.begin(); it!=fd.end(); ++it) {
    TQString s = TQString("--- ") + it.data().url.pretty() + " ";
    _list +=  s.leftJustify(totalWidth, '-');
    if ( !it.data().read ) {
      s = TQString("--- ") + i18n("File could not be read") + " ";
      _list +=  s.leftJustify(totalWidth, '-');
    }
    for (uint i=0; i<uint(it.data().lines.count()); i++) {
      const LineData &ldata = it.data().lines[i];
      TQString cline = repeat(" ", 4) + ldata.lineNumber.leftJustify(lineNumberWidth) + ": " + ldata.lineText;
      if ( ldata.codes.count()==0 ) _list += stripEndingWhiteSpaces(repeat(" ", asmWidth) + cline);
      else for (uint k=0; k<uint(ldata.codes.count()); k++) {
        if ( ldata.codes[k].opcode.isEmpty() ) continue;
        TQString line;
        line += ldata.codes[k].address + repeat(" ", 4);
        line += ldata.codes[k].opcode.leftJustify(opcodeWidth) + repeat(" ", 4);
        line += ldata.codes[k].disasm1.leftJustify(disasm1Width) + repeat(" ", 2);
        line += ldata.codes[k].disasm2.leftJustify(disasm2Width);
        _lines[fromHex(ldata.codes[k].address, 0)] = _list.count()+1;
        if ( k==0 ) line += cline;
        _list += stripEndingWhiteSpaces(line);
      }
    }
  }
}

uint Coff::TextObject::nbLines(const TQString &filename) const
{
  init();
  uint nb = 0;
  for (uint i=0; i<uint(_sections.count()); i++) {
    if ( _sections[i]->type()!=SectionType::Code ) continue;
    for (uint k=0; k<uint(_sections[i]->lines().count()); k++) {
      const CodeLine *cl = _sections[i]->lines()[k];
      if ( cl->filename()==filename ) nb = qMax(nb, cl->line());
    }
  }
  return nb;
}

TQValueVector<const Coff::CodeLine *> Coff::TextObject::findCodeLines(const TQString &filename, uint line) const
{
  init();
  TQValueVector<const CodeLine *> list;
  for (uint i=0; i<uint(_sections.count()); i++) {
    if ( _sections[i]->type()!=SectionType::Code ) continue;
    for (uint k=0; k<uint(_sections[i]->lines().count()); k++) {
      const CodeLine *cl = _sections[i]->lines()[k];
      if ( (cl->line()-1)==line && cl->filename()==filename ) list.append(cl);
    }
  }
  return list;
}

int Coff::TextObject::lineForAddress(const PURL::Url &url, Address address) const
{
  init();
  if ( url==_url && _lines.contains(address) ) return _lines[address]-1;
  for (uint i=0; i<uint(_sections.count()); i++) {
    if ( _sections[i]->type()!=SectionType::Code ) continue;
    for (uint k=0; k<uint(_sections[i]->lines().count()); k++) {
      const CodeLine *cl = _sections[i]->lines()[k];
      if ( cl->address()!=address ) continue;
      TQString filename = cl->filename();
      if ( filename.isEmpty() || urlForFilename(filename)!=url ) continue;
      return cl->line()-1;
    }
  }
  return -1;
}

TQMap<PURL::Url, uint> Coff::TextObject::sourceLinesForAddress(Address address) const
{
  TQMap<PURL::Url, uint> slines;
  init();
  for (uint i=0; i<uint(_sections.count()); i++) {
    if ( _sections[i]->type()!=SectionType::Code ) continue;
    for (uint k=0; k<uint(_sections[i]->lines().count()); k++) {
      const CodeLine *cl = _sections[i]->lines()[k];
      if ( cl->address()!=address ) continue;
      TQString filename = cl->filename();
      if ( filename.isEmpty() ) continue;
      slines[urlForFilename(filename)] = cl->line()-1;
    }
  }
  if ( _lines.contains(address) ) slines[_url] = _lines[address] - 1;
  return slines;
}

TQValueVector<Address> Coff::TextObject::addresses(const PURL::Url &url, uint line) const
{
  init();
  TQValueVector<Address> ad;
  if ( url==_url ) {
    TQMap<Address, uint>::const_iterator it;
    for (it=_lines.begin(); it!=_lines.end(); ++it)
      if ( line==(it.data()-1) ) ad.append(it.key());
    return ad;
  }
  for (uint i=0; i<uint(_sections.count()); i++) {
    if ( _sections[i]->type()!=SectionType::Code ) continue;
    for (uint k=0; k<uint(_sections[i]->lines().count()); k++) {
      const CodeLine *cl = _sections[i]->lines()[k];
      if ( line!=(cl->line()-1) ) continue;
      TQString filename = cl->filename();
      if ( filename.isEmpty() || urlForFilename(filename)!=url ) continue;
      ad.append(cl->address());
    }
  }
  return ad;
}

const TQStringList &Coff::TextObject::filenames() const
{
  init();
  return _filenames;
}

TQString Coff::TextObject::disassembly() const
{
  init();
  if ( _list.isEmpty() ) return i18n("Parsing COFF file is not supported for this device or an error occured.");
  return _list.join("\n");
}

Log::KeyList Coff::TextObject::information() const
{
  Log::KeyList keys;
  keys.append(i18n("Format:"), i18n("%1 (magic id: %2)").arg(format().label()).arg(toHexLabel(format().data().magic, 4)));
  TQString name = (format()==Format::PIC30 || device()==0 ? "?" : device()->name());
  keys.append(i18n("Device:"), name);
  OptHeaderFormat ohf = optHeaderFormat();
  TQString label = (ohf==OptHeaderFormat::Nb_Types ? i18n("Unknown") : ohf.label());
  keys.append(i18n("Option header:"), i18n("%1 (magic id: %2)").arg(label).arg(toHexLabel(optHeaderMagic(), 4)));
  keys.append(i18n("No. of sections:"), TQString::number(nbSections()));
  keys.append(i18n("No. of symbols:"), TQString::number(nbSymbols()));
  keys.append(i18n("No. of variables:"), TQString::number(variables().count()));
  return keys;
}
