"""
See also:
    https://github.com/cvxopt/cvxopt/blob/master/src/C/glpk.c
"""

from typing import Tuple, Union, Literal, Optional, Dict, Any, Set, overload, TypedDict
from cvxopt import glpk  # type: ignore
from . import Matrix, SparseMatrix


CvxMatLike = Union[Matrix, SparseMatrix]
CvxBool = Literal["GLP_ON", "GLP_OFF"]


class GLPKOptions(TypedDict, total=False):
    # Common parameters
    msg_lev: Literal["GLP_MSG_OFF", "GLP_MSG_ERR", "GLP_MSG_ON", "GLP_MSG_ALL"]
    presolve: CvxBool
    tm_lim: int
    out_frq: int
    out_dly: int
    # LP-specific parameters
    meth: Literal["GLP_PRIMAL", "GLP_DUAL", "GLP_DUALP"]
    pricing: Literal["GLP_PT_STD", "GLP_PT_PSE"]
    r_test: Literal["GLP_RT_STD", "GLP_RT_HAR"]
    tol_bnd: float
    tol_dj: float
    tol_piv: float
    obj_ll: float
    obj_ul: float
    it_lim: int
    # MILP-specific parameters
    br_tech: Literal[
        "GLP_BR_FFV", "GLP_BR_LFV", "GLP_BR_MFV", "GLP_BR_DTH", "GLP_BR_PCH"
    ]
    bt_tech: Literal["GLP_BT_DFS", "GLP_BT_BFS", "GLP_BT_BLB", "GLP_BT_BPH"]
    pp_tech: Literal["GLP_PP_NONE", "GLP_PP_ROOT", "GLP_PP_ALL"]
    fp_heur: CvxBool
    gmi_cuts: CvxBool
    mir_cuts: CvxBool
    cov_cuts: CvxBool
    clq_cuts: CvxBool
    tol_int: float
    tol_obj: float
    mip_gap: float
    cb_size: int
    binarize: CvxBool


StatusLP = Literal["optimal", "primal infeasible", "dual infeasible", "unknown"]
StatusILP = Literal[
    "optimal",
    "feasible",
    "undefined",
    "invalid formulation",
    "infeasible problem",
    "LP relaxation is primal infeasible",
    "LP relaxation is dual infeasible",
    "unknown",
]


@overload
def lp(
    c: Matrix,
    G: CvxMatLike,
    h: Matrix,
) -> Tuple[StatusLP, Optional[Matrix], Optional[Matrix]]:
    """
    (status, x, z) = lp(c, G, h)

    PURPOSE
    (status, x, z) = lp(c, G, h) solves the pair of primal and dual LPs

        minimize    c'*x            maximize    -h'*z
        subject to  G*x <= h        subject to  G'*z + c = 0
                                                z >= 0.

    ARGUMENTS
    c            nx1 dense 'd' matrix with n>=1

    G            mxn dense or sparse 'd' matrix with m>=1

    h            mx1 dense 'd' matrix

    status       'optimal', 'primal infeasible', 'dual infeasible'
                 or 'unknown'

    x            if status is 'optimal', a primal optimal solution;
                 None otherwise

    z           if status is 'optimal', the dual optimal solution;
                 None otherwise
    """


@overload
def lp(
    c: Matrix,
    G: CvxMatLike,
    h: Matrix,
    A: CvxMatLike,
    b: Matrix,
) -> Tuple[StatusLP, Optional[Matrix], Optional[Matrix], Optional[Matrix]]:
    """
    (status, x, z, y) = lp(c, G, h, A, b)

    PURPOSE
    (status, x, z, y) = lp(c, G, h, A, b) solves the pair of primal and
    dual LPs

        minimize    c'*x            maximize    -h'*z + b'*y
        subject to  G*x <= h        subject to  G'*z + A'*y + c = 0
                    A*x = b                     z >= 0.


    ARGUMENTS
    c            nx1 dense 'd' matrix with n>=1

    G            mxn dense or sparse 'd' matrix with m>=1

    h            mx1 dense 'd' matrix

    A            pxn dense or sparse 'd' matrix with p>=0

    b            px1 dense 'd' matrix

    status       'optimal', 'primal infeasible', 'dual infeasible'
                 or 'unknown'

    x            if status is 'optimal', a primal optimal solution;
                 None otherwise

    z,y          if status is 'optimal', the dual optimal solution;
                 None otherwise
    """


# https://cvxopt.org/userguide/coneprog.html#linear-programming


def lp(
    c: Matrix,
    G: CvxMatLike,
    h: Matrix,
    A: Optional[CvxMatLike] = None,
    b: Optional[Matrix] = None,
):
    """
    (status, x, z, y) = lp(c, G, h, A, b)
    (status, x, z) = lp(c, G, h)

    PURPOSE
    (status, x, z, y) = lp(c, G, h, A, b) solves the pair of primal and
    dual LPs

        minimize    c'*x            maximize    -h'*z + b'*y
        subject to  G*x <= h        subject to  G'*z + A'*y + c = 0
                    A*x = b                     z >= 0.

    (status, x, z) = lp(c, G, h) solves the pair of primal and dual LPs

        minimize    c'*x            maximize    -h'*z
        subject to  G*x <= h        subject to  G'*z + c = 0
                                                z >= 0.

    ARGUMENTS
    c            nx1 dense 'd' matrix with n>=1

    G            mxn dense or sparse 'd' matrix with m>=1

    h            mx1 dense 'd' matrix

    A            pxn dense or sparse 'd' matrix with p>=0

    b            px1 dense 'd' matrix

    status       'optimal', 'primal infeasible', 'dual infeasible'
                 or 'unknown'

    x            if status is 'optimal', a primal optimal solution;
                 None otherwise

    z,y          if status is 'optimal', the dual optimal solution;
                 None otherwise
    """
    if A is None and b is None:
        return glpk.lp(c, G, h)
    return glpk.lp(c, G, h, A, b)


def ilp(
    c: Matrix,
    G: CvxMatLike,
    h: Matrix,
    A: Optional[CvxMatLike] = None,
    b: Optional[Matrix] = None,
    I: Optional[Set[int]] = None,
    B: Optional[Set[int]] = None,
) -> Tuple[StatusILP, Optional[Matrix]]:
    """
    Solves a mixed integer linear program using GLPK.

    (status, x) = ilp(c, G, h, A, b, I, B)

    PURPOSE
    Solves the mixed integer linear programming problem

        minimize    c'*x
        subject to  G*x <= h
                    A*x = b
                    x[k] is integer for k in I
                    x[k] is binary for k in B

    ARGUMENTS
    c            nx1 dense 'd' matrix with n>=1

    G            mxn dense or sparse 'd' matrix with m>=1

    h            mx1 dense 'd' matrix

    A            pxn dense or sparse 'd' matrix with p>=0

    b            px1 dense 'd' matrix

    I            set of indices of integer variables

    B            set of indices of binary variables

    status      if status is 'optimal', 'feasible', or 'undefined',
                a value of x is returned and the status string
                gives the status of x.  Other possible values of
                status are:  'invalid formulation',
                'infeasible problem', 'LP relaxation is primal
                infeasible', 'LP relaxation is dual infeasible',
                'unknown'.

    x            a (sub-)optimal solution if status is 'optimal',
                'feasible', or 'undefined'.  None otherwise
    """
    return glpk.ilp(c, G, h, A, b, I, B)


def set_global_options(options: GLPKOptions) -> None:
    glpk.options = options


def get_global_options() -> GLPKOptions:
    return glpk.options
