# -*- coding: utf-8 -*-
# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

# Contributors:
#    INITIAL AUTHORS - initial API and implementation and/or initial
#                           documentation
#        :author: Francois Gallard
#    OTHER AUTHORS   - MACROSCOPIC CHANGES
"""A factory to create or execute a post-processor from its class name."""
from __future__ import division, unicode_literals

import logging
from typing import List, Optional, Set, Union

from gemseo.algos.opt_problem import OptimizationProblem
from gemseo.core.factory import Factory
from gemseo.post.opt_post_processor import OptPostProcessor, OptPostProcessorOptionType
from gemseo.utils.py23_compat import Path, string_types

LOGGER = logging.getLogger(__name__)


class PostFactory(object):
    """Post-processing factory to run optimization post-processors.

    List the available post-processors on the current configuration
    and execute them on demand.

    Work both from memory, from a ran optimization problem,
    and from disk, from a serialized optimization problem.
    """

    def __init__(self):
        self.factory = Factory(OptPostProcessor, ("gemseo.post",))
        self.executed_post = []

    @property
    def posts(self):  # type: (...) -> List[str]
        """The available post processors."""
        return self.factory.classes

    def is_available(
        self,
        name,  # type: str
    ):  # type: (...) -> bool
        """Check the availability of a post-processor.

        Args:
            name: The name of the post-processor.

        Returns:
            Whether the post-processor is available.
        """
        return self.factory.is_available(name)

    def create(
        self,
        opt_problem,  # type: OptimizationProblem
        post_name,  # type: str
    ):  # type: (...) -> OptPostProcessor
        """Create a post-processor from its class name.

        Args:
            opt_problem: The optimization problem to be post-processed.
            post_name: The name of the post-processor.
        """
        return self.factory.create(post_name, opt_problem=opt_problem)

    def execute(
        self,
        opt_problem,  # type: Union[str,OptimizationProblem]
        post_name,  # type: str
        save=True,  # type: bool
        show=False,  # type: bool
        file_path=None,  # type: Optional[Union[str,Path]]
        directory_path=None,  # type: Optional[Union[str,Path]]
        file_name=None,  # type: Optional[str]
        file_extension=None,  # type: Optional[str]
        **options  # type: OptPostProcessorOptionType
    ):  # type: (...) -> OptPostProcessor
        """Post-process an optimization problem.

        Args:
            opt_problem: The optimization problem to be post-processed.
            post_name: The name of the post-processor.
            save: If True, save the figure.
            show: If True, display the figure.
            file_path: The path of the file to save the figures.
                If the extension is missing, use ``file_extension``.
                If None,
                create a file path
                from ``directory_path``, ``file_name`` and ``file_extension``.
            directory_path: The path of the directory to save the figures.
                If None, use the current working directory.
            file_name: The name of the file to save the figures.
                If None, use a default one generated by the post-processing.
            file_extension: A file extension, e.g. 'png', 'pdf', 'svg', ...
                If None, use a default file extension.
            **options: The options of the post-processor.
        """
        if isinstance(opt_problem, string_types):
            opt_problem = OptimizationProblem.import_hdf(opt_problem)
        post = self.create(opt_problem, post_name)
        post.execute(
            save=save,
            show=show,
            file_path=file_path,
            file_name=file_name,
            file_extension=file_extension,
            directory_path=directory_path,
            **options
        )
        self.executed_post.append(post)
        return post

    def list_generated_plots(self):  # type:(...) -> Set[str]
        """The generated plot files."""
        plots = []
        for post in self.executed_post:
            plots.extend(post.output_files)
        return set(plots)
