# Copyright 2010-12 by Roger Bivand
sacsarlm <- function(formula, data = list(), listw, listw2=NULL, na.action, 
	Durbin, type, method="eigen", quiet=NULL, zero.policy=NULL, 
	tol.solve=1.0e-10, llprof=NULL, interval1=NULL, interval2=NULL,
        trs1=NULL, trs2=NULL, control=list()) {
    .Deprecated("spatialreg::sacsarlm", msg="Function sacsarlm moved to the spatialreg package")
#    if (!requireNamespace("spatialreg", quietly=TRUE))
#      stop("install the spatialreg package")
    if (requireNamespace("spatialreg", quietly=TRUE)) {
      return(spatialreg::sacsarlm(formula=formula, data=data,listw=listw,listw2=listw2, na.action=na.action, Durbin=Durbin, type=type, method=method, quiet=quiet, zero.policy=zero.policy, tol.solve=tol.solve, llprof=llprof, interval1=interval1, interval2=interval2, trs1=trs1, trs2=trs2, control=control))
    }
    warning("install the spatialreg package")
#  if (FALSE) {
        timings <- list()
        .ptime_start <- proc.time()
        con <- list(fdHess=NULL, #LAPACK=FALSE,
           Imult=2L, cheb_q=5L, MC_p=16L, MC_m=30L, spamPivot="MMD",
           in_coef=0.1, super=NULL, opt_method="nlminb", opt_control=list(),
           pars=NULL, npars=4L, pre_eig1=NULL, pre_eig2=NULL)
        nmsC <- names(con)
        con[(namc <- names(control))] <- control
        if (length(noNms <- namc[!namc %in% nmsC])) 
            warning("unknown names in control: ", paste(noNms, collapse = ", "))
        if (is.null(quiet)) quiet <- !get("verbose", envir = .spdepOptions)
#FIXME
        if (missing(type)) type <- "sac"
        if (type == "Durbin") type <- "sacmixed"
        if (missing(Durbin)) Durbin <- ifelse(type == "sac", FALSE, TRUE)
        if (listw$style != "W" && is.formula(Durbin)) {
            Durbin <- TRUE
            warning("formula Durbin requires row-standardised weights; set TRUE")
        }
        if (is.logical(Durbin) && isTRUE(Durbin)) type <- "sacmixed"
        if (is.formula(Durbin)) type <- "sacmixed"
        if (is.logical(Durbin) && !isTRUE(Durbin)) type <- "sac"
	switch(type, sac = if (!quiet) cat("\nSpatial ARAR model\n"),
	    sacmixed = if (!quiet) cat("\nSpatial ARAR mixed model (Manski)\n"),
	    stop("\nUnknown model type\n"))
        stopifnot(is.logical(quiet))
        if (is.null(zero.policy))
            zero.policy <- get("zeroPolicy", envir = .spdepOptions)
        stopifnot(is.logical(zero.policy))
        if (class(formula) != "formula") formula <- as.formula(formula)
	mt <- terms(formula, data = data)
	mf <- lm(formula, data, na.action=na.action, method="model.frame")
	na.act <- attr(mf, "na.action")
	if (!inherits(listw, "listw")) stop("No neighbourhood list")
        if (is.null(listw2)) listw2 <- listw
        if (!is.null(con$pre_eig1) && is.null(con$pre_eig2))
            con$pre_eig2 <- con$pre_eig1
        else if (!inherits(listw2, "listw")) stop("No 2nd neighbourhood list")
        if (is.null(con$fdHess)) con$fdHess <- method != "eigen"
        if (!is.null(con$pars)) {
            stopifnot(is.numeric(con$pars))
        }
#        stopifnot(is.integer(con$npars))
#        stopifnot(is.logical(con$fdHess))
#        stopifnot(is.logical(con$LAPACK))
#        stopifnot(is.logical(con$super))
	can.sim <- FALSE
	if (listw$style %in% c("W", "S")) 
		can.sim <- can.be.simmed(listw)
	if (!is.null(na.act)) {
	    subset <- !(1:length(listw$neighbours) %in% na.act)
	    listw <- subset(listw, subset, zero.policy=zero.policy)
	}
	can.sim2 <- FALSE
	if (listw2$style %in% c("W", "S")) 
		can.sim2 <- can.be.simmed(listw2)
	if (!is.null(na.act)) {
	    subset <- !(1:length(listw2$neighbours) %in% na.act)
	    listw2 <- subset(listw2, subset, zero.policy=zero.policy)
	}
	y <- model.response(mf, "numeric")
	if (any(is.na(y))) stop("NAs in dependent variable")
	x <- model.matrix(mt, mf)
	if (any(is.na(x))) stop("NAs in independent variable")
	if (NROW(x) != length(listw$neighbours))
	    stop("Input data and neighbourhood list have different dimensions")
	wy <- lag.listw(listw, y, zero.policy=zero.policy)
	n <- NROW(x)
	m <- NCOL(x)
        dvars <- c(m, 0L)
	if (is.formula(Durbin) || isTRUE(Durbin)) {
                prefix <- "lag"
                if (isTRUE(Durbin)) {
                    WX <- create_WX(x, listw, zero.policy=zero.policy,
                        prefix=prefix)
                } else {
	            dmf <- lm(Durbin, data, na.action=na.action, 
		        method="model.frame")
                    fx <- try(model.matrix(Durbin, dmf), silent=TRUE)
                    if (class(fx) == "try-error") 
                        stop("Durbin variable mis-match")
                    WX <- create_WX(fx, listw, zero.policy=zero.policy,
                        prefix=prefix)
                    inds <- match(substring(colnames(WX), 5,
	                nchar(colnames(WX))), colnames(x))
                    if (anyNA(inds)) stop("WX variables not in X: ",
                        paste(substring(colnames(WX), 5,
                        nchar(colnames(WX)))[is.na(inds)], collapse=" "))
                    icept <- grep("(Intercept)", colnames(x))
                    iicept <- length(icept) > 0L
                    if (iicept) {
                        xn <- colnames(x)[-1]
                    } else {
                        xn <- colnames(x)
                    }
                    wxn <- substring(colnames(WX), nchar(prefix)+2,
                        nchar(colnames(WX)))
                    zero_fill <- NULL
                    if (length((which(!(xn %in% wxn)))) > 0L)
                        zero_fill <- length(xn) + (which(!(xn %in% wxn)))
                }
                dvars <- c(NCOL(x), NCOL(WX))
                if (is.formula(Durbin)) {
                    attr(dvars, "f") <- Durbin
                    attr(dvars, "inds") <- inds
                    attr(dvars, "zero_fill") <- zero_fill
                }
		x <- cbind(x, WX)
		m <- NCOL(x)
		rm(WX)
	}
	if (NROW(x) != length(listw2$neighbours))
	    stop("Input data and neighbourhood list2 have different dimensions")
	w2y <- lag.listw(listw2, y, zero.policy=zero.policy)
	w2wy <- lag.listw(listw2, wy, zero.policy=zero.policy)
	lm.base <- lm(y ~ x - 1)
	aliased <- is.na(coefficients(lm.base))
	cn <- names(aliased)
	names(aliased) <- substr(cn, 2, nchar(cn))
	if (any(aliased)) {
          if (is.formula(Durbin)) {
	    stop("Aliased variables found: ",
                paste(names(aliased)[aliased], collapse=" "))
          } else {
	    warning("Aliased variables found: ",
                paste(names(aliased)[aliased], collapse=" "))
	    nacoef <- which(aliased)
		x <- x[,-nacoef]
          }
	}
        LL_null_lm <- NULL
	if ("(Intercept)" %in% colnames(x)) LL_null_lm <- logLik(lm(y ~ 1))
	m <- NCOL(x)
	xcolnames <- colnames(x)
	K <- ifelse(xcolnames[1] == "(Intercept)", 2, 1)
	if (any(is.na(wy)))
	    stop("NAs in lagged dependent variable")
	if (m > 1 || (m == 1 && K == 1)) {
	    WX <- matrix(nrow=n,ncol=(m-(K-1)))
	    for (k in K:m) {
		wx <- lag.listw(listw2, x[,k], zero.policy=zero.policy)
		if (any(is.na(wx)))
		    stop("NAs in lagged independent variable")
		WX[,(k-(K-1))] <- wx
	    }
	}
	if (K == 2) {
		wx1 <- as.double(rep(1, n))
		wx <- lag.listw(listw2, wx1, zero.policy=zero.policy)
		if (m > 1) WX <- cbind(wx, WX)
		else WX <- matrix(wx, nrow=n, ncol=1)
	}
	colnames(WX) <- xcolnames
	rm(wx)

#        env <- new.env(parent=globalenv())
        env <- new.env()
        assign("y", y, envir=env)
        assign("x", x, envir=env)
        assign("wy", wy, envir=env)
        assign("w2y", w2y, envir=env)
        assign("w2wy", w2wy, envir=env)
        assign("WX", WX, envir=env)
        assign("n", n, envir=env)
        assign("p", m, envir=env)
        assign("verbose", !quiet, envir=env)
        assign("family", "SAR", envir=env)
        assign("first_time", TRUE, envir=env)
#        assign("LAPACK", con$LAPACK, envir=env)
        assign("can.sim", can.sim, envir=env)
        assign("can.sim2", can.sim2, envir=env)
        assign("listw", listw, envir=env)
        assign("listw2", listw2, envir=env)
        assign("similar", FALSE, envir=env)
        assign("similar2", FALSE, envir=env)
        timings[["set_up"]] <- proc.time() - .ptime_start
        .ptime_start <- proc.time()

	if (!quiet) cat(paste("\nSpatial autoregressive error model\n", 
		"Jacobian calculated using "))

        interval1 <- jacobianSetup(method, env, con, pre_eig=con$pre_eig1,
            trs=trs1, interval=interval1, which=1)
        assign("interval1", interval1, envir=env)
        interval2 <- jacobianSetup(method, env, con, pre_eig=con$pre_eig2,
            trs=trs2, interval=interval2, which=2)
        assign("interval2", interval2, envir=env)

        nm <- paste(method, "set_up", sep="_")
        timings[[nm]] <- proc.time() - .ptime_start
        .ptime_start <- proc.time()

        pars <- con$pars
        lower <- c(interval1[1], interval2[1])
        upper <- c(interval1[2], interval2[2])

        if (!is.null(llprof)) {
            llrho <- NULL
            lllambda <- NULL
            if (length(llprof) == 1L) {
                llrho <- seq(lower[1], upper[1], length.out=llprof)
                lllambda <- seq(lower[2], upper[2], length.out=llprof)
                llprof <- as.matrix(expand.grid(llrho, lllambda))
            }
            ll_prof <- numeric(nrow(llprof))
            for (i in 1:nrow(llprof)) 
                ll_prof[i] <- sacsar.f(llprof[i,], env=env)
            nm <- paste(method, "profile", sep="_")
            timings[[nm]] <- proc.time() - .ptime_start
            .ptime_start <- proc.time()
        }

        if (is.null(pars)) {
          if (con$npars == 4L) {
             xseq <- c(lower[1], 0, upper[1], upper[1])*0.8
             yseq <- c(upper[2], 0, upper[2], lower[2])*0.8
             mpars <- cbind(xseq, yseq)
          } else {
             xseq <- seq(lower[1], upper[1], (upper[1]-lower[1])/2)*0.8
             yseq <- seq(lower[2], upper[2], (upper[2]-lower[2])/2)*0.8
             mpars <- as.matrix(expand.grid(xseq, yseq))
          }
        } else {
            mxs <- NULL
        }
        if (con$opt_method == "nlminb") {
            if (is.null(pars)) {
                mxs <- apply(mpars, 1, function(pp) -nlminb(pp, sacsar.f, 
                    env=env, control=con$opt_control, lower=lower, 
                    upper=upper)$objective)
                pars <- mpars[which.max(mxs),]
                optres <- nlminb(pars, sacsar.f, env=env,
                    control=con$opt_control, lower=lower, upper=upper)
            } else {
                optres <- nlminb(pars, sacsar.f, env=env,
                    control=con$opt_control, lower=lower, upper=upper)
            }
        } else if (con$opt_method == "L-BFGS-B"){
            if (is.null(pars)) {
                mxs <- apply(mpars, 1, function(pp) -optim(pars, sacsar.f, 
                    env=env, method="L-BFGS-B", control=con$opt_control, 
                    lower=lower, upper=upper)$objective)
                pars <- mpars[which.max(mxs),]
	        optres <- optim(pars, sacsar.f, env=env,
                    method="L-BFGS-B", control=con$opt_control,
                    lower=lower, upper=upper)
            } else {
	        optres <- optim(pars, sacsar.f, env=env,
                    method="L-BFGS-B", control=con$opt_control,
                    lower=lower, upper=upper)
            }
        } else {
            if (is.null(pars)) {
                mxs <- apply(mpars, 1, function(pp) -optim(pars, sacsar.f, 
                    env=env, method=con$opt_method, 
                    control=con$opt_control)$objective)
                pars <- mpars[which.max(mxs),]
	        optres <- optim(pars, sacsar.f, env=env,
                    method=con$opt_method, control=con$opt_control)
            } else {
	        optres <- optim(pars, sacsar.f, env=env,
                    method=con$opt_method, control=con$opt_control)
            }
        }
	LL <- -optres$objective
        if (optres$convergence != 0)
            warning(paste("convergence failure:", optres$message))
	rho <- optres$par[1]
	names(rho) <- "rho"
	lambda <- optres$par[2]
	names(lambda) <- "lambda"
        nm <- paste(method, "opt", sep="_")
        timings[[nm]] <- proc.time() - .ptime_start
        .ptime_start <- proc.time()

	lm.target <- lm(I(y - rho*wy - lambda*w2y + rho*lambda*w2wy) ~ 
            I(x - lambda*WX) - 1)
	r <- as.vector(residuals(lm.target))
	fit <- as.vector(y - r)
	p <- lm.target$rank
	SSE <- deviance(lm.target)
	s2 <- SSE/n
	coef.sac <- coefficients(lm.target)
        tarX <- model.matrix(lm.target)
        tary <- model.response(model.frame(lm.target))
	names(coef.sac) <- xcolnames
	lm.model <- lm(formula, data)
        logLik_lm.model <- logLik(lm.model)
        AIC_lm.model <- AIC(lm.model)
        ase <- FALSE
	asyvar1 <- FALSE
        force_assign_eigen <- FALSE
        if (method == "eigen") {
# taken from spatial/sac_models/sac.m
	    tr <- function(A) sum(diag(A))
            W1 <- listw2mat(listw)
            W2 <- listw2mat(listw2)
            A <- diag(n) - rho * W1
            AI <- solve(A)
            WA <- W1 %*% AI
            B <- diag(n) - lambda * W2
            BI <- solve(B)
            WB <- W2 %*% BI
            omeg <- s2*diag(n)
            omegi <- (1/s2)*diag(n)
            bhat <- coef.sac
            p3 <- p+3
            asyvar <- matrix(0.0, ncol=p3, nrow=p3)
            Bx <- B %*% x
            asyvar[4:p3, 4:p3] <- (1/s2)*crossprod(Bx)
            asyvar[1, 1] <- n/(2*s2*s2)
            term1 <- tr(WA %*% WA)
            BWABI <- B %*% WA %*% BI
            term2 = tr(omeg %*% t(BWABI) %*% omegi %*% BWABI)
            BWAxbhat <- B %*% WA %*% x %*% bhat
            term3 = t(BWAxbhat) %*% omegi %*% BWAxbhat
            asyvar[2, 2] <- term1 + term2 + term3
            term1 <- tr(WB %*% WB)
            term2 <- tr(omeg %*% t(WB) %*% omegi %*% WB)
            asyvar[3, 3] <- term1 + term2
            asyvar[2, 4:p3] <- (1/s2)*(t(Bx) %*% BWAxbhat)
            asyvar[4:p3, 2] <- asyvar[2, 4:p3]
            asyvar[2, 1] <- (1/s2)*tr(W1 %*% AI)
            asyvar[1, 2] <- asyvar[2, 1]
            asyvar[3, 1] <- (1/s2)*tr(W2 %*% BI)
            asyvar[1, 3] <- asyvar[3, 1]
            term1 = tr(t(WB) %*% omegi %*% BWABI %*% omeg)
            term2 = tr(W2 %*% WA %*% BI)
            asyvar[3, 2] <- term1 + term2
            asyvar[2, 3] <- asyvar[3, 2]
            asyvar1 <- try(solve(asyvar, tol.solve=tol.solve), silent=TRUE)
            if (class(asyvar1) == "try-error") {
                timings[["eigen_se"]] <- proc.time() - .ptime_start
                .ptime_start <- proc.time()
                con$fdHess <- TRUE
                force_assign_eigen <- TRUE
                warning(paste("inversion of asymptotic covariance",
                    "matrix failed for tol.solve =", tol.solve,
                    "\n", strsplit(attr(asyvar1, "condition")$message,
                        ":")[[1]][2], "- using numerical Hessian."))
                ase=FALSE
            } else {
                rownames(asyvar1) <- colnames(asyvar1) <- 
			c("sigma", "rho", "lambda", xcolnames)
                ase=TRUE
                rho.se <- sqrt(asyvar1[2, 2])
                lambda.se <- sqrt(asyvar1[3, 3])
                rest.se <- sqrt(diag(asyvar1)[-c(1:3)])
                nm <- "asymptotic vcov"
                timings[[nm]] <- proc.time() - .ptime_start
                .ptime_start <- proc.time()
            }
        }
        if (con$fdHess) {
            coefs <- c(rho, lambda, coef.sac)
            fdHess <- getVmatsac(coefs, env, tol.solve=tol.solve)
            rownames(fdHess) <- colnames(fdHess) <- c("rho", "lambda",
                xcolnames)
            if (!ase) {
                rho.se <- sqrt(fdHess[1, 1])
                lambda.se <- sqrt(fdHess[2, 2])
                rest.se <- sqrt(diag(fdHess)[-c(1,2)])
            }
            nm <- paste(method, "fdHess", sep="_")
            timings[[nm]] <- proc.time() - .ptime_start
        } else fdHess <- FALSE
	call <- match.call()
	names(r) <- names(y)
	names(fit) <- names(y)
	ret <- structure(list(type=type, dvars=dvars, rho=rho, lambda=lambda,
	    coefficients=coef.sac, rest.se=rest.se, ase=ase,
	    LL=LL, s2=s2, SSE=SSE, parameters=(p+3), 
            logLik_lm.model=logLik_lm.model, AIC_lm.model=AIC_lm.model,
            #lm.model=lm.model, 
	    method=method, call=call, residuals=r, #lm.target=lm.target,
            tarX=tarX, tary=tary, y=y, X=x, W2X=WX, trs1=trs1, trs2=trs2,
	    opt=optres, pars=pars, mxs=mxs, fitted.values=fit, #formula=formula,
	    similar=get("similar", envir=env), rho.se=rho.se,
	    lambda.se=lambda.se, zero.policy=zero.policy, 
	    aliased=aliased, LLNullLlm=LL_null_lm,
            fdHess=fdHess, resvar=asyvar1, listw_style=listw$style,
            optimHess=FALSE, insert=FALSE, interval1=interval1,
            interval2=interval2, timings=do.call("rbind", timings)[, c(1, 3)]),
            class=c("sarlm"))
        rm(env)
        GC <- gc()
        if (is.null(llprof)) ret$llprof <- llprof
        else {
            ret$llprof <- list(grd=llprof, ll=ll_prof, xseq=llrho,
                yseq=lllambda)
        }
	if (!is.null(na.act))
		ret$na.action <- na.act
	ret

}
#}

#if (FALSE) {
sacsar_sse <- function(coefs, env) {
    rho <- coefs[1]
    lambda <- coefs[2]
    yl <- get("y", envir=env) - rho * get("wy", envir=env) - 
        lambda * get("w2y", envir=env) + rho * lambda * get("w2wy", envir=env)
    xl <- get("x", envir=env) - lambda * get("WX", envir=env)
    xl.q <- qr.Q(qr(xl, #LAPACK=get("LAPACK", envir=env)
))
    xl.q.yl <- crossprod(xl.q, yl)
    SSE <- crossprod(yl) - crossprod(xl.q.yl)
    SSE
}


sacsar.f <- function(coefs, env) {
    SSE <- sacsar_sse(coefs, env)
    n <- get("n", envir=env)
    s2 <- SSE/n
    ldet1 <- do_ldet(coefs[1], env, which=1)
    ldet2 <- do_ldet(coefs[2], env, which=2)
    ret <- (ldet1 + ldet2 - ((n/2)*log(2*pi)) - (n/2)*log(s2) - 
        (1/(2*(s2)))*SSE)
    if (get("verbose", envir=env)) cat("rho:", coefs[1], " lambda:", coefs[2],
        " function:", ret, " Jacobian1:", ldet1, " Jacobian2:", ldet2,
        " SSE:", SSE, "\n")
    -ret
}


getVmatsac <- function(coefs, env, tol.solve=1.0e-10) {
    fd <- fdHess(coefs, f_sac_hess, env)
    mat <- fd$Hessian
    res <- solve(-(mat), tol.solve=tol.solve)
    res
}

sar_sac_hess_sse <- function(rho, lambda, beta, env) {
    yl <- get("y", envir=env) - rho * get("wy", envir=env) - 
        lambda * get("w2y", envir=env) + rho * lambda * get("w2wy", envir=env)
    xl <- get("x", envir=env) - lambda * get("WX", envir=env)
    res <- yl - (xl %*% beta)
    SSE <- c(crossprod(res))
    SSE
}

f_sac_hess <- function(coefs, env) {
    rho <- coefs[1]
    int <- get("interval1", envir=env)
    if (rho <= int[1] || rho >= int[2]) return(-Inf)
    lambda <- coefs[2]
    int <- get("interval2", envir=env)
    if (lambda <= int[1] || lambda >= int[2]) return(-Inf)
    beta <- coefs[-(1:2)]
    SSE <- sar_sac_hess_sse(rho, lambda, beta, env)
    n <- get("n", envir=env)
    s2 <- SSE/n
    ldet1 <- do_ldet(rho, env, which=1)
    ldet2 <- do_ldet(lambda, env, which=2)
    ret <- (ldet1 + ldet2 - ((n/2) * log(2 * pi)) - (n/2) * log(s2) - 
        (1/(2 * s2)) * SSE)
    if (get("verbose", envir=env)) cat("rho:", rho, "lambda:", lambda,
        " function:", ret, " Jacobian1:", ldet1, " Jacobian2:",
        ldet2, " SSE:", SSE, "\n")
    if (!is.finite(ret)) return(-Inf)
   ret
}
#}

