# R/download.R  --------------------------------------------------------------
# Download helpers for Conic Benchmark Library (CBLIB) instances.
# ---------------------------------------------------------------------------

.CBLIB_DOWNLOAD_ALL_URL <- "https://cblib.zib.de/download/all/"


#' Download a Single CBLIB Instance
#'
#' Downloads one Conic Benchmark Library (CBLIB) instance from
#' \url{https://cblib.zib.de/download/all/}. By default the downloaded
#' \code{.cbf.gz} archive is decompressed to a plain \code{.cbf} file so it can
#' be passed directly to \code{\link{read_cbf}}.
#'
#' @param instance Character string. Instance name, with or without the
#'   \code{.cbf.gz} suffix.
#' @param folder Character string. Destination directory. No default is
#'   provided; pass \code{tempdir()} for a temporary location.
#' @param url Character string. Base URL of the CBLIB download directory.
#' @param method Character string passed to \code{\link[utils]{download.file}}.
#' @param quiet Logical. Passed to \code{\link[utils]{download.file}}.
#' @param force Logical. If \code{TRUE}, download again even if the target file
#'   already exists.
#' @param decompress Logical. If \code{TRUE} (default), decompress the archive
#'   to \code{.cbf} after download.
#' @param keep_archive Logical. If \code{TRUE}, keep the downloaded
#'   \code{.cbf.gz} archive after decompression.
#'
#' @return Character string giving the path to the downloaded file. Returns the
#'   decompressed \code{.cbf} path when \code{decompress = TRUE}, otherwise the
#'   \code{.cbf.gz} archive path.
#'
#' @details
#' The CBLIB download README documents recursive retrieval from
#' \url{https://cblib.zib.de/download/all/} using \code{wget}. These helpers
#' provide the same access pattern directly from R for either a single instance
#' or the full directory listing.
#'
#' @examples
#' \dontrun{
#' path <- cblib_download("CBLIB_ex1.cbf", folder = tempdir())
#' op   <- read_cbf(path)
#' }
#'
#' @seealso \code{\link{cblib_download_all}}, \code{\link{read_cbf}}
#' @export
cblib_download <- function(instance,
                         folder,
                         url = .CBLIB_DOWNLOAD_ALL_URL,
                         method = NULL,
                         quiet = TRUE,
                         force = FALSE,
                         decompress = TRUE,
                         keep_archive = !decompress) {
    stopifnot(is.character(instance), length(instance) == 1L, nzchar(instance))
    stopifnot(is.character(folder), length(folder) == 1L, nzchar(folder))
    stopifnot(is.character(url), length(url) == 1L, nzchar(url))
    stopifnot(is.logical(quiet), length(quiet) == 1L, !is.na(quiet))
    stopifnot(is.logical(force), length(force) == 1L, !is.na(force))
    stopifnot(is.logical(decompress), length(decompress) == 1L, !is.na(decompress))
    stopifnot(is.logical(keep_archive), length(keep_archive) == 1L, !is.na(keep_archive))

    folder <- .cblib_ensure_dir(folder)
    archive_name <- .cblib_normalize_instance_name(instance)
    archive_path <- file.path(folder, archive_name)
    target_path <- if (decompress) sub("\\.gz$", "", archive_path, ignore.case = TRUE) else archive_path

    if (!force && file.exists(target_path))
        return(normalizePath(target_path, winslash = "/", mustWork = FALSE))

    download_url <- paste0(sub("/+$", "/", url), archive_name)
    utils::download.file(
        url = download_url,
        destfile = archive_path,
        method = method,
        quiet = quiet,
        mode = "wb"
    )

    if (decompress) {
        .cblib_gunzip(archive_path, target_path, overwrite = TRUE)
        if (!keep_archive) unlink(archive_path)
    }

    normalizePath(target_path, winslash = "/", mustWork = FALSE)
}


#' Download All CBLIB Instances
#'
#' Downloads every \code{.cbf.gz} instance linked from
#' \url{https://cblib.zib.de/download/all/}. By default each archive is
#' decompressed to a plain \code{.cbf} file.
#'
#' @inheritParams cblib_download
#'
#' @return Named character vector of downloaded file paths. Names are the CBLIB
#'   archive file names listed in the index.
#'
#' @details
#' According to the CBLIB download README, the complete collection is intended
#' to be fetched recursively from the \code{/download/all/} directory. This
#' helper first reads that index page, extracts all linked \code{.cbf.gz}
#' archives, and then downloads them one by one.
#'
#' @examples
#' \dontrun{
#' paths <- cblib_download_all(folder = tempdir())
#' }
#'
#' @seealso \code{\link{cblib_download}}, \code{\link{read_cbf}}
#' @export
cblib_download_all <- function(folder,
                             url = .CBLIB_DOWNLOAD_ALL_URL,
                             method = NULL,
                             quiet = TRUE,
                             force = FALSE,
                             decompress = TRUE,
                             keep_archive = !decompress) {
    instances <- .cblib_download_index(url = url, method = method, quiet = quiet)
    if (!length(instances))
        stop("No CBLIB instances found at ", sQuote(url), ".")

    paths <- vapply(
        instances,
        function(instance) {
            cblib_download(
                instance = instance,
                folder = folder,
                url = url,
                method = method,
                quiet = quiet,
                force = force,
                decompress = decompress,
                keep_archive = keep_archive
            )
        },
        FUN.VALUE = character(1L),
        USE.NAMES = FALSE
    )
    stats::setNames(paths, instances)
}


.cblib_download_index <- function(url = .CBLIB_DOWNLOAD_ALL_URL,
                                  method = NULL,
                                  quiet = TRUE) {
    tmp <- tempfile(fileext = ".html")
    on.exit(unlink(tmp), add = TRUE)

    utils::download.file(
        url = url,
        destfile = tmp,
        method = method,
        quiet = quiet,
        mode = "wb"
    )

    .cblib_extract_instance_names(readLines(tmp, warn = FALSE, encoding = "UTF-8"))
}


.cblib_extract_instance_names <- function(lines) {
    stopifnot(is.character(lines))

    html <- paste(lines, collapse = "\n")
    matches <- gregexpr('href="([^"]+\\.cbf\\.gz)"', html, perl = TRUE)
    hits <- regmatches(html, matches)[[1L]]
    if (!length(hits))
        return(character(0L))

    names <- sub('^href="', '', hits)
    names <- sub('"$', '', names)
    names <- basename(utils::URLdecode(names))

    sort(unique(names))
}


.cblib_normalize_instance_name <- function(instance) {
    stopifnot(is.character(instance), length(instance) == 1L, nzchar(instance))

    name <- basename(instance)
    lower <- tolower(name)

    if (endsWith(lower, ".cbf.gz"))
        return(name)
    if (endsWith(lower, ".cbf"))
        return(paste0(name, ".gz"))

    paste0(name, ".cbf.gz")
}


.cblib_ensure_dir <- function(folder) {
    if (!dir.exists(folder))
        dir.create(folder, recursive = TRUE, showWarnings = FALSE)
    normalizePath(folder, winslash = "/", mustWork = TRUE)
}


.cblib_gunzip <- function(src, dest, overwrite = FALSE) {
    stopifnot(is.character(src), length(src) == 1L, nzchar(src))
    stopifnot(is.character(dest), length(dest) == 1L, nzchar(dest))
    stopifnot(is.logical(overwrite), length(overwrite) == 1L, !is.na(overwrite))

    if (file.exists(dest) && !overwrite)
        stop("Destination file already exists: ", sQuote(dest))

    in_con <- gzfile(src, open = "rb")
    out_con <- file(dest, open = "wb")
    on.exit(close(in_con), add = TRUE)
    on.exit(close(out_con), add = TRUE)

    ok <- FALSE
    on.exit({
        if (!ok && file.exists(dest)) unlink(dest)
    }, add = TRUE)

    repeat {
        chunk <- readBin(in_con, what = raw(), n = 65536L)
        if (!length(chunk))
            break
        writeBin(chunk, out_con)
    }

    ok <- TRUE
    invisible(dest)
}
