HoltWinters <-
function (x,

          # smoothing parameters
          alpha    = NULL, # level
          beta     = NULL, # trend
          gamma    = NULL, # seasonal component
          seasonal = c("additive", "multiplicative"),
          start.periods = 3,

          # starting values
          l.start  = NULL, # level
          b.start  = NULL, # trend
          s.start  = NULL, # seasonal components vector of length `period'

          # starting values for optim
          optim.start = c(alpha = 0.3, beta = 0.1, gamma = 0.1),
          optim.control = list()
          )
{
    x <- as.ts(x)
    seasonal <- match.arg(seasonal)
    f <- frequency(x)

    if(!is.null(alpha) && alpha==0)
        stop ("cannot fit models without level ('alpha' must not be 0).")
    if(!all(is.null(c(alpha, beta, gamma))) &&
        any(c(alpha, beta, gamma) < 0 || c(alpha, beta, gamma) > 1))
        stop ("'alpha', 'beta' and 'gamma' must be within the unit interval.")
    if((is.null(gamma) || gamma > 0)) {
        if (seasonal == "multiplicative" && any(x <= 0))
            stop ("data must be strictly non-negative for multiplicative Holt-Winters")
        if (start.periods < 3)
            stop ("need at least 3 periods to compute seasonal start values")
    }

    ## initialization
    if(!is.null(gamma) && gamma == 0) {
        ## non-seasonal Holt-Winters
        expsmooth <- !is.null(beta) && (beta == 0)
        if(is.null(l.start))
            l.start <- if(expsmooth) x[1] else x[2]
        if(is.null(b.start))
            if(is.null(beta) || beta > 0) b.start <- x[2] - x[1]
        start.time <- 3 - expsmooth
        s.start    <- 0
    } else {
        ## seasonal Holt-Winters
        start.time <- f + 1
        wind       <- start.periods * f

        ## decompose series
        st <- decompose(ts(x[1:wind], start = start(x), frequency = f),
                        seasonal)

        ## level & intercept
        dat <- na.omit(st$trend)
        m   <- lm(dat ~ c(1:length(dat)))

        if (is.null(l.start)) l.start <- as.vector(coef(m)[1])
        if (is.null(b.start)) b.start <- as.vector(coef(m)[2])
        if (is.null(s.start)) s.start <- st$figure
    }

    ## Call to filtering loop
    len <- length(x) - start.time + 1;
    hw <- function(alpha, beta, gamma)
        .C("HoltWinters",
           as.double(x),
           as.integer(length(x)),
           as.double(alpha),
           as.double(beta),
           as.double(gamma),
           as.integer(start.time),
           as.integer(! + (seasonal == "multiplicative")),
           as.integer(f),
           a = as.double(l.start),
           b = as.double(b.start),
           s = as.double(s.start),

	   ## return values
           SSE = as.double(0),
           level = double(len + 1),
           trend = double(len + 1),
           seasonal = double(len + f),

	   PACKAGE = "stats"
           )

    ## if alpha and/or beta and/or gamma are omitted, use optim to find the
    ## values minimizing the squared prediction error
    if (is.null(gamma)) {
    ## optimize gamma
      if (is.null(alpha)) {
      ## optimize alpha
        if (is.null(beta)) {
        ## optimize beta
          ## --> optimize alpha, beta, and gamma
          error <- function (p) hw(p[1], p[2], p[3])$SSE
          sol   <- optim(optim.start, error, method = "L-BFGS-B",
                         lower = c(0, 0, 0), upper = c(1, 1, 1),
                         control = optim.control)
          alpha <- sol$par[1]
          beta  <- sol$par[2]
          gamma <- sol$par[3]
        } else {
        ## !optimize beta
          ## --> optimize alpha and gamma
          error <- function (p) hw(p[1], beta, p[2])$SSE
          sol   <- optim(c(optim.start["alpha"], optim.start["gamma"]),
                         error, method = "L-BFGS-B",
                         lower = c(0, 0), upper = c(1, 1),
                         control = optim.control)
          alpha <- sol$par[1]
          gamma <- sol$par[2]
        }
      } else {
      ## !optimize alpha
        if (is.null(beta)) {
        ## optimize beta
          ## --> optimize beta and gamma
          error <- function (p) hw(alpha, p[1], p[2])$SSE
          sol   <- optim(c(optim.start["beta"], optim.start["gamma"]),
                         error, method = "L-BFGS-B",
                         lower = c(0, 0), upper = c(1, 1),
                         control = optim.control)
          beta  <- sol$par[1]
          gamma <- sol$par[2]
        } else {
        ## !optimize beta
          ## --> optimize gamma
          error <- function (p) hw(alpha, beta, p)$SSE
          sol   <- optim(optim.start["gamma"],
                         error, method = "L-BFGS-B",
                         lower = 0, upper = 1,
                         control = optim.control)
          gamma <- sol$par
        }
      }
    } else {
    ## !optimize gamma
      if (is.null(alpha)) {
      ## optimize alpha
        if (is.null(beta)) {
        ## optimize beta
          ## --> optimize alpha and beta
          error <- function (p) hw(p[1], p[2], gamma)$SSE
          sol   <- optim(c(optim.start["alpha"], optim.start["beta"]),
                         error, method = "L-BFGS-B",
                         lower = c(0, 0), upper = c(1, 1),
                         control = optim.control)
          alpha <- sol$par[1]
          beta  <- sol$par[2]
        } else {
        ## !optimize beta
          ## --> optimize alpha
          error <- function (p) hw(p, beta, gamma)$SSE
          sol   <- optim(optim.start["alpha"], error, method = "L-BFGS-B",
                         lower = 0, upper = 1, control = optim.control)
          alpha <- sol$par
        }
      } else {
      ## !optimize alpha
        if(is.null(beta)) {
        ## optimize beta
          ## --> optimize beta
          error <- function (p) hw(alpha, p, gamma)$SSE
          sol   <- optim(optim.start["beta"], error, method = "L-BFGS-B",
                         lower = 0, upper = 1, control = optim.control)
          beta  <- sol$par
        } ## else optimize nothing!
      }
    }

    ## get (final) results
    final.fit <- hw(alpha, beta, gamma)

    ## return fitted values and estimated coefficients along with parameters used
    fitted <- ts(cbind(xhat   = final.fit$level[-len-1],
                       level  = final.fit$level[-len-1],
                       trend  = if (beta > 0) final.fit$trend[-len-1],
                       season = if (gamma > 0) final.fit$seasonal[1:len]),
                 start = start(lag(x, k = 1 - start.time)),
                 freq  = frequency(x)
                 )
    if (beta > 0) fitted[,1] <- fitted[,1] + fitted[,"trend"]
    if (gamma > 0)
      fitted[,1] <- if (seasonal == "multiplicative")
        fitted[,1] * fitted[,"season"]
      else
        fitted[,1] + fitted[,"season"]
    structure(list(fitted    = fitted,
                   x         = x,
                   alpha     = alpha,
                   beta      = beta,
                   gamma     = gamma,
                   coefficients = c(a = final.fit$level[len + 1],
                                    b = if (beta > 0) final.fit$trend[len + 1],
                                    s = if (gamma > 0) final.fit$season[len + 1:f]),
                   seasonal  = seasonal,
                   SSE       = final.fit$SSE,
                   call      = match.call()
                   ),
              class = "HoltWinters"
              )
}

## Predictions, optionally with prediction intervals
predict.HoltWinters <-
    function (object, n.ahead = 1, prediction.interval = FALSE,
              level = 0.95, ...)
{
    f <- frequency(object$x)

    vars <- function(h) {
        psi <- function(j)
            object$alpha * (1 + j * object$beta) +
                (j %% f == 0) * object$gamma * (1 - object$alpha)
        var(residuals(object)) * if (object$seasonal == "additive")
            sum(1, (h > 1) * sapply(1:(h-1), function(j) crossprod(psi(j))))
        else {
            rel <- 1 + (h - 1) %% f
            sum(sapply(0:(h-1), function(j) crossprod (psi(j) * object$coefficients[2 + rel] / object$coefficients[2 + (rel - j) %% f])))
        }
    }

    ## compute predictions
    # level
    fit <- rep(as.vector(object$coefficients[1]),n.ahead)
    # trend
    if (object$beta > 0)
        fit <- fit + as.vector((1:n.ahead)*object$coefficients[2])
        # seasonal component
    if (object$gamma > 0)
        if (object$seasonal == "additive")
            fit <- fit + rep(object$coefficients[-(1:(1+(object$beta>0)))],
                             length.out=length(fit))
        else
            fit <- fit * rep(object$coefficients[-(1:(1+(object$beta>0)))],
                             length.out=length(fit))

    ## compute prediction intervals
    if (prediction.interval)
      int <- qnorm((1 + level) / 2) * sqrt(sapply(1:n.ahead,vars))
    ts(
       cbind(fit = fit,
             upr = if(prediction.interval) fit + int,
             lwr = if(prediction.interval) fit - int
             ),
       start = end(lag(fitted(object)[,1], k = -1)),
       freq  = frequency(fitted(object)[,1])
       )
}

residuals.HoltWinters <- function (object, ...) object$x - object$fitted[,1]

plot.HoltWinters <-
    function (x, predicted.values = NA, intervals = TRUE, separator = TRUE,
              col = 1, col.predicted = 2, col.intervals = 4, col.separator = 1,
              lty = 1, lty.predicted = 1, lty.intervals = 1, lty.separator = 3,
              ylab = "Observed / Fitted", main = "Holt-Winters filtering",
              ylim = NULL, ...)
{
    if (is.null(ylim))
      ylim <- range(na.omit(c(fitted(x)[,1], x$x, predicted.values)))

    preds <- length(predicted.values) > 1 || !is.na(predicted.values)

    ## plot fitted/predicted values
    plot(ts(c(fitted(x)[,1], if(preds) predicted.values[,1]),
            start = start(fitted(x)[,1]),
            frequency = frequency(fitted(x)[,1])),
         col = col.predicted,
         ylim = ylim,
         ylab = ylab, main = main,
         lty = lty.predicted,
         ...
         )

    ## plot prediction interval
    if(preds && intervals && ncol(predicted.values) > 1) {
        lines(predicted.values[,2], col = col.intervals, lty = lty.intervals)
        lines(predicted.values[,3], col = col.intervals, lty = lty.intervals)
    }

    ## plot observed values
    lines(x$x, col = col, lty = lty)

    ## plot separator
    if (separator && preds)
        abline (v = time(x$x)[length(x$x)], lty = lty.separator, col = col.separator)
}

## print function
print.HoltWinters <- function (x, ...)
{
    cat ("Holt-Winters exponential smoothing",
         if (x$beta == 0) "without" else "with", "trend and",
         if (x$gamma == 0) "without" else
         paste(if (x$beta==0) "with ", x$seasonal, sep=""),
         "seasonal component.\n")
    cat ("\nCall:\n", deparse (x$call), "\n\n")
    cat ("Smoothing parameters:\n")
    cat (" alpha: ", x$alpha, "\n")
    cat (" beta : ", x$beta, "\n")
    cat (" gamma: ", x$gamma, "\n\n")

    cat ("Coefficients:\n")
    print(t(t(x$coefficients)))
}

# decompose additive/multiplicative series into trend/seasonal figures/noise
decompose <- function (x, type = c("additive", "multiplicative"), filter = NULL)
{
    type <- match.arg(type)
    l <- length(x)
    f <- frequency(x)
    if (f <= 1 || length(na.omit(x)) < 3 * f)
      stop ("time series has no or less than 3 periods")

    ## filter out seasonal components
    if (is.null(filter))
      filter <- if (!f %% 2)
        c(0.5, rep(1, f - 1), 0.5) / f
      else
        rep(1, f) / f

    trend <- filter(x, filter)

    ## compute seasonal components
    season <- if (type == "additive") x - trend else x / trend

    ## remove incomplete seasons at beginning/end
    season <- window(season, start(x) + c(1, 0), c(end(x)[1] - 1, f))

    ## average seasonal figures
    periods <- l %/% f
    index <- c(0, cumsum(rep (f, periods - 3)))
    figure <- numeric(f)
    for (i in 1:f) figure[i] <- mean(season[index + i])

    ## normalize figure
    figure <- if (type == "additive") figure - mean(figure)
    else figure / mean(figure)

    ## return values
    seasonal <- ts(rep(figure, periods), start = start(x), frequency = f)

    structure(
              list(seasonal = seasonal,
                   trend    = trend,
                   random   = x -
                   if (type == "additive") seasonal + trend
                   else seasonal * trend,
                   figure   = figure,
                   type     = type
                   ),
              class = "decomposed.ts"
              )
}

plot.decomposed.ts <- function(x, ...)
{
    plot(cbind(
               observed = x$random +
               if (x$type == "additive") x$trend + x$seasonal
               else x$trend * x$seasonal,
               trend    = x$trend,
               seasonal = x$seasonal,
               random   = x$random
               ),
         main = paste("Decomposition of", x$type, "time series"),
         ...)
}

