#!/usr/bin/python3
# -*- coding: utf-8 -*-   vim: set fileencoding=utf-8 :

"""General utility functions.

This is the bottom layer of the pencil package hierarchy, so modules
defined here are not allowed to import any other pencil packages.

"""

import os
import re
import warnings
import pathlib
import functools

MARKER_FILES = ["run.in", "start.in", "src/cparam.local", "src/Makefile.local"]


def is_sim_dir(path="."):
    """Decide if a path is pointing at a pencil code simulation directory.

    The heuristics used is to check for the existence of start.in, run.in,
    src/ cparam.local and src/Makefile.local .

    """
    return all([os.path.exists(os.path.join(path, f)) for f in MARKER_FILES])


def ffloat(x):
    """
    Numbers are read from fortran code, which has a specific lenght, in this case 8 char
    If we have scientific notation, it cuts the e and the number doesn't make sense.
    Example:
    Instead of 3.76e-291 it will write 3.76-291

    This function checks and converts all numbers to scientific notation in this case

    KG (2024-Apr-20): it is unclear why this function is needed at all.
    float() seems to correctly handle both "3.76e-291" and "3.76E-291",
    which are what the Fortran code outputs. This function does the
    conversion '3.76-291' -> 3.76e-291, but are there any scenarios where
    the Fortran code produces incorrectly formatted numbers like that?
    """

    try:
        return float(x)

    except:
        warnings.warn("This usage of pc.util.ffloat will be removed soon. If you believe your use-case is legitimate, please email <pencil-code-python@googlegroups.com> describing it.")
        val = re.sub(r"(-?\d+\.?\d*)([+-]\d+)", r"\1E\2", x)
        return float(val)

class PathWrapper(pathlib.WindowsPath if os.name == 'nt' else pathlib.PosixPath):
    """
    See documentation of pathlib.Path.

    This wrapper tries to avoid immediately breaking user code which assumes
    paths are always strings

    KG (2024-Oct-10): added
    KG (2024-Nov-09): fixed usage with Python<3.12 (see https://stackoverflow.com/a/78471242 )
    """
    def _add_warning(self):
        warnings.warn("Adding paths to strings will not work in the future; please change your code before it breaks. If you believe your use-case is legitimate, please email <pencil-code-python@googlegroups.com> describing it.")

    def __add__(self, other):
        self._add_warning()
        return str(self) + str(other)

    def __radd__(self, other):
        self._add_warning()
        return str(other) + str(self)

class SinglePrinter:
    """
    Instances of this class will print their output only on the MPI root process.

    KG (2025-Feb-07): added
    KG (2025-Dec-03): optimized to import mpi4py only when really needed
    """

    @property
    @functools.lru_cache()
    def print(self):
        """
        This logic really belongs in __init__, but we do things this way because
        importing mpi4py.MPI is very slow. If mpi4py.MPI were imported in
        __init__, that alone would account for half the initialization time of
        the Pencil module
        """
        try:
            from mpi4py import MPI
            comm = MPI.COMM_WORLD
            rank = comm.Get_rank()
        except ImportError:
            rank = 0

        if (rank == 0):
            return True
        else:
            return False

    def __call__(self, message):
        """
        message: str
        """
        if self.print:
            print(message)

pc_print = SinglePrinter()

def copy_docstring(original):
    """
    Decorator, to be used for wrapper functions, that makes the docstring of a
    particular function the same as another function.
    
    Rationale: if you consider pc.read.aver and pc.read.averages.Averages.read,
    both the docstrings are currently independently defined, but the former is
    simply a wrapper for the other. When new functionality is added to the
    latter, the docstring for the former is almost never updated, leading to
    the users being shown outdated help text.
    
    Copied from https://softwareengineering.stackexchange.com/a/386758
    
    NOTE: since sphinx-autoapi (used to generated the readthedocs pages) does
    not know about this decorator, it is best to manually add a minimal
    docstring of the form
    \"\"\"
    Wrapper for :py:meth:`Averages.read`
    \"\"\"
    to the wrapper function; this will allow Sphinx to create a link to the
    original function.
    """
    def wrapper(target):
        target.__doc__ = original.__doc__
        return target
    return wrapper
