from enum import IntEnum, auto

class Preference(IntEnum):
    Any = auto()
    Scip = auto()
    Gurobi = auto()
    Cplex = auto()

class VariableType(IntEnum):
    Continuous = auto()
    Integer = auto()
    Binary = auto()

class Sense(IntEnum):
    Minimize = auto()
    Maximize = auto()

class Relation(IntEnum):
    LessEqual = auto()
    Equal = auto()
    GreaterEqual = auto()

# cython appears to make these available as module-level constants as well
Any = Preference.Any
Scip = Preference.Scip
Gurobi = Preference.Gurobi
Cplex = Preference.Cplex
Continuous = VariableType.Continuous
Integer = VariableType.Integer
Binary = VariableType.Binary
Minimize = Sense.Minimize
Maximize = Sense.Maximize
Equal = Relation.Equal
GreaterEqual = Relation.GreaterEqual
LessEqual = Relation.LessEqual


class Solution:
    def __init__(self, size: int) -> None: ...
    def __len__(self) -> int: ...
    def __getitem__(self, i: int) -> float: ...
    def __setitem__(self, i: int, value: float) -> None: ...
    def resize(self, size: int) -> None: ...
    def get_value(self) -> float: ...
    def set_value(self, value: float) -> None: ...

class LinearConstraint:
    def set_coefficient(self, i, value) -> None: ...
    def get_coefficients(self) -> None: ...
    def set_relation(self, relation) -> None: ...
    def set_value(self, value) -> None: ...
    def get_relation(self) -> None: ...
    def get_value(self) -> None: ...
    def is_violated(self, solution: Solution) -> None: ...

class LinearConstraints:
    def clear(self) -> None: ...
    def add(self, constraint: LinearConstraint) -> None: ...
    def add_all(self, constraints: LinearConstraints) -> None: ...
    def __len__(self) -> int: ...

class _Objective:
    def __init__(self, size: int = 0) -> None: ...
    def __len__(self) -> int: ...
    def resize(self, size: int) -> None: ...
    def set_sense(self, sense: Sense) -> None: ...
    def get_sense(self) -> Sense: ...
    def set_coefficient(self, i: int, value: float) -> None: ...
    def get_coefficients(self) -> list[float]: ...
    def get_constant(self) -> float: ...
    def set_constant(self, value: float) -> None: ...

class LinearObjective(_Objective): ...

class QuadraticObjective(_Objective):
    def set_quadratic_coefficient(self, i: int, j: int, value: float) -> None: ...

class _Solver:
    def __init__(
        self,
        num_variables: int,
        default_variable_type: VariableType,
        variable_types: dict[int, VariableType] | None = None,
        preference: Preference = Preference.Any,
    ) -> None: ...
    def set_constraints(self, constraints: LinearConstraints) -> None: ...
    def add_constraint(self, constraint: LinearConstraint) -> None: ...
    def set_timeout(self, timeout: float) -> None: ...
    def set_optimality_gap(self, gap: float, absolute: bool = False) -> None: ...
    def set_num_threads(self, num_threads: int) -> None: ...
    def solve(self) -> tuple[Solution, str]: ...

class LinearSolver(_Solver):
    def set_objective(self, objective: LinearObjective): ...
    def set_verbose(self, verbose: bool): ...

class QuadraticSolver(_Solver):
    def set_objective(self, objective: QuadraticObjective) -> None: ...
