#' Calculate growth curves
#'
#' Calculate, compare, and plot growth curves based on different models
#'
#' @param x (data frame) Output from \code{MRgrowth::format.data()} Data frame with columns of size at capture, size at recapture, and amount of time between captures.
#' @param size0 (numeric) Initial body size at birth/hatching (should be in the same units used to calculate A and k). This will generally be a species average, rather than being specific per individual, but it can be a vector of specific birth/hatching sizes per individual if known. Note that this will not affect the final values of A, k, or their confidence intervals, but it will affect the estimated sizes at ages.
#' @param age.vect (numeric vector) Vector of ages for which you would like expected sizes and confidence intervals around the size (used for plotting growth curves). If age.vect is not supplied, a vector from 1 to 4x the largest time interval will be used (default = NULL)
#' @param runs (integer) The number of bootstraps to use for calculating confidence intervals. Large values result in long run times (see Vignette; default = 5000)
#' @param models (character vector) The model(s) used to calculate A and k. Can be any combination of "vb", "gompertz","logistic". Fitted parameters, model diagnostics, and sizes at expected ages will be returned for all included models (default = c("vb", "gompertz","logistic"))
#' @param fixed.A (numeric) This fixes the asymptotic size (A) at the specified value, meaning that only the growth rate (k) will be estimated. Use with caution (see vignette; default = NULL)
#' @param initial.A (numeric) Starting value for the search algorithm for the asymptotic size (A). This has no impact on the results, but an initial.A that is close to the final A will result in a faster run time (default = max size in your data set).
#' @param initial.k (numeric) Starting value for the search algorithm for the growth rate (k). This has no impact on the results, but an initial.k that is close to the final k will result in a faster run time (default = 0.5).
#' @param size1.col (character) Name of column containing size data at capture (default = "size1"; the output from \code{MRgrowth::format.data()})
#' @param size2.col (character) Name of column containing size data at recapture (default = "size2"; the output from \code{MRgrowth::format.data()})
#' @param times.col (character) Name of column containing time interval between captures (default = "td"; the output from \code{MRgrowth::format.data()})
#' @param type (character) Method used of fitting the model. One of "ML" (Maximum Likelihood) or "OLS" (Ordinary Least Squares; default = "ML")
#' @param seed (integer) Seed to use for random selections when calculating bootstrapped confidence intervals (default = 1701).
#' @param return.plot (logical) Determines whether a plot of growth curves should be printed (default = TRUE)
#' @param optimizer (character) One of the optimization methods available in \code{\link[stats]{optim}} (default = "L-BFGS-B")
#'
#' @return A list of two objects:
#' \describe{
#'  \item{parameters}{A data frame with one row per model containing the following columns:
#'     \describe{
#'       \item{model}{The growth model fitted}
#'       \item{A}{Estimated asymptotic size}
#'       \item{A.lower}{Lower 95\% bootstrap confidence interval for A}
#'       \item{A.upper}{Upper 95\% bootstrap confidence interval for A}
#'       \item{k}{Estimated growth coefficient}
#'       \item{k.lower}{Lower 95\% bootstrap confidence interval for k}
#'       \item{k.upper}{Upper 95\% bootstrap confidence interval for k}
#'       \item{convergence}{Convergence code from \code{\link[stats]{optim}}; 0 indicates successful convergence}
#'       \item{NLL}{Negative log-likelihood of the fitted model (only returned when \code{type="ML"})}
#'       \item{SS}{Sum of squares (only returned when \code{type="OLS"})}
#'       \item{AIC}{Akaike Information Criterion (only returned when \code{type="ML"})}
#'       \item{AICc}{Sample size corrected AIC (only returned when \code{type="ML"})}
#'       \item{fixed.AIC}{Akaike Information Criterion calculated with 2 parameters when A is fixed (only returned when \code{type="ML"} and a value is entered for \code{fixed.A})}
#'       \item{fixed.AICc}{Sample size corrected AIC calculated with 2 parameters when A is fixed (only returned when \code{type="ML"} and a value is entered for \code{fixed.A})}
#'
#'     }
#'   }
#'   \item{sizes.at.ages}{A data frame with one row per age per model containing the following columns:
#'     \describe{
#'       \item{model}{The growth model fitted}
#'       \item{age}{Age from \code{age.vect}}
#'       \item{size}{Predicted size at a given age}
#'       \item{upper}{Upper 95\% bootstrap confidence interval for predicted size}
#'       \item{lower}{Lower 95\% bootstrap confidence interval for predicted size}
#'     }
#'   }
#' }
#'
#' @details
#' This is the primary function for MRgrowth. It takes the output from \code{format.data()}, calculates growth curves the selected model(s), and returns the model parameters and the data for plotting curves. The output includes model diagnostics like AIC values that can be used to evaluate model fit and compare models.
#'
#' @examples
#'
#' data(MRdata)
#'
#' #format the data
#' formatted.data <- format.data(
#'  x = MRdata,
#'  id.col = "id",
#'  date.col = "date",
#'  size.col = "size",
#'  units = "years",
#'  retain = "max")
#'
#' #get growth curves for the gompertz and logistic methods.
#' #runs are set to 10 to keep the run time low.
#' #For actual CI estimates, much higher values are needed
#' #likewise, the plot function has been turned off
#' g.result <- calc.growth(formatted.data,
#'    size0=50,
#'    age.vect=c(1:40),
#'    models=c("gompertz","logistic"),
#'    runs=10,
#'    return.plot=FALSE)
#'
#' @references
#' von Bertalanffy, L. (1957). Quantitative laws in metabolism and growth.
#' \emph{The Quarterly Review of Biology}, 32, 217-231.
#'
#' Fabens, A.J. (1965). Properties and fitting of the Von Bertalanffy growth curve.
#' \emph{Growth}, 29, 265-289.
#'
#' Gompertz, B. (1833). On the nature of the function expressive of the law of human
#' mortality, and on a new mode of determining the value of life contingencies.
#' \emph{Philosophical Transactions of the Royal Society of London}, 123, 252-253.
#'
#' Schoener, T.W. and Schoener, A. (1978). Estimating and interpreting body-size
#' growth in some Anolis lizards. \emph{Copeia}, 1978, 390-405.
#'
#' @export
calc.growth <- function(x,size0=NULL,age.vect=NULL,models = c("vb","gompertz","logistic"),fixed.A=NULL,initial.A=NULL,initial.k=0.5,size1.col="size1",size2.col="size2",times.col="td",type="ML",runs=5000,seed=1701,return.plot=TRUE,optimizer="L-BFGS-B"){

  if(!all(models %in% c("vb", "gompertz", "logistic"))){
    stop("Invalid model name(s). The models argument must only contain only 'vb', 'gompertz', and/or 'logistic'.", call. = FALSE)}

  if(length(type) != 1 || !type %in% c("OLS", "ML")){
    stop("Invalid type. The type argument must contain one of 'OLS' or 'ML'.", call. = FALSE)}

  #set lower bounds for models based on input settings
  if (is.null(fixed.A)) {
    ssq.lower <- c(1e-6, 1e-6)
    ml.lower  <- c(1e-6, 1e-6, 1e-6)
  } else {
    ssq.lower <- c(1e-6)
    ml.lower  <- c(1e-6, 1e-6)
  }

  #rename columns
  colnames(x)[which(colnames(x)==size1.col)] <- "size1"
  colnames(x)[which(colnames(x)==size2.col)] <- "size2"
  colnames(x)[which(colnames(x)==times.col)] <- "times"

  #subset to only necessary columns and remove NAs
  x <- x[,c("size1","size2","times")]
  x <- na.omit(x)

  #extract as vectors for later functions
  size1 <- x[,"size1"]
  size2 <- x[,"size2"]
  times <- x[,"times"]


  if(is.null(age.vect)==TRUE){
    warning("No values were provided for age.vect. This will not affect the estimation of A, k, or their associated CIs, but predicted sizes will be calculated and plotted for age 1 to 4 times the maximum time difference in your data set. This may not cover the full age range of your species. Update the age.vect argument accordingly.")
    age.vect <- 1:round((max(times)*4))}

  if(is.null(size0)==TRUE){
    warning("No value was provided for size0. This will not affect the estimation of A, k, or their associated CIs, but will affect the predicted sizes at each age in age.vect and the associated plot. The smallest size in your data divided by 2 has been used as size0. This may give highly misleading predicted sizes. Please update with an appropriate value for your species")
    size0 <- min(size1)/2}

  if(is.null(initial.A)==TRUE){initial.A <-max(size2,na.rm=TRUE)}

  start.vals <- c(A=initial.A,k=initial.k) #make vector of start values

  #if(is.null(fixed.A)==FALSE){start.vals <- start.vals[2]} #changed.here

  sizes.a <- vector("list",length(models)) #make empty list
  params.a <- vector("list",length(models)) #make empty list
  for(a in 1:length(models)){

    model <- models[a]

    #calculate with least squares first to get good starting values for ML
    #changed here
    fit1 <- optim(if(is.null(fixed.A)==FALSE){start.vals[2]}else{start.vals}, ssq , size1=size1, size2=size2, td=times,method = optimizer,lower = ssq.lower,model=model,fixed.A=fixed.A)

    converg <- fit1$convergence

    if(is.null(fixed.A)==TRUE){
      fit <- fit1$par}else{ #get parameters
      fit <- fit1$par
      fit[2] <- fit[1]
      fit[1] <- fixed.A}

    if(type == "ML"){

      if(is.null(fixed.A)==TRUE){
        start.vals.ml <- c(fit[1],fit[2],sigma=sqrt(fit1$value/nrow(x)))}else{ #update starting values based on ssq
          start.vals.ml <- c(fit[2],sigma=sqrt(fit1$value/nrow(x)))}

      #get ML estimate
      fit.ml <- optim(start.vals.ml,ml,model=model,size1= size1,td= times, size2 = size2,method = optimizer,  lower  = ml.lower,fixed.A=fixed.A  )

      NLL <- fit.ml$value #negative log likelihood
      converg <- fit.ml$convergence

      if(is.null(fixed.A)==TRUE){
          fit <- fit.ml$par

          #get AIC and AICc values
          AIC  <- 2 * NLL + 2 * 3
          AICc <- AIC + (2 * 3 * (3 + 1)) / (nrow(x) - 3 - 1)}else

            {
            fit <- fit1$par
            fit[2] <- fit[1]
            fit[1] <- fixed.A
            fit[[3]] <- fit1$par[2]
            names(fit)[3] <- "sigma"

            #get AIC and AICc values
            AIC  <- 2 * NLL + 2 * 3 #calculates with 3 parameters even though only 2 were tested
            AICc <- AIC + (2 * 3 * (3 + 1)) / (nrow(x) - 3 - 1) #calculates with 3 parameters even though only 2 were tested
            fixed.AIC  <- 2 * NLL + 2 * 2 #calculates with 2 parameters for the fixed AIC
            fixed.AICc <- fixed.AIC + (2 * 2 * (2 + 1)) / (nrow(x) - 2 - 1)}
    }

    #calculate size at each age for the vector of ages provided
    sizes.at.ages <- size.at.age(fit[1],fit[2],age=age.vect,size0=size0,model=model)

  #get confidence intervals
    CI <- if(type == "ML"){
      bootstrap.ml(nboot = runs, size1=size1, size2=size2, td=times, start.vals=start.vals, ages=age.vect, size0=size0,model=model,seed=seed,fixed.A=fixed.A,optimizer=optimizer)}else{
        bootstrap.ssq(nboot = runs, size1=size1, size2=size2, td=times, start.vals=start.vals, ages=age.vect, size0=size0,model=model,seed=seed,fixed.A=fixed.A,optimizer=optimizer)}

    param <- CI$params.ci[1:2,1:4] #remove unneeded columns and rows
    param[1:2,"est"] <- fit[1:2] #replace the bootstrap fitted values with the final fitted values

    param <- rbind.data.frame(c(param[1,2:4],param[2,2:4]))
    colnames(param) <- c("A","A.lower","A.upper","k","k.lower","k.upper")
    param$convergence <- converg
    if(type=="ML"){
      param$NLL <- NLL
      param$AIC <- AIC
      param$AICc <- AICc
      if(is.null(fixed.A)==FALSE){
        param$fixed.AIC <- fixed.AIC
        param$fixed.AICc <- fixed.AICc      }
    }else{
        param$SS <- fit1$value
      }
    params.a[[a]] <- cbind.data.frame(model=model,param)

    sizes.a[[a]] <- cbind.data.frame(model=model,age = CI$size.ci$ages,size=sizes.at.ages,upper=CI$size.ci$upper,lower=CI$size.ci$lower)

  } #close for a in 1:length(models)

  params.a <- do.call("rbind.data.frame",params.a)

  sizes.a <- do.call("rbind.data.frame",sizes.a)

  if(return.plot==TRUE){
    models.p <- unique(sizes.a$model)
    cols   <- setNames(c("#0472C1","#FFC928","#6D6D6D")[1:length(models.p)],
                       models.p)  # extend if more than 3 models

    plot(NULL,
         xlim = range(sizes.a$age),
         ylim = range(c(sizes.a$upper, sizes.a$lower)),
         xlab = "Age",
         ylab = "Size",
         las  = 1)

    for (m in models.p) {
      sub <- sizes.a[sizes.a$model == m, ]
      sub <- sub[order(sub$age), ]   # ensure lines connect in order

      # Shaded CI ribbon — no border
      polygon(x      = c(sub$age, rev(sub$age)),
              y      = c(sub$upper, rev(sub$lower)),
              col    = adjustcolor(cols[m], alpha.f = 0.2),
              border = NA)

      # Central line
      lines(sub$age, sub$size,
            col = cols[m],
            lwd = 3)
    }

    legend("bottomright",
           legend = models.p,
           col    = cols[models.p],
           lwd    = 3,
           bty    = "n")}


  list(parameters = params.a,sizes.at.ages = sizes.a)
}





