export
    newcontext, freecontext, # newcontext_puts,
    # checkoutlicense, releaselicense, get_release,
    load_param_file, save_param_file, load_tuner_file,
    set_param, get_param, # reset_params_to_defaults,
    # set_feastols, set_names, set_linearvars, set_honorbnds,
    # addcompcons, chgvarbnds,
    init_problem, solve_problem, restart_problem,
    mip_init_problem, mip_solve_problem,
    #set_branching_priorities, set_findiff_relstepsizes
    get_number_FC_evals, get_number_GA_evals,
    get_number_H_evals, get_number_HV_evals,
    get_number_iters, get_number_cg_iters,
    get_abs_feas_error, get_rel_feas_error,
    get_abs_opt_error, get_rel_opt_error,
    # get_solution, get_constraint_values,
    # get_objgrad_values, get_jacobian_values, get_hessian_values,
    get_mip_num_nodes, get_mip_num_solves,
    get_mip_abs_gap, get_mip_rel_gap,
    get_mip_incumbent_obj, get_mip_relaxation_bnd,
    get_mip_lastnode_obj, get_mip_incumbent_x #, check_first_ders,

"""
Returns a pointer to the solver object that is used in all other KNITRO API calls.

A new KNITRO license is acquired and held until `freecontext` has been called,
or until the calling program ends.
"""
function newcontext()
    ptr = @ktr_ccall(new,Ptr{Nothing},())
    if ptr == C_NULL
        error("KNITRO: Error creating solver")
    end
    ptr
end

"""
Returns a pointer to the solver object that is used in all other KNITRO API calls.

A new KNITRO license is acquired and held until `freecontext` has been called,
or until the calling program ends.

This function also takes an argument `f` that sets a 'put string' callback
function to handle output generated by the KNITRO solver, and an (optional) pointer
`userParams` for passing user-defined data.
"""
function newcontext_puts(f::Function, userParams=C_NULL)
    cb = cfunction(f, Cint, (Ptr{Cchar}, Ptr{Nothing}))
    ptr = @ktr_ccall(new,Ptr{Nothing}, (Ptr{Nothing}, Ptr{Nothing}),
                     cb, userParams)
    if ptr == C_NULL
        error("KNITRO: Error creating solver with put-string")
    end
    ptr
end

"Free all memory and release any KNITRO license acquired with `kp_env`"
function freecontext(kp_env::Ptr{Nothing})
    if kp_env != C_NULL
        println("KNITRO: calling freecontext on $(kp_env)")
        println("C_NULL (for reference): $(C_NULL)")
        return_code = @ktr_ccall(free, Int32, (Ptr{Nothing},), kp_env)
        if return_code != 0
            error("KNITRO: Error freeing memory")
        end
        println("KNITRO: freecontext successful")
    end
end

# /** Allocate memory for a license from the Artelys License Manager for high
#  *  volume KNITRO applications.  The license will be checked out the first
#  *  time KTR_new_zlm is called.  The license must be checked in later by
#  *  calling ZLM_release_license.
#  *  Returns NULL on error.
#  */
# ZLM_context_ptr  KNITRO_API ZLM_checkout_license (void);

# function checkoutlicense()
#   ptr = @zlm_ccall(checkout_license,Ptr{Nothing},())
#   if ptr == C_NULL
#     error("KNITRO: Error checking out license")
#   end
#   ZLMcontext(ptr)
# end

# /** Returns a pointer to the solver object that is used in all other KNITRO
#  *  API calls.  Pass the license acquired by calling ZLM_checkout_license.
#  *  This function also takes an argument that sets a "put string" callback
#  *  function to handle output generated by the KNITRO solver, and a pointer
#  *  for passing user-defined data.  See KTR_set_puts_callback for more
#  *  information.
#  *  Returns NULL on error.
#  */
# KTR_context_ptr  KNITRO_API KTR_new_zlm (KTR_puts    * const  fnPtr,
#                                          void        * const  userParams,
#                                          ZLM_context * const  pZLMcontext);

# /** Release the KNITRO license and free allocated memory.
#  *  KNITRO will set the address of the pointer to NULL after freeing
#  *  memory, to help avoid mistakes.
#  *  Returns 0 if OK, nonzero if error.
#  */
# int  KNITRO_API ZLM_release_license (ZLM_context *  pZLMcontext);

# function releaselicense(zc::ZLMcontext)
#   if zc.context != C_NULL
#     return_code = @ktr_ccall(free, Int32, (Ptr{Nothing},), zc.context)
#     if return_code != 0
#       error("KNITRO: Error releasing license and freeing memory")
#     end
#     zc.context = C_NULL
#   end
# end

"Reset all parameters to default values"
function reset_params_to_defaults(kp::KnitroProblem)
    return_code = @ktr_ccall(reset_params_to_defaults, Int32,
                             (Ptr{Nothing},), kp.env)
    if return_code != 0
        error("KNITRO: Error resetting parameters to default values")
    end
end

"Set all parameters specified in the given file"
function load_param_file(kp::KnitroProblem, filename::AbstractString)
    return_code = @ktr_ccall(load_param_file, Int32, (Ptr{Nothing}, Ptr{Cchar}),
                             kp.env, filename)
    if return_code != 0
        error("KNITRO: Error loading parameters from $(filename)")
    end
end

"Write all current parameter values to a file"
function save_param_file(kp::KnitroProblem, filename::AbstractString)
    return_code = @ktr_ccall(save_param_file, Int32, (Ptr{Nothing}, Ptr{Cchar}),
                             kp.env, filename)
    if return_code != 0
        error("KNITRO: Error writing parameters to $(filename)")
    end
end

"Set an integer parameter by its name"
function set_param(kp::KnitroProblem, name::AbstractString, value::Integer)
    return_code = @ktr_ccall(set_int_param_by_name, Int32, (Ptr{Nothing},
                             Ptr{Cchar}, Cint), kp.env, name, value)
    if return_code != 0
        error("KNITRO: Error setting int parameter by name")
    end
end

"Set a string parameter by its name"
function set_param(kp::KnitroProblem, name::AbstractString, value::AbstractString)
    return_code = @ktr_ccall(set_char_param_by_name, Int32, (Ptr{Nothing},
                             Ptr{Cchar}, Ptr{Cchar}), kp.env, name, value)
    if return_code != 0
        error("KNITRO: Error setting char parameter by name")
    end
end

function set_param(kp::KnitroProblem, name::AbstractString, value::Float64)
    return_code = @ktr_ccall(set_double_param_by_name, Int32, (Ptr{Nothing},
                             Ptr{Cchar}, Cdouble), kp.env, name, value)
    if return_code != 0
        error("KNITRO: Error setting float parameter by name")
    end
end

function set_param(kp::KnitroProblem, id::Integer, value::Integer)
    return_code = @ktr_ccall(set_int_param, Int32, (Ptr{Nothing}, Cint, Cint),
                             kp.env, id, value)
    if return_code != 0
        error("KNITRO: Error setting int parameter by id")
    end
end

function set_param(kp::KnitroProblem, id::Integer, value::AbstractString)
    return_code = @ktr_ccall(set_char_param, Int32, (Ptr{Nothing}, Cint,
                             Ptr{Cchar}), kp.env, id, value)
    if return_code != 0
        error("KNITRO: Error setting char parameter by id")
    end
end

function set_param(kp::KnitroProblem, id::Integer, value::Float64)
    return_code = @ktr_ccall(set_double_param, Int32, (Ptr{Nothing}, Cint,
                             Cdouble), kp.env, id, value)
    if return_code != 0
        error("KNITRO: Error setting float parameter by id")
    end
end

"Get an integer parameter by its name"
function get_param(kp::KnitroProblem, name::AbstractString, value::Vector{Int32})
    @ktr_ccall(get_int_param_by_name, Int32, (Ptr{Nothing}, Ptr{Cchar}, Ptr{Cint}),
               kp.env, name, value)
end

"Get a float parameter by its name"
function get_param(kp::KnitroProblem, name::AbstractString, value::Vector{Float64})
    @ktr_ccall(get_double_param_by_name, Int32, (Ptr{Nothing}, Ptr{Cchar},
               Ptr{Cdouble}), kp.env, name, value)
end

"Get a parameter by its name"
function get_param(kp::KnitroProblem, name::AbstractString)
    # try getting an integer parameter first
    int_result = Array(Cint, 1)
    float_result = Array(Cdouble, 1)
    if get_param(kp, name, int_result) == 0
        return int_result[1]
    elseif get_param(kp, name, float_result) != 0
        # otherwise, try getting a double parameter
        error("KNITRO: Error getting parameter by name")
    end
    float_result[1]
end

"Get an integer parameter by its id"
function get_param(kp::KnitroProblem, id::Integer, value::Vector{Int32})
    @ktr_ccall(get_int_param, Int32, (Ptr{Nothing}, Cint, Ptr{Cint}),
               kp.env, id, value)
end

"Get a float parameter by its id"
function get_param(kp::KnitroProblem, id::Integer, value::Vector{Float64})
    @ktr_ccall(get_double_param, Int32, (Ptr{Nothing}, Cint, Ptr{Cdouble}),
               kp.env, id, value)
end

"Get a parameter by its id"
function get_param(kp::KnitroProblem, id::Integer)
    # try getting an integer parameter first
    int_result = Array(Cint, 1)
    float_result = Array(Cdouble, 1)
    if get_param(kp, id, int_result) == 0
        return int_result[1]
    elseif get_param(kp, id, float_result) != 0
        # otherwise, try getting a double parameter
        error("KNITRO: Error getting parameter by id")
    end
    float_result[1]
end

### ===== NEW in KNITRO 10.0 =====
# /** Sets the string param_name with the name of parameter indexed by
#  *  param_id and returns 0. Returns an error if param_id does not
#  *  correspond to any parameter, or if the parameter output_size
#  *  (the size of char array param_name) is less than the size of the
#  *  parameter's description.
#  */
# int KNITRO_API KTR_get_param_name(      KTR_context_ptr kc,
#                                   const int             param_id,
#                                         char * const    param_name,
#                                   const size_t          output_size);

# /** Sets the string description with the description of the parameter
#  *  indexed by param_id and its possible values and returns 0. Returns an
#  *  error if param_id does not correspond to any parameter, or if the
#  *  parameter output_size (the size of char array description) is less than
#  *  the size of the parameter's description.
#  */
# int KNITRO_API KTR_get_param_doc(      KTR_context_ptr kc,
#                                  const int             param_id,
#                                        char * const    description,
#                                  const size_t          output_size);

# /** Sets the int * param_type to the parameter type of parameter indexed
#  *  by param_id. Possible values are KTR_PARAMTYPE_INTEGER, KTR_PARAMTYPE_FLOAT,
#  *  KTR_PARAMTYPE_STRING. Returns an error if param_id does not correspond to
#  *  any parameter.
#  */
# int KNITRO_API KTR_get_param_type(      KTR_context_ptr kc,
#                                   const int             param_id,
#                                         int * const     param_type);

# /** Set the int * num_param_values to the number of possible parameter
#  *  values for parameter indexed by param_id and returns 0. If there is
#  *  not a finite number of possible values, num_param_values will be zero.
#  *  Returns an error if param_id does not correspond to any parameter.
#  */
# int KNITRO_API KTR_get_num_param_values(      KTR_context_ptr kc,
#                                         const int             param_id,
#                                               int * const     num_param_values);

# /** Set string param_value_string to the description of parameter value
#  *  indexed by [param_id][value_id]. Returns an error if param_id does not
#  *  correspond to any parameter, or if value_id is greater than the number
#  *  of possible parameter values, or if there are not a finite number of
#  *  possible parameter values, or if the parameter output_size (the size
#  *  of char array param_value_string) is less than the size of the
#  *  parameter's description.
#  */
# int KNITRO_API KTR_get_param_value_doc(      KTR_context_ptr kc,
#                                        const int             param_id,
#                                        const int             value_id,
#                                              char * const    param_value_string,
#                                        const size_t          output_size);

# /** Gets the integer value corresponding to the parameter name input and
#  *  copies it into param_id input. Returns zero if successful and an error
#  *  code otherwise.
#  */
# int KNITRO_API KTR_get_param_id(      KTR_context_ptr kc,
#                                 const char * const    name,
#                                       int  * const    param_id);

### ===== END OF NEW in KNITRO 10.0 =====

"""
Similar to KTR_load_param_file but specifically allows user to
specify a file of options (and option values) to explore for
the KNITRO-Tuner.
"""
function load_tuner_file(kp::KnitroProblem, filename::AbstractString)
    return_code = @ktr_ccall(load_tuner_file, Int32, (Ptr{Nothing}, Ptr{Cchar}),
                             kp.env, filename)
    if return_code != 0
        error("KNITRO: Error loading tuner file $(filename)")
    end
end


"""
Set an array of absolute feasibility tolerances (one for each
constraint and variable) to use for the termination tests.

The user options KTR_PARAM_FEASTOL/KTR_PARAM_FEASTOLABS define
a single tolerance that is applied equally to every constraint
and variable.  This API function allows the user to specify
separate feasibility termination tolerances for each constraint
and variable.  Values specified through this function will override
the value determined by KTR_PARAM_FEASTOL/KTR_PARAM_FEASTOLABS. The
tolerances should be positive values.  If a non-positive value is
specified, that constraint or variable will use the standard tolerances
based on  KTR_PARAM_FEASTOL/KTR_PARAM_FEASTOLABS.
    `c_tol` has length m,
    `x_tol` has length n, and
    `cc_tol` has length ncc, where
    `ncc` is the number of complementarity constraints added

The regular constraints are considered to be satisfied when
    c[i] - cUpBnds[i] <= cFeasTols[i]  for all i=1..m, and
    cLoBnds[i] - c[i] <= cFeasTols[i]  for all i=1..m
The variables are considered to be satisfied when
    x[i] - xUpBnds[i] <= xFeasTols[i]  for all i=1..n, and
    xLoBnds[i] - x[i] <= xFeasTols[i]  for all i=1..n
The complementarity constraints are considered to be satisfied when
    min(x1_i, x2_i) <= ccFeasTols[i]  for all i=1..ncc,
where x1 and x2 are the arrays of complementary pairs.

If there are no regular (or complementarity) constraints set
cFeasTols=NULL (or ccFeasTols=NULL).  If cFeasTols/xFeasTols/ccFeasTols=NULL,
then the standard tolerances will be used.
"""
function set_feastols(kp::KnitroProblem,
                      c_tol::Vector{Float64},
                      x_tol::Vector{Float64},
                      cc_tol::Vector{Float64})
    return_code = @ktr_ccall(set_feastols, Int32, (Ptr{Nothing}, Ptr{Cdouble},
                             Ptr{Cdouble}, Ptr{Cdouble}), kp.env, c_tol, x_tol,
                             cc_tol)
    if return_code != 0
        error("KNITRO: Error setting feasibility tolerances")
    end
end

"""
Set names for model components passed in by the user/modeling
language so that KNITRO can internally print out these names.

KNITRO makes a local copy of all inputs, so the application may
free memory after the call.

This routine must be called after calling KTR_init_problem /
KTR_mip_init_problem and before calling KTR_solve / KTR_mip_solve.
"""
function set_names(kp::KnitroProblem, objName::AbstractString,
                   varNames::Vector{AbstractString},
                   conNames::Vector{AbstractString})
    return_code = @ktr_ccall(set_names, Int32, (Ptr{Nothing}, Ptr{Cchar},
                             Ptr{Ptr{Cchar}}, Ptr{Ptr{Cchar}}),
                             kp.env, objName, varNames, conNames)
    if return_code != 0
        error("KNITRO: Error setting names for model components")
    end
end

### NEW in KNITRO 10.0
"""
This API function can be used to identify which variables only appear
linearly in the model (KTR_LINEARVAR_YES).

This information can be used by Knitro to perform more extensive preprocessing.
If a variable appears nonlinearly in any constraint or the objective (or if the
user does not know) then it should be marked as KTR_LINEARVAR_NO. Knitro makes a
local copy of all inputs, so the application may free memory after the call.

This routine must be called after calling KTR_init_problem / KTR_mip_init_problem,
and before calling KTR_solve / KTR_mip_solve.
"""
function set_linearvars(kp::KnitroProblem, linearVars::Vector{Cint})
    return_code = @ktr_ccall(set_linearvars, Int32, (Ptr{Nothing}, Ptr{Cint}),
                             kp.env, linearVars)
    # Returns 0 if OK, nonzero if error.
    if return_code != 0
        error("KNITRO: Error setting linear variables")
    end
end

"""
This API function can be used to identify which variables should satisfy its
variable bounds throughout the optimization process (KTR_HONORBNDS_ALWAYS).

The user option KTR_PARAM_HONORBNDS can be used to set ALL variables to honor
their bounds.  This routine takes precedence over the setting of
KTR_PARAM_HONORBNDS and is used to customize the settings for individual
variables. Knitro makes a local copy of all inputs, so the application may
free memory after the call.

This routine must be called after calling KTR_init_problem / KTR_mip_init_problem,
and before calling KTR_solve / KTR_mip_solve.
"""
function set_honorbnds(kp::KnitroProblem, honorBnds::Vector{Cint})
    return_code = @ktr_ccall(set_honorbnds, Int32, (Ptr{Nothing}, Ptr{Cint}),
                             kp.env, honorBnds)
    # Returns 0 if OK, nonzero if error.
    if return_code != 0
        error("KNITRO: Error setting honor bounds")
    end
end

### END OF NEW in KNITRO 10.0

# ----- Problem modification -----

"""
This function adds complementarity constraints to the problem.

It must be called after KTR_init_problem and before KTR_solve.
The two lists are of equal length, and contain matching pairs of
variable indices.  Each pair defines a complementarity constraint
between the two variables.  The function can be called more than once
to accumulate a long list of complementarity constraints in KNITRO's
internal problem definition.
"""
function addcompcons(kp::KnitroProblem,
                     ncons::Integer,
                     index1::Vector{Int32},
                     index2::Vector{Int32})
    return_code = @ktr_ccall(addcompcons, Int32, (Ptr{Nothing}, Cint, Ptr{Cint},
                             Ptr{Cint}), kp.env, ncons, index1, index2)
    # Returns 0 if OK, or a negative value on error.
    if return_code != 0
        error("KNITRO: Error adding complementary constraints")
    end
end

"""
Prepare KNITRO to re-optimize the current problem after
modifying the variable bounds from a previous solve.

It must be called after KTR_init_problem and precedes a call to KTR_solve.
"""
function chgvarbnds(kp::KnitroProblem,
                    x_L::Vector{Float64},
                    x_U::Vector{Float64})
    return_code = @ktr_ccall(chgvarbnds, Int32, (Ptr{Nothing},Ptr{Cdouble},
                             Ptr{Cdouble}), kp.env, x_L, x_U)
    # Returns 0 if OK, nonzero if error.
    if return_code != 0
        error("KNITRO: Error modifying variable bounds")
    end
end

# /* ----- Solving ----- */
"""
Initialize Knitro with a new problem.

Brief description of the arguments (consult the Knitro manual for details):
 *    objGoal        - objective goal (KTR_OBJGOAL_MINIMIZE or _MAXIMIZE)
 *    objType        - objective type (eg, KTR_OBJTYPE_GENERAL)
 *    x_L            - (length n) lower bounds on the variables
 *    x_U            - (length n) upper bounds on the variables
 *    c_Type         - (length m) constraint type (eg, KTR_CONTYPE_GENERAL)
 *    c_L            - (length m) lower bounds on the constraints
 *    c_U            - (length m) upper bounds on the constraints
 *    jac_var        - (length nnzJ) variable index (column) of each nonzero
 *    jac_cons       - (length nnzJ) constraint index (row) of each nonzero
 *    hess_rows      - (length nnzH) row index of each nonzero
 *    hess_cols      - (length nnzH) column index of each nonzero

[Optional] Keyword arguments:
 *    initial_x      - (length n) initial start point for the unknowns
 *    initial_lambda - (length m+n) initial start point for multipliers

Knitro makes a local copy of all inputs, so the application may free memory
after the call completes.

 - If `x_L` or `x_U` are not provided, then Knitro assumes all variables
 are unbounded in that direction.
 - If "initial_x" or "initial_lambda" are not provided, then Knitro determines
 the initial start point.
 - If a particular bound does not exist, then load the array element with
 `KTR_INFBOUND` for an upper bound, or `-KTR_INFBOUND` for a lower bound.
 Knitro interprets this value as infinity and ignores the bound.
 - To make the `j`-th constraint be an equality constraint, set `c_L[j]` equal
 to `c_U[j]`.
"""
function init_problem(kp::KnitroProblem,
                      objGoal::Integer,
                      objType::Integer,
                      x_L::Vector{Float64},     # length n
                      x_U::Vector{Float64},     # length n
                      c_Type::Vector{Int32},    # length m
                      c_L::Vector{Float64},     # length m
                      c_U::Vector{Float64},     # length m
                      jac_var::Vector{Int32},   # length nnzJ
                      jac_cons::Vector{Int32},  # length nnzJ
                      hess_rows::Vector{Int32}, # length nnzH
                      hess_cols::Vector{Int32}; # length nnzH
                      initial_x = C_NULL,         # length n
                      initial_lambda = C_NULL)    # length m+n
    n = length(x_L)
    m = length(c_Type)
    nnzJ = length(jac_var)
    nnzH = length(hess_rows)
    return_code = @ktr_ccall(init_problem, Int32, (Ptr{Nothing}, Cint, Cint,
                             Cint, Ptr{Cdouble}, Ptr{Cdouble}, Cint,
                             Ptr{Cint}, Ptr{Cdouble}, Ptr{Cdouble}, Cint,
                             Ptr{Cint}, Ptr{Cint}, Cint, Ptr{Cint},
                             Ptr{Cint}, Ptr{Nothing}, Ptr{Nothing}), kp.env, n,
                             objGoal, objType, x_L, x_U, m, c_Type, c_L, c_U,
                             nnzJ, jac_var, jac_cons, nnzH, hess_rows,
                             hess_cols, initial_x, initial_lambda)
    if return_code != 0
        error("KNITRO: Error initializing problem")
    end
end

function init_problem(kp::KnitroProblem,
                      objGoal::Integer,
                      objType::Integer,
                      x_L::Vector{Float64},     # length n
                      x_U::Vector{Float64},     # length n
                      c_Type::Vector{Int32},    # length m
                      c_L::Vector{Float64},     # length m
                      c_U::Vector{Float64},     # length m
                      jac_var::Vector{Int32},   # length nnzJ
                      jac_cons::Vector{Int32};  # length nnzJ
                      initial_x = C_NULL,       # length n
                      initial_lambda = C_NULL)  # length m+n
    n = length(x_L)
    m = length(c_Type)
    nnzJ = length(jac_var)
    return_code = @ktr_ccall(init_problem, Int32, (Ptr{Nothing}, Cint, Cint, Cint,
                             Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cint},
                             Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cint},
                             Ptr{Cint}, Cint, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing},
                             Ptr{Nothing}), kp.env, n, objGoal, objType, x_L, x_U,
                             m, c_Type, c_L, c_U, nnzJ, jac_var, jac_cons, 0,
                             C_NULL, C_NULL, initial_x, initial_lambda)
    if return_code != 0
        error("KNITRO: Error initializing problem without exact hessians")
    end
end

"""
Call KNITRO to solve the problem.

If the application provides callback functions for evaluating the function,
constraints, and derivatives, then a single call to `solve_problem` returns
the solution.  Otherwise, KNITRO operates in reverse communications mode and
returns a status code that may request another call.

Returns one of the status codes KTR_RC_*. In particular:
<0 - Knitro terminated with a fatal error
0  - KNITRO is finished: x, lambda, and obj contain the optimal solution
1  - call `solve_problem` again (reverse comm) with obj and c containing
     the objective and constraints evaluated at x
2  - call `solve_problem` again (reverse comm) with objGrad and jac containing
     the objective and constraint first derivatives evaluated at x
3  - call `solve_problem` again (reverse comm) with hess containing
     H(x,lambda), the Hessian of the Lagrangian evaluated at x and lambda
7  - call `solve_problem` again (reverse comm) with hessVector containing
     the result of H(x,lambda) * hessVector

Brief description of the arguments (consult the KNITRO manual for details):
x          - output (length n) solution point estimate
lambda     - output (length m+n) Lagrange multiplier estimate
evalStatus - input  evaluation status (0=OK)
obj        - input  (length 1) objective at x
             output            optimal objective when finished
cons       - input  (length m) constraints at x
objGrad    - input  (length n) objective gradient at x
jac        - input  (length nnzJ) sparse constraint gradient at x
hess       - input  (length nnzH) sparse Hessian at x and lambda
hessVector - input  (length n) result of H(x,lambda) * hessVector
             output            vector to multiply Hessian by

If `gradopt` is set to compute finite differences for first derivatives,
then `solve_problem` will modify `objGrad` and `jac`; otherwise, these
arguments are not modified.
"""
function solve_problem(kp::KnitroProblem,
                       x::Vector{Float64},
                       lambda::Vector{Float64},
                       evalStatus::Integer,
                       obj::Vector{Float64},
                       cons::Vector{Float64},
                       objGrad::Vector{Float64},
                       jac::Vector{Float64},
                       hess::Vector{Float64},
                       hessVector::Vector{Float64})
    @ktr_ccall(solve, Int32, (Ptr{Nothing}, Ptr{Cdouble},
               Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble},
               Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
               Ptr{Cdouble}, Any), kp.env, x, lambda, evalStatus,
               obj, cons, objGrad, jac, hess, hessVector, kp)
end

function solve_problem(kp::KnitroProblem,
                       x::Vector{Float64},
                       lambda::Vector{Float64},
                       evalStatus::Integer,
                       obj::Vector{Float64})
    # For callback mode
    @ktr_ccall(solve, Int32, (Ptr{Nothing}, Ptr{Cdouble},
               Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Nothing},
               Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}, Any),
               kp.env, x, lambda, evalStatus, obj, C_NULL,
               C_NULL, C_NULL, C_NULL, C_NULL, kp)
end

"""
Prepare KNITRO to restart the current problem at start point [x_0, lambda_0].

If output to a file is enabled, this will erase the current file.
KNITRO parameter values are not changed by this call.
"""
function restart_problem(kp::KnitroProblem,
                         x_0::Vector{Cdouble},
                         lambda_0::Vector{Cdouble})
    return_code = @ktr_ccall(restart, Int32, (Ptr{Nothing}, Ptr{Cdouble},
                             Ptr{Cdouble}), kp.env, x_0, lambda_0)
    if return_code != 0
        error("KNITRO: Error restarting problem")
    end
end

"""
Prepare KNITRO to restart the current problem.

If output to a file is enabled, this will erase the current file.
KNITRO parameter values are not changed by this call.
"""
function restart_problem(kp::KnitroProblem)
    return_code = @ktr_ccall(restart, Int32, (Ptr{Nothing}, Ptr{Cdouble},
                             Ptr{Cdouble}), kp.env, C_NULL, C_NULL)
    if return_code != 0
        error("KNITRO: Error restarting problem")
    end
end

"""
Initialize KNITRO with a new MIP problem.  KNITRO makes a local copy of
all inputs, so the application may free memory after the call completes.
"""
function mip_init_problem(kp::KnitroProblem,
                          objGoal::Integer,
                          objType::Integer,
                          objFnType::Integer,
                          x_Type::Vector{Int32},    # length n
                          x_L::Vector{Float64},     # length n
                          x_U::Vector{Float64},     # length n
                          c_Type::Vector{Int32},    # length m
                          c_FnType::Vector{Int32},  # length m
                          c_L::Vector{Float64},     # length m
                          c_U::Vector{Float64},     # length m
                          jac_var::Vector{Int32},   # length nnzJ
                          jac_cons::Vector{Int32},  # length nnzJ
                          hess_rows::Vector{Int32}, # length nnzH
                          hess_cols::Vector{Int32}; # length nnzH
                          initial_x = C_NULL,       # length n
                          initial_lambda = C_NULL)  # length m+n
    n = length(x_L)
    m = length(c_Type)
    nnzJ = length(jac_var)
    nnzH = length(hess_rows)
    return_code = @ktr_ccall(mip_init_problem, Int32, (Ptr{Nothing}, Cint, Cint,
                             Cint, Cint, Ptr{Cint}, Ptr{Cdouble}, Ptr{Cdouble},
                             Cint, Ptr{Cint}, Ptr{Cint}, Ptr{Cdouble},
                             Ptr{Cdouble}, Cint, Ptr{Cint}, Ptr{Cint},Cint,
                             Ptr{Cint},Ptr{Cint}, Ptr{Nothing}, Ptr{Nothing}),
                             kp.env, n, objGoal, objType, objFnType, x_Type,
                             x_L, x_U, m, c_Type, c_FnType, c_L, c_U, nnzJ,
                             jac_var, jac_cons, nnzH, hess_rows, hess_cols,
                             initial_x, initial_lambda)
    if return_code != 0
        error("KNITRO: Error initializing MIP problem")
    end
end

function mip_init_problem(kp::KnitroProblem,
                          objGoal::Integer,
                          objType::Integer,
                          objFnType::Integer,
                          x_Type::Vector{Int32},    # length n
                          x_L::Vector{Float64},     # length n
                          x_U::Vector{Float64},     # length n
                          c_Type::Vector{Int32},    # length m
                          c_FnType::Vector{Int32},  # length m
                          c_L::Vector{Float64},     # length m
                          c_U::Vector{Float64},     # length m
                          jac_var::Vector{Int32},   # length nnzJ
                          jac_cons::Vector{Int32};  # length nnzJ
                          initial_x = C_NULL,       # length n
                          initial_lambda = C_NULL)  # length m+n
    n = length(x_L)
    m = length(c_Type)
    nnzJ = length(jac_var)
    return_code = @ktr_ccall(mip_init_problem, Int32, (Ptr{Nothing}, Cint, Cint,
                             Cint, Cint, Ptr{Cint}, Ptr{Cdouble}, Ptr{Cdouble},
                             Cint, Ptr{Cint}, Ptr{Cint}, Ptr{Cdouble},
                             Ptr{Cdouble},Cint,Ptr{Cint}, Ptr{Cint}, Cint,
                             Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}),
                             kp.env, n, objGoal, objType, objFnType, x_Type,
                             x_L, x_U, m, c_Type, c_FnType, c_L, c_U, nnzJ,
                             jac_var, jac_cons, 0, C_NULL, C_NULL,
                             initial_x, initial_lambda)
    if return_code != 0
        error("KNITRO: Error initializing MIP problem without exact hessians")
    end
end

"""
Set the branching priorities for integer variables.

Priorities must be positive numbers (variables with non-positive values
are ignored).  Variables with higher priority values will be considered
for branching before variables with lower priority values.  When
priorities for a subset of variables are equal, the branching rule is
applied as a tiebreaker.
"""
function set_branching_priorities(kp::KnitroProblem,
                                  xPriorities::Vector{Int})
    return_code = @ktr_ccall(mip_set_branching_priorities, Int32, (Ptr{Nothing},
                             Ptr{Cint}), kp.env, xPriorities)
    if return_code != 0
        error("KNITRO: Error setting MIP branching priorities")
    end
end

"""
Call KNITRO to solve the MIP problem.

If the application provides callback functions for evaluating the function,
constraints, and derivatives, then a single call to KTR_mip_solve returns
the solution.
Otherwise, KNITRO operates in reverse communications mode and returns a
status code that may request another call.

Returns one of the status codes KTR_RC_*. In particular:
0 - KNITRO is finished: x, lambda, and obj contain the optimal solution
1 - call KTR_solve again (reverse comm) with obj and c containing
    the objective and constraints evaluated at x
2 - call KTR_solve again (reverse comm) with objGrad and jac containing
    the objective and constraint first derivatives evaluated at x
3 - call KTR_solve again (reverse comm) with hess containing
    H(x,lambda), the Hessian of the Lagrangian evaluated at x and lambda
7 - call KTR_solve again (reverse comm) with hessVector containing
    the result of H(x,lambda) * hessVector

If `gradopt` is set to compute finite differences for first derivatives,
then KTR_mip_solve will modify objGrad and jac; otherwise, these arguments
are not modified.
"""
function mip_solve_problem(kp::KnitroProblem,
                           x::Vector{Float64},
                           lambda::Vector{Float64},
                           evalStatus::Integer,
                           obj::Vector{Float64},
                           cons::Vector{Float64},
                           objGrad::Vector{Float64},
                           jac::Vector{Float64},
                           hess::Vector{Float64},
                           hessVector::Vector{Float64})
    @ktr_ccall(mip_solve, Int32, (Ptr{Nothing}, Ptr{Cdouble},
               Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble},
               Ptr{Cdouble},Ptr{Cdouble}, Ptr{Cdouble},
               Ptr{Cdouble}, Any), kp.env, x, lambda, evalStatus,
               obj, cons, objGrad, jac, hess, hessVector, kp)
end

function mip_solve_problem(kp::KnitroProblem,
                           x::Vector{Float64},
                           lambda::Vector{Float64},
                           evalStatus::Integer,
                           obj::Vector{Float64})
    # For callback mode
    @ktr_ccall(mip_solve, Int32, (Ptr{Nothing}, Ptr{Cdouble},
               Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Nothing},
               Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}, Any),
               kp.env, x, lambda, evalStatus, obj, C_NULL,
               C_NULL, C_NULL, C_NULL, C_NULL, kp)
end

"""
Set an array of relative stepsizes to use for the finite-difference
gradient/Jacobian computations when using finite-difference
first derivatives.

Finite-difference step sizes `delta` in KNITRO are
computed as:

          delta[i] = relStepSizes[i]*max(abs(x[i]),1)

The default relative step sizes for each component of `x` are `sqrt(eps)`
for forward finite differences, and `eps^(1/3)` for central finite
differences.  Use this function to overwrite the default values.
Array relStepSizes has length n and all values should be non-zero.
"""
function set_findiff_relstepsizes(kp::KnitroProblem,
                                  relStepSizes::Vector{Float64})
    return_code = @ktr_ccall(set_findiff_relstepsizes, Int32, (Ptr{Nothing},
                             Ptr{Cdouble}), kp.env, relStepSizes)
    if return_code != 0
        error("KNITRO: Error setting relative stepsizes for the
              finite-difference gradient/Jacobian computations")
    end
end

# ----- Reading solution properties -----

"""
Return the number of function evaluations requested by KTR_solve.

A single request evaluates the objective and all constraint functions.
"""
function get_number_FC_evals(kp::KnitroProblem)
    n = @ktr_ccall(get_number_FC_evals, Int32, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error returning number of function evaluations")
    end
    n
end

"""
Return the number of gradient evaluations requested by KTR_solve.

A single request evaluates first derivatives of the objective and
all constraint functions.
"""
function get_number_GA_evals(kp::KnitroProblem)
    n = @ktr_ccall(get_number_GA_evals, Int32, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error returning number of gradient evaluations")
    end
    n
end

"""
Return the number of Hessian evaluations requested by KTR_solve.

A single request evaluates second derivatives of the objective and
all constraint functions.
"""
function get_number_H_evals(kp::KnitroProblem)
    n = @ktr_ccall(get_number_H_evals, Int32, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error returning number of hessian evaluations")
    end
    n
end

"""
Return the number of Hessian-vector products requested by KTR_solve.

A single request evaluates the product of the Hessian of the
Lagrangian with a vector submitted by KNITRO.
"""
function get_number_HV_evals(kp::KnitroProblem)
    n = @ktr_ccall(get_number_HV_evals, Int32, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error returning number of hessian-vector evaluations")
    end
    n
end

# /* ----- Solution properties for continuous problems only ----- */

"Return the number of iterations made by KTR_solve."
function get_number_iters(kp::KnitroProblem)
    n = @ktr_ccall(get_number_iters, Int32, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error returning number of iterations")
    end
    n
end

"Return the number of conjugate gradient (CG) iterations by KTR_solve"
function get_number_cg_iters(kp::KnitroProblem)
    n = @ktr_ccall(get_number_cg_iters, Int32, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error returning no. of conjugate gradient iterations")
    end
    n
end

"""
Return the absolute feasibility error at the solution

Refer to the KNITRO manual section on Termination Tests for a
detailed definition of this quantity.
"""
function get_abs_feas_error(kp::KnitroProblem)
    n = @ktr_ccall(get_abs_feas_error, Float64, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error getting absolute feasibility error at solution")
    end
    n
end

"""
Return the relative feasibility error at the solution

Refer to the KNITRO manual section on Termination Tests for a
detailed definition of this quantity.
"""
function get_rel_feas_error(kp::KnitroProblem)
    n = @ktr_ccall(get_rel_feas_error, Float64, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error getting relative feasibility error at solution")
    end
    n
end

"""
Return the absolute optimality error at the solution.

Refer to the KNITRO manual section on Termination Tests for a
detailed definition of this quantity.
"""
function get_abs_opt_error(kp::KnitroProblem)
    n = @ktr_ccall(get_abs_opt_error, Float64, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error getting absolute optimality error at solution")
    end
    n
end

"""
Return the relative optimality error at the solution.

Refer to the KNITRO manual section on Termination Tests for a
detailed definition of this quantity.
"""
function get_rel_opt_error(kp::KnitroProblem)
    n = @ktr_ccall(get_rel_opt_error, Float64, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error getting relative optimality error at solution")
    end
    n
end

"""
Return the solution status, objective, primal and dual variables.

The `status`, `obj`, `x`, and `lambda` vectors will be modified in-place
with the values returned by the routine.
"""
function get_solution(kp::KnitroProblem,
                      status::Vector{Int32},
                      obj::Vector{Float64},
                      x::Vector{Float64},
                      lambda::Vector{Float64})
    return_code = @ktr_ccall(get_solution, Int32, (Ptr{Nothing}, Ptr{Cint},
                             Ptr{Cdouble},Ptr{Cdouble},Ptr{Cdouble}), kp.env,
                             status, obj, x, lambda)
    if return_code < 0
        error("KNITRO: Error getting the solution status and values")
    end
end

"Return the values of the constraint vector c(x) in `c` through `cons`"
function get_constraint_values(kp::KnitroProblem, cons::Vector{Float64})
    return_code = @ktr_ccall(get_constraint_values, Int32, (Ptr{Nothing},
                             Ptr{Cdouble}), kp.env, cons)
    if return_code < 0
        error("KNITRO: Error getting the values of the constraint vector c(x)")
    end
end

"Return the values of the objective gradient vector through `objGrad`"
function get_objgrad_values(kp::KnitroProblem, objGrad::Vector{Float64})
    return_code = @ktr_ccall(get_objgrad_values, Int32, (Ptr{Nothing},
                             Ptr{Cdouble}), kp.env, objGrad)
    if return_code < 0
        error("KNITRO: Error getting the objective gradient vector")
    end
end

"Return the values of the (non-zero) sparse Jacobian through `jac`"
function get_jacobian_values(kp::KnitroProblem, jac::Vector{Float64})
    return_code = @ktr_ccall(get_jacobian_values, Int32, (Ptr{Nothing},
                             Ptr{Cdouble}), kp.env, jac)
    if return_code < 0
        error("KNITRO: Error getting the values of the sparse Jacobian")
    end
end

"""
Return the values of the (non-zero sparse) Hessian (or possibly Hessian
approximation) through `hess`.  This routine is currently only valid
if 1 of the 2 following cases holds:
  1) KTR_HESSOPT_EXACT (presolver on or off), or;
  2) KTR_HESSOPT_BFGS or KTR_HESSOPT_SR1, but only with the
     KNITRO presolver off (i.e. KTR_PRESOLVE_NONE).

In all other cases, either KNITRO does not have an internal
representation of the Hessian (or Hessian approximation),
or the internal Hessian approximation corresponds only to
the presolved problem form and may not be valid for the
original problem form.  In these cases `hess` is left
unmodified, and the routine has return code 1.

Note that in case 2 above (KTR_HESSOPT_BFGS or KTR_HESSOPT_SR1)
the values returned in `hess` are the upper triangular values
of the dense quasi-Newton Hessian approximation stored row-wise.
There are ((n*n - n)/2 + n) such values (where `n` is the number
of variables in the problem. These values may be quite different
from the values of the exact Hessian.

When KTR_HESSOPT_EXACT (case 1 above) the Hessian values
returned correspond to the non-zero sparse Hessian indices
provided by the user in `init_problem()`.

Returns 0 if call is successful;
        1 if `hess` was not set because KNITRO does not
        have a valid Hessian for the model stored.
"""
function get_hessian_values(kp::KnitroProblem,
                            hess::Vector{Float64})
    return_code = @ktr_ccall(get_hessian_values, Int32, (Ptr{Nothing},
                             Ptr{Cdouble}), kp.env, hess)
    if return_code < 0
        error("KNITRO: Error getting the values of the Hessian")
    end
    return_code
end


# /* ----- Solution properties for MIP problems only ----- */

"Return the number of nodes processed in MIP solve"
function get_mip_num_nodes(kp::KnitroProblem)
    n = @ktr_ccall(get_mip_num_nodes, Int32, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error getting the number of nodes processed in
              MIP solve")
    end
    n
end

"Return the number of continuous subproblems processed in MIP solve."
function get_mip_num_solves(kp::KnitroProblem)
    n = @ktr_ccall(get_mip_num_solves, Int32, (Ptr{Nothing},), kp.env)
    if n < 0
        error("KNITRO: Error getting the number of subproblems processed in
              MIP solve")
    end
    n
end

"""
Return the final absolute integrality gap in the MIP solve.

Returns KTR_INFBOUND if no incumbent (i.e., integer feasible) point found.

Refer to the KNITRO manual section on Termination Tests for
a detailed definition of this quantity.
"""
function get_mip_abs_gap(kp::KnitroProblem)
    return_code = @ktr_ccall(get_mip_abs_gap, Float64, (Ptr{Nothing},), kp.env)
    if return_code == KTR_RC_BAD_KCPTR
        error("KNITRO: Error getting the final absolute integrality gap in
              MIP solve.")
    end
    return_code
end

"""
Return the final relative integrality gap in the MIP solve.

Refer to the KNITRO manual section on Termination Tests for
a detailed definition of this quantity.
"""
function get_mip_rel_gap(kp::KnitroProblem)
    return_code = @ktr_ccall(get_mip_rel_gap, Float64, (Ptr{Nothing},), kp.env)
    if return_code == KTR_RC_BAD_KCPTR
        error("KNITRO: Error getting the final relative integrality gap in
              MIP solve.")
    end
    return_code
end

"Return the objective value of the MIP incumbent solution"
function get_mip_incumbent_obj(kp::KnitroProblem)
    return_code = @ktr_ccall(get_mip_incumbent_obj, Float64, (Ptr{Nothing},),
                             kp.env)
    if return_code == KTR_RC_BAD_KCPTR
        error("KNITRO: Error getting the objective value of the MIP incumbent
              solution.")
    end
    return_code
end

"Return the value of the current MIP relaxation bound"
function get_mip_relaxation_bnd(kp::KnitroProblem)
    return_code = @ktr_ccall(get_mip_relaxation_bnd, Float64, (Ptr{Nothing},),
                             kp.env)
    if return_code == KTR_RC_BAD_KCPTR
        error("KNITRO: Error getting the value of the current MIP relaxation
              bound.")
    end
    return_code
end

"Return the objective value of the most recent MIP node subproblem"
function get_mip_lastnode_obj(kp::KnitroProblem)
    return_code = @ktr_ccall(get_mip_lastnode_obj, Float64, (Ptr{Nothing},),
                             kp.env)
    if return_code == KTR_RC_BAD_KCPTR
        error("KNITRO: Error getting the objective value of the most recent
              MIP subproblem.")
    end
    return_code
end

"""
Return the MIP incumbent solution in 'x' if it exists

Returns 1 if incumbent solution exists and call is successful;
        0 if no incumbent (i.e., integer feasible) exists
"""
function get_mip_incumbent_x(kp::KnitroProblem, x::Vector{Float64})
    return_code = @ktr_ccall(get_mip_incumbent_x, Int32, (Ptr{Nothing},
                             Ptr{Cdouble}), kp.env, x)
    if return_code < 0
        error("KNITRO: Error getting the MIP incumbent solution 'x'.")
    end
    return_code
end

"""
Compare the application's analytic first derivatives to a finite
difference approximation at x.  The objective and all constraint
functions are checked.

Returns one of the status codes KTR_RC_*. In particular:
0 - routine is finished
1 - call routine again (reverse comm) with obj and c containing
    the objective and constraints evaluated at x
2 - call routine again (reverse comm) with objGrad and jac containing
    the objective and constraint first derivatives evaluated at x

Description of the arguments:
x                - input  (length n) point at which to check derivatives
                   output            point at which to evaluate obj and c
finiteDiffMethod - 1 = forward differences, 2 = central differences
absThreshold     - print when |estimate - analytic| > threshold
relThreshold     - print when |estimate - analytic| > threshold * scale
                   where scale = max{1, |analytic|}
evalStatus       - input            evaluation status (0=OK)
obj              - input            objective at x
c                - input (length m) constraints at x
objGrad          - input (length n) analytic gradient at x
jac              - input (length nnzJ) analytic constraint Jacobian at x
"""
function check_first_ders(kp::KnitroProblem,
                          x::Vector{Float64},
                          finiteDiffMethod::Integer,
                          absThreshold::Float64,
                          relThreshold::Float64,
                          evalStatus::Integer,
                          obj::Float64,
                          cons::Vector{Float64},
                          objGrad::Vector{Float64},
                          jac::Vector{Float64})
    return_code = @ktr_ccall(check_first_ders, Int32, (Ptr{Nothing}, Ptr{Cdouble},
                             Cint, Cdouble, Cdouble, Cint, Cdouble,
                             Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble},
                             Ptr{Nothing}), kp.env, x, finiteDiffMethod,
                             absThreshold, relThreshold, evalStatus,
                             obj, cons, objGrad, jac, C_NULL)
    if return_code < 0
        error("KNITRO: Error comparing the application's analytic first
              derivatives to a finite difference approximation at x")
    end
end
