# Some boilerplate transformations people tend to use
from __future__ import annotations

MINIMAL_TRANSFORMERS = {
    '_tr_sqrt': 'np.sqrt(X)',
    '_tr_log(x+1)': 'np.log(X + 1)',
    '_tr_sqrt(abs(x))': 'np.sqrt(np.abs(X))',
    '_tr_log(abs(x)+1)': 'np.log(np.abs(X) + 1)',
}

DEFAULT_TRANSFORMERS = {
    '_tr_sqrt': 'np.sqrt(X)',
    '_tr_log(x+1)': 'np.log(X + 1)',
    '_tr_sqrt(abs(x))': 'np.sqrt(np.abs(X))',
    '_tr_log(abs(x)+1)': 'np.log(np.abs(X) + 1)',
    '_tr_div(x,abs(x))*log(abs(x))': 'np.divide(X, np.abs(X)) * np.log(np.abs(X))',
    '_tr_log(x + sqrt(pow(x,2), 1)': 'np.log(X + np.sqrt(np.power(X, 2) + 1))',
    '_tr_log*sqrt': 'np.log(X + 1) * np.sqrt(X)',
    '_tr_log*100': 'np.round(np.log(X + 1) * 100, 0)',
    '_tr_nonzero': 'np.where(X != 0, 1, 0)',
    '_tr_round(div(x,max))': 'np.round(np.divide(X, np.max(X)), 0)',
}

EXTENDED_TRANSFORMERS = {
    '_tr_sqrt': 'np.sqrt(X)',
    '_tr_log(x+1)': 'np.log(X + 1)',
    '_tr_sqrt(abs(x))': 'np.sqrt(np.abs(X))',
    '_tr_log(abs(x)+1)': 'np.log(np.abs(X) + 1)',
    '_tr_div(x,abs(x))*log(abs(x))': 'np.divide(X, np.abs(X)) * np.log(np.abs(X))',
    '_tr_log(x + sqrt(pow(x,2), 1)': 'np.log(X + np.sqrt(np.power(X, 2) + 1))',
    '_tr_log*sqrt': 'np.log(X + 1) * np.sqrt(X)',
    '_tr_log*100': 'np.round(np.log(X + 1) * 100, 0)',
    '_tr_nonzero': 'np.where(X != 0, 1, 0)',
    '_tr_round(div(x,max))': 'np.round(np.divide(X, np.max(X)), 0)',
    '_tr_squared': 'np.square(X)',
    '_tr_cubed': 'np.power(X, 3)',
    '_tr_inverse': 'np.divide(1, X, out=np.zeros_like(X), where=X!=0)',
    '_tr_exp': 'np.exp(X)',
    '_tr_exp_minus_1': 'np.expm1(X)',
    '_tr_sin': 'np.sin(X)',
    '_tr_cos': 'np.cos(X)',
    '_tr_tan': 'np.tan(X)',
    '_tr_arcsin_safe': 'np.arcsin(np.clip(X, -1, 1))',
    '_tr_arccos_safe': 'np.arccos(np.clip(X, -1, 1))',
    '_tr_arctan': 'np.arctan(X)',
    '_tr_sign': 'np.sign(X)',
    '_tr_z_score': '(X - np.mean(X)) / np.std(X)',
    '_tr_min_max_scale': '(X - np.min(X)) / (np.max(X) - np.min(X))',
    '_tr_quadratic_root': 'np.cbrt(X)',
    '_tr_shifted_sigmoid': '1 / (1 + np.exp(-X - 0.5))',
    '_tr_logistic': '1 / (1 + np.exp(-X))',
}

VERBOSE_TRANSFORMERS = {
    '_tr_sqrt': 'np.sqrt(X)',
    '_tr_log(x+1)': 'np.log(X + 1)',
    '_tr_sqrt(abs(x))': 'np.sqrt(np.abs(X))',
    '_tr_log(abs(x)+1)': 'np.log(np.abs(X) + 1)',
    '_tr_div(x,abs(x))*log(abs(x))': 'np.divide(X, np.abs(X)) * np.log(np.abs(X))',
    '_tr_log(x + sqrt(pow(x,2), 1)': 'np.log(X + np.sqrt(np.power(X, 2) + 1))',
    '_tr_log*sqrt': 'np.log(X + 1) * np.sqrt(X)',
    '_tr_log*100': 'np.round(np.log(X + 1) * 100, 0)',
    '_tr_nonzero': 'np.where(X != 0, 1, 0)',
    '_tr_round(div(x,max))': 'np.round(np.divide(X, np.max(X)), 0)',
    '_tr_squared': 'np.square(X)',
    '_tr_cubed': 'np.power(X, 3)',
    '_tr_inverse': 'np.divide(1, X, out=np.zeros_like(X), where=X!=0)',
    '_tr_exp': 'np.exp(X)',
    '_tr_exp_minus_1': 'np.expm1(X)',
    '_tr_sin': 'np.sin(X)',
    '_tr_cos': 'np.cos(X)',
    '_tr_tan': 'np.tan(X)',
    '_tr_arcsin_safe': 'np.arcsin(np.clip(X, -1, 1))',
    '_tr_arccos_safe': 'np.arccos(np.clip(X, -1, 1))',
    '_tr_arctan': 'np.arctan(X)',
    '_tr_sign': 'np.sign(X)',
    '_tr_z_score': '(X - np.mean(X)) / np.std(X)',
    '_tr_min_max_scale': '(X - np.min(X)) / (np.max(X) - np.min(X))',
    '_tr_quadratic_root': 'np.cbrt(X)',
    '_tr_shifted_sigmoid': '1 / (1 + np.exp(-X - 0.5))',
    '_tr_logistic': '1 / (1 + np.exp(-X))',
    '_tr_reciprocal': 'np.reciprocal(X, out=np.zeros_like(X), where=X!=0)',
    '_tr_log1p': 'np.log1p(X)',
    '_tr_expm1': 'np.expm1(X)',
    '_tr_softplus': 'np.log1p(np.exp(X))',
    '_tr_absolute': 'np.abs(X)',
    '_tr_clip_0_1': 'np.clip(X, 0, 1)',
    '_tr_normalize': '(X - np.min(X)) / (np.max(X) - np.min(X))',
    '_tr_standardize': '(X - np.mean(X)) / np.std(X)',
    '_tr_cumulative_sum': 'np.cumsum(X)',
    '_tr_cumulative_prod': 'np.cumprod(X)',
    '_tr_diff': 'np.diff(X, prepend=0)',
    '_tr_fourth_root': 'np.sqrt(np.sqrt(X))',
    '_tr_log10(x+1)': 'np.log10(X + 1)',
    '_tr_fourth_root(abs(x))': 'np.sqrt(np.sqrt(np.abs(X)))',
    '_tr_log10(abs(x)+1)': 'np.log10(np.abs(X) + 1)',
    '_tr_div(x,abs(x))*log10(abs(x))': 'np.divide(X, np.abs(X)) * np.log10(np.abs(X))',
    '_tr_log10*sqrt': 'np.log10(X + 1) * np.sqrt(X)',
    '_tr_log10*100': 'np.round(np.log10(X + 1) * 100, 0)',
    '_tr_positive': 'np.where(X > 0, 1, 0)',
    '_tr_round(div(x,median))': 'np.round(np.divide(X, np.median(X)), 0)',
    '_tr_fourth_power': 'np.power(X, 4)',
    '_tr_fifth_power': 'np.power(X, 5)',
    '_tr_inverse_squared': 'np.divide(1, np.square(X), out=np.zeros_like(X), where=X!=0)',
    '_tr_exp2': 'np.exp2(X)',
    '_tr_sinh': 'np.sinh(X)',
    '_tr_cosh': 'np.cosh(X)',
    '_tr_tanh': 'np.tanh(X)',
    '_tr_log2': 'np.log2(X)',
    '_tr_log10': 'np.log10(X)',
    '_tr_sinc': 'np.sinc(X)',
    '_tr_mean': 'np.mean(X)',
    '_tr_median': 'np.median(X)',
    '_tr_variance': 'np.var(X)',
    '_tr_std_dev': 'np.std(X)',
    '_tr_percentile_25': 'np.percentile(X, 25)',
    '_tr_percentile_50': 'np.percentile(X, 50)',
    '_tr_percentile_75': 'np.percentile(X, 75)',
    '_tr_reverse': 'X[::-1]',
    '_tr_sort': 'np.sort(X)',
    '_tr_argsort': 'np.argsort(X)',
    '_tr_is_nan': 'np.isnan(X)',
    '_tr_is_finite': 'np.isfinite(X)',
    '_tr_is_infinite': 'np.isinf(X)',
    '_tr_is_positive': 'np.greater(X, 0)',
    '_tr_is_negative': 'np.less(X, 0)',
    '_tr_is_zero': 'np.equal(X, 0)',
    '_tr_log2(x+1)': 'np.log2(X + 1)',
    '_tr_log2(abs(x)+1)': 'np.log2(np.abs(X) + 1)',
    '_tr_log2*sqrt': 'np.log2(X + 1) * np.sqrt(X)',
    '_tr_log2*100': 'np.round(np.log2(X + 1) * 100, 0)',
    '_tr_sec': '1 / np.cos(X)',
    '_tr_cosec': '1 / np.sin(X)',
    '_tr_cot': '1 / np.tan(X)',
    '_tr_arcsinh': 'np.arcsinh(X)',
    '_tr_arccosh': 'np.arccosh(X)',
    '_tr_arctanh': 'np.arctanh(X)',
    '_tr_poly2': 'np.polyval([1, 0, 0], X)',
    '_tr_poly3': 'np.polyval([1, 0, 0, 0], X)',
    '_tr_poly4': 'np.polyval([1, 0, 0, 0, 0], X)',
    '_tr_custom1': 'X ** 2 + 2 * X + 1',
    '_tr_custom2': 'np.sin(X) + np.cos(X)',
    '_tr_custom3': 'np.exp(X) - np.log(X + 1)',
    '_tr_empirical_cdf': 'np.searchsorted(np.sort(X), X, side="right") / len(X)',
    '_tr_uniform_cdf': 'np.clip(X, 0, 1)',
    '_tr_exponential_cdf': '1 - np.exp(-X)',
    '_tr_square_root_abs': 'np.sqrt(np.abs(X))',
    '_tr_log1p_abs': 'np.log1p(np.abs(X))',
    '_tr_exp_minus_one': 'np.expm1(X)',
    '_tr_inverse_abs': 'np.divide(1, np.abs(X), out=np.zeros_like(X), where=X!=0)',
    '_tr_square_root_inverse': 'np.sqrt(np.divide(1, X, out=np.zeros_like(X), where=X!=0))',
    '_tr_cubed_root': 'np.cbrt(X)',
    '_tr_log1p_squared': 'np.log1p(np.square(X))',
    '_tr_exp_squared': 'np.exp(np.square(X))',
    '_tr_sin_squared': 'np.sin(np.square(X))',
    '_tr_cos_squared': 'np.cos(np.square(X))',
    '_tr_tan_squared': 'np.tan(np.square(X))',
    '_tr_arctan_squared': 'np.arctan(np.square(X))',
    '_tr_sign_squared': 'np.sign(np.square(X))',
    '_tr_z_score_abs': '(np.abs(X) - np.mean(np.abs(X))) / np.std(np.abs(X))',
    '_tr_min_max_scale_abs': '(np.abs(X) - np.min(np.abs(X))) / (np.max(np.abs(X)) - np.min(np.abs(X)))',
    '_tr_shifted_sigmoid_abs': '1 / (1 + np.exp(-np.abs(X) - 0.5))',
    '_tr_logistic_abs': '1 / (1 + np.exp(-np.abs(X)))',
    '_tr_reciprocal_abs': 'np.reciprocal(np.abs(X), out=np.zeros_like(X), where=X!=0)',
    '_tr_log1p_cubed': 'np.log1p(np.power(X, 3))',
    '_tr_expm1_cubed': 'np.expm1(np.power(X, 3))',
    '_tr_softplus_cubed': 'np.log1p(np.exp(np.power(X, 3)))',
    '_tr_absolute_squared': 'np.abs(np.square(X))',
    '_tr_clip_0_2': 'np.clip(X, 0, 2)',
    '_tr_normalize_abs': '(np.abs(X) - np.min(np.abs(X))) / (np.max(np.abs(X)) - np.min(np.abs(X)))',
    '_tr_standardize_abs': '(np.abs(X) - np.mean(np.abs(X))) / np.std(np.abs(X))',
    '_tr_cumulative_sum_abs': 'np.cumsum(np.abs(X))',
    '_tr_cumulative_prod_abs': 'np.cumprod(np.abs(X))',
    '_tr_diff_abs': 'np.diff(np.abs(X), prepend=0)',
    '_tr_fourth_root_abs': 'np.sqrt(np.sqrt(np.abs(X)))',
    '_tr_log10_abs(x+1)': 'np.log10(np.abs(X) + 1)',
    '_tr_fourth_root_squared': 'np.sqrt(np.sqrt(np.square(X)))',
    '_tr_log10_squared(abs(x)+1)': 'np.log10(np.square(np.abs(X)) + 1)',
    '_tr_div_squared(x,abs(x))*log10(abs(x))': 'np.divide(np.square(X), np.abs(X)) * np.log10(np.abs(X))',
    '_tr_log10*sqrt_squared': 'np.log10(np.square(X) + 1) * np.sqrt(np.square(X))',
    '_tr_log10*100_squared': 'np.round(np.log10(np.square(X) + 1) * 100, 0)',
    '_tr_positive_squared': 'np.where(np.square(X) > 0, 1, 0)',
    '_tr_round_squared(div(x,median))': 'np.round(np.divide(np.square(X), np.median(np.square(X))), 0)',
    '_tr_fourth_power_squared': 'np.power(np.square(X), 4)',
    '_tr_fifth_power_squared': 'np.power(np.square(X), 5)',
    '_tr_inverse_squared_squared': 'np.divide(1, np.square(np.square(X)), out=np.zeros_like(X), where=X!=0)',
    '_tr_exp2_squared': 'np.exp2(np.square(X))',
    '_tr_sinh_squared': 'np.sinh(np.square(X))',
    '_tr_cosh_squared': 'np.cosh(np.square(X))',
    '_tr_tanh_squared': 'np.tanh(np.square(X))',
    '_tr_log2_squared': 'np.log2(np.square(X))',
    '_tr_log10_squared': 'np.log10(np.square(X))',
    '_tr_sinc_squared': 'np.sinc(np.square(X))',
    '_tr_mean_squared': 'np.mean(np.square(X))',
    '_tr_median_squared': 'np.median(np.square(X))',
    '_tr_variance_squared': 'np.var(np.square(X))',
    '_tr_std_dev_squared': 'np.std(np.square(X))',
    '_tr_percentile_25_squared': 'np.percentile(np.square(X), 25)',
    '_tr_percentile_50_squared': 'np.percentile(np.square(X), 50)',
    '_tr_percentile_75_squared': 'np.percentile(np.square(X), 75)',
    '_tr_reverse_squared': 'np.square(X)[::-1]',
    '_tr_sort_squared': 'np.sort(np.square(X))',
    '_tr_argsort_squared': 'np.argsort(np.square(X))',
    '_tr_is_nan_squared': 'np.isnan(np.square(X))',
    '_tr_is_finite_squared': 'np.isfinite(np.square(X))',
    '_tr_is_infinite_squared': 'np.isinf(np.square(X))',
    '_tr_is_positive_squared': 'np.greater(np.square(X), 0)',
    '_tr_is_negative_squared': 'np.less(np.square(X), 0)',
    '_tr_is_zero_squared': 'np.equal(np.square(X), 0)',
    '_tr_log2_squared(x+1)': 'np.log2(np.square(X) + 1)',
    '_tr_log2_squared(abs(x)+1)': 'np.log2(np.square(np.abs(X)) + 1)',
    '_tr_log2*sqrt_squared': 'np.log2(np.square(X) + 1) * np.sqrt(np.square(X))',
    '_tr_log2*100_squared': 'np.round(np.log2(np.square(X) + 1) * 100, 0)',
    '_tr_sec_squared': '1 / np.cos(np.square(X))',
    '_tr_cosec_squared': '1 / np.sin(np.square(X))',
    '_tr_cot_squared': '1 / np.tan(np.square(X))',
    '_tr_arcsinh_squared': 'np.arcsinh(np.square(X))',
    '_tr_arccosh_squared': 'np.arccosh(np.square(X))',
    '_tr_arctanh_squared': 'np.arctanh(np.square(X))',
    '_tr_poly2_squared': 'np.polyval([1, 0, 0], np.square(X))',
    '_tr_poly3_squared': 'np.polyval([1, 0, 0, 0], np.square(X))',
    '_tr_poly4_squared': 'np.polyval([1, 0, 0, 0, 0], np.square(X))',
    '_tr_custom1_squared': 'np.square(X) ** 2 + 2 * np.square(X) + 1',
    '_tr_custom2_squared': 'np.sin(np.square(X)) + np.cos(np.square(X))',
    '_tr_custom3_squared': 'np.exp(np.square(X)) - np.log(np.square(X) + 1)',
    '_tr_empirical_cdf_squared': 'np.searchsorted(np.sort(np.square(X)), np.square(X), side="right") / len(np.square(X))',
    '_tr_uniform_cdf_squared': 'np.clip(np.square(X), 0, 1)',
    '_tr_exponential_cdf_squared': '1 - np.exp(-np.square(X))',
    '_tr_fill_nan_with_zero': 'np.nan_to_num(X, nan=0.0)',
    '_tr_fill_nan_with_mean': 'np.where(np.isnan(X), np.nanmean(X), X)',
    '_tr_fill_nan_with_median': 'np.where(np.isnan(X), np.nanmedian(X), X)',
    '_tr_fill_nan_with_max': 'np.where(np.isnan(X), np.nanmax(X), X)',
    '_tr_fill_nan_with_min': 'np.where(np.isnan(X), np.nanmin(X), X)',
    '_tr_fill_inf_with_zero': 'np.where(np.isinf(X), 0.0, X)',
    '_tr_fill_inf_with_mean': 'np.where(np.isinf(X), np.nanmean(X), X)',
    '_tr_fill_inf_with_median': 'np.where(np.isinf(X), np.nanmedian(X), X)',
    '_tr_fill_inf_with_max': 'np.where(np.isinf(X), np.nanmax(X), X)',
    '_tr_fill_inf_with_min': 'np.where(np.isinf(X), np.nanmin(X), X)',
    '_tr_fill_zero_with_nan': 'np.where(X == 0, np.nan, X)',
    '_tr_fill_zero_with_mean': 'np.where(X == 0, np.nanmean(X), X)',
    '_tr_fill_zero_with_median': 'np.where(X == 0, np.nanmedian(X), X)',
    '_tr_fill_zero_with_max': 'np.where(X == 0, np.nanmax(X), X)',
    '_tr_fill_zero_with_min': 'np.where(X == 0, np.nanmin(X), X)',
    '_tr_fill_negative_with_zero': 'np.where(X < 0, 0, X)',
    '_tr_fill_negative_with_nan': 'np.where(X < 0, np.nan, X)',
    '_tr_fill_negative_with_mean': 'np.where(X < 0, np.nanmean(X), X)',
    '_tr_fill_negative_with_median': 'np.where(X < 0, np.nanmedian(X), X)',
    '_tr_fill_negative_with_max': 'np.where(X < 0, np.nanmax(X), X)',
    '_tr_fill_negative_with_min': 'np.where(X < 0, np.nanmin(X), X)',
    '_tr_fill_positive_with_zero': 'np.where(X > 0, 0, X)',
    '_tr_fill_positive_with_nan': 'np.where(X > 0, np.nan, X)',
    '_tr_fill_positive_with_mean': 'np.where(X > 0, np.nanmean(X), X)',
    '_tr_fill_positive_with_median': 'np.where(X > 0, np.nanmedian(X), X)',
    '_tr_fill_positive_with_max': 'np.where(X > 0, np.nanmax(X), X)',
    '_tr_fill_positive_with_min': 'np.where(X > 0, np.nanmin(X), X)',
    '_tr_threshold_10_with_max': 'np.where(X > 10, np.nanmax(X), X)',
    '_tr_threshold_10_with_min': 'np.where(X > 10, np.nanmin(X), X)',
    '_tr_threshold_10_with_mean': 'np.where(X > 10, np.nanmean(X), X)',
    '_tr_threshold_10_with_median': 'np.where(X > 10, np.nanmedian(X), X)',
    '_tr_threshold_minus_10_with_zero': 'np.where(X < -10, 0, X)',
    '_tr_threshold_minus_10_with_nan': 'np.where(X < -10, np.nan, X)',
    '_tr_threshold_minus_10_with_mean': 'np.where(X < -10, np.nanmean(X), X)',
    '_tr_threshold_minus_10_with_median': 'np.where(X < -10, np.nanmedian(X), X)',
    '_tr_clip_outliers_1_99': 'np.clip(X, np.percentile(X, 1), np.percentile(X, 99))',
    '_tr_clip_outliers_5_95': 'np.clip(X, np.percentile(X, 5), np.percentile(X, 95))',
    '_tr_clip_outliers_10_90': 'np.clip(X, np.percentile(X, 10), np.percentile(X, 90))',
    '_tr_clip_outliers_25_75': 'np.clip(X, np.percentile(X, 25), np.percentile(X, 75))',
    '_tr_boxcox_lambda_0.5': 'np.where(X > 0, (X ** 0.5 - 1) / 0.5, np.nan)',
    '_tr_yeo_johnson_lambda_0': 'np.sign(X) * np.log(np.abs(X) + 1)',
    '_tr_relu': 'np.maximum(0, X)',
    '_tr_leaky_relu': 'np.where(X > 0, X, 0.01 * X)',
    '_tr_elu': 'np.where(X > 0, X, np.exp(X) - 1)',
    '_tr_softsign': 'X / (1 + np.abs(X))',
    '_tr_swish': 'X / (1 + np.exp(-X))',
    '_tr_gelu_approx': '0.5 * X * (1 + np.tanh(np.sqrt(2 / np.pi) * (X + 0.044715 * X ** 3)))',
    '_tr_mish': 'X * np.tanh(np.log1p(np.exp(X)))',
    '_tr_hard_sigmoid': 'np.clip((X + 1) / 2, 0, 1)',
    '_tr_hard_tanh': 'np.clip(X, -1, 1)',
    '_tr_swish_flick': 'X / (1 + np.exp(X))',
    '_tr_reciprocal_softplus': '1 / np.log1p(np.exp(X))',
    '_tr_bell_shape': 'np.exp(-X ** 2)',
    '_tr_gumbel_smx': 'np.exp(-np.exp(-(X - 3.0) / 4.0))',
    '_tr_gumbel_smx1': 'np.exp(-np.exp(-(X - 0.5) / 2.0))',
    '_tr_gumbel_smx2': 'np.exp(-np.exp(-(X - 2.0) / 1.2))',
    '_tr_fancy_beta': 'np.sqrt(2 * np.pi / X) * (X / np.e) ** X',
    '_tr_energy_distance': 'np.sqrt(2 * np.var(X))',
}


EXTENDED_ROUNDED_TRANSFORMERS = {}
for k, v in EXTENDED_TRANSFORMERS.items():
    for round_param in [1, 2, 3, 4]:
        new_key = k + f'_round{round_param}'
        new_value = f'np.round(np.astype(np.array(' + v + f'), np.float32), {round_param})'
        EXTENDED_ROUNDED_TRANSFORMERS[new_key] = new_value


if __name__ == '__main__':
    import numpy as np

    # generate some input (call it X)
    X = np.random.random(100)

    # get some transformer
    some_transformer = DEFAULT_TRANSFORMERS.get('_tr_nonzero')

    if some_transformer is None:
        some_transformer = ''

    # evaluate to get output
    output = eval(some_transformer)

    # check output somehow
    print(output)
