GARCHX_estimate <- function(fit, optim.method){
  # fit has the following attributes:
  # eps, X
  # p, q, d
  # omega, alpha, beta, gamma, delta, r

  # Calculate length of time series data
  n <- length(fit$eps)

  # Assign initial values for omega, alpha, beta, and gamma
  fit$alpha <- rep(0.05, fit$order[1])
  # treat when ARCH model for q = 0 and X is NULL
  if (fit$order[2] == 0) {
    fit$beta = NULL
  } else {
    fit$beta = rep(0.05, fit$order[2])
  }
  if (fit$d == 0) {
    fit$gamma = NULL
  } else {
    fit$gamma = rep(1, fit$d)
  }
    
  # Store coefficients in a list for minimization ## ARCH coef alpha are always present
  
  valinit <- c(0.05, fit$alpha, fit$beta, fit$gamma)

  # Initialize Sigma^delta
  sigma.init <- mean(fit$eps[1:fit$r]^fit$delta)

  # Calculate epsilon^delta
  eps.delta <- fit$eps^fit$delta

  # Optimization Function Calls
  if(optim.method == "NR"){
    res <- stats::nlminb(valinit, GARCHX_objf, lower = c(0.0000001, rep(0, sum(fit$order)), rep(0, fit$d)),
                  upper = c(100, rep(0.9999, sum(fit$order)), rep(100, fit$d)),
                  delta = fit$delta, order = fit$order, d = fit$d, r = fit$r, eps = fit$eps,
                  n = n, X = fit$selectedX, eps.delta = eps.delta, sigma.init = sigma.init
                  )
    objective_value <- res$objective
  } else if(optim.method %in% c("L-BFGS-B")){
    # optim does minimization by default
    # upper and lower is only for L-BFGS-B and Brent
    res <- stats::optim(par = valinit, fn = GARCHX_objf, delta = fit$delta, order = fit$order, d = fit$d, r = fit$r, eps = fit$eps,
                 n = n, X = fit$selectedX, eps.delta = eps.delta, sigma.init = sigma.init,
                 method = optim.method,
                 lower = c(0.0000001, rep(0, sum(fit$order)), rep(0, fit$d)),
                 upper = c(100, rep(0.9999, sum(fit$order)), rep(100, fit$d)))
    objective_value <- res$value
  } else if(optim.method == "GA"){
    fitness_fn <- function(coefs){
      -GARCHX_objf(coefs, delta = fit$delta, order = fit$order, d = fit$d, r = fit$r, eps = fit$eps,
            n = n, X = fit$selectedX, eps.delta = eps.delta, sigma.init = sigma.init
            )
    } # End fitness_fn
    sol <- GA::ga(type = "real-valued", fitness = fitness_fn,
              lower = c(0.0000001, rep(0, sum(fit$order)), rep(0, fit$d)),
              upper = c(100, rep(0.9999, sum(fit$order)), rep(100, fit$d)),
              maxiter = 100, monitor = FALSE)
    res <- list(par = sol@solution)
    objective_value <- -sol@fitnessValue
  } else if(optim.method == "SA"){
    res <- GenSA::GenSA(par = valinit, fn = GARCHX_objf,
                 lower = c(0.0000001, rep(0, sum(fit$order)), rep(0, fit$d)),
                 upper = c(100, rep(0.9999, sum(fit$order)), rep(100, fit$d)),
                 delta = fit$delta, order = fit$order, d = fit$d, r = fit$r,
                 eps = fit$eps, n = n, X = fit$selectedX, eps.delta = eps.delta,
                 sigma.init = sigma.init)
    objective_value <- res$value
  } else if(optim.method == "PS"){
    res <- pso::psoptim(par = valinit, fn = GARCHX_objf, gr = NULL, delta = fit$delta,
                   order = fit$order, d = fit$d, r = fit$r, eps = fit$eps, n = n,
                   X = fit$selectedX, eps.delta = eps.delta,
                   sigma.init = sigma.init,
                   lower = c(0.0000001, rep(0, sum(fit$order)), rep(0, fit$d)),
                   upper = c(100, rep(0.9999, sum(fit$order)), rep(100, fit$d)))
    objective_value <- res$value
  } # End if elses

  # Extract coefficients
  p <- fit$order[1]
  q <- fit$order[2]
  d <- fit$d
  omega <- res$par[1]
  alpha <- res$par[2:(p+1)]
  if(q != 0){
    beta <- res$par[(p+2):(p+q+1)]
  } else {
    beta <- NULL
  } # End if


  if(d != 0){
    gamma <- res$par[(p+q+2):(1+p+q+fit$d)]
  } else {
    gamma <- NULL
  } # End if

  # Calculate variance of GARCHX
  varasymp <- GARCHX_variance(fit, omega, alpha, beta, gamma)

  # Return list of coef, sd, t.ratio, pval, Sigma, I matrix, J matrix, and minimum
  list(coef = res$par, sd = varasymp$sd, t.ratio = varasymp$t.ratio,
       pval = varasymp$pval, Sigma = varasymp$Sigma, I = varasymp$I,
       J = varasymp$J, minimum = objective_value)
} # End estimate.GARCHXselect
