# ____________________________________________________________________________________
#
# 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 time
import pyomo.opt
from pyomo.opt import SolverFactory
from pyomo.core import TransformationFactory
from pyomo.common.collections import Bunch


@SolverFactory.register(
    'mpec_nlp', doc='MPEC solver that optimizes a nonlinear transformation'
)
class MPEC_Solver1(pyomo.opt.OptSolver):
    def __init__(self, **kwds):
        kwds['type'] = 'mpec_nlp'
        pyomo.opt.OptSolver.__init__(self, **kwds)
        self._metasolver = True

    def _presolve(self, *args, **kwds):
        #
        # Cache the instance
        #
        self._instance = args[0]
        pyomo.opt.OptSolver._presolve(self, *args, **kwds)

    def _apply_solver(self):
        start_time = time.time()
        #
        # Transform instance
        #
        xfrm = TransformationFactory('mpec.simple_nonlinear')
        xfrm.apply_to(self._instance)
        #
        # Solve with a specified solver
        #
        solver = self.options.solver
        if not self.options.solver:  # pragma:nocover
            self.options.solver = solver = 'ipopt'

        # use the with block here so that deactivation of the
        # solver plugin always occurs thereby avoiding memory
        # leaks caused by plugins!
        with pyomo.opt.SolverFactory(solver) as opt:
            self.results = []
            epsilon_final = self.options.get('epsilon_final', 1e-7)
            epsilon = self.options.get('epsilon_initial', epsilon_final)
            while True:
                self._instance.mpec_bound.set_value(epsilon)
                #
                # **NOTE: It would be better to override _presolve on the
                #         base class of this solver as you might be
                #         missing a number of keywords that were passed
                #         into the solve method (e.g., none of the
                #         io_options are getting relayed to the subsolver
                #         here).
                #
                res = opt.solve(
                    self._instance, tee=self._tee, timelimit=self._timelimit
                )
                self.results.append(res)
                epsilon /= 10.0
                if epsilon < epsilon_final:
                    break
            #
            # Reclassify the Complementarity components
            #
            from pyomo.mpec import Complementarity

            for cuid in self._instance._transformation_data[
                'mpec.simple_nonlinear'
            ].compl_cuids:
                cobj = cuid.find_component_on(self._instance)
                cobj.parent_block().reclassify_component_type(cobj, Complementarity)
            #
            # Update timing
            #
            stop_time = time.time()
            self.wall_time = stop_time - start_time
            #
            # Return the sub-solver return condition value and log
            #
            return Bunch(rc=getattr(opt, '_rc', None), log=getattr(opt, '_log', None))

    def _postsolve(self):
        #
        # Create a results object
        #
        results = pyomo.opt.SolverResults()
        #
        # SOLVER
        #
        solv = results.solver
        solv.name = self.options.subsolver
        solv.wallclock_time = self.wall_time
        cpu_ = []
        for res in self.results:  # pragma:nocover
            if not getattr(res.solver, 'cpu_time', None) is None:
                cpu_.append(res.solver.cpu_time)
        if len(cpu_) > 0:  # pragma:nocover
            solv.cpu_time = sum(cpu_)
        # solv.termination_condition = pyomo.opt.TerminationCondition.maxIterations
        #
        # PROBLEM
        #
        self._instance.compute_statistics()
        prob = results.problem
        prob.name = self._instance.name
        prob.number_of_constraints = self._instance.statistics.number_of_constraints
        prob.number_of_variables = self._instance.statistics.number_of_variables
        prob.number_of_binary_variables = (
            self._instance.statistics.number_of_binary_variables
        )
        prob.number_of_integer_variables = (
            self._instance.statistics.number_of_integer_variables
        )
        prob.number_of_continuous_variables = (
            self._instance.statistics.number_of_continuous_variables
        )
        prob.number_of_objectives = self._instance.statistics.number_of_objectives
        #
        # SOLUTION(S)
        #
        self._instance.solutions.store_to(results)
        #
        # Uncache the instance and return the results
        #
        self._instance = None
        return results
