/*
 * LibrePCB - Professional EDA for everyone!
 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
 * https://librepcb.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 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/>.
 */

/*******************************************************************************
 *  Includes
 ******************************************************************************/
#include "downloadlibrarytab.h"

#include "guiapplication.h"
#include "librariesmodel.h"
#include "librarydownload.h"
#include "utils/slinthelpers.h"

#include <librepcb/core/workspace/workspace.h>
#include <librepcb/core/workspace/workspacelibrarydb.h>

#include <QtCore>

/*******************************************************************************
 *  Namespace
 ******************************************************************************/
namespace librepcb {
namespace editor {

/*******************************************************************************
 *  Constructors / Destructor
 ******************************************************************************/

DownloadLibraryTab::DownloadLibraryTab(GuiApplication& app,
                                       QObject* parent) noexcept
  : WindowTab(app, parent),
    onDerivedUiDataChanged(*this),
    mUiData{
        slint::SharedString(),  // URL
        slint::SharedString(),  // URL error
        slint::SharedString(),  // URL suggestion
        slint::SharedString(),  // Directory
        slint::SharedString(),  // Directory default
        slint::SharedString(),  // Directory error
        false,  // Valid
        false,  // Download running
        0,  // Download progress
        slint::SharedString(),  // Download error
    } {
  validate();
}

DownloadLibraryTab::~DownloadLibraryTab() noexcept {
}

/*******************************************************************************
 *  General Methods
 ******************************************************************************/

ui::TabData DownloadLibraryTab::getUiData() const noexcept {
  return ui::TabData{
      ui::TabType::DownloadLibrary,  // Type
      q2s(tr("Download Library")),  // Title
      ui::TabFeatures{},  // Features
      false,  // Read-only
      false,  // Unsaved changes
      slint::SharedString(),  // Undo text
      slint::SharedString(),  // Redo text
      slint::SharedString(),  // Find term
      nullptr,  // Find suggestions
      nullptr,  // Layers
  };
}

void DownloadLibraryTab::setDerivedUiData(
    const ui::DownloadLibraryTabData& data) noexcept {
  mUiData = data;
  validate();
}

void DownloadLibraryTab::trigger(ui::TabAction a) noexcept {
  switch (a) {
    case ui::TabAction::Cancel: {
      if (mDownload) {
        mDownload.reset();
        mUiData.download_running = false;
        mUiData.download_progress = 0;
        mUiData.download_status = slint::SharedString();
        onDerivedUiDataChanged.notify();
      } else {
        emit closeRequested();
      }
      break;
    }

    case ui::TabAction::Accept: {
      try {
        if ((!mUrl) || (!mDirectory.isValid()) ||
            (mDirectory.isExistingDir()) || mDirectory.isExistingFile() ||
            (mDirectory.getSuffix() != "lplib") || mDownload) {
          throw LogicError(__FILE__, __LINE__);
        }

        mUiData.download_running = true;
        mUiData.download_progress = 0;
        onDerivedUiDataChanged.notify();

        mDownload.reset(new LibraryDownload(*mUrl, mDirectory));
        connect(mDownload.get(), &LibraryDownload::progressState,
                [this](const QString& state) {
                  mUiData.download_status = q2s(state);
                  onDerivedUiDataChanged.notify();
                });
        connect(mDownload.get(), &LibraryDownload::progressPercent,
                [this](int percent) {
                  mUiData.download_progress = percent;
                  onDerivedUiDataChanged.notify();
                });
        connect(mDownload.get(), &LibraryDownload::finished, this,
                &DownloadLibraryTab::downloadFinished);
        mDownload->start();
      } catch (const Exception& e) {
        mUiData.download_status = q2s(e.getMsg());
        onDerivedUiDataChanged.notify();
      }
      break;
    }

    default: {
      WindowTab::trigger(a);
      break;
    }
  }
}

/*******************************************************************************
 *  Private Methods
 ******************************************************************************/

void DownloadLibraryTab::validate() noexcept {
  const QString SUFFIX = ".lplib";
  const FilePath::CleanFileNameOptions CLEAN_OPTIONS =
      FilePath::CleanFileNameOption::ReplaceSpaces |
      FilePath::CleanFileNameOption::KeepCase;
  const int MAX_DIRNAME_LENGTH = 50;  // Just some sane maximum dirname length.

  const QString urlStr = s2q(mUiData.url);
  mUrl = validateUrl(urlStr, mUiData.url_error, false);

  mUiData.url_suggestion = slint::SharedString();
  if (mUrl && (!mUrl->toString().endsWith(".zip"))) {
    auto urlWithSuffix = [this](const QString& suffix) {
      QString url = mUrl->toString();
      while ((!url.isEmpty()) && (url.endsWith("/"))) {
        url.chop(1);
      }
      return url + suffix;
    };

    QString suggestion;
    if (mUrl->host().toLower().contains("github")) {
      suggestion = urlWithSuffix("/archive/refs/heads/master.zip");
    } else if (mUrl->host().toLower().contains("gitlab")) {
      const QString repo = mUrl->path().split("/", Qt::SkipEmptyParts).last();
      suggestion = urlWithSuffix("/-/archive/master/" % repo % "-master.zip");
    }
    mUiData.url_suggestion = q2s(QUrl(suggestion).toString());
  }

  QString left = urlStr.left(urlStr.indexOf(SUFFIX, Qt::CaseInsensitive));
  QString libName = left.right(left.length() - left.lastIndexOf("/"));
  if (libName == urlStr) {
    libName = mUrl ? mUrl->fileName() : QString();
  }
  libName.remove("-master");
  libName.remove("-main");
  libName.remove(".zip");
  libName.remove(".lplib");
  QString dirDefault = FilePath::cleanFileName(
      libName, CLEAN_OPTIONS, MAX_DIRNAME_LENGTH - SUFFIX.length());
  if (!dirDefault.isEmpty()) {
    dirDefault.append(SUFFIX);
  }
  mUiData.directory_default = q2s(dirDefault);

  QString dirStr = s2q(mUiData.directory).trimmed();
  if (dirStr.isEmpty()) {
    dirStr = s2q(mUiData.directory_default);
  }
  const std::optional<QString> dirName =
      validateFileName(dirStr, mUiData.directory_error, CLEAN_OPTIONS,
                       MAX_DIRNAME_LENGTH, SUFFIX);
  mDirectory = dirName
      ? mApp.getWorkspace().getLibrariesPath().getPathTo("local/" % *dirName)
      : FilePath();
  if (mDirectory.isValid() &&
      (mDirectory.isExistingFile() || mDirectory.isExistingDir())) {
    mDirectory = FilePath();
    mUiData.directory_error = q2s(tr("Exists already"));
  }

  mUiData.valid = mUrl && mDirectory.isValid();
  onDerivedUiDataChanged.notify();
}

void DownloadLibraryTab::downloadFinished(bool success,
                                          const QString& errMsg) noexcept {
  mDownload.reset();

  if (success) {
    // Highlight the new library in the libraries tab.
    emit panelPageRequested(ui::PanelPage::Libraries);
    mApp.getLocalLibraries().highlightLibraryOnNextRescan(mDirectory);

    // Force rescan to index the new library.
    mApp.getWorkspace().getLibraryDb().startLibraryRescan();
    emit closeRequested();
  } else {
    mUiData.download_status = q2s(errMsg);
    mUiData.download_running = false;
    mUiData.download_progress = 0;
    onDerivedUiDataChanged.notify();
  }
}

/*******************************************************************************
 *  End of File
 ******************************************************************************/

}  // namespace editor
}  // namespace librepcb
