#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
This module contains usefule utilities for many
different python scripts
"""
from __future__ import print_function

import re
from functools import wraps
from subprocess import Popen, PIPE
from sys import __stdout__
from os import mkdir
from os.path import dirname, exists
from typing import Iterable, Optional, List, Callable, TypeVar, Union, Any
from pathlib import Path
from collections import OrderedDict

from urllib3.exceptions import MaxRetryError
from python_translator import Translator


def running_average(X: List[float], n: int) -> List[float]:
    """creates a running average of X with n entries in both dircetions"""
    X_new = []
    for i in range(n):
        X_new += [sum(X[0: i + 1]) / (i + 1)]
    for i in range(n, len(X) - n):
        X_new += [sum(X[i - n: i + n + 1]) / (2 * n + 1)]
    for i in range(len(X) - n, len(X)):
        X_new += [sum(X[2 * i - len(X) + 1:]) / ((len(X) - (i + 1)) * 2 + 1)]
    return X_new


def quality_of_fit(X: List[float], Y: List[float]) -> float:
    """calculates the quality of a fit 'bestimmtheitsmass'"""
    mean_x = sum(X) / len(X)
    return sum(x ** 2 - mean_x ** 2 for x in X)\
        / sum(x ** 2 - mean_x ** 2 for x in Y)


Return = TypeVar("Return")


def print_redirect(f: Callable[..., Return]) -> Callable[..., Return]:
    """
    wraps print to print to both stdout and __stdout__
    the reason for doing that is that abaqus replaces
    stdout and prints everything to abaqus
    this way everything can be seen both in the command line and in abaqus
    """

    @wraps(f)
    def inner_function(*args: Any, **argv: Any) -> Return:
        f(*args, **argv)
        for i, arg in enumerate(args):
            if isinstance(type, arg):
                del arg[i]
        for _, val in argv.items():
            if isinstance(type, val):
                del val
        argv["file"] = __stdout__
        f(*args, **argv)
        try:
            return f(*args, **argv)
        except TypeError:
            print("The argument 'file' is twice in a print statement")
            raise

    return inner_function


def filter_function(x: str) -> bool:
    """filter function in order to remove unused
    arguments from this script before parsing"""
    if ":" in x or "rwth" in x:
        return False
    analysed_item = x.replace("--", ".")
    analysed_item = analysed_item.replace("-", "!")
    if len(analysed_item) > 2\
            and "." not in analysed_item\
            and "!" in analysed_item:
        return False
    return True


def filter_argv(X: list[str]) -> list[str]:
    """removes unessecery entries from
    argv which have been generated by abaqus"""
    Y: list[str] = list(X)
    for i, x in enumerate(X):
        if not filter_function(x):
            del Y[i]
            if i < len(Y):
                del Y[i + 1]
    return Y


def dumb_plot(X: list[float], Y: list[float],
              title: str = "title", log: bool = False) -> None:
    """plot X and Y using dumb gnuplot"""
    try:
        with Popen(["gnuplot"], stdin=PIPE) as gnuplot:
            assert gnuplot, """Gnuplot could not be started."""
            assert gnuplot.stdin, """Input to Gnuplot could not be found."""
            gnuplot.stdin.write(bytes("set term dumb 79 25\n", "utf-8"))
            if log:
                gnuplot.stdin.write(bytes("set logscale xy\n", "utf-8"))
            gnuplot.stdin.write(
                bytes(
                    f"""plot '-' using 1:2 title '{title}'
                    with linespoints \n""",
                    "utf-8"
                )
            )
            for x, y in zip(X, Y):
                gnuplot.stdin.write(bytes(f"{x} {y}\n", "utf-8"))
            gnuplot.stdin.write(bytes("e\n", "utf-8"))
            gnuplot.stdin.flush()
    except FileNotFoundError:
        print("There is no installed instance of gnuplot")
        return


def read_file(filename: Union[str, Path],
              header: int = 0,
              type_: type = float) -> tuple[list[float], ...]:
    """read a file with given filename into
    a python-tuple. Skip n='header' lines in the beginning
    of the document"""
    # reads a given file and returns lists
    lists: list[list[float]] = []
    with open(filename, "r", encoding="utf-8") as input_file:
        for i, line in enumerate(input_file):
            if i < header:
                continue
            lists += [[type_(v) for v in line.split()]]
    return tuple(list(v) for v in zip(*lists))


def write_file(filename: Union[Path, str],
               *args: Iterable[float],
               title: Optional[str] = None) -> None:
    """write the rows given in 'arga' to a file"""
    # write data to a file
    if not any(args[0]):
        raise Exception("Tried to write an empty row to a file")
    if isinstance(filename, str):
        filename = Path(filename)
    with open(filename, "w", encoding="utf-8") as output_file:
        if title is not None:
            output_file.write(title + "\n")
        for row in zip(*args):
            output_file.write(" ".join(str(r) for r in row) + "\n")


def mkdir_p(foldername: str) -> None:
    """creates a new folder if the folder does not exist"""
    try:
        if exists(foldername):
            return
        mkdir(foldername)
    except IOError as e:
        # recursive creation of needed folders
        if e.errno != 2:
            raise
        mkdir_p(dirname(foldername))
        mkdir_p(foldername)


def trash_remover(func: Callable[..., tuple[Return, ...]])\
        -> Callable[..., Return]:
    """only keeps the first output of a given function"""

    @wraps(func)
    def inner_function(*args: Any, **kwargs: Any) -> Return:
        result = func(*args, **kwargs)
        return result[0]

    return inner_function


def use_translator(string: str, lang1: str, lang2: str) -> str:
    """
    Translate the given input string from lang2 to lang1 using google
    translate.
    """
    # one letter strings
    if len(string) <= 1:
        return string

    if r"\times" in string:
        return string

    try:
        translator = Translator()
        result: str
        result = translator.translate(string, lang1, lang2)
    except (OSError, MaxRetryError):
        return string
    return result


def translate(string: str, reverse: bool = False) -> str:
    """translates a string from english to german
    @input string any string wich contains specific english words
    @input reverse translate ger->en if set to true
    @return the german translation of the same string"""

    if reverse:
        string = re.sub(r'(\d+),(\d+)', r'\1\.\2', string)
    else:
        string = re.sub(r'(\d+)\.(\d+)', r'\1,\2', string)

    _dict: dict[str, str] = OrderedDict({
        "leakage": "Leckage",
        "contact pressure": "Kontaktdruck",
        "fluid pressure": "Fluiddruck",
        "pressure": "Druck",
        "density": "Dichte",
        "roundness": "Rundheit",
        "eccentricity": r"Exzentrizit\"at",
        "contact area": r"Kontaktoberfl\"ache",
        "area": r"Fl\"ache",
        "maximal": "Maximale",
        "time": "Zeit",
        "normal-force": "Normalkraft",
        "normal force": "Normalkraft",
        "total force": "Gesamtkraft",
        "force": "Kraft",
        "distance": "Abstand",
        "position": "Position",
        "contact broadness": "Kontaktbreite",
        "broadness": "Breite",
        "contact": "Kontakt-",
        "seat": "Sitz",
        "ball": "Kugel",
        "high": "hoch",
        "low": "tief",
        "elastic": "elastisch",
        "plastic": "plastisch",
        "angle": "Winkel",
        "degree": "Grad",
        "deg": "Grad",
        "hard": "hart",
        "soft": "weich",
        "fit": "Fit",
        "data": "Messwerte",
        "velocity": "Geschwindigkeit",
        "measurement": "Messung",
        "experiment": "Experiment",
        "simulation": "Simulation",
        "analytical": "analytisch",
        "signal": "Signal",
        "valve control": "Ventilansteuerung",
        "off": "zu",
        "on": "auf",
        "status": "Zustand",
        "valve": "Ventil",
        "relative pressure": "Relativdruck",
        "absolute pressure": "Absolutdruck",
        "relative": "relativ",
        "absolute": "absolut",
        "plot": "Graph"
    })
    if not reverse and r"\times" not in string:
        for key, value in _dict.items():
            if key in string.lower():
                string = re.sub(key, value, string, flags=re.IGNORECASE)
                break
        else:
            string = use_translator(string, "german", "english")
    else:
        for key, value in _dict.items():
            if value.lower() in string.lower():
                string = string.lower().replace(value.lower(), key.lower())
                break
        else:
            string = use_translator(string, "english", "german")
    return string
