# measure
# - integrated loudness
# - peak
# - DC offset
# - crest factor

import os
import glob
import resampy
import argparse
import numpy as np
import pandas as pd
import soundfile as sf
import pyloudnorm as pyln
import matplotlib.pyplot as plt

from tqdm import tqdm
from typing import List


def get_stats(
    filepath: str, silence_threshold: float = 0.001, measure_true_peak: bool = False
):
    x, sample_rate = sf.read(filepath)

    # check duration is longer than 60 sec
    dur_sec = x.shape[0] / sample_rate
    if dur_sec < 60.0:
        print(f"Skipping {filepath} with duration = {dur_sec:0.2f} sec")
        return

    # check if energy is above threshold
    energy = np.mean(x**2)
    if energy < silence_threshold:
        print(f"Skipping {filepath} with energy = {energy:0.6f}")
        return

    meter = pyln.Meter(sample_rate)

    peak_lin = np.max(np.abs(x))
    peak_db = 20 * np.log10(peak_lin + 1e-8)
    lufs_db = meter.integrated_loudness(x)
    dc_offset = np.mean(x)
    rms = np.sqrt(np.mean(x**2))
    crest_factor = peak_lin / (rms + 1e-8)

    # for a fair comparision (loudness normalize to -16 dBLUFS and recompute)
    gain_lin = 10 ** ((-16.0 - lufs_db) / 20)
    x_norm = x * gain_lin
    peak_lin_norm = np.max(np.abs(x))
    rms_norm = np.sqrt(np.mean(x_norm**2))
    crest_factor_norm = peak_lin_norm / (rms_norm + 1e-8)

    # compute the compression factor by peak normalizing and then
    x_peak_norm = x / np.clip(peak_lin, a_min=1e-8, a_max=None)
    compression_factor = meter.integrated_loudness(x_peak_norm)

    # resample 4x to get true-peak (this is slow)
    if measure_true_peak:
        x_up = resampy.resample(x, sample_rate, int(4 * sample_rate), axis=0)
        true_peak_lin = np.max(np.abs(x_up))
        true_peak_db = 20 * np.log10(true_peak_lin + 1e-8)

    if peak_lin > 1.0:
        print(filepath, peak_lin)

    result = {
        "filename": os.path.basename(filepath),
        "peak_lin": peak_lin,
        "peak_db": peak_db,
        # "true_peak_lin" : true_peak_lin,
        # "true_peak_db" : true_peak_db,
        "lufs_db": lufs_db,
        "dc_offset": dc_offset,
        "rms": rms,
        "crest_factor": crest_factor,
        "rms_norm": rms_norm,
        "crest_factor_norm": crest_factor_norm,
        "compression_factor": compression_factor,
    }

    return result


def get_stats_from_directory(directory: str, output_dir: str = "outputs"):
    directory_name = os.path.basename(directory)
    print(f"Getting stats from audio files in {directory_name}...")
    # find all audio files
    filepaths = []
    for ext in ["flac", "wav", "mp3", "ogg"]:
        filepaths += glob.glob(os.path.join(directory, f"*.{ext}"))

    results = {}

    for filepath in tqdm(filepaths):
        result = get_stats(filepath)

        if result is not None:
            for key, val in result.items():
                if key not in results:
                    results[key] = []
                results[key].append(val)

    # create dataframe
    df = pd.DataFrame(results)

    # save to csv
    csv_filepath = os.path.join(output_dir, f"{directory_name}_stats.csv")
    df.to_csv(csv_filepath, index=False)


def plot_stats_from_csv(filepaths: List[str], output_dir: str = "outputs"):

    dfs = {}
    for filepath in filepaths:
        filename = os.path.basename(filepath).replace(".csv", "")
        short_name = filename.split("_")[0]
        df = pd.read_csv(filepath)
        print(df.head())
        dfs[short_name] = df

    # use the first df to get features
    skipped_features = ["filename", "crest_factor"]
    features = [
        col for col in dfs[list(dfs.keys())[0]].columns if col not in skipped_features
    ]

    fig, axs = plt.subplots(nrows=4, ncols=2, figsize=(8, 10))
    axs = np.reshape(axs, -1)

    feature_names = {
        "peak_lin": "Peak (linear)",
        "peak_db": "Peak (dB)",
        "lufs_db": "dB LUFS",
        "dc_offset": "DC Offset",
        "rms": "RMS",
        "rms_norm": "RMS (post-normalization)",
        "crest_factor_norm": "Crest Factor (post-normalization)",
        "compression_factor": "Compression Factor",
    }

    heights = [0.8, 0.7, 0.6, 0.5]

    for feature_idx, feature in enumerate(features):
        value_arrays = []
        df_names = []
        print(feature)

        for df_idx, (df_name, df) in enumerate(dfs.items()):
            values = np.array(df[feature].to_list())

            # Create a boolean mask where the value is not -inf
            mask = np.isfinite(values)

            # Apply the mask to get a new array without -inf values
            values = values[mask]

            mean_values = np.mean(values)
            std_values = np.std(values)
            median_values = np.median(values)
            max_values = np.max(values)
            min_values = np.min(values)

            value_arrays.append(values)
            df_names.append(df_name)

        bins = np.histogram(np.hstack(value_arrays), bins=64)[1]  # get the bin edges

        for df_idx, (df_name, value_array) in enumerate(zip(df_names, value_arrays)):

            stats_string = (
                f"[{np.min(value_array):0.2f}, {np.max(value_array):0.2f}]  {df_name} "
            )

            print(feature)
            _, bins, _ = axs[feature_idx].hist(
                value_array,
                bins=bins,
                rwidth=1.0,
                label=stats_string,
                alpha=0.5,
                density=True,
            )

            axs[feature_idx].set_xlabel(feature_names[feature])
            axs[feature_idx].set_ylabel("Density")
            # axs[feature_idx].set_yscale("log")

            # axs[feature_idx].text(
            #    0.15,
            #    heights[df_idx],
            #    stats_string,
            #    transform=axs[feature_idx].transAxes,
            # )
            # axs[feature_idx].set_position(
            #    [0.1, 0.1, 0.65, 0.8]
            # )  # left, bottom, width, height

            axs[feature_idx].legend()

    plt.tight_layout()
    plot_filepath = os.path.join(output_dir, f"stat_.png")
    plt.savefig(plot_filepath, dpi=300)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--output_dir", default="outputs")
    parser.add_argument(
        "--analyze",
        help="Path to directory containing audio files to analyze",
    )
    parser.add_argument(
        "--plot",
        nargs="+",
        help="List of paths to csv files containing statistics.",
    )
    parser.add_argument("--max_files", default=1000)
    args = parser.parse_args()

    if args.analyze:
        get_stats_from_directory(args.analyze, args.output_dir)
    if args.plot:
        plot_stats_from_csv(args.plot, args.output_dir)
