/***************************************************************************
    copyright            : (C) 2006 by Robby Stephenson
    email                : robby@periapsis.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License as  *
 *   published by the Free Software Foundation;                            *
 *                                                                         *
 ***************************************************************************/

#include "manager.h"
#include "newscript.h"
#include "../filehandler.h"
#include "../tellico_debug.h"
#include "../tellico_utils.h"
#include "../tellico_kernel.h"
#include "../fetch/fetch.h"

#include <kurl.h>
#include <ktar.h>
#include <kglobal.h>
#include <kio/netaccess.h>
#include <kconfig.h>
#include <ktempfile.h>
#include <kio/job.h>
#include <kfileitem.h>
#include <tdeversion.h>
#include <knewstuff/entry.h>
#include <kstandarddirs.h>

#include <tqfileinfo.h>
#include <tqdir.h>
#include <tqptrstack.h>
#include <tqvaluestack.h>
#include <tqwidget.h>

#include <sys/types.h>
#include <sys/stat.h>

using Tellico::NewStuff::Manager;

Manager::Manager(TQObject* parent_) : TQObject(parent_), m_tempFile(0) {
  m_infoList.setAutoDelete(true);
}

Manager::~Manager() {
  delete m_tempFile;
  m_tempFile = 0;
}

bool Manager::installTemplate(const KURL& url_, const TQString& entryName_) {
  FileHandler::FileRef* ref = FileHandler::fileRef(url_);

  TQString xslFile;
  TQStringList allFiles;

  bool success = true;

  // is there a better way to figure out if the url points to a XSL file or a tar archive
  // than just trying to open it?
  KTar archive(ref->fileName());
  if(archive.open(IO_ReadOnly)) {
    const KArchiveDirectory* archiveDir = archive.directory();
    archiveDir->copyTo(Tellico::saveLocation(TQString::fromLatin1("entry-templates/")));

    allFiles = archiveFiles(archiveDir);
    // remember files installed for template
    xslFile = findXSL(archiveDir);
  } else { // assume it's an xsl file
    TQString name = entryName_;
    if(name.isEmpty()) {
      name = url_.fileName();
    }
    if(!name.endsWith(TQString::fromLatin1(".xsl"))) {
      name += TQString::fromLatin1(".xsl");
    }
    KURL dest;
    dest.setPath(Tellico::saveLocation(TQString::fromLatin1("entry-templates/")) + name);
    success = true;
    if(TQFile::exists(dest.path())) {
      myDebug() << "Manager::installTemplate() - " << dest.path() << " exists!" << endl;
      success = false;
    } else if(KIO::NetAccess::file_copy(url_, dest)) {
      xslFile = dest.fileName();
      allFiles += xslFile;
    }
  }

  if(xslFile.isEmpty()) {
    success = false;
  } else {
    // remove ".xsl"
    xslFile.truncate(xslFile.length()-4);
    KConfigGroup config(KGlobal::config(), "KNewStuffFiles");
    config.writeEntry(xslFile, allFiles);
    m_urlNameMap.insert(url_, xslFile);
  }

  checkCommonFile();

  delete ref;
  return success;
}

bool Manager::removeTemplate(const TQString& name_) {
  KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles");
  TQStringList files = fileGroup.readListEntry(name_);
  // at least, delete xsl file
  if(files.isEmpty()) {
    kdWarning() << "Manager::deleteTemplate()  no file list found for " << name_ << endl;
    files += name_;
  }

  bool success = true;
  TQString path = Tellico::saveLocation(TQString::fromLatin1("entry-templates/"));
  for(TQStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
    if((*it).endsWith(TQChar('/'))) {
      // ok to not delete all directories
      TQDir().rmdir(path + *it);
    } else {
      success = success && TQFile(path + *it).remove();
      if(!success) {
        myDebug() << "Manager::removeTemplate() - failed to remove " << (path+*it) << endl;
      }
    }
  }

  // remove config entries even if unsuccessful
  fileGroup.deleteEntry(name_);
  KConfigGroup statusGroup(KGlobal::config(), "KNewStuffStatus");
  TQString entryName = statusGroup.readEntry(name_);
  statusGroup.deleteEntry(name_);
  statusGroup.deleteEntry(entryName);

  return success;
}

bool Manager::installScript(const KURL& url_) {
  FileHandler::FileRef* ref = FileHandler::fileRef(url_);

  KTar archive(ref->fileName());
  if(!archive.open(IO_ReadOnly)) {
    myDebug() << "Manager::installScript() - can't open tar file" << endl;
    return false;
  }

  const KArchiveDirectory* archiveDir = archive.directory();

  TQString exeFile = findEXE(archiveDir);
  if(exeFile.isEmpty()) {
    myDebug() << "Manager::installScript() - no exe file found" << endl;
    return false;
  }

  TQFileInfo exeInfo(exeFile);
  DataSourceInfo* info = new DataSourceInfo();

  TQString copyTarget = Tellico::saveLocation(TQString::fromLatin1("data-sources/"));
  TQString scriptFolder;

  // package could have a top-level directory or not
  // it should have a directory...
  const KArchiveEntry* firstEntry = archiveDir->entry(archiveDir->entries().first());
  if(firstEntry->isFile()) {
    copyTarget += exeInfo.baseName(true) + '/';
    if(TQFile::exists(copyTarget)) {
      KURL u;
      u.setPath(scriptFolder);
      myLog() << "Manager::installScript() - deleting " << scriptFolder << endl;
      KIO::NetAccess::del(u, Kernel::self()->widget());
      info->isUpdate = true;
    }
    scriptFolder = copyTarget;
  } else {
    scriptFolder = copyTarget + firstEntry->name() + '/';
    if(TQFile::exists(copyTarget + exeFile)) {
      info->isUpdate = true;
    }
  }

  // overwrites stuff there
  archiveDir->copyTo(copyTarget);

  info->specFile = scriptFolder + exeInfo.baseName(true) + ".spec";
  if(!TQFile::exists(info->specFile)) {
    myDebug() << "Manager::installScript() - no spec file for script! " << info->specFile << endl;
    delete info;
    return false;
  }
  info->sourceName = exeFile;
  info->sourceExec = copyTarget + exeFile;
  m_infoList.append(info);

  KURL dest;
  dest.setPath(info->sourceExec);
  KFileItem item(KFileItem::Unknown, KFileItem::Unknown, dest, true);
  ::chmod(TQFile::encodeName(dest.path()), item.permissions() | S_IXUSR);

  {
    KConfig spec(info->specFile, false, false);
    // update name
    info->sourceName = spec.readEntry("Name", info->sourceName);
    spec.writePathEntry("ExecPath", info->sourceExec);
    spec.writeEntry("NewStuffName", info->sourceName);
    spec.writeEntry("DeleteOnRemove", true);
  }

  {
    KConfigGroup config(KGlobal::config(), "KNewStuffFiles");
    config.writeEntry(info->sourceName, archiveFiles(archiveDir));
    m_urlNameMap.insert(url_, info->sourceName);
  }


//  myDebug() << "Manager::installScript() - exeFile = " << exeFile << endl;
//  myDebug() << "Manager::installScript() - sourceExec = " << info->sourceExec << endl;
//  myDebug() << "Manager::installScript() - sourceName = " << info->sourceName << endl;
//  myDebug() << "Manager::installScript() - specFile = " << info->specFile << endl;

  delete ref;
  return true;
}

bool Manager::removeScript(const TQString& name_) {
  KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles");
  TQStringList files = fileGroup.readListEntry(name_);

  bool success = true;
  TQString path = Tellico::saveLocation(TQString::fromLatin1("data-sources/"));
  for(TQStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
    if((*it).endsWith(TQChar('/'))) {
      // ok to not delete all directories
      TQDir().rmdir(path + *it);
    } else {
      success = success && TQFile(path + *it).remove();
      if(!success) {
        myDebug() << "Manager::removeScript() - failed to remove " << (path+*it) << endl;
      }
    }
  }

  // remove config entries even if unsuccessful
  fileGroup.deleteEntry(name_);
  KConfigGroup statusGroup(KGlobal::config(), "KNewStuffStatus");
  TQString entryName = statusGroup.readEntry(name_);
  statusGroup.deleteEntry(name_);
  statusGroup.deleteEntry(entryName);

  return success;
}

Tellico::NewStuff::InstallStatus Manager::installStatus(KNS::Entry* entry_) {
  KConfigGroup config(KGlobal::config(), "KNewStuffStatus");
  TQString datestring = config.readEntry(entry_->name());
  if(datestring.isEmpty()) {
    return NotInstalled;
  }

  TQDate date = TQDate::fromString(datestring, Qt::ISODate);
  if(!date.isValid()) {
    return NotInstalled;
  }
  if(date < entry_->releaseDate()) {
    return OldVersion;
  }

  // also check that executable files exists
  KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles");
  TQStringList files = fileGroup.readListEntry(entry_->name());
  TQString path = Tellico::saveLocation(TQString::fromLatin1("data-sources/"));
  for(TQStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
    if(!TQFile::exists(path + *it)) {
      return NotInstalled;
    }
  }
  return Current;
}

TQStringList Manager::archiveFiles(const KArchiveDirectory* dir_, const TQString& path_) {
  TQStringList list;

  const TQStringList dirEntries = dir_->entries();
  for(TQStringList::ConstIterator it = dirEntries.begin(); it != dirEntries.end(); ++it) {
    const TQString& entry = *it;
    const KArchiveEntry* curEntry = dir_->entry(entry);
    if(curEntry->isFile()) {
      list += path_ + entry;
    } else if(curEntry->isDirectory()) {
      list += archiveFiles(static_cast<const KArchiveDirectory*>(curEntry), path_ + entry + '/');
      // add directory AFTER contents, since we delete from the top down later
      list += path_ + entry + '/';
    }
  }

  return list;
}

// don't recurse, the .xsl must be in top directory
TQString Manager::findXSL(const KArchiveDirectory* dir_) {
  const TQStringList entries = dir_->entries();
  for(TQStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
    const TQString& entry = *it;
    if(entry.endsWith(TQString::fromLatin1(".xsl"))) {
      return entry;
    }
  }
  return TQString();
}

TQString Manager::findEXE(const KArchiveDirectory* dir_) {
  TQPtrStack<KArchiveDirectory> dirStack;
  TQValueStack<TQString> dirNameStack;

  dirStack.push(dir_);
  dirNameStack.push(TQString());

  do {
    const TQString dirName = dirNameStack.pop();
    const KArchiveDirectory* curDir = dirStack.pop();
    const TQStringList entries = curDir->entries();
    for(TQStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
      const TQString& entry = *it;
      const KArchiveEntry* archEntry = curDir->entry(entry);

      if(archEntry->isFile() && (archEntry->permissions() & S_IEXEC)) {
        return dirName + entry;
      } else if(archEntry->isDirectory()) {
        dirStack.push(static_cast<const KArchiveDirectory*>(archEntry));
        dirNameStack.push(dirName + entry + '/');
      }
    }
  } while(!dirStack.isEmpty());

  return TQString();
}

void Manager::install(DataType type_, KNS::Entry* entry_) {
  if(m_tempFile) {
    delete m_tempFile;
  }
  m_tempFile = new KTempFile();
  m_tempFile->setAutoDelete(true);

  KURL destination;
  destination.setPath(m_tempFile->name());
  KIO::FileCopyJob* job = KIO::file_copy(entry_->payload(), destination, -1, true);
  connect(job, TQT_SIGNAL(result(KIO::Job*)), TQT_SLOT(slotDownloadJobResult(KIO::Job*)));
  m_jobMap.insert(job, EntryPair(entry_, type_));
}

void Manager::slotDownloadJobResult(KIO::Job* job_) {
  KIO::FileCopyJob* job = static_cast<KIO::FileCopyJob*>(job_);
  if(job->error()) {
    GUI::CursorSaver cs(TQt::arrowCursor);
    delete m_tempFile;
    m_tempFile = 0;
    job->showErrorDialog(Kernel::self()->widget());
    emit signalInstalled(0); // still need to notify dialog that install failed
    return;
  }

  KNS::Entry* entry = m_jobMap[job_].first;
  DataType type = m_jobMap[job_].second;

  bool deleteTempFile = true;
  if(type == EntryTemplate) {
    installTemplate(job->destURL(), entry->name());
  } else {
#if KDE_IS_VERSION(3,3,90)
    // needed so the GPG signature can be checked
    NewScript* newScript = new NewScript(this, Kernel::self()->widget());
    connect(newScript, TQT_SIGNAL(installFinished()), this, TQT_SLOT(slotInstallFinished()));
    // need to delete temp file if install was not a success
    // if it was a success, it gets deleted later
    deleteTempFile = !newScript->install(job->destURL().path());
    m_scriptEntryMap.insert(newScript, entry);
#endif
    // if failed, emit empty signal now
    if(deleteTempFile) {
      emit signalInstalled(0);
    }
  }
  if(deleteTempFile) {
    delete m_tempFile;
    m_tempFile = 0;
  }
}

void Manager::slotInstallFinished() {
  const NewScript* newScript = ::tqqt_cast<const NewScript*>(sender());
  if(newScript && newScript->successfulInstall()) {
    const TQString name = m_urlNameMap[newScript->url()];
    KNS::Entry* entry = m_scriptEntryMap[newScript];
    KConfigGroup config(KGlobal::config(), "KNewStuffStatus");
    // have to keep a config entry that maps the name of the file to the name
    // of the newstuff entry, so we can track which entries are installed
    // name and entry-name() are probably the same for scripts, but not a problem
    config.writeEntry(name, entry->name());
    config.writeEntry(entry->name(), TQString(entry->releaseDate().toString(Qt::ISODate)));
    config.sync();
    emit signalInstalled(entry);
  } else {
    emit signalInstalled(0);
    kdWarning() << "Manager::slotInstallFinished() - Failed to install" << endl;
  }
  delete m_tempFile;
  m_tempFile = 0;
}

bool Manager::checkCommonFile() {
  // look for a file that gets installed to know the installation directory
  // need to check timestamps
  TQString userDataDir = Tellico::saveLocation(TQString());
  TQString userCommonFile = userDataDir + '/' + TQString::fromLatin1("tellico-common.xsl");
  if(TQFile::exists(userCommonFile)) {
    // check timestamps
    // pics/tellico.png is not likely to be in a user directory
    TQString installDir = KGlobal::dirs()->findResourceDir("appdata", TQString::fromLatin1("pics/tellico.png"));
    TQString installCommonFile = installDir + '/' + TQString::fromLatin1("tellico-common.xsl");
#ifndef NDEBUG
    if(userCommonFile == installCommonFile) {
      kdWarning() << "Manager::checkCommonFile() - install location is same as user location" << endl;
    }
#endif
    TQFileInfo installInfo(installCommonFile);
    TQFileInfo userInfo(userCommonFile);
    if(installInfo.lastModified() > userInfo.lastModified()) {
      // the installed file has been modified more recently than the user's
      // remove user's tellico-common.xsl file so it gets copied again
      myLog() << "Manager::checkCommonFile() - removing " << userCommonFile << endl;
      myLog() << "Manager::checkCommonFile() - copying  " << installCommonFile << endl;
      TQFile::remove(userCommonFile);
    } else {
      return true;
    }
  }
  KURL src, dest;
  src.setPath(KGlobal::dirs()->findResource("appdata", TQString::fromLatin1("tellico-common.xsl")));
  dest.setPath(userCommonFile);
  return KIO::NetAccess::file_copy(src, dest);
}

#include "manager.moc"
