#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright 2018 D. de Vries

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

This file contains the definition the `DisciplineComponent` class.
"""
from __future__ import absolute_import, division, print_function

from typing import Optional

from .abstract_discipline import AbstractDiscipline
from .xml_component import XMLComponent


class DisciplineComponent(XMLComponent):
    """Specialized `XMLComponent` wrapping an `AbstractDiscipline`.

    This version of `XMLComponent` defines in- and output variables based on the in- and output
    template XML files generated by a subclass of `AbstractDiscipline`. The `execute()` method
    simply forwards to that of the discipline.

    Attributes
    ----------
        discipline
    """

    def __init__(self, discipline, data_folder='', keep_files=False, base_file=None):
        # type: (AbstractDiscipline, Optional[str], bool, Optional[str]) -> None
        """Initialize a `Component` using a given `discipline`.

        Stores a reference to the given `discipline`. The in- and output XML templates should
        already exist at the paths specified in the `discipline`. This constructor uses those files
        to create the ``OpenMDAO`` `params` and `unknowns` using the methods exposed by the
        `XMLComponent` class this class inherits from.

        Parameters
        ----------
            discipline : :obj:`AbstractDiscipline`
                Instance of a subclass of `AbstractDiscipline` this `Component` will represent.

            data_folder : str(''), optional
                Path to folder in which (temporary) data of this `Component` is stored.

            keep_files : bool(False), optional
                Set to `True` to keep the data files generated by this `Component` during execution.

            base_file : str, optional
                Path to an XML file which should be kept up-to-date with the latest data.

        Notes
        -----
            Although this constructor could use the supplied `discipline` to also automatically
            generate its in- and output XML templates on the fly, the user is left in control of
            their generation. This is to allow for a `discipline` to generate different in- and
            output templates dynamically based on certain parameters. During execution only the
            static methods of the `discipline`s are used. Hence, any instance variables will not be
            accessible then. Therefore it is impossible to guarantee consistency if the in- and
            output XML files are generated here.
        """
        self._discipline = discipline
        self.number_of_computes = 0
        self.number_of_linearizes = 0
        super(DisciplineComponent, self).__init__(discipline.in_file_content, discipline.out_file_content,
                                                  discipline.partials_file_content, data_folder, keep_files, base_file)
        if not discipline.supplies_partials:
            self.partials_from_xml = None

    @property
    def discipline(self):
        # type: () -> AbstractDiscipline
        """:obj:`AbstractDiscipline`: Read-only reference to the specific discipline this
        `Component` wraps."""
        return self._discipline

    def execute(self, input_xml=None, output_xml=None):
        # type: (str, str) -> None
        """Call the `execute()` method of this `Component`'s discipline.

        Parameters
        ----------
            input_xml : str
                Path to the input XML file.

            output_xml : str
                Path to the output XML file.

        Raises
        ------
            ValueError
                If either no `input_xml` or `output_xml` path was specified.

        Notes
        -----
            Since this class inherits from `XMLComponent` the interface, including the optionality
            of its arguments, are left untouched. For this method this means the `input_xml` and
            `output_xml` parameters are strictly optional. However, in the context of the
            `DisciplineComponent` they should always be given. Therefore an exception is raised here
             when one of them or both are omitted.
        """
        if input_xml is None or output_xml is None:
            raise ValueError('Both an input_xml and output_xml path are expected.')
        self.discipline.execute(input_xml, output_xml)
        self.number_of_computes += 1

    def linearize(self, input_xml=None, partials_xml=None):
        # type: (str, str) -> None
        """Call the `linearize()` method of this `Component`'s discipline.

        Parameters
        ----------
            input_xml : str
                Path to the input XML file.

            partials_xml : str
                Path to the partials XML file.

        Raises
        ------
            ValueError
                If either no `input_xml` or `partials_xml` path was specified.
        """
        if self.discipline.supplies_partials:
            if input_xml is None or partials_xml is None:
                raise ValueError('Both an input_xml and a partials_xml path are expected.')
            self.discipline.linearize(input_xml, partials_xml)
        self.number_of_linearizes += 1

    def cleanup(self):
        super(DisciplineComponent, self).cleanup()
        self._discipline.cleanup()
