# -*- coding: utf-8 -*- #
"""*********************************************************************************************"""
#   FileName     [ upstream/log_stft/expert.py ]
#   Synopsis     [ the wrapper for STFT magnitude ]
#   Author       [ Zili Huang ]
"""*********************************************************************************************"""


import math

###############
# IMPORTATION #
###############
import os
import random

# -------------#
import torch
import torch.nn as nn
import yaml
from torch.nn.utils.rnn import pad_sequence

# -------------#

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")


class Spectrogram(nn.Module):
    def __init__(self, cfg, **kwargs):
        super(Spectrogram, self).__init__()
        self.eps = 1e-8
        self.cfg = cfg
        self.n_fft = cfg["spectrogram"]["n_fft"]
        self.hop_length = cfg["spectrogram"]["hop_length"]
        self.win_length = cfg["spectrogram"]["win_length"]
        if cfg["spectrogram"]["window"] == "hann":
            self.window = torch.hann_window(cfg["spectrogram"]["win_length"]).to(device)
        else:
            raise ValueError("Window type not defined.")
        self.center = cfg["spectrogram"]["center"]
        self.log = cfg["spectrogram"]["log"]

    def get_output_dim(self):
        return self.n_fft // 2 + 1

    def get_downsample_rate(self):
        return self.hop_length

    def forward(self, waveform):
        # waveform: (time, )
        x = torch.transpose(
            torch.abs(
                torch.stft(
                    waveform,
                    n_fft=self.n_fft,
                    hop_length=self.hop_length,
                    win_length=self.win_length,
                    window=self.window,
                    center=self.center,
                    pad_mode="reflect",
                    normalized=False,
                    return_complex=True,
                )
            ),
            0,
            1,
        )
        if self.log:
            x = torch.log(torch.clamp(x, min=self.eps))
        # x: (feat_seqlen, feat_dim)
        return x


###################
# UPSTREAM EXPERT #
###################
class UpstreamExpert(nn.Module):
    """
    Extract spectrogram features from wavforms with torchaudio
    """

    def __init__(self, model_config=None, **kwargs):
        super(UpstreamExpert, self).__init__()

        with open(model_config, "r") as file:
            self.config = yaml.load(file, Loader=yaml.FullLoader)
        self.extracter = Spectrogram(self.config)
        self.output_dim = self.extracter.get_output_dim()
        self.downsample_rate = self.extracter.get_downsample_rate()

    def get_downsample_rates(self, key: str) -> int:
        return self.downsample_rate

    def _extractor_forward(self, wavs):
        feats = []
        for wav in wavs:
            feats.append(self.extracter(wav))
        return feats

    def forward(self, wavs):
        feats = self._extractor_forward(wavs)
        feats = pad_sequence(feats, batch_first=True)
        return {"last_hidden_state": [feats], "hidden_states": [feats]}
