/**********************************************************************************************
    Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>

    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 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

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

#include "dem/CDemItem.h"

#include <QtWidgets>

#include "dem/CDemDraw.h"
#include "dem/CDemVRT.h"
#include "dem/CDemWCS.h"
#include "dem/IDemProp.h"
#include "map/CMapItemDelegate.h"
#include "misc.h"

QRecursiveMutex CDemItem::mutexActiveDems;

CDemItem::CDemItem(CDemDraw* dem) : dem(dem) {
  setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
  connect(dem, &CDemDraw::sigScaleChanged, this, &CDemItem::slotScaleChanged);
}

CDemItem::~CDemItem() {}

void CDemItem::activate(bool yes) {
  if (yes) {
    activate();
  } else {
    deactivate();
  }

  emit sigChanged();
}

void CDemItem::setFilename(const QString& name, const QString& fallbackKey) {
  filename = name;

  QFile f(filename);
  if (f.exists() && f.open(QIODevice::ReadOnly)) {
    QByteArray header = f.read(4096);
    f.close();

    QCryptographicHash md5(QCryptographicHash::Md5);
    md5.addData(header.left(1024));
    shortKey = md5.result().toHex();

    md5.reset();
    md5.addData(header);
    key = md5.result().toHex();
  } else {
    shortKey = key = fallbackKey;
  }
}

void CDemItem::configToShadowConfig(const QSettings& cfg) {
  shadowConfig.clear();
  const QStringList& keys = cfg.childKeys();
  for (const QString& key : keys) {
    shadowConfig[key] = cfg.value(key);
  }
}

void CDemItem::shadowConfigToConfig(QSettings& cfg) const {
  const QStringList& keys = shadowConfig.keys();
  for (const QString& key : keys) {
    cfg.setValue(key, shadowConfig[key]);
  }
}

void CDemItem::saveConfig(QSettings& cfg) const {
  cfg.beginGroup(key);
  if (demfile.isNull()) {
    shadowConfigToConfig(cfg);
  } else {
    demfile->saveConfig(cfg);
    cfg.setValue("isActive", true);
  }
  cfg.setValue("filename", filename);
  cfg.endGroup();
}

void CDemItem::loadConfig(QSettings& cfg, bool triggerActivation) {
  if (!demfile.isNull()) {
    // dem is already active, read the config
    cfg.beginGroup(key);
    configToShadowConfig(cfg);
    demfile->loadConfig(cfg);
    cfg.endGroup();
    return;
  }

  // let's see what we can do...
  cfg.beginGroup(key);
  bool active = cfg.value("isActive", false).toBool();
  configToShadowConfig(cfg);
  cfg.endGroup();

  if (!QFile::exists(filename)) {
    setStatus(eStatus::Missing);
    return;
  } else if (noShadowConfig()) {
    setStatus(eStatus::Unused);
  } else {
    setStatus(eStatus::Inactive);
    if (triggerActivation) {
      // Evil hack: If you activate the DEM directly Qt will crash internally.
      QPointer<CDemItem> self(this);
      QTimer::singleShot(100, this, [self, active]() {
        if (!self.isNull()) self->activate(active);
      });
    }
  }
}

void CDemItem::showChildren(bool yes) {
  if (demfile.isNull()) {
    return;
  }
  if (yes) {
    QTreeWidget* tw = treeWidget();

    QTreeWidgetItem* item = new QTreeWidgetItem(this);
    item->setFlags(Qt::ItemIsEnabled);
    tw->setItemWidget(item, 0, demfile->getSetup());
  } else {
    QList<QTreeWidgetItem*> items = takeChildren();
    qDeleteAll(items);
    delete demfile->getSetup();
  }
}

void CDemItem::updateIcon() {
  if (filename.isEmpty()) {
    return;
  }

  QString iconPath = "://icons/Map.svgt";
  QFileInfo fi(filename);
  if (fi.suffix().toLower() == "vrt") {
    iconPath = "://icons/MimeDemVRT.svgt";
  } else if (fi.suffix().toLower() == "wcs") {
    iconPath = "://icons/MimeDemWCS.svgt";
  }

  setIcon(0, QIcon(iconPath));
}

bool CDemItem::isActivated() {
  QMutexLocker lock(&mutexActiveDems);
  return !demfile.isNull();
}

void CDemItem::deactivate() {
  QMutexLocker lock(&mutexActiveDems);

  if (demfile.isNull()) {
    return;
  }
  // save current configuration into
  // the shadow configuration
  QTemporaryFile file;
  openFileCheckSuccess(QIODevice::ReadWrite, file);
  QSettings cfg(file.fileName(), QSettings::IniFormat);
  demfile->saveConfig(cfg);
  configToShadowConfig(cfg);
  // Record the deactivated intent: the DEM's own saveConfig() writes no isActive key,
  // so without this a reload keeps the stale on-disk isActive=true and re-activates.
  shadowConfig["isActive"] = false;

  // remove demfile setup dialog as child of this item
  showChildren(false);

  // remove demfile object
  delete demfile;

  // maybe used to reflect changes in the icon
  updateIcon();

  setStatus(eStatus::Inactive);
}

bool CDemItem::activate() {
  QMutexLocker lock(&mutexActiveDems);

  delete demfile;

  // load DEM by suffix
  QFileInfo fi(filename);
  if (fi.suffix().toLower() == "vrt") {
    demfile = new CDemVRT(filename, dem);
  } else if (fi.suffix().toLower() == "wcs") {
    demfile = new CDemWCS(filename, dem);
  }

  updateIcon();

  // no demfile loaded? Bad.
  if (demfile.isNull()) {
    // Present file that failed to load: clear the re-arm flag so it isn't retried
    // (and re-warned) on every restart. The missing-drive case exits earlier via QFile::exists().
    shadowConfig["isActive"] = false;
    setStatus(eStatus::Inactive);
    return false;
  }

  // if DEM is activated successfully add to the list of DEM files
  // else delete all previous loaded DEMs and abort
  if (!demfile->activated()) {
    delete demfile;
    shadowConfig["isActive"] = false;
    setStatus(eStatus::Inactive);
    return false;
  }

  // setup DEM with settings stored in
  // the shadow config
  QTemporaryFile file;
  openFileCheckSuccess(QIODevice::ReadWrite, file);
  QSettings cfg(file.fileName(), QSettings::IniFormat);

  shadowConfigToConfig(cfg);
  demfile->loadConfig(cfg);
  // On first activation the shadow config is empty. (state *new*)
  // Therfore always read back the config and store it as new
  // shadow config
  demfile->saveConfig(cfg);
  configToShadowConfig(cfg);

  // Add the demfile setup dialog as child of this item
  showChildren(true);

  // make sure the delegate is updated
  setStatus(eStatus::Active);
  slotScaleChanged(dem->getScale());
  return true;
}

void CDemItem::setAccess(const QString& ele) {
  const QModelIndex& index = treeWidget()->indexFromItem(this);
  CMapItemDelegate* delegate = dynamic_cast<CMapItemDelegate*>(treeWidget()->itemDelegate());
  if (delegate == nullptr) {
    return;
  }

  delegate->setAccess(index, ele);
}

void CDemItem::setProcessing(bool on) {
  const QModelIndex& index = treeWidget()->indexFromItem(this);
  CMapItemDelegate* delegate = dynamic_cast<CMapItemDelegate*>(treeWidget()->itemDelegate());
  if (delegate == nullptr) {
    return;
  }

  delegate->setProcessing(index, on);
}

void CDemItem::slotScaleChanged(const QPointF& scale) {
  if (demfile.isNull()) {
    return;
  }

  const QModelIndex& index = treeWidget()->indexFromItem(this);
  CMapItemDelegate* delegate = dynamic_cast<CMapItemDelegate*>(treeWidget()->itemDelegate());
  if (delegate == nullptr) {
    return;
  }

  outOfScale = demfile->isOutOfScale(scale);
  if (outOfScale) {
    delegate->setColor(index, kColorOut);
  } else {
    delegate->setColor(index, kColorIn);
  }
}

bool CDemItem::showsOverviewWarning() const {
  if (status != eStatus::Active || demfile.isNull()) {
    return false;
  }
  return demfile->showsOverviewWarning();
}

bool CDemItem::hasOverviewInfo() const {
  if (status != eStatus::Active || demfile.isNull()) {
    return false;
  }
  return demfile->hasOverviewInfo();
}

void CDemItem::triggerOverviewAdvisory() {
  if (status != eStatus::Active || demfile.isNull()) {
    return;
  }
  if (auto* vrt = dynamic_cast<CDemVRT*>(demfile.data())) {
    dem->emitOverviewAdvisory(vrt);
  }
}
