# $NetBSD: R2pkg.R,v 1.30 2020/03/11 16:13:35 brook Exp $
#
# Copyright (c) 2014,2015,2016,2017,2018,2019
#	Brook Milligan.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name of the author nor the names of any contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# Create an R package in the current directory
#

arg.level                <- as.integer(Sys.getenv('LEVEL'))
arg.rpkg                 <- Sys.getenv('RPKG')
arg.packages_list        <- Sys.getenv('PACKAGES_LIST')
arg.r2pkg                <- Sys.getenv('R2PKG')
arg.args                 <- Sys.getenv('ARGS')
arg.recursive            <- as.logical(Sys.getenv('RECURSIVE'))
arg.update               <- as.logical(Sys.getenv('UPDATE'))
arg.dependency_list      <- Sys.getenv('DEPENDENCY_LIST')
arg.maintainer_email     <- Sys.getenv('MAINTAINER_EMAIL')
arg.rpkg_description_url <- Sys.getenv('RPKG_DESCRIPTION_URL')
arg.quiet_curl           <- as.logical(Sys.getenv('QUIET_CURL'))
arg.make                 <- Sys.getenv('MAKE')

mkcvsid <- paste0('# $', 'NetBSD$')

level.message <- function(...)
  message('[ ', arg.level, ' ] ', ...)

level.warning <- function(...)
  level.message('WARNING: ', ...)

trim.space <- function(s) gsub('[[:space:]]', '', s)
one.line <- function(s) gsub('\n', ' ', s)
pkg.vers <- function(s) gsub('_', '.', s)
varassign <- function(varname, value) paste0(varname, '=\t', value)
relpath_category <- function(relpath)
  unlist(sapply(strsplit(relpath, '/'), '[', 3))

# The list of "recommended packages which are to be included in all
# binary distributions of R." (R FAQ 5.1.2 2018-10-18)
#
base.packages.FAQ.5.1.2 <- c(
  'KernSmooth',
  'MASS',
  'Matrix',
  'boot',
  'class',
  'cluster',
  'codetools',
  'foreign',
  'lattice',
  'mgcv',
  'nlme',
  'nnet',
  'rpart',
  'spatial',
  'survival')

# Other R packages that appear in dependency lists but are included in
# the R package.
#
base.packages.other <- c(
  'grDevices',
  'graphics',
  'grid',
  'methods',
  'parallel',
  'splines',
  'stats',
  'tools',
  'utils')
base.packages <- c('R', base.packages.FAQ.5.1.2, base.packages.other)

licenses <- list()
licenses[['ACM']]                                    <- 'acm-license'
licenses[['ACM | file LICENSE']]                     <- 'acm-license\t# OR file LICENSE'
licenses[['APACHE']]                                 <- 'apache-1.1 OR apache-2.0'
licenses[['Apache License 2.0']]                     <- 'apache-2.0'
licenses[['Apache License (== 2.0)']]                <- 'apache-2.0'
licenses[['Apache License (== 2.0) | file LICENSE']] <- 'apache-2.0\t# OR file LICENSE'
licenses[['ARTISTIC']]                               <- 'artistic OR artistic-2.0'
licenses[['Artistic-2.0']]                           <- 'artistic-2.0'
licenses[['BSD']]                                    <- '2-clause-bsd OR modified-bsd OR original-bsd'
licenses[['BSD-2']]                                  <- '2-clause-bsd'
licenses[['BSD_2_clause + file LICENSE']]            <- '2-clause-bsd\t# + file LICENSE'
licenses[['BSD 3 clause']]                           <- 'modified-bsd'
licenses[['BSD 3 clause + file LICENSE']]            <- 'modified-bsd\t# + file LICENSE'
licenses[['BSD_3_clause + file LICENSE']]            <- 'modified-bsd\t# + file LICENSE'
licenses[['BSL-1.0']]                                <- 'boost-license'
licenses[['CC0']]                                    <- 'cc0-1.0-universal'
licenses[['GPL']]                                    <- 'gnu-gpl-v1 OR gnu-gpl-v2 OR gnu-gpl-v3'
licenses[['GPL-1']]                                  <- 'gnu-gpl-v1'
licenses[['GPL-2']]                                  <- 'gnu-gpl-v2'
licenses[['GPL-2 | file LICENSE']]                   <- 'gnu-gpl-v2\t# OR file LICENSE'
licenses[['GPL-3']]                                  <- 'gnu-gpl-v3'
licenses[['GPL-2 | GPL-3']]                          <- 'gnu-gpl-v2 OR gnu-gpl-v3'
licenses[['GPL (>= 2)']]                             <- 'gnu-gpl-v2 OR gnu-gpl-v3'
licenses[['GPL (>= 2.0)']]                           <- 'gnu-gpl-v2 OR gnu-gpl-v3'
licenses[['GPL (>= 2) | file LICENSE']]              <- 'gnu-gpl-v2 OR gnu-gpl-v3\t# OR file LICENSE'
licenses[['GPL (>= 3)']]                             <- 'gnu-gpl-v3'
licenses[['LGPL']]                                   <- 'gnu-lgpl-v2 OR gnu-lgpl-v2.1 OR gnu-lgpl-v3'
licenses[['LGPL-2']]                                 <- 'gnu-lgpl-v2'
licenses[['LGPL-2.1']]                               <- 'gnu-lgpl-v2.1'
licenses[['LGPL-3']]                                 <- 'gnu-lgpl-v3'
licenses[['LGPL-3 + file LICENSE']]                  <- 'gnu-lgpl-v3\t# + file LICENSE'
licenses[['LGPL-2 | LGPL-3']]                        <- 'gnu-lgpl-v2 OR gnu-lgpl-v3'
licenses[['LGPL (>= 2)']]                            <- 'gnu-lgpl-v2 OR gnu-lgpl-v2.1 OR gnu-lgpl-v3'
licenses[['LUCENT']]                                 <- 'lucent'
licenses[['Lucent Public License']]                  <- 'lucent'
licenses[['MIT']]                                    <- 'mit'
licenses[['MIT + file LICENSE']]                     <- 'mit\t# + file LICENSE'
licenses[['MIT + file LICENSE | Unlimited']]         <- 'mit\t# + file LICENSE OR unlimited'
licenses[['MPL-1.0']]                                <- 'mpl-1.0'
licenses[['MPL-1.1']]                                <- 'mpl-1.1'
licenses[['MPL-2.0']]                                <- 'mpl-2.0'
licenses[['MPL-2.0 | file LICENSE']]                 <- 'mpl-2.0\t# OR file LICENSE'
licenses[['POSTGRESQL']]                             <- 'postgresql-license'

adjacent.duplicates <- function(lines)
  c(FALSE, lines[-length(lines)] == lines[-1])

paste2 <- function(s1, s2) {
  if (is.na(s1) && is.na(s2)) return('')
  if (is.na(s1) && !is.na(s2)) return(s2)
  if (!is.na(s1) && is.na(s2)) return(s1)
  if (!is.na(s1) && !is.na(s2)) return(paste(s1, s2))
}

end.paragraph <- function(lines)
  if (length(lines) > 0) append(lines, '') else lines

as.sorted.list <- function(df) {
  l <- list()
  df <- df[!duplicated(df),]
  if (nrow(df) > 0)
  {
    key <- as.vector(df[, 1])
    value <- as.vector(df[, 2])
    key <- order(key, value)
    l <- as.list(value[key])
  }
  l
}

mklines.get_value <- function(mklines, varname, default = '') {
  values <- mklines$old_value[mklines$key == varname]
  if (length(values) == 0)
    values <- mklines$old_value[mklines$key == paste0('#', varname)]
  if (length(values) == 1) values[1] else default
}

categorize.key_value <- function(df, line='line') {
  re_varassign <- paste0(
    '^',
    ' *',
    '((?:#[\t ]*TODO[\t ]*:[\t ]*)*)',  # $old_todo
    '([^+=\t ]+)',  # varname ($key)
    '[\t ]*',
    '(\\+?=)',      # operator
    '([\t ]*)',     # delimiter
    '(.*)',         # value ($old_value)
    '$')

  va <- grepl(re_varassign, df[, line])
  df$key_value     <- va
  df$old_todo[va]  <- sub(re_varassign, '\\1', df[, line][va])
  df$key <- NA  # XXX: why is this line necessary here, and not in the other columns?
  df$key[va]       <- sub(re_varassign, '\\2', df[, line][va])
  df$operator[va]  <- sub(re_varassign, '\\3', df[, line][va])
  df$delimiter[va] <- sub(re_varassign, '\\4', df[, line][va])
  df$old_value[va] <- sub(re_varassign, '\\5', df[, line][va])
  df
}

categorize.depends <- function(df, line='line') {
  df$depends <- df$key_value & df$key == 'DEPENDS'
  df$category[df$depends] <- unlist(relpath_category(df[df$depends, line]))
  df
}

categorize.buildlink <- function(df, line='line')
{
  df$buildlink3.mk <- grepl('buildlink3.mk', df[, line])
  df$category[df$buildlink3.mk] <- relpath_category(df[df$buildlink3.mk, line])
  df
}

fix.continued.lines <- function(df, line='line') {
  if (nrow(df) < 2)
    return(df)

  continued <- grepl('\\\\$', df[, line])
  continued_key_value <- df$key_value & continued

  if (FALSE %in% df[continued, 'key_value'])
    level.warning('unhandled continued line(s)')

  for (i in 1:(nrow(df) - 1)) {
    if (!continued_key_value[i])
      next

    df[i, line] <- sub('[\t ]*\\\\$', '', df[i, line])
    df$key_value[i + 1] <- TRUE
    df$key[i + 1] <- df$key[i]
    df[i + 1, line] <- paste0(df$key[i], '+=', df[i + 1, line])
  }
  df
}

read_mklines <- function(filename) {
  df <- data.frame()
  for (line in as.list(readLines(filename)))
    df <- rbind(df, data.frame(line = line, stringsAsFactors = FALSE))

  df$order <- 1:nrow(df)

  df <- categorize.key_value(df)
  df <- fix.continued.lines(df)
  df$category <- NA
  df <- categorize.depends(df)
  df <- categorize.buildlink(df)
  df
}

read.file.as.list <- function(filename) {
  result <- list()
  info <- file.info(filename)
  size <- info[filename, 'size']
  if (!is.na(size) && size > 0) {
    contents <- readLines(filename)
    result <- as.list(contents)
  }
  result
}

simplify.whitespace <- function(s) gsub('[[:blank:]]+', ' ', s)
remove.punctuation <- function(s) gsub('[,-]', '', s)
remove.quotes <- function(s) gsub('[\'`"]', '', s)
remove.articles <- function(s) gsub('\\b[Aa]n?\\b', '\\1', s)

case.insensitive.equals <- function(s1, s2) {
  s1.lower <- tolower(simplify.whitespace(s1))
  s2.lower <- tolower(simplify.whitespace(s2))
  s1.lower == s2.lower
}

weakly.equals <- function(s1, s2) {
  case.insensitive.equals(
    remove.articles(remove.quotes(remove.punctuation(s1))),
    remove.articles(remove.quotes(remove.punctuation(s2))))
}

license <- function(mklines, s) {
  license <- licenses[[s]]
  if (is.null(license))
    license <- s
  old.license <- mklines.get_value(mklines, 'LICENSE')
  if (old.license != '' && old.license != license)
    license <- paste0(license, '\t# [R2pkg] previously: ', old.license)
  license
}

find.Rcpp <- function(imps, deps)
  any(grepl('Rcpp', paste(imps, deps)))

buildlink3.mk <- function(imps, deps) {
  BUILDLINK3.MK <- data.frame()
  buildlink3.mk.list <- read.file.as.list('BUILDLINK3.MK')
  for (line in buildlink3.mk.list) {
    fields <- strsplit(line[[1]], '/', fixed = TRUE)
    key <- fields[[1]][3]
    value <- line
    BUILDLINK3.MK <- rbind(BUILDLINK3.MK, data.frame(key = key, value = value))
  }
  if (find.Rcpp(imps, deps)) {
    buildlink3.line <- '.include "../../devel/R-Rcpp/buildlink3.mk"'
    key <- 'devel'
    value <- buildlink3.line
    BUILDLINK3.MK <- rbind(BUILDLINK3.MK, data.frame(key = key, value = value))
  }
  BUILDLINK3.MK
}

varassigns <- function(key, values) {
  fields <- list()
  for (l in values) {
    value <- unlist(l)
    if (value != '')
      fields <- append(fields, varassign(key, list(value)))
    else
      fields <- append(fields, list(''))
  }
  fields
}

categories <- function() basename(dirname(getwd()))

filter.imports <- function(s) {
  for (pkg in base.packages) {
    re.pkg <- paste('^', pkg, sep = '')
    s <- s[!grepl(re.pkg, s)]
  }
  s
}

make.imports <- function(s1, s2) {
  s <- paste2(s1, s2)
  s <- gsub('([[:alnum:]]+)[[:blank:]]+(\\([^\\)]*\\))?[[:blank:]]*,?', '\\1 \\2,', s)
  imports <- na.omit(unlist(strsplit(s, ',[[:blank:]]*')))
  imports <- trim.space(imports)
  imports <- filter.imports(imports)
  imports
}

make.dependency <- function(s) {
  s <- gsub('\\)', '', s)
  s <- gsub('-', '.', s)
  unlist(strsplit(s, '\\('))
}

depends <- function(dependency) dependency[1]

depends.pkg <- function(dependency)
  Sys.glob(paste0('../../*/R-', depends(dependency)))

new.depends.pkg <- function(dependency)
  Sys.glob(paste0('../../wip/R-', depends(dependency)))

depends.pkg.fullname <- function(dependency, index=1) {
  cmd <- sprintf('cd %s && %s show-var VARNAME=PKGNAME',
    depends.pkg(dependency)[index], arg.make)
  system(cmd, intern = TRUE)
}

depends.pkg.vers <- function(dependency, index=1) {
  result <- sub('^(.*)-([^-]*)$', '\\2', depends.pkg.fullname(dependency, index))
  result
}

depends.vers <- function(dependency, index=1) {
  if (length(dependency) == 2)
    trim.space(dependency[2])
  else
    trim.space(paste0('>=', depends.pkg.vers(dependency, index)))
}

depends.vers.2 <- function(dependency)
  ifelse(length(dependency) == 2, trim.space(dependency[2]), '>=???')

depends.dir <- function(dependency, index=1) {
  fields <- strsplit(depends.pkg(dependency)[index], '/', fixed = TRUE)
  fields[[1]][3]
}

depends.line <- function(dependency, index=1) {
  paste0('DEPENDS+=\tR-', depends(dependency), depends.vers(dependency, index), ':', depends.pkg(dependency)[index])
}

depends.line.2 <- function(dependency) {
  result <- paste0('DEPENDS+=\tR-', depends, depends.vers.2(dependency), ':../../???/R-', depends)
  result <- paste0(result, '\t# XXX - found')
  for (pkg in depends.pkg(dependency))
    result <- paste(result, pkg)
  result
}

buildlink3.file <- function(dependency, index=1)
  sprintf("%s/buildlink3.mk", depends.pkg(dependency)[index])

buildlink3.line <- function(dependency, index=1)
  sprintf('.include "%s"', buildlink3.file(dependency, index))

dependency.dir <- function(dependency) {
  result <- paste0('../../wip/R-', depends(dependency))
  result
}

message.wip.dependency <- function(dependency, index=1) {
  dir <- depends.dir(dependency, index)
  dir.in.wip <- grepl('wip', dir)
  wd.in.wip <- grepl('/wip/', getwd())
  if (dir.in.wip && !wd.in.wip)
    level.warning('R-', arg.rpkg, ' should not depend on a wip package: ', depends.pkg(dependency)[index])
}

update.dependency <- function(dependency, index=1) {
  level.message('updating dependency for ', depends(dependency), ': ', depends.pkg(dependency)[index])
  command <- sprintf(
    'grep -E -q -e "%s" %s || (cd %s && %s %s %s)',
    depends(dependency), arg.packages_list,
    depends.pkg(dependency)[index], arg.r2pkg, arg.args, depends(dependency))
  error <- system(command)
  if (error != 0)
    level.warning('error updating dependency for ', depends(dependency))
}

make.depends <- function(imps, deps) {
  warn_too_many_dependencies <- function(dependency) {
    level.warning(sprintf('too many dependencies found for %s: %s',
                          depends(dependency), paste(depends.pkg(dependency))))
  }

  imports <- make.imports(imps, deps)
# XXX  message('===> imports:')
# XXX print(imports)
  DEPENDS <- data.frame()
  BUILDLINK3.MK <- buildlink3.mk(imps, deps)
  if (length(imports) > 0) {
    for (i in 1:length(imports)) {
      dependency <- make.dependency(imports[i])
      depdirs <- depends.pkg(dependency)
      # XXX message('[ ',${LEVEL},' ] ===> ',i,' / ',length(imports),': ',depends(dependency))
      if (length(depdirs) == 0) { # a dependency cannot be found
        level.message('0 dependencies match ', dependency)
        if (arg.recursive) {
          dir.create(path = dependency.dir(dependency), recursive = TRUE)
          update.dependency(dependency)
        } else
          level.warning('dependency needed for ', depends(dependency))
      } else if (length(depdirs) == 1) { # a unique dependency found
        level.message('1 dependency matches ', dependency, ': ', depdirs)
        message.wip.dependency(dependency)
        if (arg.recursive && arg.update)
          update.dependency(dependency)
        if (file.exists(buildlink3.file(dependency)))
          BUILDLINK3.MK <- rbind(BUILDLINK3.MK, data.frame(key = depends.dir(dependency), value = buildlink3.line(dependency)))
        else
          DEPENDS <- rbind(DEPENDS, data.frame(key = depends.dir(dependency), value = depends.line(dependency)))
      } else if (length(depdirs) == 2) { # two dependencies found
        index <- grep('/wip/', depdirs, invert = TRUE)
        level.message('2 dependencies match ', dependency, ':', paste(' ', depdirs))
      # message('===> depends(dependency): ',depends(dependency))
      # message('===> depends.pkg(dependency):',paste(' ',d))
      # message('===> index: ',index)
      # message('===> buildlink3.line(): ',buildlink3.line(dependency,index))
        if (length(index) == 1) { # a unique, non-wip, dependency found
          level.message('choosing unique non-wip dependency for ', dependency, ': ', depdirs[index])
          if (arg.recursive && arg.update)
            update.dependency(dependency, index)
          if (file.exists(buildlink3.file(dependency, index)))
            BUILDLINK3.MK <- rbind(BUILDLINK3.MK, data.frame(key = depends.dir(dependency, index), value = buildlink3.line(dependency, index)))
          else
            DEPENDS <- rbind(DEPENDS, data.frame(key = depends.dir(dependency, index), value = depends.line(dependency, index)))
        } else {
          level.message('no unique non-wip dependency matches')
          warn_too_many_dependencies(dependency)
          DEPENDS <- rbind(DEPENDS, data.frame(key = '???', value = depends.line.2(dependency)))
        }
      } else { # more than 2 dependencies found
        level.message(length(depdirs), ' dependencies match ', dependency, ':', paste(' ', depdirs))
        warn_too_many_dependencies(dependency)
        DEPENDS <- rbind(DEPENDS, data.frame(key = '???', value = depends.line.2(dependency)))
      }
      if (length(new.depends.pkg(dependency)) > 0)
        system(paste('echo', depends(dependency), arg.rpkg, '>>', arg.dependency_list))
    }
  }
  DEPENDS <- end.paragraph(as.sorted.list(DEPENDS))
  BUILDLINK3.MK <- as.sorted.list(BUILDLINK3.MK)
  list(DEPENDS, BUILDLINK3.MK)
}

use_languages <- function(imps, deps)
  if (find.Rcpp(imps, deps)) 'c c++' else '# none'

write.Makefile <- function(orig_mklines, metadata) {
  maintainer    <- mklines.get_value(orig_mklines, 'MAINTAINER', arg.maintainer_email)
  license       <- license(orig_mklines, metadata$License)
  use_languages <- use_languages(metadata$Imports, metadata$Depends)
  dependencies  <- make.depends(metadata$Imports, metadata$Depends)

  lines <- c(
    mkcvsid,
    '',
    varassign('R_PKGNAME', one.line(metadata$Package)),
    varassign('R_PKGVER', one.line(metadata$Version)),
    varassign('CATEGORIES', categories()),
    '',
    varassign('MAINTAINER', maintainer),
    varassign('COMMENT', one.line(metadata$Title)),
    varassign('LICENSE', license),
    '',
    dependencies[1],
    varassign('USE_LANGUAGES', use_languages),
    '',
    '.include "../../math/R/Makefile.extension"',
    dependencies[2],
    '.include "../../mk/bsd.pkg.mk"',
    recursive = TRUE)

  writeLines(lines, 'Makefile')
}

element <- function(mklines, varname, field, quiet=FALSE) {
  i <- match(varname, mklines$key, 0)
  if (i != 0 && mklines$key_value[i])
    return(mklines[i, field])

  if (!quiet) {
    if (i == 0)
      level.warning(varname, ' not found')
    else
      level.warning(varname, ' is not a key-value field')
  }
  '???'
}

make.categories <- function(mklines) {
  directory <- basename(dirname(getwd()))
  categories <- unlist(element(mklines, 'CATEGORIES', 'old_value'))
  categories <- unlist(strsplit(categories, '[[:blank:]]+'))
  categories <- c(directory, categories)
  categories <- categories[categories != 'R']
  if (directory != 'wip')
    categories <- categories[categories != 'wip']
  categories <- categories[!duplicated(categories)]
  paste(categories, collapse = ' ')
}

make.maintainer <- function(mklines) {
  old.maintainer <- element(mklines, 'MAINTAINER', 'old_value')
  new.maintainer <- element(mklines, 'MAINTAINER', 'new_value')
  if (old.maintainer == '') new.maintainer else old.maintainer
}

make.comment <- function(mklines) {
  old.comment <- element(mklines, 'COMMENT', 'old_value')
  new.comment <- element(mklines, 'COMMENT', 'new_value')
  if (weakly.equals(old.comment, new.comment))
    old.comment
  else
    paste0(old.comment, '\t# [R2pkg] updated to: ', new.comment)
}

make.new_license <- function(df, license) {
  new_license <- licenses[[license]]
  if (is.null(new_license))
    new_license <- license
  df$new_value[df$key == 'LICENSE'] <- new_license
  df
}

license.in.pkgsrc <- function(license) license %in% sapply(licenses, '[', 1)

make.license <- function(df) {
  # message('===> make.license():')
  old_license <- element(df, 'LICENSE', 'old_value')
  old_todo <- element(df, 'LICENSE', 'old_todo')
  new_license <- element(df, 'LICENSE', 'new_value')

  old_known <- license.in.pkgsrc(old_license)
  new_known <- license.in.pkgsrc(new_license)

  license <- if (!old_known)
    paste0(new_license, '\t# [R2pkg] previously: ', old_license)
  else if (!new_known)
    paste0(old_license, '\t# [R2pkg] updated to: ', new_license)
  else if (case.insensitive.equals(old_license, new_license))
    old_license
  else
    paste0(new_license, '\t# [R2pkg] previously: ', old_license)

  df$value[df$key == 'LICENSE'] <- license
  df$todo[df$key == 'LICENSE'] <- if (new_known) old_todo else '# TODO: '

  df
}

find.order <- function(df, key, field) {
  x <- df[, key]
  value <- match(TRUE, x)
  if (!is.na(value))
    value <- df[value, field]
  value
}

mklines.update_with_metadata <- function(df, metadata) {
  df$new_value <- NA

  df <- make.new_license(df, metadata$License)

  df$new_value[df$key == 'CATEGORIES'] <- categories()
  df$new_value[df$key == 'MAINTAINER'] <- arg.maintainer_email
  df$new_value[df$key == 'COMMENT'] <- one.line(metadata$Title)
  df$new_value[df$key == 'R_PKGVER'] <- one.line(metadata$Version)
  df
}

mklines.update_value <- function(df) {
  df$value <- NA
  df$todo <- ''
  df <- make.license(df)
  df$value[df$key == 'CATEGORIES'] <- make.categories(df)
  df$value[df$key == 'MAINTAINER'] <- make.maintainer(df)
  df$value[df$key == 'COMMENT'] <- make.comment(df)
  df$value[df$key == 'R_PKGVER'] <- element(df, 'R_PKGVER', 'new_value')
  df
}

mklines.update_new_line <- function(df) {
  df$new_line <- df$line

  va <- df$key_value & !is.na(df$value)
  df$new_line[va] <- paste0(
    df$todo[va], df$key[va], df$operator[va], df$delimiter[va], df$value[va])

  df
}

mklines.annotate_distname <- function(mklines) {
  match <- grepl('^[[:blank:]]*DISTNAME', mklines$new_line)
  line <- mklines$new_line[match]
  value <- sub('^[[:blank:]]*DISTNAME[[:blank:]]*=[[:blank:]]*', '', line)
  comment <- sprintf(
    '\t# [R2pkg] replace this line with %s and %s as first stanza',
    paste0('R_PKGNAME=', sub('_.+$', '', value)),
    paste0('R_PKGVER=', sub('^.+_', '', value)))
  mklines$new_line[match] <- paste0(line, comment)
  mklines
}

mklines.remove_lines_before_update <- function(mklines) {
  remove <- (
    grepl('^[[:blank:]]*MASTER_SITES', mklines$new_line) |
      grepl('^[[:blank:]]*HOMEPAGE', mklines$new_line) |
      grepl('^[[:blank:]]*BUILDLINK_ABI_DEPENDS', mklines$new_line) |
      grepl('^[[:blank:]]*BUILDLINK_API_DEPENDS', mklines$new_line))
  mklines[!remove,]
}

mklines.reassign_order <- function(mklines) {
  r_pkgname.order <- element(mklines, 'R_PKGNAME', 'order')
  categories.order <- element(mklines, 'CATEGORIES', 'order')
  if (r_pkgname.order > categories.order) {
    r_pkgname.index <- mklines$key == 'R_PKGNAME'
    r_pkgname.index[is.na(r_pkgname.index)] <- FALSE
    r_pkgver.index <- mklines$key == 'R_PKGVER'
    r_pkgver.index[is.na(r_pkgver.index)] <- FALSE
    mklines[r_pkgname.index, 'order'] <- categories.order - 0.2
    mklines[r_pkgver.index, 'order'] <- categories.order - 0.1
  }
  mklines
}

conflicts <- function(pkg) {
  conflict <- sprintf('R>=%s.%s', R.version$major, R.version$minor)
  conflicts <- list()
  if (pkg %in% base.packages) {
    conflicts <- append(conflicts, varassign('CONFLICTS', conflict))
    conflicts <- end.paragraph(conflicts)
  }
  conflicts
}

make.df.conflicts <- function(df, metadata) {
  df.conflicts <- data.frame()
  conflicts.exist <- element(df, 'CONFLICTS', 'old_value', quiet = TRUE) != '???'
  if (!conflicts.exist) {
    c <- conflicts(metadata$Package)
    order <- element(df, 'COMMENT', 'order') + 2.5
    i <- 0
    for (conflict in c) {
      i <- i + 1
      category <- as.character(i)
      depends <- FALSE
      buildlink3.mk <- FALSE
      x <- data.frame(new_line = conflict, order = order, category = category, depends = depends, buildlink3.mk = buildlink3.mk)
      df.conflicts <- rbind(df.conflicts, x)
    }
  }
  df.conflicts
}

make.df.depends <- function(df, DEPENDS) {
  # message('===> make.df.depends():')
  # str(df)
  # print(df)
  df.depends <- data.frame()
  if (TRUE %in% df$depends)
    df.depends <- data.frame(new_line = df[df$depends, 'line'], stringsAsFactors = FALSE)
  for (line in DEPENDS)
    df.depends <- rbind(df.depends, data.frame(new_line = line, stringsAsFactors = FALSE))
  if (nrow(df.depends) > 0) {
    df.depends$category <- NA
    df.depends$buildlink3.mk <- FALSE
    df.depends <- categorize.key_value(df.depends, 'new_line')
    df.depends <- categorize.depends(df.depends, 'new_line')
    df.depends$key_value <- NULL
    df.depends$key <- NULL
    df.depends <- df.depends[!duplicated(df.depends),]
    df.depends$order <- find.order(df, 'depends', 'order')
    fields <- c('new_line', 'order', 'category', 'depends', 'buildlink3.mk')
    df.depends <- df.depends[,fields]
  }
  # message('===> df.depends:')
  # str(df.depends)
  # print(df.depends)
  df.depends
}

make.df.buildlink3 <- function(df, BUILDLINK3.MK) {
  # message('===> make.df.buildlink3():')
  df.buildlink3.mk <- data.frame()
  if (TRUE %in% df$buildlink3.mk)
    df.buildlink3.mk <- data.frame(new_line = df[df$buildlink3.mk, 'line'], stringsAsFactors = FALSE)
  for (line in BUILDLINK3.MK)
    df.buildlink3.mk <- rbind(df.buildlink3.mk, data.frame(new_line = line, stringsAsFactors = FALSE))
  if (nrow(df.buildlink3.mk) > 0) {
    df.buildlink3.mk$category <- NA
    df.buildlink3.mk$depends <- FALSE
    df.buildlink3.mk <- categorize.buildlink(df.buildlink3.mk, 'new_line')
    df.buildlink3.mk <- df.buildlink3.mk[!duplicated(df.buildlink3.mk),]
    df.buildlink3.mk$order <- find.order(df, 'buildlink3.mk', 'order')
  }
  # str(df.buildlink3.mk)
  # print(df.buildlink3.mk)
  df.buildlink3.mk
}

#' updates the dependencies and returns the lines to be written to the
#' updated package Makefile.
mklines.lines <- function(mklines, df.conflicts, df.depends, df.buildlink3.mk) {
  fields <- c('new_line', 'order', 'category', 'depends', 'buildlink3.mk')
  lines <- mklines[!mklines$depends & !mklines$buildlink3.mk, fields]
  lines <- rbind(lines, df.conflicts, df.depends, df.buildlink3.mk)
  lines <- lines[order(lines$order, lines$category, lines$new_line),]
  lines <- lines[!adjacent.duplicates(lines$new_line),]
  lines$new_line
}

update.Makefile <- function(mklines, metadata) {
  DEPENDENCIES  <- make.depends(metadata$Imports, metadata$Depends)
  DEPENDS       <- DEPENDENCIES[[1]]
  BUILDLINK3.MK <- DEPENDENCIES[[2]]

  mklines <- mklines.update_with_metadata(mklines, metadata)
  mklines <- mklines.update_value(mklines)
  mklines <- mklines.update_new_line(mklines)
  mklines <- mklines.annotate_distname(mklines)
  mklines <- mklines.remove_lines_before_update(mklines)
  mklines <- mklines.reassign_order(mklines)

  conflicts   <- make.df.conflicts(mklines, metadata)
  depends     <- make.df.depends(mklines, DEPENDS)
  buildlink3  <- make.df.buildlink3(mklines, BUILDLINK3.MK)
  lines       <- mklines.lines(mklines, conflicts, depends, buildlink3)

  write(lines, 'Makefile')
}

create.Makefile <- function(metadata) {
  if (arg.update && file.exists('Makefile.orig'))
    update.Makefile(read_mklines('Makefile.orig'), metadata)
  else
    write.Makefile(read_mklines(textConnection('')), metadata)
}

create.DESCR <- function(metadata) {
  descr <- strwrap(metadata$Description, width = 71)
  write(descr, 'DESCR')
}

make.metadata <- function(description.filename) {
  fields <- c('Package', 'Version', 'Title', 'Description', 'License', 'Imports', 'Depends')
  metadata <- as.list(read.dcf(description.filename, fields))
  names(metadata) <- fields
  metadata
}

main <- function() {
  Sys.setlocale('LC_ALL', 'C')

  error <- download.file(url = arg.rpkg_description_url, destfile = 'DESCRIPTION', quiet = arg.quiet_curl, method = 'curl')
  if (error) {
    message('ERROR: Downloading the DESCRIPTION file for ', arg.rpkg, ' failed;')
    message('       perhaps the package no longer exists?')
    quit(save = 'no', status = error)
  }

  metadata <- make.metadata('DESCRIPTION')
  create.Makefile(metadata)
  create.DESCR(metadata)
}
