# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and Engineering
# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this
# software.  This software is distributed under the 3-clause BSD License.
# ____________________________________________________________________________________

import json
import os
from os.path import dirname, abspath, join

thisDir = dirname(abspath(__file__))

import pyomo.common.unittest as unittest

from pyomo.common.fileutils import import_file
from pyomo.kernel import SolverFactory, variable, maximize, minimize
from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases

problems = ['convex_var', 'concave_var', 'piecewise_var', 'step_var']

testing_solvers = {}
testing_solvers['gurobi', 'nl'] = False
# testing_solvers['cplex','lp'] = False
# testing_solvers['cplex','nl'] = False
# testing_solvers['ipopt','nl'] = False
# testing_solvers['cplex','python'] = False
# testing_solvers['_cplex_persistent','python'] = False
for _solver, _io in _test_solver_cases():
    if (_solver, _io) in testing_solvers and _test_solver_cases(_solver, _io).available:
        testing_solvers[_solver, _io] = True


def createTestMethod(pName, problem, solver, writer, kwds):
    def testMethod(obj):
        if not testing_solvers[solver, writer]:
            obj.skipTest("Solver %s (interface=%s) is not available" % (solver, writer))

        m = import_file(
            os.path.join(thisDir, 'kernel_problems', problem + '.py'), clear_cache=True
        )

        model = m.define_model(**kwds)

        opt = SolverFactory(solver, solver_io=writer)
        results = opt.solve(model)

        # non-recursive
        new_results = (
            (var.name, var.value)
            for var in model.components(
                ctype=variable.ctype, active=True, descend_into=False
            )
        )
        baseline_results = getattr(obj, problem + '_results')
        for name, value in new_results:
            if abs(baseline_results[name] - value) > 0.00001:
                raise IOError(
                    "Difference in baseline solution values and "
                    "current solution values using:\n"
                    + "Solver: "
                    + solver
                    + "\n"
                    + "Writer: "
                    + writer
                    + "\n"
                    + "Variable: "
                    + name
                    + "\n"
                    + "Solution: "
                    + str(value)
                    + "\n"
                    + "Baseline: "
                    + str(baseline_results[name])
                    + "\n"
                )

    return testMethod


def assignTests(cls, problem_list):
    for solver, writer in testing_solvers:
        for PROBLEM in problem_list:
            aux_list = [{'simplify': True}, {'simplify': False}]
            for AUX in aux_list:
                for REPN in ['sos2', 'mc', 'inc', 'cc', 'dcc', 'dlog', 'log']:
                    for BOUND_TYPE in ['lb', 'ub', 'eq']:
                        for SENSE in [maximize, minimize]:
                            if not (
                                ((BOUND_TYPE == 'lb') and (SENSE == maximize))
                                or ((BOUND_TYPE == 'ub') and (SENSE == minimize))
                                or ((REPN == 'mc') and ('step' in PROBLEM))
                            ):
                                kwds = {}
                                kwds['sense'] = SENSE
                                kwds['repn'] = REPN
                                kwds['bound'] = BOUND_TYPE
                                if SENSE == maximize:
                                    attrName = "test_{0}_{1}_{2}_{3}_{4}_{5}".format(
                                        PROBLEM,
                                        REPN,
                                        BOUND_TYPE,
                                        'maximize',
                                        solver,
                                        writer,
                                    )
                                else:
                                    assert SENSE == minimize
                                    attrName = "test_{0}_{1}_{2}_{3}_{4}_{5}".format(
                                        PROBLEM,
                                        REPN,
                                        BOUND_TYPE,
                                        'minimize',
                                        solver,
                                        writer,
                                    )
                                assert len(AUX) == 1
                                kwds.update(AUX)
                                attrName += '_simplify_' + str(AUX['simplify'])
                                setattr(
                                    cls,
                                    attrName,
                                    createTestMethod(
                                        attrName, PROBLEM, solver, writer, kwds
                                    ),
                                )
                                with open(
                                    join(
                                        thisDir,
                                        'kernel_baselines',
                                        PROBLEM + '_baseline_results.json',
                                    ),
                                    'r',
                                ) as f:
                                    baseline_results = json.load(f)
                                    setattr(cls, PROBLEM + '_results', baseline_results)


class PiecewiseLinearKernelTest(unittest.TestCase):
    pass


assignTests(PiecewiseLinearKernelTest, problems)

if __name__ == "__main__":
    unittest.main()
