#!/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]) -> 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 a folder in which to store (temporary) data of this `Component` during execution.

            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, if required.

        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
        if discipline.supplies_partials:
            super(DisciplineComponent, self).__init__(self._discipline.in_file,
                                                      self._discipline.out_file,
                                                      self._discipline.partials_file,
                                                      data_folder, keep_files, base_file)
        else:
            super(DisciplineComponent, self).__init__(self._discipline.in_file,
                                                      self._discipline.out_file,
                                                      None,
                                                      data_folder, keep_files, base_file)
            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)

    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)
