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

#include <tqtimer.h>
#include <tqregexp.h>

#include "devices/list/device_list.h"
#include "common/global/process.h"
#include "common/gui/misc_gui.h"
#include "libgui/text_editor.h"
#include "libgui/editor_manager.h"
#include "libgui/project.h"
#include "compile_config.h"
#include "compile_manager.h"

//-----------------------------------------------------------------------------
const Compile::ArgumentData Compile::ARGUMENT_DATA[] = {
  { "$(SRCPATH)",  I18N_NOOP("Replaced by the source directory.") },
  { "$LKR(xxx)",   I18N_NOOP("Replaced by \"xxx\" when there is a custom linker script.") },
  { "$WINE(xxx)",  I18N_NOOP("Replaced by \"xxx\" when \"wine\" is used.") },
  { "$NO_AUTO_DEVICE(xxx)", I18N_NOOP("Replaced by \"xxx\" when the device name is specified.") },
  { "%OBJS",       I18N_NOOP("Replaced by the list of additionnal objects.") },
  { "%LIBS",       I18N_NOOP("Replaced by the list of additionnal libraries.") },
  { "%O",          I18N_NOOP("Replaced by the output filename.") },
  { "%PROJECT",    I18N_NOOP("Replaced by the project name.") },
  { "%COFF",       I18N_NOOP("Replaced by the COFF filename.") },
  { "%MAP",        I18N_NOOP("Replaced by the map filename.") },
  { "%SYM",        I18N_NOOP("Replaced by the symbol filename.") },
  { "%LIST",       I18N_NOOP("Replaced by the list filename.") },
  { "%DEVICE",     I18N_NOOP("Replaced by the device name.") },
  { "%FAMILY",     I18N_NOOP("Replaced by the device family name (when needed).") },
  { "%I",          I18N_NOOP("Replaced by the relative input filepath(s).") },
  { "%OBJECT",     I18N_NOOP("Replaced by the object file name.") },
  { "%LKR_PATH",   I18N_NOOP("Replaced by the linker script path.") },
  { "%LKR_NAME",   I18N_NOOP("Replaced by the linker script basename.") },
  { "%LKR",        I18N_NOOP("Replaced by the linker script filename.") },
  { "%SEP",        I18N_NOOP("Replaced by a separation into two arguments.") },
  { 0, 0 }
};

//-----------------------------------------------------------------------------
Compile::FileData::List Compile::FileData::List::onlyExistingFiles() const
{
  List list;
  List::const_iterator it;
  for (it=begin(); it!=end(); ++it) {
    FileData data = *it;
    if ( PURL::findExistingUrl(data.url) ) list.append(data);
  }
  return list;
}

void Compile::FileData::List::cleanGenerated() const
{
  Log::StringView sview;
  List::const_iterator it;
  for (it=begin(); it!=end(); ++it)
    if ( (*it).actions & Generated ) (*it).url.del(sview);
}

//-----------------------------------------------------------------------------
Compile::LogWidget::LogWidget(TQWidget *parent)
  : Log::Widget(parent, "compile_log")
{
  connect(this, TQT_SIGNAL(clicked(int, int)), TQT_SLOT(lineClicked(int)));
}

void Compile::LogWidget::clear()
{
  Log::Widget::clear();
  _map.clear();
}

void Compile::LogWidget::appendLine(Log::LineType type, const TQString &message, const TQString &filepath, uint line)
{
  log(type, message, Log::Delayed);
  if ( !filepath.isEmpty() ) _map[paragraphs()-1] = Data(filepath, line);
}

void Compile::LogWidget::appendLine(Log::DebugLevel level, const TQString &message, const TQString &filepath, uint line)
{
  log(level, message, Log::Delayed);
  if ( !filepath.isEmpty() ) _map[paragraphs()-1] = Data(filepath, line);
}

void Compile::LogWidget::lineClicked(int line)
{
  if ( !_map.contains(line) ) return;
  PURL::Url url = PURL::Url::fromPathOrUrl(_map[line].filepath);
  TextEditor *e = ::tqqt_cast<TextEditor *>(Main::editorManager().openEditor(url));
  if ( e==0 ) return;
  e->setCursor(_map[line].line, 0);
}

//-----------------------------------------------------------------------------
Compile::BaseProcess::BaseProcess()
  : TQObject(0, "compile_process"), _manager(0), _process(0)
{}

void Compile::BaseProcess::init(const Data &data, Manager *manager)
{
  _data = data;
  _manager = manager;
}

PURL::Directory Compile::BaseProcess::directory(uint i) const
{
  if (_data.project) return _data.project->directory();
  Q_ASSERT( i<_data.items.count() );
  return _data.items[i].url.directory();
}

bool Compile::BaseProcess::start()
{
  _stdout = TQString();
  _stderr = TQString();
  delete _process;
  _process = new ::Process::LineSignal;
  connect(_process, TQT_SIGNAL(done(int)), TQT_SLOT(done(int)));
  connect(_process, TQT_SIGNAL(timeout()), TQT_SLOT(timeout()));
  connect(_process, TQT_SIGNAL(logStdoutLine(const TQString &)), TQT_SLOT(logStdoutLine(const TQString &)));
  connect(_process, TQT_SIGNAL(logStderrLine(const TQString &)), TQT_SLOT(logStderrLine(const TQString &)));
  _process->setWorkingDirectory(directory().path());
  setupProcess();
  _manager->log(Log::LineType::Command, _process->arguments().join(" "));
  return _process->start(0); // no timeout
}

void Compile::BaseProcess::done(int code)
{
  if ( code!=0 ) {
    _manager->log(Log::LineType::Error, i18n("*** Exited with status: %1 ***").arg(code));
    _manager->processFailed();
  } else if ( _manager->hasError() ) {
    _manager->log(Log::LineType::Error, i18n("*** Error ***"));
    _manager->processFailed();
  } else _manager->processDone();
}

void Compile::BaseProcess::timeout()
{
  _manager->log(Log::LineType::Error, i18n("*** Timeout ***"));
  _manager->processFailed();
}

//-----------------------------------------------------------------------------
void Compile::Process::init(const Data &data, Manager *manager)
{
  BaseProcess::init(data, manager);
  _config = group().createConfig(const_cast<Project *>(data.project));
}

Compile::Process::~Process()
{
  delete _config;
}

bool Compile::Process::check() const
{
  return group().check(_data.device, _manager);
}

PURL::Url Compile::Process::url(PURL::FileType type, uint i) const
{
  PURL::Url url;
  if ( _data.project && (type==PURL::Hex || _data.category==Tool::Category::Linker || _data.category==Tool::Category::BinToHex) )
    url = _data.project->url();
  else if ( _data.project && (type==PURL::Library || _data.category==Tool::Category::Librarian) )
    return _data.project->url().toExtension(libraryExtension());
  else {
    Q_ASSERT( i<_data.items.count() );
    url = _data.items[i].url;
  }
  if ( type==PURL::Nb_FileTypes ) return url;
  return url.toFileType(type);
}

TQString Compile::Process::filepath(PURL::FileType type, uint i) const
{
  return url(type, i).relativeTo(directory(), Compile::Config::withWine(group()) ? PURL::WindowsSeparator : PURL::UnixSeparator);
}

TQString Compile::Process::outputFilepath() const
{
  if ( _data.category==Tool::Category::Librarian ) return filepath(PURL::Library);
  return filepath(PURL::Hex);
}

Compile::FileData Compile::Process::fileData(PURL::FileType type, FileActions actions) const
{
  return FileData(url(type, nbFiles()-1), actions);
}

Compile::FileData::List Compile::Process::files(bool *ok) const
{
  if (ok) *ok = true;
  FileData::List list;
  TQRegExp rexp("PURL::(.*)");
  TQStringList files = TQStringList::split(" ", outputFiles());
  for (uint i=0; i<files.count(); i++) {
    if ( rexp.exactMatch(files[i]) ) {
      PURL::FileType type = PURL::FileType::fromKey(rexp.cap(1));
      if ( type==PURL::Nb_FileTypes ) {
        if (ok) *ok = false;
        qWarning("Unknown PURL::FileType in file list for %s", _manager->label().latin1());
        continue;
      }
      if ( type.data().group==PURL::LinkerScript ) {
        PURL::Url lkr = Main::toolGroup().linkerScript(_data.project, _data.linkType);
        list += FileData(lkr, Included | InProject);
      } else {
        FileActions actions = Generated;
        if ( type.data().group==PURL::Source || type==PURL::Hex
             || type==PURL::Map || type==PURL::Coff || type==PURL::Library ) actions |= InProject;
        if ( type==PURL::Hex && _data.project==0 ) actions |= Show;
        list += fileData(type, actions);
      }
    } else list += FileData(url().toExtension(files[i]), Compile::Generated);
  }
  return list;
}

bool Compile::Process::checkIs(const TQString &s, const TQString &key)
{
  if ( !s.contains(key) ) return false;
  if ( s!=key ) qWarning("Argument should be only %s, the rest will be ignored...", key.latin1());
  return true;
}

TQString Compile::Process::replaceIf(const TQString &s, const TQString &key, bool condition)
{
  TQRegExp rexp("(.*)\\$" + key + "\\(([^)]*)\\)(.*)");
  if ( !rexp.exactMatch(s) ) return s;
  return rexp.cap(1) + (condition ? rexp.cap(2) : TQString()) + rexp.cap(3);
}

TQStringList Compile::Process::arguments() const
{
  bool custom = _config->hasCustomArguments(_data.category);
  bool withWine = Compile::Config::withWine(group());
  TQStringList args = (custom ? _config->customArguments(_data.category) : genericArguments(*_config));
  PURL::Url lkr = Main::toolGroup().linkerScript(_data.project, _data.linkType);
  TQStringList nargs;
  for (uint i=0; i<args.count(); i++) {
    if ( args[i].contains("$(SRCPATH)") ) {
      args[i].replace("$(SRCPATH)", directory().path());
      args[i].replace("//", "/");
    }
    args[i] = replaceIf(args[i], "WINE", withWine);
    args[i] = replaceIf(args[i], "LKR", hasLinkerScript());
    args[i] = replaceIf(args[i], "NO_AUTO_DEVICE", _data.device!=Device::AUTO_DATA.name);
    if ( checkIs(args[i], "%OBJS") ) { // before %O
      if (_data.project) nargs += _data.project->objectsForLinker(objectExtension(), withWine);
      else {
        PURL::Url tmp = url(PURL::Object);
        if ( !objectExtension().isEmpty() ) tmp = tmp.toExtension(objectExtension());
        nargs += tmp.relativeTo(directory(), withWine ? PURL::WindowsSeparator : PURL::UnixSeparator);
      }
      continue;
    }
    if ( checkIs(args[i], "%LIBS") ) {
      if (_data.project) nargs += _data.project->librariesForLinker(TQString(), withWine);
      continue;
    }
    args[i].replace("%OBJECT", filepath(PURL::Object)); // before %O
    args[i].replace("%O", outputFilepath());
    args[i].replace("%COFF", filepath(PURL::Coff));
    if (_data.project) args[i].replace("%PROJECT", _data.project->name());
    else  args[i].replace("%PROJECT", url().basename());
    args[i].replace("%MAP", filepath(PURL::Map));
    args[i].replace("%SYM", url().toExtension("sym").relativeTo(directory(), withWine ? PURL::WindowsSeparator : PURL::UnixSeparator));
    args[i].replace("%LIST", filepath(PURL::Lst));
    args[i].replace("%DEVICE", deviceName());
    args[i].replace("%FAMILY", familyName());
    args[i].replace("%LKR_PATH", lkr.path()); // before %LKR
    args[i].replace("%LKR_NAME", lkr.filename()); // before LKR
    args[i].replace("%LKR", lkr.filepath());
    if ( checkIs(args[i], "%I") ) {
      for (uint k=0; k<nbFiles(); k++) nargs += inputFilepath(k);
      continue;
    }
    if ( !args[i].isEmpty() ) nargs += args[i];
  }
  args.clear();
  for (uint i=0; i<nargs.count(); i++) args += TQStringList::split("%SEP", nargs[i]);
  return args;
}

void Compile::Process::setupProcess()
{
  bool withWine = Compile::Config::withWine(group());
  TQString exec = tool()->baseExecutable(withWine, Compile::Config::outputExecutableType(group()));
  TQString path = tool()->executableDirectory().path();
  _process->setup(path + exec, arguments(), withWine);
}

Log::LineType Compile::Process::filterType(const TQString &type) const
{
  TQString s = type.lower();
  if ( s.startsWith("warning") ) return Log::LineType::Warning;
  if ( s.startsWith("error") )   return Log::LineType::Error;
  if ( s.startsWith("message") ) return Log::LineType::Information;
  return Log::LineType::Normal;
}

bool Compile::Process::parseErrorLine(const TQString &s, const ParseErrorData &data)
{
  TQRegExp re(data.pattern);
  if ( !re.exactMatch(s) ) return false;
  TQString file;
  if ( data.indexFile>=0 ) {
    file = re.cap(data.indexFile).stripWhiteSpace();
    if ( file.endsWith(".") ) file = file.mid(0, file.length()-1);
    if ( file=="-" ) file = TQString();
  }
  bool ok;
  int line = -1;
  if ( data.indexLine>=0 ) line = re.cap(data.indexLine).stripWhiteSpace().toUInt(&ok) - 1;
  if ( !ok ) line = -1;
  TQString message;
  if ( data.indexMessage>=0 ) message= re.cap(data.indexMessage).stripWhiteSpace();
  Log::LineType type = data.defaultLineType;
  if ( data.indexLogType>=0 ) {
    TQString s = re.cap(data.indexLogType).stripWhiteSpace();
    if ( s.isEmpty() ) type = data.defaultLineType;
    else type = filterType(s);
  }
  doLog(type, message, file, line);
  return true;
}

void Compile::Process::doLog(const TQString &type, const TQString &message, const TQString &surl, uint line)
{
  doLog(filterType(type), message, surl, line);
}

void Compile::Process::doLog(Log::LineType type, const TQString &message, const TQString &surl, uint line)
{
  if ( surl.isEmpty() ) {
    _manager->log(type, message);
    return;
  }
  PURL::Url url = PURL::Url::fromPathOrUrl(surl);
  TQString s;
  if ( !url.isEmpty() ) {
    if ( !url.exists() && !url.isInto(directory()) ) url = PURL::Url(directory(), surl);
    s += url.filename() + ":" + TQString::number(line+1) + ": ";
  }
  switch (type.type()) {
    case Log::LineType::Warning:     s += i18n("warning: "); break;
    case Log::LineType::Error:       s += i18n("error: ");   break;
    case Log::LineType::Information: s += i18n("message: "); break;
    default: break;
  }
  _manager->log(type, s + message.stripWhiteSpace(), url.filepath(), line);
}

//-----------------------------------------------------------------------------
void Compile::CustomProcess::setupProcess()
{
  _process->setUseShell(true);
  _process->setup(_command, TQStringList(), false);
}

void Compile::CustomProcess::logStderrLine(const TQString &line)
{
  _manager->log(Log::LineType::Normal, line);
}
