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

    Copyright (c) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net>

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "dialog.h"
#include "../gui/listview.h"
#include "../latin1literal.h"
#include "../tellico_utils.h"
#include "../tellico_debug.h"

#include <tdelocale.h>
#include <kpushbutton.h>
#include <tdeconfig.h>
#include <kiconloader.h>
#include <kstatusbar.h>
#include <tdeio/job.h>
#include <tdeio/netaccess.h>
#include <tdeaccelmanager.h>
#include <tdenewstuff/entry.h>
#include <tdenewstuff/provider.h>
#include <tdetempfile.h>

#include <tqlabel.h>
#include <tqtextedit.h>
#include <tqlayout.h>
#include <tqwhatsthis.h>
#include <tqregexp.h>
#include <tqvbox.h>
#include <tqimage.h>
#include <tqtimer.h>
#include <tqprogressbar.h>

#define OPENDESKTOP_REDIRECT_URL "opendesktop.org/content/download.php?content="
#define OPENDESKTOP_REDIRECT_TEXT "If the download does not start in 3 seconds:</span><span class=\"defaulttext\">&nbsp;<a href=\""

namespace {
  static const int NEW_STUFF_MIN_WIDTH = 600;
  static const int NEW_STUFF_MIN_HEIGHT = 400;
  static const int PROGRESS_STATUS_ID = 0;
}

using Tellico::NewStuff::Dialog;

class Dialog::Item : public GUI::ListViewItem {
public:
  Item(GUI::ListView* parent) : GUI::ListViewItem(parent) {}

  InstallStatus status() const { return m_status; }
  void setStatus(InstallStatus status) {
    m_status = status;
    if(m_status  == Current) {
      setPixmap(0, SmallIcon(TQString::fromLatin1("ok")));
    } else if(m_status == OldVersion) {
      setPixmap(0, SmallIcon(TQString::fromLatin1("reload")));
    }
  }

  TQString key(int col, bool asc) const {
    if(col == 2 || col == 3) {
      TQString s;
      s.sprintf("%08d", text(col).toInt());
      return s;
    } else if(col == 4) {
      TQString s;
      TQDate date = TDEGlobal::locale()->readDate(text(col));
      s.sprintf("%08d", date.year() * 366 + date.dayOfYear());
      return s;
    }
    return GUI::ListViewItem::key(col, asc);
  }

private:
  InstallStatus m_status;
};

Dialog::Dialog(NewStuff::DataType type_, TQWidget* parent_)
    : KDialogBase(KDialogBase::Plain, i18n("Get Hot New Stuff"), 0, (KDialogBase::ButtonCode)0, parent_)
    , m_manager(new Manager(this))
    , m_type(type_)
    , m_timer(new TQTimer(this))
    , m_cursorSaver(new GUI::CursorSaver())
    , m_tempPreviewImage(0)
    , m_lastPreviewItem(0) {

  m_lang = TDEGlobal::locale()->language();

  TQFrame* frame = plainPage();
  TQBoxLayout* boxLayout = new TQVBoxLayout(frame, 0, KDialog::spacingHint());

  m_split = new TQSplitter(TQt::Vertical, frame);
  boxLayout->addWidget(m_split);

  m_listView = new GUI::ListView(m_split);
  m_listView->setAllColumnsShowFocus(true);
  m_listView->setSelectionMode(TQListView::Single);
  m_listView->addColumn(i18n("Name"));
  m_listView->addColumn(i18n("Version"));
  m_listView->addColumn(i18n("Rating"));
  m_listView->addColumn(i18n("Downloads"));
  m_listView->addColumn(i18n("Release Date"));
  m_listView->setSorting(2, false);
  m_listView->setResizeMode(TQListView::AllColumns);
  connect(m_listView, TQ_SIGNAL(clicked(TQListViewItem*)), TQ_SLOT(slotSelected(TQListViewItem*)));
  TQWhatsThis::add(m_listView, i18n("This is a list of all the items available for download. "
                                   "Previously installed items have a checkmark icon, while "
                                   "items with new version available have an update icon"));

  TQWidget* widget = new TQWidget(m_split);
  TQBoxLayout* boxLayout2 = new TQVBoxLayout(widget, 0, KDialog::spacingHint());

  m_iconLabel = new TQLabel(widget);
  m_iconLabel->setAlignment(TQt::AlignTop | TQt::AlignLeft);
  m_iconLabel->setMargin(0);

  m_nameLabel = new TQLabel(widget);
  TQFont font = m_nameLabel->font();
  font.setBold(true);
  font.setItalic(true);
  m_nameLabel->setFont(font);
  TQWhatsThis::add(m_nameLabel, i18n("The name and license of the selected item"));

  m_infoLabel = new TQLabel(widget);
  TQWhatsThis::add(m_infoLabel, i18n("The author of the selected item"));

  m_install = new KPushButton(i18n("Install"), widget);
  m_install->setIconSet(SmallIconSet(TQString::fromLatin1("knewstuff")));
  m_install->setEnabled(false);
  connect(m_install, TQ_SIGNAL(clicked()), TQ_SLOT(slotInstall()));

  // the button's text changes later
  // I don't want it resizing, so figure out the maximum size and set that
  m_install->polish();
  int maxWidth = m_install->sizeHint().width();
  int maxHeight = m_install->sizeHint().height();
  m_install->setGuiItem(KGuiItem(i18n("Update"), SmallIconSet(TQString::fromLatin1("knewstuff"))));
  maxWidth = TQMAX(maxWidth, m_install->sizeHint().width());
  maxHeight = TQMAX(maxHeight, m_install->sizeHint().height());
  m_install->setMinimumWidth(maxWidth);
  m_install->setMinimumHeight(maxHeight);

  TQPixmap pix;
  if(m_type == EntryTemplate) {
    pix = DesktopIcon(TQString::fromLatin1("preferences-desktop"), TDEIcon::SizeLarge);
    TQWhatsThis::add(m_install, i18n("Download and install the selected template."));
  } else {
    pix = UserIcon(TQString::fromLatin1("script"));
    TQWhatsThis::add(m_install, i18n("Download and install the selected script. Some scripts "
                                    "may need to be configured after being installed."));
  }
  m_iconLabel->setPixmap(pix);

  TQBoxLayout* boxLayout3 = new TQHBoxLayout(boxLayout2);

  TQBoxLayout* boxLayout4 = new TQVBoxLayout(boxLayout3);
  boxLayout4->addWidget(m_iconLabel);
  boxLayout4->addStretch(10);

  boxLayout3->addSpacing(4);

  TQBoxLayout* boxLayout5 = new TQVBoxLayout(boxLayout3);
  boxLayout5->addWidget(m_nameLabel);
  boxLayout5->addWidget(m_infoLabel);
  boxLayout5->addStretch(10);

  boxLayout3->addStretch(10);

  TQBoxLayout* boxLayout6 = new TQVBoxLayout(boxLayout3);
  boxLayout6->addWidget(m_install);
  boxLayout6->addStretch(10);

  m_descLabel = new TQTextEdit(widget);
  m_descLabel->setReadOnly(true);
  m_descLabel->setTextFormat(TQt::RichText);
  m_descLabel->setPaper(colorGroup().background());
  m_descLabel->setMinimumHeight(5 * fontMetrics().height());
  boxLayout2->addWidget(m_descLabel, 10);
  TQWhatsThis::add(m_descLabel, i18n("A description of the selected item is shown here."));

  TQHBox* box = new TQHBox(frame, "statusbox");
  boxLayout->addWidget(box);
  box->setSpacing(KDialog::spacingHint());

  m_statusBar = new KStatusBar(box, "statusbar");
  m_statusBar->insertItem(TQString(), PROGRESS_STATUS_ID, 1, false);
  m_statusBar->setItemAlignment(PROGRESS_STATUS_ID, AlignLeft | AlignVCenter);
  m_progress = new TQProgressBar(m_statusBar, "progress");
  m_progress->setTotalSteps(0);
  m_progress->setFixedHeight(fontMetrics().height()+2);
  m_statusBar->addWidget(m_progress, 0, true);

  KPushButton* closeButton = new KPushButton(KStdGuiItem::close(), box);
  connect(closeButton, TQ_SIGNAL(clicked()), TQ_SLOT(slotClose()));
  closeButton->setFocus();

  connect(m_timer, TQ_SIGNAL(timeout()), TQ_SLOT(slotMoveProgress()));

  setMinimumWidth(TQMAX(minimumWidth(), NEW_STUFF_MIN_WIDTH));
  setMinimumHeight(TQMAX(minimumHeight(), NEW_STUFF_MIN_HEIGHT));
  resize(configDialogSize(TQString::fromLatin1("NewStuff Dialog Options")));

  TDEConfigGroup dialogConfig(TDEGlobal::config(), "NewStuff Dialog Options");
  TQValueList<int> splitList = dialogConfig.readIntListEntry("Splitter Sizes");
  if(!splitList.empty()) {
    m_split->setSizes(splitList);
  }

  setStatus(i18n("Downloading information..."));

  KNS::ProviderLoader* loader = new KNS::ProviderLoader(this);
  connect(loader, TQ_SIGNAL(providersLoaded(Provider::List*)), this, TQ_SLOT(slotProviders(Provider::List*)));
  connect(loader, TQ_SIGNAL(percent(TDEIO::Job*, unsigned long)), this, TQ_SLOT(slotShowPercent(TDEIO::Job*, unsigned long)));
  connect(loader, TQ_SIGNAL(error()), this, TQ_SLOT(slotProviderError()));

  TDEConfigGroup config(TDEGlobal::config(), "TDENewStuff");
  TQString prov = config.readEntry("ProvidersUrl");
  if(prov.isEmpty()) {
    prov = TQString::fromLatin1("https://www.trinitydesktop.org/ocs/providers.xml");
  }
  if(m_type == EntryTemplate) {
    m_typeName = TQString::fromLatin1("tellico template");
  } else {
    m_typeName = TQString::fromLatin1("tellico script");
  }
  loader->load(m_typeName, prov);

  TDEAcceleratorManager::manage(this);
}

Dialog::~Dialog() {
  delete m_cursorSaver;
  m_cursorSaver = 0;

  saveDialogSize(TQString::fromLatin1("NewStuff Dialog Options"));
  TDEConfigGroup config(TDEGlobal::config(), "NewStuff Dialog Options");
  config.writeEntry("Splitter Sizes", m_split->sizes());
}

void Dialog::slotProviderError() {
  if(m_listView->childCount() == 0) {
    myDebug() << "NewStuff::Dialog::slotCheckError() - no available items" << endl;
    setStatus(TQString());

    delete m_cursorSaver;
    m_cursorSaver = 0;
  }
}

void Dialog::slotProviders(Provider::List* list_) {
  for(KNS::Provider* prov = list_->first(); prov; prov = list_->next()) {
    TDEIO::TransferJob* job = TDEIO::get(prov->downloadUrl());
    m_jobs[job] = prov;
    connect(job, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
            TQ_SLOT(slotData(TDEIO::Job*, const TQByteArray&)));
    connect(job, TQ_SIGNAL(result(TDEIO::Job*)), TQ_SLOT(slotResult(TDEIO::Job*)));
    connect(job, TQ_SIGNAL(percent(TDEIO::Job*, unsigned long)),
            TQ_SLOT(slotShowPercent(TDEIO::Job*, unsigned long)));
  }
}

void Dialog::slotData(TDEIO::Job* job_, const TQByteArray& data_) {
  TQDataStream stream(m_data[job_], IO_WriteOnly | IO_Append);
  stream.writeRawBytes(data_.data(), data_.size());
}

void Dialog::slotResult(TDEIO::Job* job_) {
//  myDebug() << "NewStuff::Dialog::slotResult()" << endl;
  TQDomDocument dom;
  if(!dom.setContent(m_data[job_])) {
    KNS::Provider* prov = m_jobs[job_];
    KURL u = prov ? prov->downloadUrl() : KURL();
    myDebug() << "NewStuff::Dialog::slotResult() - can't load result: " << u.url() << endl;
    m_jobs.remove(job_);
    if(m_jobs.isEmpty()) {
      setStatus(i18n("Ready."));
      delete m_cursorSaver;
      m_cursorSaver = 0;
    }
    return;
  }

  TQDomElement tdenewstuff = dom.documentElement();
  TQDomElement content;

  for(TQDomNode pn = tdenewstuff.firstChild(); !pn.isNull(); pn = pn.nextSibling())
  {
    TQDomElement stuff = pn.toElement();

    if(stuff.tagName() == "data")
    {
      content = pn.toElement();
    }
  }

  for(TQDomNode pn = content.firstChild(); !pn.isNull(); pn = pn.nextSibling()) {
    TQDomElement stuff = pn.toElement();
    if(stuff.isNull()) {
      continue;
    }

    if(stuff.tagName() == Latin1Literal("content")) {
      KNS::Entry* entry = new KNS::Entry(stuff);
      if(!entry->type().isEmpty() && entry->type() != m_typeName) {
        myLog() << "NewStuff::Dialog::slotResult() - type mismatch, skipping " << entry->name(m_lang) << endl;
        continue;
      }

      addEntry(entry);
    }
  }
  m_jobs.remove(job_);
  if(m_jobs.isEmpty()) {
    setStatus(i18n("Ready."));
    delete m_cursorSaver;
    m_cursorSaver = 0;
  }
}

void Dialog::addEntry(KNS::Entry* entry_) {
  if(!entry_) {
    return;
  }

  Item* item = new Item(m_listView);
  item->setText(0, entry_->name(m_lang));
  item->setText(1, entry_->version());
  item->setText(2, TQString::number(entry_->rating()));
  item->setText(3, TQString::number(entry_->downloads()));
  item->setText(4, TDEGlobal::locale()->formatDate(entry_->releaseDate(), true /*short format */));
  item->setStatus(NewStuff::Manager::installStatus(entry_));
  m_entryMap.insert(item, entry_);

  if(!m_listView->selectedItem()) {
    m_listView->setSelected(item, true);
    slotSelected(item);
  }
}

void Dialog::slotSelected(TQListViewItem* item_) {
  if(!item_) {
    return;
  }

  KNS::Entry* entry = m_entryMap[item_];
  if(!entry) {
    return;
  }

  KURL preview = entry->preview(m_lang);
  if(!preview.isEmpty() && preview.isValid()) {
    delete m_tempPreviewImage;
    m_tempPreviewImage = new KTempFile();
    m_tempPreviewImage->setAutoDelete(true);
    KURL dest;
    dest.setPath(m_tempPreviewImage->name());
    TDEIO::FileCopyJob* job = TDEIO::file_copy(preview, dest, -1, true, false, false);
    connect(job, TQ_SIGNAL(result(TDEIO::Job*)), TQ_SLOT(slotPreviewResult(TDEIO::Job*)));
    connect(job, TQ_SIGNAL(percent(TDEIO::Job*, unsigned long)),
            TQ_SLOT(slotShowPercent(TDEIO::Job*, unsigned long)));
    m_lastPreviewItem = item_;
  }
  TQPixmap pix = m_type == EntryTemplate
              ? DesktopIcon(TQString::fromLatin1("preferences-desktop"), TDEIcon::SizeLarge)
              : UserIcon(TQString::fromLatin1("script"));
  m_iconLabel->setPixmap(pix);

  TQString license = entry->license();
  if(!license.isEmpty()) {
    license.prepend('(').append(')');
  }
  TQString name = TQString::fromLatin1("%1 %2").arg(entry->name(m_lang)).arg(license);
  TQFont font = m_nameLabel->font();
  font.setBold(true);
  font.setItalic(false);
  m_nameLabel->setFont(font);
  m_nameLabel->setText(name);

  m_infoLabel->setText(entry->author());

  TQString desc = entry->summary(m_lang);
  desc.replace(TQRegExp(TQString::fromLatin1("\\n")), TQString::fromLatin1("<br>"));
  m_descLabel->setText(desc);

  InstallStatus installed = static_cast<Item*>(item_)->status();
  m_install->setText(installed == OldVersion ? i18n("Update Stuff", "Update") : i18n("Install"));
  m_install->setEnabled(installed != Current);
}

void Dialog::slotInstall() {
  TQListViewItem* item = m_listView->currentItem();
  if(!item) {
    return;
  }

  KNS::Entry* entry = m_entryMap[item];
  if(!entry) {
    return;
  }

  delete m_cursorSaver;
  m_cursorSaver = new GUI::CursorSaver();
  setStatus(i18n("Installing item..."));
  m_progress->show();
  m_timer->start(100);
  connect(m_manager, TQ_SIGNAL(signalInstalled(KNS::Entry*)), TQ_SLOT(slotDoneInstall(KNS::Entry*)));
  mJobEntry = entry;

  // OpenDesktop.org broke the basic functionality of TDEHNS by forcing
  // downloads though an advertising display page.  This in turn forces
  // the user to download and manually install the wallpaper, which
  // is relatively complex and negates much of the benefit of TDEHNS.
  // Therefore, if the download URL contains OPENDESKTOP_REDIRECT_URL
  // then download the raw HTML page and extract the real download URL for use below.
  // In the future we may want to figure out how to display unobtrusive ads
  // during the download process, but OpenDesktop.org would need to add back
  // in the direct download ability for this to even be considered.
  if (entry->payload().url().contains(OPENDESKTOP_REDIRECT_URL)) {
    TDEIO::TransferJob *job = TDEIO::get( KURL( entry->payload() ), false, false );
    connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ),
             TQ_SLOT( slotJobResult( TDEIO::Job * ) ) );
    connect( job, TQ_SIGNAL( data( TDEIO::Job *, const TQByteArray & ) ),
             TQ_SLOT( slotJobData( TDEIO::Job *, const TQByteArray & ) ) );
  }
  else {
    slotInstallPhase2();
  }
}

void Dialog::slotInstallPhase2() {
  m_manager->install(m_type, mJobEntry);
  delete m_cursorSaver;
  m_cursorSaver = 0;
}

void Dialog::slotJobData( TDEIO::Job *, const TQByteArray &data )
{
  kdDebug() << "DownloadDialog::slotJobData()" << endl;

  if ( data.size() == 0 ) return;

  TQCString str( data, data.size() + 1 );

  mJobData.append( TQString::fromUtf8( str ) );
}

void Dialog::slotJobResult( TDEIO::Job *job )
{
  if ( job->error() ) {
    job->showErrorDialog( this );
    slotInstallPhase2();
    return;
  }

  // See previous note regarding OpenDesktop.org
  if (mJobEntry->payload().url().contains(OPENDESKTOP_REDIRECT_URL)) {
    int pos = mJobData.find("<a href=\"/CONTENT/content-files/");
    if (pos >= 0) {
      TQString realURL = mJobData.mid(pos);
      realURL = realURL.mid(0, realURL.find("\">"));
      realURL = realURL.mid(strlen("<a href=\""));
      realURL = mJobEntry->payload().protocol() + "://opendesktop.org" + realURL;
      mJobEntry->setPayload(realURL);
    }
    else if ((pos = mJobData.find(OPENDESKTOP_REDIRECT_TEXT)) > 0) {
      pos = pos + strlen(OPENDESKTOP_REDIRECT_TEXT);
      TQString realURL = mJobData.mid(pos);
      realURL = realURL.mid(0, realURL.find("\">"));
      mJobEntry->setPayload(realURL);
    }
  }

  // Reset for next load
  mJobData = TQString::null;

  slotInstallPhase2();
}

void Dialog::slotDoneInstall(KNS::Entry* entry_) {
  TQMap<TQListViewItem*, KNS::Entry*>::Iterator it;
  for(it = m_entryMap.begin(); entry_ && it != m_entryMap.end(); ++it) {
    if(it.data() == entry_) {
      InstallStatus installed = Manager::installStatus(entry_);
      static_cast<Item*>(it.key())->setStatus(installed);
      m_install->setEnabled(installed != Current);
      break;
    }
  }
  delete m_cursorSaver;
  m_cursorSaver = 0;
  setStatus(i18n("Ready."));
  m_timer->stop();
  m_progress->hide();
}

void Dialog::slotMoveProgress() {
  m_progress->setProgress(m_progress->progress()+5);
}

void Dialog::setStatus(const TQString& text_) {
  m_statusBar->changeItem(TQChar(' ') + text_, PROGRESS_STATUS_ID);
}

void Dialog::slotShowPercent(TDEIO::Job*, unsigned long pct_) {
  if(pct_ >= 100) {
    m_progress->hide();
  } else {
    m_progress->show();
    m_progress->setProgress(static_cast<int>(pct_), 100);
  }
}

void Dialog::slotPreviewResult(TDEIO::Job* job_) {
  TDEIO::FileCopyJob* job = static_cast<TDEIO::FileCopyJob*>(job_);
  if(job->error()) {
    return;
  }
  TQString tmpFile = job->destURL().path(); // might be different than m_tempPreviewImage->name()
  TQPixmap pix(tmpFile);

  if(!pix.isNull()) {
    if(pix.width() > 64 || pix.height() > 64) {
      pix.convertFromImage(pix.convertToImage().smoothScale(64, 64, TQImage::ScaleMin));
    }
    // only set label if it's still current
    if(m_listView->selectedItem() == m_lastPreviewItem) {
      m_iconLabel->setPixmap(pix);
    }
  }
  delete m_tempPreviewImage;
  m_tempPreviewImage = 0;
}

#include "dialog.moc"
