using Random
using LinearAlgebra

function genQf(n::Integer; fseed::Integer=0, rank::Real=1.1, conv::Real=1, ecc::Real=0.99, dom::Real=1, box::Real=1, q::Union{Vector, Nothing}=nothing, v::Union{Real, Nothing}=nothing)::Tuple{Matrix, Union{Vector, Nothing}, Union{Real, Nothing}}
    if n <= 0
        throw(ArgumentError(n, "n must be > 0"))
    end
    if rank <= 0
        throw(ArgumentError(rank, "rank must be > 0"))
    end
    if !(0 <= conv <= 1)
        throw(ArgumentError(conv, "conv must be in [0, 1]"))
    end
    if !(0 <= ecc < 1)
        throw(ArgumentError(ecc, "ecc must be in [0, 1)"))
    end
    if dom < 0
        throw(ArgumentError(dom, "dom must be >= 0"))
    end
    if box == 0
        throw(ArgumentError(box, "box must not be 0"))
    end

    Random.seed!(fseed)
    
    # generate Q- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # first step: generate the appropriate rank and positive / negative
    #             definiteness

    r = round(Int, rank * n)
    p = round(Int, r * conv)

    if p > 0
        G = rand(p, n)
        Q = G' * G
    else
        Q = zeros(n, n)
    end

    if r > p
        G = rand(r - p, n)
        Q = Q - (G' * G)
    end

    # second step: if dom ~= 1, modify the diagonal
    # increase or decrease randomly each element by [ - 1/3 , 1/3 ] of its
    # initial value, then multiply it by dom

    if dom != 1
        D = diag(Q)
        D = D .* (1 .+ (2 .* rand(n, 1) .- 1) ./ 3)
        D = dom * D
        @view(Q[diagind(Q)]) .= D
    end

    # compute eigenvalue decomposition    
    F = eigen(Q); # V * D * V' = Q , D( 1 , 1 ) = \lambda_n
    V = F.vectors
    D = F.values

    if D[1] > 1e-14
        # modify eccentricity only if \lambda_n > 0, for when \lambda_n = 0 the
        # eccentricity is 1 by default
        #
        # the formula is:
        #
        #                         \lambda_i - \lambda_n             2 * ecc
        # \lambda_i = \lambda_n + --------------------- * \lambda_n -------
        #                         \lambda_1 - \lambda_n             1 - ecc
        #
        # This leaves \lambda_n unchanged, and modifies all the other ones
        # proportionally so that
        #
        #   \lambda_1 - \lambda_n
        #   --------------------- = ecc      (exercise: check)
        #   \lambda_1 - \lambda_n
        l = D[1] .* ones(n) + ((D[1] / (D[n] - D[1])) * (2 * ecc / (1 - ecc))) .* (D - D[1] .* ones(n))
        Q = V * diagm(l) * V';
    end

    if q != nothing || v != nothing
        # if so required generate q- - - - - - - - - - - - - - - - - - - - - - -
        #
        # we first generate the unconstrained minimum x_* of the problem in the
        # box [ - abs( box ) , abs( box ) ] and then we set q = - Q * x_*

        x = 2 * abs(box) .* rand(n) .- abs(box)
        q = -Q * x

        # if so required, we now randomly destroy the alignment between q and
        # the image of Q so as to make it hard to solve Q x = q
        
        if ( box < 0 ) && (D[1] <= 1e-14 )
            q = q .* ((4/3) .* rand(n) .- (2/3))
        end
    end

    if v != nothing
        # if so required compute v. - - - - - - - - - - - - - - - - - - - - -
        # v is finite-valued only if either Q is strictly positive definite
        # or it is positive semidefinite but q has been constructed in such
        # a was that Q * x + q = 0 has a solution (that is the x we have)
        if (D[1] > 1e-14) || ((D[1] > -1e-14) && (box > 0))
            v = dot(q', x) / 2
        else
            v = -Inf
        end
    end

    return (Q, q, v)
end