"""
The artof_utils module provide static utility functions to be used in the artof package.
"""

import datetime
import os

import numpy as np
import pytz

from artof.data_read import Metadata


def get_next_step(it: int, step: int, lens_steps: int):
    """
    Get the next step of a run

    Args:
        it: Current iteration.
        step: Current step.
        lens_steps: Total number of steps per iteration.

    Returns:
        Next iteration and step
    """

    if step == lens_steps - 1:
        return it + 1, 0
    return it, step + 1


def get_previous_step(it: int, step: int, lens_steps: int):
    """
    Get the previous step of a run

    Args:
        it: Current iteration.
        step: Current step.
        lens_steps: Total number of steps per iteration.

    Returns:
        Previous iteration and step
    """
    if step == 0:
        return it - 1, lens_steps - 1
    return it, step - 1


def is_last_step(it: int, step: int, stop_iter: int, lens_steps: int):
    """
    Check if the current file is the last file of a run

    Args:
        it: Current iteration.
        step: Current step in iteration.
        lens_steps: Total number of steps.
        stop_iter: Maximum number of iterations.

    Returns:
        Boolean value if the current file is the last file
    """

    return it == stop_iter - 1 and step == lens_steps - 1


def next_file_exists(path: str, it: int, step: int, lens_steps: int):
    """
    Check if the next file of a run exists

    Args:
        path: Path where data files are located.
        it: Current iteration.
        step: Current step.
        lens_steps: Total number of steps.

    Returns:
        Boolean value if the next file exists
    """

    next_step = get_next_step(it, step, lens_steps)
    return os.path.exists(f"{path}/{next_step[0]}_{next_step[1]}")


def previous_file_exists(path: str, it: int, step: int, lens_steps: int):
    """
    Check if the previous file of a run exists

    Args:
        path: Path where data files are located.
        it: Current iteration.
        step: Current step.
        lens_steps: Total number of steps.

    Returns:
        Boolean value if the next file exists
    """

    previous_step = get_previous_step(it, step, lens_steps)
    return os.path.exists(f"{path}/{previous_step[0]}_{previous_step[1]}")


def print_progress(progress_info: dict):
    """
    Print the progress of the current run

    Args:
        progress_info: Dictionary containing progress information.
    """
    name = progress_info["name"]
    current = progress_info["current"]
    total = progress_info["total"]
    print("\r", end="")
    print(
        f'{name} loaded: [{"=" * min(int(current * 20 / total), 20):<20}] {current}/{total}',
        end="\r",
    )


def get_datetime_string(time: datetime.datetime) -> str:
    """
    Convert a datetime object to an ISO 8601 formatted string in the Europe/Berlin timezone.

    Args:
        time: A datetime object.

    Returns:
        An ISO 8601 formatted string representing the datetime in the Europe/Berlin timezone.
    """

    if time.tzinfo is None:
        return pytz.timezone("Europe/Berlin").localize(time).isoformat()

    return time.isoformat()


def get_incident_energy(metadata: Metadata) -> float:
    """
    Extract the incident energy from the metadata, if available. If not available, return -1.

    Args:
        metadata: Metadata object containing experimental information.

    Returns:
        The incident energy as a float.

    """
    try:
        incident_en = float(metadata.extra.start["Mono_energy"])
    except (AttributeError, KeyError, TypeError):
        incident_en = -1
    return incident_en


def get_incident_energy_for_binding(metadata: Metadata, incident_energy: float) -> None:
    """
    Get the incident energy for binding energy plotting/exporting. If the incident energy is not
    provided, use the value from the metadata.

    Args:
        metadata: Metadata object containing experimental information.
        incident_energy: Incident energy value. If -1, the value from metadata will be used.

    Returns:
        The incident energy as a float, or None if not available.

    Raises:
        ValueError: If the incident energy is not provided and cannot be found in the metadata.
    """

    if incident_energy is None:
        incident_energy = get_incident_energy(metadata)
        if incident_energy == -1:
            raise ValueError("Incident energy not found in metadata. Please provide it manually.")
    return incident_energy


def load_data_from_csv(path: str) -> tuple[list[np.ndarray], np.ndarray]:
    """
    Load data from a CSV file.

    Args:
        path: Path to the CSV file.

    Returns:
        axes: List of numpy arrays representing the axes.
    """
    with open(path, "r", encoding="utf-8") as f:
        lines = f.readlines()
        header_lines = [line for line in lines if line.startswith("#")]
        axes_values = []
        for line in header_lines[1:]:
            values_str = line.split(":")[1].strip()
            values = [float(val) for val in values_str.split(",")]
            axes_values.append(values)
    data = np.loadtxt(path, delimiter=",")
    if len(axes_values) == 1:
        axes_values = axes_values[0]
    return axes_values, data
