using LIBSVM

# function input D is a distance matrix, K is a Kernel matrix
# count number of recurrences per time point i
"""
    REC(D::AbstractArray, rec_threshold::Float64, temp_excl::Int = 0)

Count the number of observations (recurrences) which fall into a radius `rec_threshold` of a distance matrix `D`. Exclude steps which are closer than `temp_excl` to be count as recurrences (default: `temp_excl = 5`)

Marwan, N., Carmen Romano, M., Thiel, M., & Kurths, J. (2007). Recurrence plots for the analysis of complex systems. Physics Reports, 438(5-6), 237–329. http://doi.org/10.1016/j.physrep.2006.11.001
"""
function REC(D::AbstractArray, rec_threshold::Float64, temp_excl::Int = 0)
  rec_out = init_REC(D)
  REC!(rec_out, D, rec_threshold, temp_excl)
  return(rec_out)
end

"""
    init_REC(D::Array{Float64, 2})
    init_REC(T::Int)

get object for memory efficient `REC!()` versions. Input can be a distance matrix `D` or the number of timesteps (observations) `T`.
"""
function init_REC(T::Int)
  rec_out = zeros(Float64, T)
end

function init_REC(D::Array{Float64, 2})
  rec_out = zeros(Float64, size(D, 1))
end

"""
    REC!(rec_out::AbstractArray, D::AbstractArray, rec_threshold::Float64, temp_excl::Int = 0)

Memory efficient version of `REC()` for use within a loop. `rec_out` is preallocated output, should be initialised with `init_REC()`.
"""
function REC!(rec_out::AbstractArray, D::AbstractArray, rec_threshold::Float64, temp_excl::Int = 0)
    N = size(D, 1)
    @assert temp_excl < N - 1
    @inbounds for i = 1:N
    rec_out[i] = 0.0
    if(i-temp_excl-1 >= 1)
      for j = 1:(i-temp_excl-1)
        if D[i, j] < rec_threshold && i != j
          rec_out[i] = rec_out[i] + 1
        end
      end
    end
    if(i+temp_excl+1 <= N)
      for j = (i+temp_excl+1):N
        if D[i, j] < rec_threshold && i != j
          rec_out[i] = rec_out[i] + 1
        end
      end
    end
  end
  broadcast!(/, rec_out, rec_out, N)
  broadcast!(-, rec_out, 1.0, rec_out)
  return(rec_out)
end

"""
    KDE(K)

Compute a Kernel Density Estimation (the Parzen sum), given a Kernel matrix `K`.

Parzen, E. (1962). On Estimation of a Probability Density Function and Mode. The Annals of Mathematical Statistics, 33, 1–1065–1076.
"""
function KDE(K::AbstractArray)
    KDEout = init_KDE(size(K, 1))
    KDE!(KDEout, K)
    return(KDEout)
end

"""
    init_KDE(K::Array{Float64, 2})
    init_KDE(T::Int)

Returns `KDE_out` object for usage in `KDE!()`. Use either a Kernel matrix `K` or the number of time steps/observations `T` as argument.
"""
function init_KDE(T::Int)
  KDE_out = zeros(Float64, T)
end

function init_KDE(K::Array{Float64, 2})
  KDE_out = zeros(Float64, size(K, 1))
end

"""
    KDE!(KDE_out, K)

Memory efficient version of `KDE()`. Additionally uses preallocated `KDE_out` object for writing the results. Initialize `KDE_out` with `init_KDE()`.
"""
function KDE!(KDE_out, K::AbstractArray)
  @assert size(K, 1) == size(KDE_out, 1)
  mean!(KDE_out, K)
  broadcast!(-, KDE_out, 1.0, KDE_out)
  return(KDE_out)
end

"""
    init_T2(VAR::Int, T::Int)
    init_T2{tp}(data::AbstractArray{tp,2})

initialize `t2_out` object for `T2!` either with number of variables `VAR` and observations/time steps `T` or with a two dimensional `data` matrix (time * variables)
"""
function init_T2(VAR::Int, T::Int)
  diagS =  zeros(Float64, VAR, VAR);
  Qinv =  zeros(Float64, VAR, VAR);
  data_norm = zeros(Float64, T, VAR);
  cdata = zeros(Float64, T, VAR);
  maha = zeros(Float64, T);
  t2_out = (maha, diagS, cdata, Qinv, data_norm)
  return(t2_out)
end

function init_T2(data::AbstractArray{tp,N}) where {tp, N}
  VAR = size(data, N)
  T = size(data, 1)
  diagS =  zeros(tp, VAR, VAR);
  Qinv =  zeros(tp, VAR, VAR);
  data_norm = zeros(tp, T, VAR);
  cdata = zeros(tp, T, VAR);
  maha = zeros(tp, T);
  t2_out = (maha, diagS, cdata, Qinv, data_norm)
  return(t2_out)
end

"""
    T2!(t2_out, data, Q[, mv])

Memory efficient version of `T2()`, for usage within a loop etc. Initialize the `t2_out` object with `init_T2()`.
`t2_out[1]` contains the squred Mahalanobis distance after computation.
"""
function T2!(t2_out::Tuple{Array{tp,1},Array{tp,2},Array{tp,2},Array{tp,2},Array{tp,2}}
             , data::AbstractArray{tp,2}, Q::AbstractArray{tp,2}, mv = 0) where {tp}
  (maha, diagS, cdata, Qinv, data_norm) = t2_out
  if(mv == 0)
    copyto!(cdata, data)
  elseif(size(mv, 1) == 1)
    copyto!(cdata, data .- mv)
  elseif(size(mv, 1) != 1)
    copyto!(cdata, data .- mv')
  end
  USVt = svd(Q)
  copyto!(view(diagS, diagind(diagS)), (USVt.S .+ 1e-10)  .^ (-0.5))
  transpose!(Qinv, USVt.U * diagS * USVt.Vt)
  copyto!(data_norm, cdata * Qinv)
  broadcast!(*, data_norm, data_norm, data_norm)
  sum!(maha, data_norm)
  return(t2_out[1])
end

# Hotelling's T^2 (Mahalanobis distance to the data mean)
# input is time * var data matrix
"""
    T2{tp}(data::AbstractArray{tp,2}, Q::AbstractArray[, mv])

Compute Hotelling's T2 control chart (the squared Mahalanobis distance to the data's mean vector (`mv`), given the covariance matrix `Q`).
Input data is a two dimensional data matrix (observations * variables).

Lowry, C. A., & Woodall, W. H. (1992). A Multivariate Exponentially Weighted Moving Average Control Chart. Technometrics, 34, 46–53.
"""
function T2(data::AbstractArray{tp,2}, Q::AbstractArray{tp, 2}, mv = 0) where {tp}
  t2_out = init_T2(data)
  T2!(t2_out, data, Q, mv)
  return(t2_out[1])
end

# mean of k nearest neighbor distances
"""
    KNN_Gamma(knn_dists_out)

This function computes the mean distance of the K nearest neighbors given a `knn_dists_out` object from `knn_dists()` as input argument.

Harmeling, S., Dornhege, G., Tax, D., Meinecke, F., & Müller, K.-R. (2006). From outliers to prototypes: Ordering data. Neurocomputing, 69(13-15), 1608–1618. http://doi.org/10.1016/j.neucom.2005.05.015
"""
function KNN_Gamma(knn_dists_out::Tuple{Int64,Array{Int64,1},Array{Float64,1},Array{Int64,2},Array{Float64,2}})
  NNdists = knn_dists_out[5]
  N = size(NNdists,1)
  KNN_Gamma_out = zeros(Float64, N)
  mean!(KNN_Gamma_out, NNdists)
  return(KNN_Gamma_out)
end

#T: number of timesteps in the datacube
"""
    init_KNN_Gamma(T::Int)
    init_KNN_Gamma(knn_dists_out)

initialize a `KNN_Gamma_out` object for `KNN_Gamma!` either with `T`, the number of observations/time steps or with a `knn_dists_out` object.
"""
function init_KNN_Gamma(T::Int)
  KNN_Gamma_out = zeros(Float64, T)
end

function init_KNN_Gamma(knn_dists_out::Tuple{Int64,Array{Int64,1},Array{Float64,1},Array{Int64,2},Array{Float64,2}})
  KNN_Gamma_out = zeros(Float64, size(knn_dists_out[2], 1))
end

"""
    KNN_Gamma!(KNN_Gamma_out, knn_dists_out)

Memory efficient version of `KNN_Gamma`, to be used in a loop. Initialize `KNN_Gamma_out` with `init_KNN_Gamma()`.
"""
function KNN_Gamma!(KNN_Gamma_out, knn_dists_out::Tuple{Int64,Array{Int64,1},Array{Float64,1},Array{Int64,2},Array{Float64,2}})
  NNdists = knn_dists_out[5]
  @assert size(NNdists,1) ==  size(KNN_Gamma_out,1) || error("input size KNN_Gamma_out and NNdists not equal")
  N = size(NNdists,1)
  mean!(KNN_Gamma_out, NNdists)
  return(KNN_Gamma_out)
end

"""
    init_KNN_Delta(T, VAR, k)

return a `KNN_Delta_out` object to be used for `KNN_Delta!`. Input: time steps/observations `T`, variables `VAR`, number of K nearest neighbors `k`.
"""
function init_KNN_Delta(T::Int, VAR::Int, k::Int)
  r = Array{Float64}(undef, T)
  x_i = Array{Float64}(undef, 1, VAR)
  d_x = Array{Float64}(undef, k, VAR)
  KNN_Delta_out = (r, x_i, d_x)
  return(KNN_Delta_out)
end

function init_KNN_Delta(knn_dists_out::Tuple{Int64,Array{Int64,1},Array{Float64,1},Array{Int64,2},Array{Float64,2}}, VAR::Int)
  T = size(knn_dists_out[2], 1)
  K = knn_dists_out[1]
  r = Array{Float64}(undef, T)
  x_i = Array{Float64}(undef, 1, VAR)
  d_x = Array{Float64}(undef, K, VAR)
  KNN_Delta_out = (r, x_i, d_x)
  return(KNN_Delta_out)
end

"""
    KNN_Delta!(KNN_Delta_out, knn_dists_out, data)

Memory Efficient Version of `KNN_Delta()`. `KNN_Delta_out[1]` is the vector difference of the k-nearest neighbors.
"""
function KNN_Delta!(KNN_Delta_out::Tuple{Array{Float64,1},Array{Float64,2},Array{Float64,2}}
                    , knn_dists_out::Tuple{Int64,Array{Int64,1},Array{Float64,1},Array{Int64,2},Array{Float64,2}}
                    , data::AbstractArray{Float64,2})
    (r, x_i, d_x) = KNN_Delta_out
    indices = knn_dists_out[4]
    K = size(indices, 2)
    T = size(data,1) #dimensions
    VAR = size(data, 2)
    @assert size(x_i, 2) == size(d_x, 2) == VAR || error("size of d_X and x_i not size of size(datacube, 4)")
    @assert size(r, 1) == T || error("size of r not of size(datacube ,1)")
    dists = 0.0
    for i = 1:T
        dists = 0.0
        inds=indices[i,:]
        for k=1:length(inds), j=1:size(data,2)
          d_x[k,j] = data[i,j] - data[inds[k],j]
        end
        sum!(x_i, d_x)
        for j = 1:VAR
            dists += (x_i[1,j] / K)^2
        end
        r[i] = sqrt.(dists)
    end
    return(r)
end

"""
    KNN_Delta(knn_dists_out, data)

Compute Delta as vector difference of the k-nearest neighbors. Arguments are a `knn_dists()` object (`knn_dists_out`) and a `data` matrix (observations * variables)

Harmeling, S., Dornhege, G., Tax, D., Meinecke, F., & Müller, K.-R. (2006). From outliers to prototypes: Ordering data. Neurocomputing, 69(13-15), 1608–1618. http://doi.org/10.1016/j.neucom.2005.05.015
"""
function KNN_Delta(knn_dists_out::Tuple{Int64,Array{Int64,1},Array{Float64,1},Array{Int64,2},Array{Float64,2}}
                    , data::AbstractArray{Float64,2})
  KNN_Delta_out = init_KNN_Delta(knn_dists_out, size(data, 2))
  KNN_Delta!(KNN_Delta_out, knn_dists_out, data)
  return(KNN_Delta_out[1])
end

"""
    init_UNIV(T::Int, VAR::Int)
    init_UNIV{tp}(data::AbstractArray{tp, 2})

initialize a `univ_out` object to be used in `UNIV!()` either with number of time steps/observations `T` and variables `VAR` or with a `data` matrix observations * variables.
"""
function init_UNIV(T::Int, VAR::Int)
    var_dat = zeros(tp, T)
    varquants = zeros(Float64,T, VAR)
    univ_out = (var_dat, varquants)
    return univ_out
end

function init_UNIV(data::AbstractArray{tp, N}) where {tp,N}
     T = size(data, 1)
     VAR = size(data, N)
     var_dat = zeros(tp, T)
     varquants = zeros(Float64,T, VAR)
     univ_out = (var_dat, varquants)
     return univ_out
end

"""
    UNIV!(univ_out, data)

Memory efficient version of `UNIV()`, input an `univ_out` object from `init_UNIV()` and some `data` matrix observations * variables
"""
function UNIV!(univ_out::Tuple{Array{tp,1},Array{Float64,2}}, data::AbstractArray{tp, N}) where {tp, N}
  (var_dat, varquants) = univ_out
  @assert size(var_dat, 1) == size(varquants, 1) == size(data, 1)
  @assert size(varquants, 2) == size(data, 2)
  for variable = 1:size(data, 2)
    # copy with little allocation
    copyto!(var_dat, view(data, :, variable))
    get_quantile_scores!(view(varquants, :, variable), var_dat)
  end
  maximum!(var_dat, varquants)
  return var_dat
end

"""
    UNIV(data)

order the values in each varaible and return their maximum, i.e. any of the variables in `data` (observations * variables) is above a given quantile,
the highest quantile will be returned.
"""
function UNIV(data::AbstractArray{tp, N}) where {tp, N}
  univ_out = init_UNIV(data)
  return UNIV!(univ_out, data)
end

"""
    SVDD_train(K, nu)

train a one class support vecort machine model (i.e. support vector data description), given a kernel matrix K and and the highest possible percentage of outliers `nu`.
Returns the model object (`svdd_model`). Requires LIBSVM.

Tax, D. M. J., & Duin, R. P. W. (1999). Support vector domain description. Pattern Recognition Letters, 20, 1191–1199.
Schölkopf, B., Williamson, R. C., & Bartlett, P. L. (2000). New Support Vector Algorithms. Neural Computation, 12, 1207–1245.
"""
function SVDD_train(K::AbstractArray, nu::Float64)
# function in LIBSVM.jl for optional parameter settings.
  #svdd_model = svmtrain(fill(1, size(K, 1)), K
  #                  , kernel_type = Int32(4), svm_type  = Int32(2), nu = nu
  #                  , probability_estimates = false);
  svdd_model = svmtrain(K,  svmtype = OneClassSVM, nu = nu, kernel = Kernel.Precomputed);
  return(svdd_model)
end

"""
    SVDD_predict(svdd_model, K)

predict the outlierness of an object given the testing Kernel matrix `K` and the `svdd_model` from SVDD_train(). Requires LIBSVM.

Tax, D. M. J., & Duin, R. P. W. (1999). Support vector domain description. Pattern Recognition Letters, 20, 1191–1199.
Schölkopf, B., Williamson, R. C., & Bartlett, P. L. (2000). New Support Vector Algorithms. Neural Computation, 12, 1207–1245.
"""
function SVDD_predict(svdd_model::LIBSVM.SVM, K::AbstractArray)
    (predicted_labels, decision_values) = svmpredict(svdd_model, K)
end

"""
    KNFST_predict(model, K)

predict the outlierness of some data (represented by the kernel matrix `K`), given some KNFST `model` from `KNFST_train(K)`. Compute `K`with `kernel_matrix()`.

Paul Bodesheim and Alexander Freytag and Erik Rodner and Michael Kemmler and Joachim Denzler:
"Kernel Null Space Methods for Novelty Detection". Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2013.

"""
function KNFST_predict(KNFST_mod, K)
  knfst_out = init_KNFST(size(K,2),KNFST_mod)
  KNFST_predict!(knfst_out, KNFST_mod, K)
  return(knfst_out)
end

"""
    init_KNFST(T, KNFST_mod)

initialize a `KNFST_out`object for the use with `KNFST_predict!`, given `T`, the number of observations and the model output `KNFST_train(K)`.
"""
function init_KNFST(T::Int, KNFST_mod::Tuple{Array{Float64,2},Array{Float64,2}})
  diffs = zeros(Float64, T, size(KNFST_mod[2], 2));
  scores = zeros(Float64, T);
  Ktransposed = zeros(Float64, T, size(KNFST_mod[1],1));
  KNFST_out = (scores, diffs, Ktransposed)
  return(KNFST_out)
end

"""
    KNFST_predict!(KNFST_out, KNFST_mod, K)

predict the outlierness of some data (represented by the kernel matrix `K`), given a `KNFST_out` object (`init_KNFST()`), some KNFST model (`KNFST_mod = KNFST_train(K)`)
and the testing kernel matrix K.

Paul Bodesheim and Alexander Freytag and Erik Rodner and Michael Kemmler and Joachim Denzler:
"Kernel Null Space Methods for Novelty Detection". Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2013.
"""
function KNFST_predict!(scores, diffs, Ktransposed, proj, targetValue, K)
  @assert size(scores, 1) == size(K, 2) == size(diffs, 1)
  @assert size(Ktransposed, 1) == size(K, 2) && size(Ktransposed, 2) == size(K, 1)
  transpose!(Ktransposed, K)
  # projected test samples: Ktransposed * model["proj"]
  mul!(diffs, Ktransposed, proj)
  # differences to the target value:
  broadcast!(-,diffs,diffs, targetValue)
  broadcast!(*,diffs, diffs, diffs)
  sum!(scores, diffs)
  broadcast!(sqrt, scores, scores)
  return(scores)
end

function KNFST_predict!(KNFST_out::Tuple{Array{Float64,1},Array{Float64,2},Array{Float64,2}}
                                     , KNFST_mod::Tuple{Array{Float64,2},Array{Float64,2}}, K::Array{Float64,2})
  (scores, diffs, Ktransposed) = KNFST_out
  (proj, targetValue) = KNFST_mod
  KNFST_predict!(scores, diffs, Ktransposed, proj, targetValue, K)
  return(scores)
end

# Learning method for novelty detection with KNFST according to the work:
#
# Paul Bodesheim and Alexander Freytag and Erik Rodner and Michael Kemmler and Joachim Denzler:
# "Kernel Null Space Methods for Novelty Detection".
# Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2013.
#
# Please cite that paper if you are using this code!
#
# proj = calculateKNFST(K, labels)
#
# calculates projection matrix of KNFST
#
# INPUT:
#   K -- (n x n) kernel matrix containing similarities of n training samples
#   labels -- (n x 1) vector containing (multi-class) labels of the n training samples
#
# OUTPUT:
#   proj -- projection matrix for data points (project x via kx*proj,
#           where kx is row vector containing kernel values of x and
#           training data)
#
#
function calculateKNFST(K, labels)

    classes = unique(labels);

    ### check labels
    if length(unique(labels)) == 1
      error("calculateKNFST.jl: not able to calculate a nullspace from data of a single class using KNFST (input variable 'labels' only contains a single value)");
    end

    ### check kernel matrix
    (n,m) = size(K);
    if n != m
        error("calculateKNFST.jl: kernel matrix must be quadratic");
    end

    ### calculate weights of orthonormal basis in kernel space
    centeredK = copy(K); # because we need original K later on again
    centerKernelMatrix(centeredK);
    (basisvecsValues,basisvecs) = eigen(centeredK);
    basisvecs = basisvecs[:,basisvecsValues .> 1e-12];
    basisvecsValues = basisvecsValues[basisvecsValues .> 1e-12];
    basisvecsValues = Diagonal(1 ./ sqrt.(basisvecsValues));
    basisvecs = basisvecs*basisvecsValues;

    ### calculate transformation T of within class scatter Sw:
    ### T= B'*Sw*B = H*H'  and H = B'*K*(I-L) and L a block matrix
    L = zeros(n,n);
    for i=1:length(classes)

       L[labels .== classes[i], labels .== classes[i]] .= 1.0 / sum(labels .== classes[i]);

    end

    ### need Matrix M with all entries 1/m to modify basisvecs which allows usage of
    ### uncentered kernel values:  (eye(size(M))-M)*basisvecs
    M = ones(m,m)./m;

    ### compute helper matrix H
    H = ((I - M)*basisvecs)' * K * (I - L);

    ### T = H*H' = B'*Sw*B with B=basisvecs
    T = H*H';

    ### calculate weights for null space
    eigenvecs = nullspace(T);

    if size(eigenvecs,2) < 1

      (eigenvals,eigenvecs) = eigen(T);
      (min_val,min_ID) = findmin(eigenvals);
      eigenvecs = eigenvecs[:,min_ID];

    end

    ### calculate null space projection and return it
    proj = ((I - M) * basisvecs) * eigenvecs;

end

#############################################################################################################

function centerKernelMatrix(kernelMatrix)
# centering the data in the feature space only using the (uncentered) Kernel-Matrix
#
# INPUT:
#       kernelMatrix -- uncentered kernel matrix
# OUTPUT:
#       centeredKernelMatrix -- centered kernel matrix

  ### get size of kernelMatrix
  n = size(kernelMatrix, 1);

  ### get mean values of each row/column
  columnMeans = mean(kernelMatrix, dims = 2); ### NOTE: columnMeans = rowMeans because kernelMatrix is symmetric
  matrixMean = mean(columnMeans);

  centeredKernelMatrix = kernelMatrix;

  for k=1:n
    for j=1:n
      centeredKernelMatrix[k,j] -= columnMeans[j];
      centeredKernelMatrix[j,k] -= columnMeans[j];
    end
  end
  #This line will not have any effect
  centeredKernelMatrix .+= matrixMean;

end

"""
    KNFST_train(K)

train a one class novelty KNFST model on a Kernel matrix `K` according to
Paul Bodesheim and Alexander Freytag and Erik Rodner and Michael Kemmler and Joachim Denzler:
"Kernel Null Space Methods for Novelty Detection". Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2013.

# Output
`(proj, targetValue)`
`proj` 	-- projection vector for data points (project x via kx*proj, where kx is row vector containing kernel values of x and training data)
`targetValue` -- value of all training samples in the null space
"""
function KNFST_train(K)

    # get number of training samples
    n = size(K,1);

    # include dot products of training samples and the origin in feature space (these dot products are always zero!)
    K_ext = [K zeros(n,1); zeros(1,n) 0];

    # create one-class labels + a different label for the origin
    labels = push!(ones(n),0);

    # get model parameters
    proj = calculateKNFST(K_ext,labels);
    targetValue = mean(K_ext[labels.==1,:]*proj, dims = 1);
    proj = proj[1:n,:];

    # return both variables
    return proj, targetValue

end

"""
    Dist2Centers(centers::AbstractArray{tp, 2}) where {tp}

Compute the distance to the nearest centers of i.e. a K-means clustering output.
Large Distances to the nearest center are anomalies. data: Observations * Variables.
# Example
`(proj, targetValue)`

"""
function Dist2Centers(data::AbstractArray{tp,2}, centers::AbstractArray{tp,2}) where {tp}
  (minD, minDIdx) = findmin(pairwise(Euclidean(), data', centers'), 2)
  clustassgin = zeros(Int, size(data, 1))
  for i = 1:size(data, 1)
    (u, clustassgin[i]) = ind2sub((size(data, 1), size(centers, 1)), minDIdx[i])
  end
  return minD, clustassgin
end

###################################
# end


using UnsafeArrays
import Base: showarg, eltype
const DSYEVR_ = (BLAS.@blasfunc(dsyevr_),Base.liblapack_name)
const SSYEVR_ = (BLAS.@blasfunc(ssyevr_),Base.liblapack_name)

# ----------------------------------------------------
# Zero cone
# ----------------------------------------------------
"""
    ZeroSet(dim)

Creates the zero set ``\\{ 0 \\}^{dim}`` of dimension `dim`. If `x` ∈ `ZeroSet` then all entries of x are zero.
"""
struct ZeroSet{T} <: AbstractConvexCone{T}
	dim::Int
	function ZeroSet{T}(dim::Int) where {T}
		dim >= 0 ? new(dim) : throw(DomainError(dim, "dimension must be nonnegative"))
	end
end
ZeroSet(dim) = ZeroSet{DefaultFloat}(dim)

function project!(x::AbstractVector{T}, ::ZeroSet{T}) where{T}
	x .= zero(T)
	return nothing
end

function in_dual(x::AbstractVector{T}, ::ZeroSet{T}, tol::T) where{T}
	return true
end

function in_pol_recc(x::AbstractVector{T}, ::ZeroSet{T}, tol::T) where{T}
	!any( x-> (abs(x) > tol), x)
end

function allocate_memory!(cone::AbstractConvexSet{T}) where {T}
  return nothing
end

# ----------------------------------------------------
# Nonnegative orthant
# ----------------------------------------------------
"""
    Nonnegatives(dim)

Creates the nonnegative orthant ``\\{ x \\in \\mathbb{R}^{dim} : x \\ge 0 \\}``  of dimension `dim`.
"""
struct Nonnegatives{T} <: AbstractConvexCone{T}
	dim::Int
	constr_type::BitArray{1} #store rows of constraints that are loose (+1)
	function Nonnegatives{T}(dim::Int) where {T}
		dim >= 0 ? new(dim, falses(dim)) : throw(DomainError(dim, "dimension must be nonnegative"))
	end
end
Nonnegatives(dim) = Nonnegatives{DefaultFloat}(dim)

"Classify the inequality constraints as loose, if b very large, as s = -Ax + b >= 0."
function classify_constraints!(constr_type::BitArray, b::AbstractVector{T}, COSMO_INFTY::Real, MIN_SCALING::Real) where {T}
	for i = 1:length(b)
		if b[i] > COSMO_INFTY * MIN_SCALING
			constr_type[i] = true
		end
	end
	return nothing
end

function project!(x::AbstractVector{T}, C::Nonnegatives{T}) where{T}
	x .= max.(x, zero(T))
	return nothing
end

function in_dual(x::AbstractVector{T}, ::Nonnegatives{T}, tol::T) where{T}
	return !any( x-> (x < -tol), x)
end

function in_pol_recc(x::AbstractVector{T}, ::Nonnegatives{T}, tol::T) where{T}
	return !any( x-> (x > tol), x)
end

# ----------------------------------------------------
# Second Order Cone
# ----------------------------------------------------
"""
    SecondOrderCone(dim)

Creates the second-order cone (or Lorenz cone) ``\\{ (t,x) \\in \\mathrm{R}^{dim} : || x ||_2  \\leq t \\}``.
"""
struct SecondOrderCone{T} <: AbstractConvexCone{T}
	dim::Int
	function SecondOrderCone{T}(dim::Int) where {T}
		dim >= 0 ? new(dim) : throw(DomainError(dim, "dimension must be nonnegative"))
	end
end
SecondOrderCone(dim) = SecondOrderCone{DefaultFloat}(dim)

function project!(x::AbstractVector{T}, ::SecondOrderCone{T}) where{T}
	t = x[1]
	xt = view(x, 2:length(x))
	norm_x = norm(xt, 2)
	if norm_x <= t
		nothing
	elseif norm_x <= -t
		x[:] .= zero(T)
	else
		x[1] = (norm_x + t) / T(2)
		#x(2:end) assigned via view
		@. xt = (norm_x + t) / (T(2) * norm_x) * xt
	end
	return nothing
end

function in_dual(x::AbstractVector{T}, ::SecondOrderCone{T}, tol::T) where{T}
	@views norm(x[2:end]) <= (tol + x[1]) #self dual
end

function in_pol_recc(x::AbstractVector{T}, ::SecondOrderCone, tol::T) where{T}
	@views norm(x[2:end]) <= (tol - x[1]) #self dual
end

# ----------------------------------------------------
# Positive Semidefinite Cone
# ----------------------------------------------------

#a type to maintain internal workspace data for the BLAS syevr function
mutable struct PsdBlasWorkspace{T}
    m::Base.RefValue{BLAS.BlasInt}
    w::Vector{T}
    Z::Matrix{T}
    isuppz::Vector{BLAS.BlasInt}
    work::Vector{T}
    lwork::BLAS.BlasInt
    iwork::Vector{BLAS.BlasInt}
    liwork::BLAS.BlasInt
    info::Base.RefValue{BLAS.BlasInt}

    function PsdBlasWorkspace{T}(n::Integer) where{T}

        BlasInt = BLAS.BlasInt

        #workspace data for BLAS
        m      = Ref{BlasInt}()
        w      = Vector{T}(undef,n)
        Z      = Matrix{T}(undef,n,n)
        isuppz = Vector{BlasInt}(undef, 2*n)
        work   = Vector{T}(undef, 1)
        lwork  = BlasInt(-1)
        iwork  = Vector{BlasInt}(undef, 1)
        liwork = BlasInt(-1)
        info   = Ref{BlasInt}()

        new(m,w,Z,isuppz,work,lwork,iwork,liwork,info)
    end
end

for (syevr, elty) in
    ((DSYEVR_,:Float64),
     (SSYEVR_,:Float32))
   @eval begin
        function _syevr!(A::AbstractMatrix{$elty}, ws::PsdBlasWorkspace{$elty})

            #Float64 only support for now since we call dsyevr_ directly
            n       = size(A,1)
            ldz     = n
            lda     = stride(A,2)

                ccall($syevr, Cvoid,
                (Ref{UInt8}, Ref{UInt8}, Ref{UInt8}, Ref{BLAS.BlasInt},
                Ptr{$elty}, Ref{BLAS.BlasInt}, Ref{$elty}, Ref{$elty},
                Ref{BLAS.BlasInt}, Ref{BLAS.BlasInt}, Ref{$elty}, Ptr{BLAS.BlasInt},
                Ptr{$elty}, Ptr{$elty}, Ref{BLAS.BlasInt}, Ptr{BLAS.BlasInt},
                Ptr{$elty}, Ref{BLAS.BlasInt}, Ptr{BLAS.BlasInt}, Ref{BLAS.BlasInt},
                Ptr{BLAS.BlasInt}),
                'V', 'A', 'U', n,
                A, max(1,lda), 0.0, 0.0,
                0, 0, -1.0,
                ws.m, ws.w, ws.Z, ldz, ws.isuppz,
                ws.work, ws.lwork, ws.iwork, ws.liwork,
                ws.info)
                LAPACK.chklapackerror(ws.info[])
        end
    end #@eval
end #for

function _project!(X::AbstractMatrix, ws::PsdBlasWorkspace{T}) where{T}

    #computes the upper triangular part of the projection of X onto the PSD cone

     #allocate additional workspace arrays if the ws
     #work and iwork have not yet been sized
     if ws.lwork == -1
         _syevr!(X,ws)
         ws.lwork = BLAS.BlasInt(real(ws.work[1]))
         resize!(ws.work, ws.lwork)
         ws.liwork = ws.iwork[1]
         resize!(ws.iwork, ws.liwork)
     end

	 # below LAPACK function does the following: w,Z  = eigen!(Symmetric(X))
	 	_syevr!(X, ws)
		# compute upper triangle of: X .= Z*Diagonal(max.(w, 0.0))*Z'
		rank_k_update!(X, ws)
end

function rank_k_update!(X::AbstractMatrix, ws::COSMO.PsdBlasWorkspace{T}) where {T}
  n = size(X, 1)
  @. X = zero(T)
  nnz_λ = 0
  for j = 1:length(ws.w)
    λ = ws.w[j]
    if λ > 0
      nnz_λ += 1
      @inbounds for i = 1:n
        ws.Z[i, j] = ws.Z[i, j] * sqrt(λ)
      end
    end
  end

  if nnz_λ > 0
    V = uview(ws.Z, :, (n - nnz_λ + 1):n)
    BLAS.syrk!('U', 'N', one(T), V, one(T), X)
  end
  return nothing
end

"""
    PsdCone(dim)

Creates the cone of symmetric positive semidefinite matrices ``\\mathcal{S}_+^{dim}``. The entries of the matrix `X` are stored column-by-column in the vector `x` of dimension `dim`.
Accordingly  ``X \\in \\mathbb{S}_+ \\Rightarrow x \\in \\mathcal{S}_+^{dim}``, where ``X = \\text{mat}(x)``.
"""
struct PsdCone{T} <: AbstractConvexCone{T}
	dim::Int
	sqrt_dim::Int
  work::PsdBlasWorkspace{T}
  tree_ind::Int  # tree number that this cone belongs to
  clique_ind::Int
	function PsdCone{T}(dim::Int, tree_ind::Int, clique_ind::Int) where{T}
		dim >= 0       || throw(DomainError(dim, "dimension must be nonnegative"))
		iroot = isqrt(dim)
		iroot^2 == dim || throw(DomainError(dim, "dimension must be a square"))
		new(dim, iroot, PsdBlasWorkspace{T}(iroot), tree_ind, clique_ind)
	end
end
PsdCone(dim) = PsdCone{DefaultFloat}(dim)
PsdCone{T}(dim::Int) where{T} = PsdCone{T}(dim, 0, 0)

struct DensePsdCone{T} <: AbstractConvexCone{T}
  dim::Int
  sqrt_dim::Int
  work::PsdBlasWorkspace{T}
  function DensePsdCone{T}(dim::Int) where{T}
    dim >= 0       || throw(DomainError(dim, "dimension must be nonnegative"))
    iroot = isqrt(dim)
    iroot^2 == dim || throw(DomainError(dim, "dimension must be a square"))
    new(dim, iroot, PsdBlasWorkspace{T}(iroot))
  end
end
DensePsdCone(dim) = DensePsdCone{DefaultFloat}(dim)

function project!(x::AbstractVector{T}, cone::Union{PsdCone{T}, DensePsdCone{T}}) where{T}
	n = cone.sqrt_dim

    # handle 1D case
    if length(x) == 1
        x .= max(x[1], zero(T))
    else
        # symmetrized square view of x
        X = reshape(x, n, n)
        symmetrize_upper!(X)
        _project!(X, cone.work)

        #fill in the lower triangular part
        for j=1:n, i=1:(j-1)
            X[j,i] = X[i,j]
        end
    end
    return nothing
end

# Notice that this is an in-place version that uses x as workspace
function in_dual!(x::AbstractVector{T}, cone::Union{PsdCone{T}, DensePsdCone{T}}, tol::T) where{T}
	n = cone.sqrt_dim
	X = reshape(x, n, n)
  return COSMO.is_pos_def!(X, tol)
end
in_dual(x::AbstractVector{T}, cone::Union{PsdCone{T}, DensePsdCone{T}}, tol::T) where{T} = in_dual!(copy(x), cone, tol)

function in_pol_recc!(x::AbstractVector{T}, cone::Union{PsdCone{T}, DensePsdCone{T}}, tol::T) where{T}
	n = cone.sqrt_dim
	X = reshape(x, n, n)
	return COSMO.is_neg_def!(X, tol)
end
in_pol_recc(x::AbstractVector{T}, cone::Union{PsdCone{T}, DensePsdCone{T}}, tol::T) where{T} = in_pol_recc!(copy(x), cone, tol)

# ----------------------------------------------------
# Positive Semidefinite Cone (Triangle)
# ----------------------------------------------------
# Psd cone given by upper-triangular entries of matrix
"""
    PsdConeTriangle(dim)

Creates the cone of symmetric positive semidefinite matrices. The entries of the upper-triangular part of matrix `X` are stored in the vector `x` of dimension `dim`.
A ``r \\times r`` matrix has ``r(r+1)/2`` upper triangular elements and results in a vector of ``\\mathrm{dim} = r(r+1)/2``.

### Examples
The matrix
```math
\\begin{bmatrix} x_1 & x_2 & x_4\\\\ x_2 & x_3 & x_5\\\\ x_4 & x_5 & x_6 \\end{bmatrix}
```
is transformed to the vector ``[x_1, x_2, x_3, x_4, x_5, x_6]^\\top `` with corresponding constraint  `PsdConeTriangle(6)`.

"""
mutable struct PsdConeTriangle{T} <: AbstractConvexCone{T}
    dim::Int #dimension of vector
    sqrt_dim::Int # side length of matrix
    X::Array{T,2}
    work::PsdBlasWorkspace{T}
    tree_ind::Int # tree number that this cone belongs to
    clique_ind::Int

    function PsdConeTriangle{T}(dim::Int, tree_ind::Int, clique_ind::Int) where{T}
        dim >= 0       || throw(DomainError(dim, "dimension must be nonnegative"))
        side_dimension = Int(sqrt(0.25 + 2 * dim) - 0.5);
        new(dim, side_dimension, zeros(side_dimension, side_dimension), PsdBlasWorkspace{T}(side_dimension), tree_ind, clique_ind)

    end
end
PsdConeTriangle(dim) = PsdConeTriangle{DefaultFloat}(dim)
PsdConeTriangle{T}(dim::Int) where{T} = PsdConeTriangle{T}(dim, 0, 0)

DecomposableCones{T} = Union{PsdCone{T}, PsdConeTriangle{T}}

mutable struct DensePsdConeTriangle{T} <: AbstractConvexCone{T}
    dim::Int #dimension of vector
    sqrt_dim::Int # side length of matrix
    X::Array{T,2}
    work::PsdBlasWorkspace{T}

    function DensePsdConeTriangle{T}(dim::Int) where{T}
        dim >= 0       || throw(DomainError(dim, "dimension must be nonnegative"))
        side_dimension = Int(sqrt(0.25 + 2 * dim) - 0.5);
        new(dim, side_dimension, zeros(side_dimension, side_dimension), PsdBlasWorkspace{T}(side_dimension))
    end
end
DensePsdConeTriangle(dim) = DensePsdConeTriangle{DefaultFloat}(dim)

function project!(x::AbstractArray, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}) where{T}
    # handle 1D case
    if length(x) == 1
        x .= max(x[1],zero(T))
    else
        populate_upper_triangle!(cone.X, x, 1 / sqrt(2))
        _project!(cone.X,cone.work)
        extract_upper_triangle!(cone.X, x, sqrt(2) )
    end
    return nothing
end

# Notice that we are using a (faster) in-place version that modifies the input
function in_dual!(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}, tol::T) where{T}
    n = cone.sqrt_dim
    populate_upper_triangle!(cone.X, x, 1 / sqrt(2))
    return COSMO.is_pos_def!(cone.X, tol)
end
in_dual(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}, tol::T) where {T} = in_dual!(x, cone, tol)

function in_pol_recc!(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}, tol::T) where{T}
    n = cone.sqrt_dim
    populate_upper_triangle!(cone.X, x, 1 / sqrt(2))
    Xs = Symmetric(cone.X)
    return COSMO.is_neg_def!(cone.X, tol)
end
in_pol_recc(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}, tol::T) where {T} = in_pol_recc!(x, cone, tol)

function allocate_memory!(cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}) where {T}
  cone.X = zeros(cone.sqrt_dim, cone.sqrt_dim)
end

function populate_upper_triangle!(A::AbstractMatrix, x::AbstractVector, scaling_factor::Float64)
 	k = 0
  	for j in 1:size(A, 2)
     	for i in 1:j
        	k += 1
        	if i != j
        		A[i, j] = scaling_factor * x[k]
        	else
        		A[i, j] = x[k]
        	end
      	end
  	end
  	nothing
end

function extract_upper_triangle!(A::AbstractMatrix, x::AbstractVector, scaling_factor::Float64)
	k = 0
  	for j in 1:size(A, 2)
     	for i in 1:j
        	k += 1
        	if i != j
        		x[k] = scaling_factor * A[i, j]
        	else
        		x[k] = A[i, j]
        	end
      	end
  	end
	nothing
end

"""
    ExponentialCone(MAX_ITERS = 100, EXP_TOL = 1e-8)

Creates the exponential cone ``\\mathcal{K}_{exp} = \\{(x, y, z) \\mid y \\geq 0 ye^{x/y} ≤ z\\} \\cup \\{ (x,y,z) \\mid   x \\leq 0, y = 0, z \\geq 0 \\}``
"""
struct ExponentialCone{T} <: AbstractConvexCone{T}
  dim::Int
  v0::Vector{T}
  MAX_ITER::Int
  EXP_TOL::T

  function ExponentialCone{T}(dim = 3, MAX_ITERS = 100, EXP_TOL = T(1e-8)) where{T}
    new(3, zeros(T, 3), MAX_ITERS, EXP_TOL)
  end
end
ExponentialCone(args...) = ExponentialCone{DefaultFloat}(args...)

function project!(v::AbstractVector{T}, cone::ExponentialCone{T}) where{T}

  # Check the four different cases
  # 1. v in K_exp => v = v
  in_cone(v, cone, zero(T)) && return nothing

  # 2. -v in K_exp^* => v = 0
  if in_dual(-v, cone, zero(T))
    v .= zero(T)
    return nothing
  end

  # 3. x < 0 and y < 0 => v = (x, 0, max(z, 0))
  if v[1] < 0 && v[2] < 0
    v[2] = T(0)
    v[3] = max(v[3], zero(T))
    return nothing
  end

  # 4. Otherwise solve the following minimisation problem
  # min_w (1/2) ||v - v0||_2^2
  # s.t.  y * exp(x/y) == z
  #       y > 0
  project_exp!(v, cone)
end

# This is a modified version of the projection code used in SCS
# https://github.com/cvxgrp/scs/blob/master/src/cones.c
# We are solving the dual problem g(λ) via a bisection method
function project_exp!(v::AbstractVector{T}, cone::ExponentialCone{T}) where {T <: AbstractFloat}
  # save input vector and use v as working variable
  @. cone.v0 = v
  l, u = get_bisection_bounds(v, cone.v0, cone.EXP_TOL)

  for k = 1:cone.MAX_ITER
    λ = (u + l) / 2
    g = grad_dual!(λ, v, cone.v0, cone.EXP_TOL)
    g > 0 ? (l = λ) : (u = λ)
    u - l < cone.EXP_TOL && break
  end
end

function get_bisection_bounds(v::AbstractVector{T}, v0::Vector{T}, tol::T) where {T <: AbstractFloat}
  l = zero(T)
  λ = T(0.125)
  g = grad_dual!(λ, v, v0, tol)
  while g > 0
    l = λ
    λ *= 2
    g = grad_dual!(λ, v, v0, tol)
  end
  u = λ
  return l, u
end

function grad_dual!(λ::T, v::AbstractVector{T}, v0::Vector{T}, tol::T) where {T <: AbstractFloat}
  find_minimizers!(λ, v, v0, tol)
  v[2] == 0 ? (g = v[1]) : (g = v[1] + v[2] * log(v[2] / v[3]))
  return g
end

function find_minimizers!(λ::T, v::AbstractVector{T}, v0::Vector{T}, tol::T) where {T <: AbstractFloat}
  v[3] = find_min_t(λ, v0[2], v0[3], tol)
  # s* = (t - t0) * t / λ
  v[2] = (1 / λ) * (v[3] - v0[3]) * v[3]
  # r* = r0 - λ
  v[1] = v0[1] - λ
end

# use Newton method to find minimizer t* for given λ, i.e. find the zero of
# f(t) = t * (t - t0) / lambda - s0 + λ * log( t - t0 / λ) + λ
# Define Δt = t - t0
function find_min_t(λ::T, s0::T, t0::T, tol::T) where {T <: AbstractFloat}
  Δt = max(-t0, tol)
  for k = 1:150
    f = Δt * (Δt + t0) / λ^2 - s0 / λ + log(Δt / λ) + one(T)
    grad_f = (2 * Δt + t0) / λ^2 + one(T) / Δt
    Δt = Δt - f / grad_f

    if (Δt <= -t0)
      Δt = -t0
      break
    elseif (Δt <= 0)
      Δt = zero(T)
      break
    elseif abs(f) < tol
      break
    end
  end
  return Δt + t0
end

function in_cone(v::AbstractVector{T}, cone::ExponentialCone{T}, tol::T) where {T <: AbstractFloat}
  x = v[1]
  y = v[2]
  z = v[3]
  return (y > 0 && y * exp(x/y) <= z + tol) || (x <= tol &&  y == 0. && z >= -tol )
end
# Kexp^* = { (x,y,z) | x < 0, -xe^(y/x) <= e^1 z } cup { (0,y,z) | y >= 0,z >= 0 }
function in_dual(v::AbstractVector{T}, cone::ExponentialCone{T}, tol::T) where {T <: AbstractFloat}
  x = v[1]
  y = v[2]
  z = v[3]
  return (x < 0 && -x * exp(y / x) - exp(1) *  z <= tol) || (abs(x) <= tol && y >= -tol && z >= -tol)
end

function in_pol_recc(v::AbstractVector{T},cone::ExponentialCone{T}, tol::T) where {T <: AbstractFloat}
  return in_dual(-v, cone, tol)
end

"""
    PowerCone(alpha::Float64, MAX_ITERS::Int = 20, POW_TOL = 1e-8)

Creates the 3-d power cone ``\\mathcal{K}_{pow} = \\{(x, y, z) \\mid x^\\alpha y^{(1-\\alpha)} \\geq  \\|z\\|, x \\geq 0, y \\geq 0 \\}`` with ``0 < \\alpha < 1``
"""
struct PowerCone{T} <: AbstractConvexCone{T}
  dim::Int
  α::T
  MAX_ITER::Int
  POW_TOL::T

  function PowerCone{T}(alpha::Real, MAX_ITERS::Int = 20, POW_TOL::Real = 1e-8) where{T <: AbstractFloat}
    (alpha <= 0 || alpha >= one(T)) && throw(DomainError("The exponent α of the power cone has to be in (0, 1)."))
    new(3, alpha, MAX_ITERS, POW_TOL)
  end
end
PowerCone(alpha::T, args...) where {T <: AbstractFloat}= PowerCone{T}(alpha, args...)

# Allow precision conversion of PowerCones
function convert(dest_type::Type{COSMO.PowerCone{Ta}}, src::COSMO.PowerCone{Tb}) where {Ta <: AbstractFloat, Tb <: AbstractFloat}
	dest_type(Ta(src.α), src.MAX_ITER, Ta(src.POW_TOL))
end

# The projection onto the power cone is described in
# Hien - Differential properties of Euclidean projections onto power cone (2015)
function project!(v::AbstractVector{T}, cone::PowerCone{T}) where{T}
  # Check the special cases first
  # 1. v in K_pow => v = v
  in_cone(v, cone, zero(T)) && return nothing

  # 2. -v in K_pow^* => v .= 0
  if in_dual(-v, cone, zero(T))
    v .= zero(T)
    return nothing
  end

  # 3. v not in K_pow and -v not in K_pow^* and z == 0 => x = max(x, 0), y = max(y, 0)
  if abs(v[3]) <= cone.POW_TOL
    v[1] = max(v[1], zero(T))
    v[2] = max(v[2], zero(T))
    return nothing
  end

  # 4. Otherwise solve the following problem
  # find  r
  # s.t.  sigma(x0, y0, z0, r) == 0
  # and   0 < r < ||z0||
  #
  # x = 0.5 * (x0 + sqrt(x0^2 + 4α*r*(||z0||-r)))
  # y = 0.5 * (y0 + sqrt(y0^2 + 4(1-α)*r*(||z0||-r)))
  # z = z0 * r / ||z0||
  project_pow!(v, cone)
end

 # find the zero of above condition for r by applying Newton's method
  function project_pow!(v::AbstractVector{T}, cone::PowerCone{T}) where {T <: AbstractFloat}
  x0 = v[1]
  y0 = v[2]
  z0 = v[3]
  r = abs(z0) / T(2)
  ϕx = zero(T)
  ϕy = zero(T)
  # compute a zero of phi(v, r, α)
  for k = 1:cone.MAX_ITER
    ϕx = ϕc(x0, z0, r, cone.α)
    ϕy = ϕc(y0, z0, r, 1 - cone.α)
    phi = ϕ(ϕx, ϕy, r, cone.α)
    abs(phi) < cone.POW_TOL && break

    dϕx_dr = dϕc_dr(ϕx, x0, z0, r, cone.α)
    dϕy_dr = dϕc_dr(ϕy, y0, z0, r, 1 - cone.α)
    dphi_dr = dϕ_dr(ϕx, ϕy, dϕx_dr, dϕy_dr, r, cone.α)
    r = r - phi / dphi_dr

    # ensure 0 < r < abs(z0)
    r = min(max(r, 0), abs(z0))
  end

  # given a solution r, update the vector components (x, y, z)
  v[1] = ϕx
  v[2] = ϕy
  v[3] = z0 * r / abs(z0)
  return nothing
end

function ϕc(x0::T, z0::T, r::T, α::T) where{T <: Real}
  return max(T(0.5) * (x0 + sqrt(x0^2 + T(4) * α * r * (abs(z0) - r))), T(1e-10))
end

function dϕc_dr(ϕx::T, x0::T, z0::T, r::T, α::T) where{T <: Real}
  return α / (2 * ϕx - x0) * (abs(z0) - 2 * r)
end

function ϕ(ϕx::T, ϕy::T, r::T, α::T) where{T <: Real}
  return ϕx^α * ϕy^(1 - α) - r
end

# dϕ / dr = Π fi^αi * (Σ αi fi' / fi) - 1
function dϕ_dr(ϕx::T, ϕy::T, ϕx_dr::T, ϕy_dr::T, r::T, α::T) where{T <: Real}
  return ϕx^α * ϕy^(1-α) * (α * ϕx_dr / ϕx + (1 - α) * ϕy_dr / ϕy) - 1
end

function in_cone(v::AbstractVector{T}, cone::PowerCone{T}, tol::T) where{T}
  x = v[1]
  y = v[2]
  z = v[3]
  α = cone.α
  return x >= 0 && y >= 0 && x^α * y^(1 - α) >= abs(z) - tol
end

# Kpow^* = { (s, t, w) | (s / α)^α * (t/(1-α))^(1-α) >= abs(w), s >= 0, t >= 0 }
function in_dual(v::AbstractVector{T}, cone::PowerCone{T}, tol::T) where{T}
  s = v[1]
  t = v[2]
  w = v[3]
  α = cone.α
  return s >= -tol && t >= -tol && s^α * t^(1-α) >= abs(w) * α^α * (1-α)^(1-α) - tol
end

function in_pol_recc(v::AbstractVector{T},cone::PowerCone{T}, tol::T) where{T}
  return in_dual(-v, cone, tol)
end

"""
    DualExponentialCone(MAX_ITERS::Int = 100, EXP_TOL = 1e-8)

Creates the dual exponential cone ``\\mathcal{K}^*_{exp} = \\{(x, y, z) \\mid x < 0,  -xe^{y/x} \\leq e^1 z \\} \\cup \\{ (0,y,z) \\mid   y \\geq 0, z \\geq 0 \\}``
"""
struct DualExponentialCone{T} <: AbstractConvexCone{T}
  dim::Int
  v0::Vector{T}
  primal_cone::ExponentialCone{T}

  function DualExponentialCone{T}(dim::Int = 3, MAX_ITERS::Int = 100, EXP_TOL = T(1e-8)) where{T}
    new(3, zeros(T, 3), ExponentialCone{T}(dim, MAX_ITERS, EXP_TOL))
  end
end
DualExponentialCone(args...) = DualExponentialCone{DefaultFloat}(args...)

"""
    DualPowerCone(alpha::Float64, MAX_ITERS::Int = 20, POW_TOL = 1e-8)

Creates the 3-d dual power cone ``\\mathcal{K}^*_{pow} = \\{(u, v, w) \\mid \\left( \\frac{u}{\\alpha}\\right)^\\alpha \\left( \\frac{v}{1-\\alpha}\\right)^{(1-\\alpha)} \\geq  \\|w\\|, u \\geq 0, v \\geq 0 \\}`` with ``0 < \\alpha < 1``
"""
struct DualPowerCone{T} <: AbstractConvexCone{T}
  dim::Int
  v0::Vector{T}
  primal_cone::PowerCone{T}

  function DualPowerCone{T}(alpha::Real, MAX_ITERS::Int = 20, POW_TOL = T(1e-8)) where{T}
    (alpha <= 0 || alpha >= 1) && throw(DomainError, "The exponent α of the dual power cone has to be in (0, 1).")
    new(3, zeros(T,3), PowerCone{T}(alpha, MAX_ITERS, POW_TOL))
  end
end
DualPowerCone(alpha::T, args...) where {T <: AbstractFloat}= DualPowerCone{T}(alpha, args...)

DualCones{T} = Union{DualExponentialCone{T}, DualPowerCone{T}}
in_cone(v::AbstractVector{T}, cone::DualCones{T}, tol::T) where {T <: AbstractFloat} = in_dual(v, cone.primal_cone, tol)
in_dual(v::AbstractVector{T}, cone::DualCones{T}, tol::T) where {T <: AbstractFloat} = in_cone(v, cone.primal_cone, tol)
in_pol_recc(v::AbstractVector{T}, cone::DualCones{T}, tol::T) where {T <: AbstractFloat} = in_dual(-v, cone, tol)

# Project dual cones by using Moreau decomposition: Proj^*(v) = v + Proj(-v)
function project!(v::AbstractVector{T}, cone::DualCones{T}) where{T <: AbstractFloat}
  @. cone.v0 = v
  @. v *= -one(T)
  project!(v, cone.primal_cone)
  @. v += cone.v0
end

# Union for all types where the user has to provide extra information to create the cone
ArgumentCones = Union{PowerCone, DualPowerCone}

# ----------------------------------------------------
# Box
# ----------------------------------------------------
"""
    Box(l, u)

Creates a box or intervall with lower boundary vector ``l \\in  \\mathbb{R}^m \\cup \\{-\\infty\\}^m`` and upper boundary vector``u \\in \\mathbb{R}^m\\cup \\{+\\infty\\}^m``.
"""
struct Box{T} <: AbstractConvexSet{T}
	dim::Int
	constr_type::Vector{Int} #store type of constraint {-1: loose, 0: inequality, 1: equality}
	l::Vector{T}
	u::Vector{T}
	function Box{T}(dim::Int) where {T <: AbstractFloat}
		dim >= 0 || throw(DomainError(dim, "dimension must be nonnegative"))
		l = fill!(Vector{T}(undef, dim), -Inf)
		u = fill!(Vector{T}(undef, dim), +Inf)
		return new(dim, zeros(Int, dim), l, u)
	end
	function Box{T}(l::Vector{T}, u::Vector{T}) where {T <: AbstractFloat}
		length(l) == length(u) || throw(DimensionMismatch("bounds must be same length"))
		#enforce consistent bounds
        _box_check_bounds(l,u)
		return new(length(l), zeros(Int, length(l)), l, u)
	end
end
Box(dim) = Box{DefaultFloat}(dim)
Box(l::AbstractArray{T}, u::AbstractArray{T}) where {T <: AbstractFloat} = Box{T}(l, u)

function _box_check_bounds(l,u)
    for i in eachindex(l)
        l[i] > u[i] && error("Box set: inconsistent lower/upper bounds specified at index i = ", i, ": l[i] = ",l[i],", u[i] = ",u[i])
    end
end

"Classify the type of constraint of the box into loose inequality (-1), inequality (0) or equality (+1)."
function classify_box_constraints!(box::COSMO.Box{T}, COSMO_INFTY::Real, MIN_SCALING::Real, RHO_TOL::Real) where{T}
	@inbounds for i = 1:length(box.l)
		if box.l[i] < (-COSMO_INFTY * MIN_SCALING) && box.u[i] > (COSMO_INFTY * MIN_SCALING)
			box.constr_type[i] = -1
		elseif (box.u[i] - box.l[i]) < RHO_TOL
			box.constr_type[i] = 1
		else
			box.constr_type[i] = 0
		end
	end
	return nothing
end

function project!(x::AbstractVector{T}, box::Box{T}) where{T}
	@. x = clip(x, box.l, box.u)
	return nothing
end

function support_function(x::AbstractVector{T}, B::Box{T}, tol::T) where{T}
    s = 0.
    for i in eachindex(x)
        s+= ( abs(x[i]) > tol && x[i] > 0) ? x[i]*B.u[i] : x[i]*B.l[i]
    end
    return s
end
support_function!(x::AbstractVector{T}, B::Box{T}, tol::T) where{T} = support_function(x, B, tol)

function in_pol_recc(x::AbstractVector{T}, B::Box{T}, tol::T) where{T}
    !any(XU -> (XU[2] == Inf && XU[1] > tol), zip(x,B.u)) && !any(XL -> (XL[2] == -Inf && XL[1] < -tol), zip(x,B.l))
end

function scale!(box::Box{T}, e::AbstractVector{T}) where{T}
	@. box.l = box.l * e
	@. box.u = box.u * e
	return nothing
end

function Base.deepcopy(box::Box{T}) where {T}
  Box{T}(deepcopy(box.l), deepcopy(box.u))
end

# ----------------------------------------------------
# Composite Set
# ----------------------------------------------------

#struct definition is provided in projections.jl, since it
#must be available to SplitVector, which in turn must be
#available for most of the methods here.

CompositeConvexSet(args...) = CompositeConvexSet{DefaultFloat}(args...)

function project!(x::SplitVector{T}, C::CompositeConvexSet{T}) where{T}
    for i = 1:length(C.sets)
        project!(x.views[i],C.sets[i])
    end
	#foreach(xC -> project!(xC[1], xC[2]), zip(x.views, C.sets))
	return nothing
end

function support_function!(x::SplitVector{T}, C::CompositeConvexSet{T}, tol::T) where{T}
	sum(xC -> support_function!(xC[1], xC[2], tol), zip(x.views, C.sets))
end

function in_pol_recc(x::SplitVector{T}, C::CompositeConvexSet{T}, tol::T) where{T}
	all(xC -> in_pol_recc(xC[1], xC[2], tol), zip(x.views, C.sets))
end

function in_pol_recc!(x::SplitVector{T}, C::CompositeConvexSet{T}, tol::T) where{T}
  all(xC -> in_pol_recc!(xC[1], xC[2], tol), zip(x.views, C.sets))
end

function scale!(C::CompositeConvexSet{T}, e::SplitVector{T}) where{T}
	for i = eachindex(C.sets)
		scale!(C.sets[i], e.views[i])
	end
end

function rectify_scaling!(E::SplitVector{T},
	work::SplitVector{T},
	C::CompositeConvexSet{T}) where {T}
	any_changed = false
	for i = eachindex(C.sets)
		any_changed |= rectify_scaling!(E.views[i], work.views[i], C.sets[i])
	end
	return any_changed
end

#-------------------------
# general AbstractConvexCone operations
#-------------------------

# sup_{z in K_tilde_b = {-K} x {b} } <z,δy> = { <y,b> ,if y in Ktilde_polar
#                                                 +∞   ,else}

function support_function(y::SplitView{T}, cone::AbstractConvexCone{T}, tol::T) where{T}
  in_dual(-y, cone, tol) ? 0. : Inf;
end

# An in-place method that is faster, but uses the input variable y as workspace
function support_function!(y::SplitView{T}, cone::AbstractConvexCone{T}, tol::T) where{T}
  @. y *= - one(T)
  in_dual!(y, cone, tol) ? 0. : Inf;
end

# Notice: for every convex set apart from PsdCone and PsdConeTriangle use the normal non-modifying function
# for PsdCone and PsdConeTriangle we are using (faster) in-place functions.
function in_dual!(x::AbstractVector{T}, cone::AbstractConvexSet{T}, tol::T) where{T}
  return in_dual(x, cone, tol)
end

function in_pol_recc!(x::AbstractVector{T}, cone::AbstractConvexSet{T}, tol::T) where{T}
  return in_pol_recc(x, cone, tol)
end

function scale!(cone::AbstractConvexSet{T}, ::AbstractVector{T}) where{T}
  return nothing
end

# The fall-back method. Notice that this is conservative as not all custom cones that users might define have to be scalar-scaled.
rectify_scaling!(E, work, set::AbstractConvexSet{T}) where {T} = rectify_scalar_scaling!(E, work)

function rectify_scaling!(E, work, set::Union{SecondOrderCone{T}, PsdCone{T}, DensePsdCone{T}, PsdConeTriangle{T}, DensePsdConeTriangle{T}, PowerCone{T}, DualPowerCone{T}, ExponentialCone{T}, DualExponentialCone{T}}) where{T}
  return rectify_scalar_scaling!(E, work)
end
rectify_scaling!(E, work, set::Union{ZeroSet{<:Real}, Nonnegatives{<:Real}, Box{<:Real}}) = false

#-------------------------
# generic set operations
#-------------------------
# function Base.showarg(io::IO, C::AbstractConvexSet{T}, toplevel) where{T}
#    print(io, typeof(C), " in dimension '", A.dim, "'")
# end

eltype(::AbstractConvexSet{T}) where{T} = T
num_subsets(C::AbstractConvexSet{T}) where{T}  = 1
num_subsets(C::CompositeConvexSet{T}) where{T} = length(C.sets)

function get_subset(C::AbstractConvexSet, idx::Int)
	idx == 1 || throw(DimensionMismatch("Input only has 1 subset (itself)"))
	return C
end
get_subset(C::CompositeConvexSet, idx::Int) = C.sets[idx]

function rectify_scalar_scaling!(E, work)
	tmp = mean(E)
	work .= tmp ./ E
	return true
end

# computes the row indices of A,b for each convex set
function get_set_indices(sets::Array{COSMO.AbstractConvexSet, 1})
	sidx = 0
	indices = Array{UnitRange{Int}, 1}(undef, length(sets))
	for i = eachindex(sets)
		indices[i] = (sidx + 1) : (sidx + sets[i].dim)
		sidx += sets[i].dim
	end
	return indices
end


"""
    `Metropolis(β, J, L)` - Set up the Metropolis sampler

### Fields
* `β` - Inverse temperature
* `J` - Coupling constant>0
* `L` - Lattice size, L×L
"""
struct Metropolis{TF<:AbstractFloat, TI<:Integer} <:AbstractSampler
    β::TF
    J::TF
    L::TI
end
mutable struct MetropolisState{TI<:Integer, Tx<:AbstractArray{TI}} <:AbstractSamplerState
    x::Tx
    x_proposal::Tx
end

function InitState!(x, sampler::Metropolis)

    return MetropolisState(x, deepcopy(x));
end

function InitState(x₀, sampler::Metropolis)

    return MetropolisState(deepcopy(x₀), deepcopy(x₀));
end

function UpdateState!(state::MetropolisState, sampler::Metropolis)

    i = rand(1:sampler.L);
    j = rand(1:sampler.L);
    state.x_proposal[i,j] *= -1;

    e = 0.0;

    k,l = mod1(i+1,sampler.L), j;
    e += - 0.5 * sampler.J * state.x_proposal[i,j] * state.x_proposal[k,l];
    k,l = mod1(i-1,sampler.L), j;
    e += - 0.5 * sampler.J * state.x_proposal[i,j] * state.x_proposal[k,l];
    k,l = i, mod1(j+1,sampler.L);
    e += - 0.5 * sampler.J * state.x_proposal[i,j] * state.x_proposal[k,l];
    k,l = i, mod1(j-1,sampler.L);
    e += - 0.5 * sampler.J * state.x_proposal[i,j] * state.x_proposal[k,l];

    # double once for double counting of bonds and again for the sign change
    ΔE = 2 * 2 * e;

    a = min(1, exp(-sampler.β * ΔE))

    if rand() <a
        state.x[i,j] *= -1;
    else
        state.x_proposal[i,j] *= -1;
    end
    state
end


#!/usr/bin/env julia

module PE22

using DelimitedFiles

const pnames = readdlm(joinpath(@__DIR__,"p022_names.txt"),',',String)[1,:];

score(str::AbstractString,index)=index*sum(Int.(collect(str)).-Int('A').+1)

end

export pe22

pe22(data::Vector{<:AbstractString}=PE22.pnames)=sum(PE22.score(val,i) for (i,val) in enumerate(sort(data)))

if !haskey(ENV,"PROJECT_EULER_WITHOUT_RESULT")
    pe22()|>print
end


# advent of code 2020 day 1

function load_data(path:: String):: AbstractArray
    file = open(path)
    data_array = readlines(file)
    close(file)
    # cast string to integer
    parse.(Int, data_array)
end

function find_sum_of_2(data_array:: AbstractArray, sum_to_find:: Int)
    length_data_array = length(data_array)
    for (i, d_1) in enumerate(data_array)
        for d_2 in data_array[i:length_data_array]
            _sum = d_1 + d_2
            if _sum == sum_to_find
                return d_1, d_2
            end

        end
    end

end

function find_sum_of_3(data_array:: AbstractArray, sum_to_find:: Int)
    length_data_array = length(data_array)
    for (i, d_1) in enumerate(data_array)
        for (j, d_2) in enumerate(data_array[i:length_data_array])
            for d_3 in data_array[j:length_data_array]

                _sum = d_1 + d_2 + d_3
                if _sum == sum_to_find
                    return d_1, d_2, d_3
                end
            end

        end
    end

end

data_array = load_data("./data/day1.txt")

sum_to_find = 2020

numbers = find_sum_of_2(data_array, sum_to_find)
numbers_product = prod(numbers)

println("results of 2: $numbers_product")

numbers = find_sum_of_3(data_array, sum_to_find)
numbers_product = prod(numbers)

println("results of 3: $numbers_product")


function backtracking_line_search!(d::Union(DifferentiableFunction,
                                            TwiceDifferentiableFunction),
                                   x::Vector,
                                   p::Vector,
                                   new_x::Vector,
                                   new_gradient::Vector;
                                   c1::Real = 1e-6,
                                   c2::Real = 0.9,
                                   rho::Real = 0.9,
                                   iterations::Integer = 1_000)

    # Keep track of the number of iterations
    iteration = 0

    # Keep track of calls to f and g
    f_calls, g_calls = 0, 0

    # Calculate parameter space size
    n = length(x)

    # Evaluate the function and gradient at current position
    f_calls += 1
    g_calls += 1
    f_x = d.fg!(x, new_gradient)
    gxp = dot(new_gradient, p)

    # The default step-size is always 1.0
    alpha = 1.0

    # Propose a new x after moving length alpha in direction p
    for i in 1:n
        new_x[i] = x[i] + alpha * p[i]
    end

    # Expand step-size
    # NB: This is very expensive if the gradient costs much more than f(x)
    # In my tests, it also increases the number of steps, not decreases it
    # while (dot(new_gradient, p) < c2 * gxp) && (alpha < 65536.0)
    #     alpha *= 2.0
    #     for i in 1:n
    #         new_x[i] = x[i] + alpha * p[i]
    #     end
    #     g_calls += 1
    #     g!(new_x, new_gradient)
    # end

    # Keep coming closer to x until we find a point that is
    # as good as the gradient suggests we can achieve
    f_calls += 1
    f_new_x = d.f(new_x)
    while f_new_x > f_x + c1 * alpha * gxp
        iteration += 1
        if iteration > iterations
            error("Too many iterations in backtracking_line_search")
        end
        alpha *= rho
        for i in 1:n
            new_x[i] = x[i] + alpha * p[i]
        end
        f_calls += 1
        f_new_x = d.f(new_x)
    end

    return alpha, f_calls, g_calls
end


module GaussianRandomFields

# dependencies
using SpecialFunctions, RecipesBase, FastGaussQuadrature, Arpack, FFTW

using LinearAlgebra, Statistics

using Base.Cartesian

# export statements
export CovarianceStructure, IsotropicCovarianceStructure, AnisotropicCovarianceStructure, AbstractCovarianceFunction, CovarianceFunction, apply # from covariance_functions.jl

export SeparableCovarianceFunction # from separable.jl

export Matern # from matern.jl

export Exponential # from exponential.jl

export Linear # from linear.jl

export Spherical # from spherical.jl

export Whittle # from whittle.jl

export SquaredExponential, Gaussian # from squaredexponential.jl

export AnisotropicExponential # from anisotropic_exponential.jl

export GaussianRandomFieldGenerator, GaussianRandomField, sample, randdim # from gaussian_random_fields.jl

export Cholesky # from cholesky.jl

export Spectral # from spectral.jl

export KarhunenLoeve, rel_error # from karhunen_loeve.jl

export CirculantEmbedding # from circulant_embedding.jl

export QuadratureRule, GaussLegendre, EOLE, Simpson, Midpoint, Trapezoidal, AbstractEigenSolver, EigsSolver, EigenSolver # from quadrature.jl

export star, Lshape # from fem_data/

# include statements
include("covariance_functions/covariance_functions.jl")

include("covariance_functions/matern.jl")

include("covariance_functions/exponential.jl")

include("covariance_functions/linear.jl")

include("covariance_functions/spherical.jl")

include("covariance_functions/whittle.jl")

include("covariance_functions/squared_exponential.jl")

include("covariance_functions/anisotropic_exponential.jl")

include("gaussian_random_fields.jl")

include("fem.jl")

include("generators/cholesky.jl")

include("generators/spectral.jl")

include("generators/quadrature.jl")

include("generators/karhunen_loeve.jl")

include("generators/circulant_embedding.jl")

include("analytic.jl")

include("separable.jl")

include("../fem_data/star.jl")

include("../fem_data/Lshape.jl")

include("plot.jl")

end # module