#' Lower Partial Moment
#'
#' This function generates a univariate lower partial moment for any degree or target.
#'
#' @param degree numeric; \code{(degree = 0)} is frequency, \code{(degree = 1)} is area.
#' @param target numeric; Set to \code{target = mean(variable)} for classical equivalences, but does not have to be.
#'   When \code{excess_ret = FALSE}, this can be a scalar or a vectorized target for the standard partial moment calculation.
#'   When \code{excess_ret = TRUE}, it is interpreted element-wise as the benchmark/threshold relative to \code{variable}.
#' @param variable a numeric vector. \link{data.frame} or \link{list} type objects are not permissible.
#' @param excess_ret logical; \code{FALSE} (default). If \code{TRUE}, switches from the standard vectorized-target
#'   partial moment to an element-wise excess-deviation calculation. For \code{LPM}, this computes
#'   \code{pmax(target - variable, 0)} raised to \code{degree} and averaged. In this mode, \code{target}
#'   must have length 1 or the same length as \code{variable}.
#' @return LPM of variable
#' @author Fred Viole, OVVO Financial Systems
#' @references Viole, F. and Nawrocki, D. (2013) "Nonlinear Nonparametric Statistics: Using Partial Moments" (ISBN: 1490523995, 2nd edition: \url{https://ovvo-financial.github.io/NNS/book/})
#' @examples
#' set.seed(123)
#' x <- rnorm(100)
#' LPM(0, mean(x), x)
#' @export

LPM <- function(degree, target, variable, excess_ret = FALSE) {
  target   <- as.numeric(target)
  variable <- as.numeric(variable)
  
  if (!all(is.finite(variable))) stop("`variable` must be finite.")
  if (!all(is.finite(target))) stop("`target` must be finite.")
  
  if (!excess_ret && length(target) > 1) {
    return(.Call("_NNS_LPM_CPv", degree, target, variable))
  }
  
  .Call("_NNS_LPM_RCPP", degree, target, variable, excess_ret)
  
}


#' Upper Partial Moment
#'
#' This function generates a univariate upper partial moment for any degree or target.
#'
#' @param degree numeric; \code{(degree = 0)} is frequency, \code{(degree = 1)} is area.
#' @param target numeric; Set to \code{target = mean(variable)} for classical equivalences, but does not have to be.
#'   When \code{excess_ret = FALSE}, this can be a scalar or a vectorized target for the standard partial moment calculation.
#'   When \code{excess_ret = TRUE}, it is interpreted element-wise as the benchmark/threshold relative to \code{variable}.
#' @param variable a numeric vector. \link{data.frame} or \link{list} type objects are not permissible.
#' @param excess_ret logical; \code{FALSE} (default). If \code{TRUE}, switches from the standard vectorized-target
#'   partial moment to an element-wise excess-deviation calculation. For \code{UPM}, this computes
#'   \code{pmax(variable - target, 0)} raised to \code{degree} and averaged. In this mode, \code{target}
#'   must have length 1 or the same length as \code{variable}.
#' @return UPM of variable
#' @author Fred Viole, OVVO Financial Systems
#' @references Viole, F. and Nawrocki, D. (2013) "Nonlinear Nonparametric Statistics: Using Partial Moments" (ISBN: 1490523995, 2nd edition: \url{https://ovvo-financial.github.io/NNS/book/})
#' @examples
#' set.seed(123)
#' x <- rnorm(100)
#' UPM(0, mean(x), x)
#' @export

UPM <- function(degree, target, variable, excess_ret = FALSE) {
  target   <- as.numeric(target)
  variable <- as.numeric(variable)
  
  if (!all(is.finite(variable))) stop("`variable` must be finite.")
  if (!all(is.finite(target))) stop("`target` must be finite.")
  
  if (!excess_ret && length(target) > 1) {
    return(.Call("_NNS_UPM_CPv", degree, target, variable))
  }
  
  .Call("_NNS_UPM_RCPP", degree, target, variable, excess_ret)
  
}


#' Co‑Lower Partial Moment nD
#'
#' This function generates an n‑dimensional co‑lower partial moment (n >= 2) for any degree or target.
#'
#' @param data   A numeric matrix with observations in rows and variables in columns.
#' @param target A numeric vector, length equal to ncol(data).
#' @param degree numeric; degree for lower deviations (0 = frequency, 1 = area).
#' @param norm   logical; if \code{TRUE} (default) normalize to the maximum observed value (→ [0,1]), otherwise return the raw moment.
#' @return Numeric; the n‑dimensional co‑lower partial moment.
#' @examples
#' \dontrun{
#' mat <- matrix(rnorm(200), ncol = 4)
#' Co.LPM_nD(mat, rep(0, ncol(mat)), degree = 1, norm = FALSE)
#' }
#' @export
Co.LPM_nD <- function(data, target, degree = 0.0, norm = TRUE) {
  data   <- as.matrix(data)
  target <- as.numeric(target)
  degree <- as.numeric(degree)
  norm   <- as.logical(norm)
  
  if (!all(is.finite(data))) stop("`data` must be finite.")
  if (!all(is.finite(target))) stop("`target` must be finite.")
  
  .Call("_NNS_CoLPM_nD_RCPP", data, target, degree, norm)
}


#' Co‑Upper Partial Moment nD
#'
#' This function generates an n‑dimensional co‑upper partial moment (n >= 2) for any degree or target.
#'
#' @param data   A numeric matrix with observations in rows and variables in columns.
#' @param target A numeric vector, length equal to ncol(data).
#' @param degree numeric; degree for upper deviations (0 = frequency, 1 = area).
#' @param norm   logical; if \code{TRUE} (default) normalize to the maximum observed value (→ [0,1]), otherwise return the raw moment.
#' @return Numeric; the n‑dimensional co‑upper partial moment.
#' @examples
#' \dontrun{
#' mat <- matrix(rnorm(200), ncol = 4)
#' Co.UPM_nD(mat, rep(0, ncol(mat)), degree = 1, norm = FALSE)
#' }
#' @export
Co.UPM_nD <- function(data, target, degree = 0.0, norm = TRUE) {
  data   <- as.matrix(data)
  target <- as.numeric(target)
  degree <- as.numeric(degree)
  norm   <- as.logical(norm)
  
  if (!all(is.finite(data))) stop("`data` must be finite.")
  if (!all(is.finite(target))) stop("`target` must be finite.")
  
  .Call("_NNS_CoUPM_nD_RCPP", data, target, degree, norm)
}


#' Divergent Partial Moment nD
#'
#' This function generates the aggregate n‑dimensional divergent partial moment (n >= 2) for any degree or target.
#'
#' @param data   A numeric matrix with observations in rows and variables in columns.
#' @param target A numeric vector, length equal to ncol(data).
#' @param degree numeric; degree for upper deviations (0 = frequency, 1 = area).
#' @param norm   logical; if \code{TRUE} (default) normalize to the maximum observed value (→ [0,1]), otherwise return the raw moment.
#' @return Numeric; the n‑dimensional co‑upper partial moment.
#' @examples
#' \dontrun{
#' mat <- matrix(rnorm(200), ncol = 4)
#' DPM_nD(mat, rep(0, ncol(mat)), degree = 1, norm = FALSE)
#' }
#' @export
DPM_nD <- function(data, target, degree = 0.0, norm = TRUE) {
  data   <- as.matrix(data)
  target <- as.numeric(target)
  degree <- as.numeric(degree)
  norm   <- as.logical(norm)
  
  if (!all(is.finite(data))) stop("`data` must be finite.")
  if (!all(is.finite(target))) stop("`target` must be finite.")
  
  .Call("_NNS_DPM_nD_RCPP", data, target, degree, norm)
}



#' NNS CDF
#'
#' This function generates an empirical CDF using partial moment ratios \link{LPM.ratio}, and resulting survival, hazard and cumulative hazard functions.
#'
#' @param variable a numeric vector or data.frame of >= 2 variables for joint CDF.
#' @param degree numeric; \code{(degree = 0)} (default) is frequency, \code{(degree = 1)} is area.
#' @param target numeric; \code{NULL} (default) Must lie within support of each variable.
#' @param type options("CDF", "survival", "hazard", "cumulative hazard"); \code{"CDF"} (default) Selects type of function to return for bi-variate analysis.  Multivariate analysis is restricted to \code{"CDF"}.
#' @param plot logical; plots CDF.
#' @return Returns:
#' \itemize{
#'  \item{\code{"Function"}} a data.table containing the observations and resulting CDF of the variable.
#'  \item{\code{"target.value"}} value from the \code{target} argument.
#' }
#' @author Fred Viole, OVVO Financial Systems
#' @references Viole, F. and Nawrocki, D. (2013) "Nonlinear Nonparametric Statistics: Using Partial Moments" (ISBN: 1490523995, 2nd edition: \url{https://ovvo-financial.github.io/NNS/book/})
#'
#' Viole, F. (2017) "Continuous CDFs and ANOVA with NNS"  \doi{10.2139/ssrn.3007373}
#' 
#' @examples
#' \dontrun{
#' set.seed(123)
#' x <- rnorm(100)
#' NNS.CDF(x)
#'
#' ## Empirical CDF (degree = 0)
#' NNS.CDF(x)
#'
#' ## Continuous CDF (degree = 1)
#' NNS.CDF(x, 1)
#'
#' ## Joint CDF
#' x <- rnorm(5000) ; y <- rnorm(5000)
#' A <- cbind(x,y)
#'
#' NNS.CDF(A, 0)
#'
#' ## Joint CDF with target
#' NNS.CDF(A, 0, target = rep(0, ncol(A)))
#' }
#' @export


NNS.CDF <- function(variable,
                    degree = 0,
                    target = NULL,
                    type   = "CDF",
                    plot   = TRUE) {
  
  # — Flatten tibbles/data.tables
  if (any(class(variable) %in% c("tbl","data.table")) && ncol(variable)==1) {
    variable <- as.vector(unlist(variable))
  }
  if (any(class(variable) %in% c("tbl","data.table"))) {
    variable <- as.data.frame(variable)
  }
  
  # — Bounds check
  if (!is.null(target)) {
    if (is.null(dim(variable))||ncol(variable)==1) {
      if (target<min(variable)||target>max(variable)) stop("target out of bounds")
    } else {
      if (target[1]<min(variable[,1])||target[1]>max(variable[,1])||
          target[2]<min(variable[,2])||target[2]>max(variable[,2])) stop("target out of bounds")
    }
  }
  
  # — Validate type
  type <- tolower(type)
  if (!type%in%c("cdf","survival","hazard","cumulative hazard")) stop("invalid type")
  
  # — Axis labels
  mc <- match.call(); vc <- mc$variable
  if (is.null(dim(variable))||ncol(variable)==1) {
    vn <- deparse(vc)
  } else if (!is.null(colnames(variable))) {
    xlab <- colnames(variable)[1]; ylab <- colnames(variable)[2]
  } else {
    expr <- deparse(vc); xlab <- paste0(expr,"[,1]"); ylab <- paste0(expr,"[,2]")
  }
  
  # — Univariate branch
  if (is.null(dim(variable))||ncol(variable)==1) {
    x    <- sort(variable)
    pval <- LPM.ratio(degree,x,variable)
    DT   <- data.table::data.table(x, pval)
    colname <- switch(type,
                      cdf="CDF",
                      survival="S(x)",
                      hazard="h(x)",
                      `cumulative hazard`="H(x)")
    data.table::setnames(DT, c("x",colname))
    
    # adjust pval for survival/hazard/cumhaz
    if (type=="survival") DT[[2]] <- 1-DT[[2]]
    if (type=="hazard") {
      n <- length(x); w <- min(10,n-1)
      F <- pval
      proxy <- vapply(seq_along(x),function(i){lo<-max(1,i-w%/%2);hi<-min(n,i+w%/%2);(F[hi]-F[lo])/(x[hi]-x[lo])},numeric(1))
      fit <- NNS.reg(x, pmax(proxy,1e-10), order=NULL, n.best=1, point.est=target, plot=FALSE)
      DT[[2]] <- pmin(pmax(fit$Fitted$y.hat / pmax(1-F,1e-10),0),1e6)
    }
    if (type=="cumulative hazard") DT[[2]] <- pmax(-log(pmax(1-pval,1e-10)),0)
    
    # compute target.value
    if (is.null(target)) {
      Pv <- numeric(0)
    } else {
      Pv <- LPM.ratio(degree,target,variable)
      if (type=="survival") Pv <- 1-Pv
      if (type=="hazard") Pv <- fit$Point.est / pmax(1-pval[which.min(abs(x-target))],1e-10)
      if (type=="cumulative hazard") Pv <- NNS.reg(x,DT[[2]],order=NULL,n.best=1,point.est=target,plot=FALSE)$Point.est
    }
    
    # plotting
    if (plot) {
      plot(DT$x,DT[[2]],type="s",lwd=2,pch=19,col="steelblue",xlab=vn,ylab=colname,main=toupper(type))
      points(DT$x,DT[[2]],pch=19,col="steelblue")
      if(length(Pv)){
        segments(target,0,target,Pv,col="red",lty=2,lwd=2)
        segments(min(x),Pv,target,Pv,col="red",lty=2,lwd=2)
        points(target,Pv,pch=19,col="green")
      }
    }
    
    return(list(Function=DT,target.value=Pv))
  }
  
  # — Multivariate case (d >= 2)
  if (!is.null(dim(variable)) && ncol(variable) >= 2) {
    xlab <- colnames(variable)[1]
    ylab <- if(ncol(variable) >= 2) colnames(variable)[2] else ""
    
    # Compute joint conditional CDF using clpm_nD
    CDF <- apply(variable, 1, function(row) Co.LPM_nD(variable, row, degree = degree))
    
    # Apply transformation based on type
    if (type == "survival") {
      marginal_probs <- apply(variable, 2, function(col) LPM.ratio(degree, col, col))
      CDF <- pmax(0, pmin(1, 1 - rowSums(marginal_probs) + CDF))
    }
    
    if (type == "hazard") {
      f <- NNS.reg(variable, pmax(CDF, 1e-10), order = "max", plot = FALSE)$Fitted$y.hat
      marginals <- apply(variable, 2, function(col) LPM.ratio(degree, col, col))
      CDF <- pmax(f / pmax(1 - rowSums(marginals) + CDF, 1e-10), 0)
    }
    
    if (type == "cumulative hazard") {
      marginals <- apply(variable, 2, function(col) LPM.ratio(degree, col, col))
      CDF <- pmax(-log(pmax(1 - rowSums(marginals) + CDF, 1e-10)), 0)
    }
    
    # Target evaluation
    Pv <- numeric(0)
    if (!is.null(target)) {
      Pv <- Co.LPM_nD(variable, target, degree = degree)
      if (type == "survival") {
        marg_target <- mapply(LPM.ratio, degree, target, as.data.frame(variable))
        Pv <- max(0, min(1, 1 - sum(marg_target) + Pv))
      }
      if (type == "hazard") {
        Pv <- NNS.reg(variable, CDF, order = "max", plot = FALSE, point.est = target)$Point.est /
          pmax(1 - Pv, 1e-10)
      }
      if (type == "cumulative hazard") {
        Pv <- pmax(-log(pmax(1 - Pv, 1e-10)), 0)
      }
    }
    
    if (plot && ncol(variable) == 2) {
      x1 <- variable[, 1]; x2 <- variable[, 2]
      u1 <- LPM.ratio(degree, x1, x1)
      u2 <- LPM.ratio(degree, x2, x2)
      
      rgl::plot3d(u1, u2, CDF,
                  xlab = paste0(xlab, " uniform"), ylab = paste0(ylab, " uniform"), zlab = toupper(type),
                  col  = "steelblue", pch = 19, box = FALSE)
      
      if (length(Pv)) {
        ut1 <- LPM.ratio(degree, target[1], x1)
        ut2 <- LPM.ratio(degree, target[2], x2)
        
        # Target point (green)
        rgl::points3d(ut1, ut2, Pv, col = "green", pch = 19)
        
        # Horizontal segment along x at level Pv
        rgl::segments3d(
          x = c(min(u1), ut1),
          y = c(ut2,     ut2),
          z = c(Pv,      Pv),
          col = "red", lwd = 2, lty = "dashed"
        )
        rgl::text3d(ut1,min(u2), Pv, 
                    text = paste0("x = ", round(target[1], 3)), 
                    col = "red", pos = 2, cex = 0.9)
        
        # Horizontal segment along y at level Pv
        rgl::segments3d(
          x = c(ut1,     ut1),
          y = c(min(u2), ut2),
          z = c(Pv,      Pv),
          col = "red", lwd = 2, lty = "dashed"
        )
        rgl::text3d(min(u1), ut2, Pv, 
                    text = paste0("y = ", round(target[2], 3)), 
                    col = "red", pos = 2, cex = 0.9)
        
        # Final segment to CDF axis (min u1, min u2, Pv)
        rgl::segments3d(
          x = c(ut1,      max(u1)),
          y = c(ut2,      max(u2)),
          z = c(Pv,       Pv),
          col = "red", lwd = 2, lty = "dashed"
        )
        rgl::text3d(max(u1), max(u2), Pv,
                    text = paste0("CDF = ", round(Pv, 4)),
                    col = "red", pos = 2, cex = 0.9)
      }
    }
    
    outDT <- data.table::data.table(variable, CDF = CDF)
    return(list(Function = outDT, target.value = Pv))(list(Function = outDT, target.value = Pv))(list(Function=outDT,target.value=Pv))
  }
}




#' NNS moments
#'
#' This function returns the first 4 moments of the distribution.
#'
#' @param x a numeric vector.
#' @param population logical; \code{TRUE} (default) Performs the population adjustment.  Otherwise returns the sample statistic.
#' @return Returns:
#' \itemize{
#'  \item{\code{"$mean"}} mean of the distribution.
#'  \item{\code{"$variance"}} variance of the distribution.
#'  \item{\code{"$skewness"}} skewness of the distribution.
#'  \item{\code{"$kurtosis"}} excess kurtosis of the distribution.
#' }
#' @author Fred Viole, OVVO Financial Systems
#' @references Viole, F. and Nawrocki, D. (2013) "Nonlinear Nonparametric Statistics: Using Partial Moments" (ISBN: 1490523995, 2nd edition: \url{https://ovvo-financial.github.io/NNS/book/})
#'
#' @examples
#' \dontrun{
#' set.seed(123)
#' x <- rnorm(100)
#' NNS.moments(x)
#' }
#' @export

NNS.moments <- function(x, population = TRUE){
  n <- length(x)
  mean <- UPM(1, 0, x) - LPM(1, 0, x)
  variance <- (UPM(2, mean(x), x) + LPM(2, mean(x), x))
  skew_base <- (UPM(3,mean(x),x) - LPM(3,mean(x),x))
  kurt_base <- (UPM(4,mean(x),x) + LPM(4,mean(x),x))
  
  if(population){
    skewness <- skew_base / variance^(3/2)
    kurtosis <- (kurt_base / variance^2) - 3
  } else {
    skewness <- (n / ((n-1)*(n-2))) * ((n*skew_base) / variance^(3/2))
    kurtosis <- ((n * (n+1)) / ((n-1)*(n-2)*(n-3))) * ((n*kurt_base) / (variance * (n / (n - 1)))^2) - ( (3 * ((n-1)^2)) / ((n-2)*(n-3)))
    variance <- variance * (n / (n - 1))
  }
  
  return(list("mean" = mean,
              "variance" = variance,
              "skewness" = skewness,
              "kurtosis" = kurtosis))
}




#' Partial Moment Matrix
#' @name PM.matrix
#' @title Partial Moment Matrix
#' @description Builds a list containing CUPM, DUPM, DLPM, CLPM and the overall covariance matrix.
#' @param LPM_degree numeric; lower partial moment degree (0 = freq, 1 = area).
#' @param UPM_degree numeric; upper partial moment degree (0 = freq, 1 = area).
#' @param target numeric vector; thresholds for each column (defaults to colMeans).
#' @param variable numeric matrix or data.frame.
#' @param pop_adj logical; TRUE adjusts population vs. sample moments.
#' @param norm logical; default FALSE. If TRUE, each quadrant matrix is cell-wise normalized so their sum is 1 at each (i,j).  
#' @return A list: $cupm, $dupm, $dlpm, $clpm, $cov.matrix.
#' @note When \code{norm = TRUE}, each cell (i,j) of the four quadrant matrices
#' is normalized so that their sum equals 1.  In this case, 
#' \code{$cov.matrix} is computed as 
#' \code{$cupm + $clpm - $dupm - $dlpm}, yielding a dimensionless,
#' signed dependence measure bounded between -1 and 1.
#' This representation discards magnitude information and is therefore
#' a lossy nonlinear correlation matrix.  A higher fidelity nonlinear
#' correlation matrix is available via the \code{NNS.dep} function.
#' @examples
#' set.seed(123)
#' A <- cbind(rnorm(100), rnorm(100), rnorm(100))
#' 
#' # Uses norm = FALSE by default
#' PM.matrix(1, 1, target = NULL, variable = A, pop_adj = TRUE)
#' 
#' # Enable normalization
#' PM.matrix(1, 1, target = NULL, variable = A, pop_adj = TRUE, norm = TRUE)
#' 
#' # Use 0's for targets
#' PM.matrix(1, 1, target = rep(0, ncol(A)), variable = A, pop_adj = TRUE)        
#' 
#' # Use variable medians as targets
#' PM.matrix(1, 1, target = apply(A, 2, "median"), variable = A, pop_adj = TRUE)  
#' @export
PM.matrix <- function(LPM_degree, UPM_degree, target, variable, pop_adj, norm = FALSE) {
  .Call(`_NNS_PMMatrix_RCPP`, LPM_degree, UPM_degree, target, variable, pop_adj, norm)
}

#' @name Co.LPM
#' @title Co‑Lower Partial Moment
#' @description
#'   Computes the co‑lower partial moment (lower‑left quadrant 4) between two
#'   equal‑length numeric vectors at any degree and target.
#' @param degree_lpm numeric; degree for x ("degree_x"). degree = 0 gives frequency, degree = 1 gives area.
#' @param x numeric vector of observations.
#' @param y numeric vector of the same length as x.
#' @param target_x numeric vector; thresholds for x (defaults to mean(x)).
#' @param target_y numeric vector; thresholds for y (defaults to mean(y)).
#' @param degree_y numeric; optional degree for y. If omitted, `degree_lpm` is
#'   used for both x and y.
#' @return Numeric vector of co‑LPM values.
#' @author Fred Viole, OVVO Financial Systems
#' @references
#'   Viole, F. & Nawrocki, D. (2013) *Nonlinear Nonparametric Statistics: Using Partial Moments* (ISBN:1490523995)
#' @examples
#'   set.seed(123)
#'   x <- rnorm(100); y <- rnorm(100)
#'   Co.LPM(0, x, y, mean(x), mean(y))
#' @export
Co.LPM <- function(degree_lpm, x, y, target_x, target_y, degree_y = NULL) {
  if (is.null(degree_y)) {
    degree_y <- degree_lpm
  }
  .Call(`_NNS_CoLPM_RCPP`, degree_lpm, x, y, target_x, target_y, degree_y)
}


#' @name Co.UPM
#' @title Co‑Upper Partial Moment
#' @description
#'   Computes the co‑upper partial moment (upper‑right quadrant 1) between two
#'   equal‑length numeric vectors at any degree and target.
#' @param degree_upm numeric; degree for x ("degree_x"). degree = 0 gives frequency, degree = 1 gives area.
#' @param x numeric vector of observations.
#' @param y numeric vector of the same length as x.
#' @param target_x numeric vector; thresholds for x (defaults to mean(x)).
#' @param target_y numeric vector; thresholds for y (defaults to mean(y)).
#' @param degree_y numeric; optional degree for y. If omitted, `degree_upm` is
#'   used for both x and y.
#' @return Numeric vector of co‑UPM values.
#' @author Fred Viole, OVVO Financial Systems
#' @references
#'   Viole, F. & Nawrocki, D. (2013) *Nonlinear Nonparametric Statistics: Using Partial Moments* (ISBN:1490523995)
#' @examples
#'   set.seed(123)
#'   x <- rnorm(100); y <- rnorm(100)
#'   Co.UPM(0, x, y, mean(x), mean(y))
#' @export
Co.UPM <- function(degree_upm, x, y, target_x, target_y, degree_y = NULL) {
  if (is.null(degree_y)) {
    degree_y <- degree_upm
  }
  .Call(`_NNS_CoUPM_RCPP`, degree_upm, x, y, target_x, target_y, degree_y)
}