#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright 2019 Tomoki Hayashi
#  MIT License (https://opensource.org/licenses/MIT)

"""Calculate statistics of feature files."""

import argparse
import logging
import os

import numpy as np
import yaml
from sklearn.preprocessing import StandardScaler
from tqdm import tqdm

from zerovox.parallel_wavegan.datasets import MelDataset, MelSCPDataset
from zerovox.parallel_wavegan.utils import read_hdf5, write_hdf5


def main():
    """Run preprocessing process."""
    parser = argparse.ArgumentParser(
        description=(
            "Compute mean and variance of dumped raw features "
            "(See detail in parallel_wavegan/bin/compute_statistics.py)."
        )
    )
    parser.add_argument(
        "--feats-scp",
        "--scp",
        default=None,
        type=str,
        help=(
            "kaldi-style feats.scp file. "
            "you need to specify either feats-scp or rootdir."
        ),
    )
    parser.add_argument(
        "--rootdir",
        type=str,
        required=True,
        help=(
            "directory including feature files. "
            "you need to specify either feats-scp or rootdir."
        ),
    )
    parser.add_argument(
        "--config",
        type=str,
        required=True,
        help="yaml format configuration file.",
    )
    parser.add_argument(
        "--dumpdir",
        default=None,
        type=str,
        required=True,
        help=(
            "directory to save statistics. if not provided, "
            "stats will be saved in the above root directory."
        ),
    )
    parser.add_argument(
        "--target-feats",
        type=str,
        default="feats",
        choices=["feats", "local"],
        help="target name to compute statistics.",
    )
    parser.add_argument(
        "--utt2spk",
        default=None,
        type=str,
        help=(
            "kaldi-style spk2utt file. if given, calculate statistics of each speaker."
        ),
    )
    parser.add_argument(
        "--verbose",
        type=int,
        default=1,
        help="logging level. higher is more logging.",
    )
    args = parser.parse_args()

    # set logger
    if args.verbose > 1:
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
        )
    elif args.verbose > 0:
        logging.basicConfig(
            level=logging.INFO,
            format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
        )
    else:
        logging.basicConfig(
            level=logging.WARN,
            format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
        )
        logging.warning("Skip DEBUG/INFO messages")

    # load config
    with open(args.config) as f:
        config = yaml.load(f, Loader=yaml.Loader)
    config.update(vars(args))

    # check arguments
    if (args.feats_scp is not None and args.rootdir is not None) or (
        args.feats_scp is None and args.rootdir is None
    ):
        raise ValueError("Please specify either --rootdir or --feats-scp.")

    # check directory existence
    if not os.path.exists(args.dumpdir):
        os.makedirs(args.dumpdir)

    # get dataset
    if args.feats_scp is None:
        if config["format"] == "hdf5":
            mel_query = "*.h5"
            mel_load_fn = lambda x: read_hdf5(x, args.target_feats)  # NOQA
        elif config["format"] == "npy":
            mel_query = f"*-{args.target_feats}.npy"
            mel_load_fn = np.load
        else:
            raise ValueError("support only hdf5 or npy format.")
        dataset = MelDataset(
            args.rootdir,
            mel_query=mel_query,
            mel_load_fn=mel_load_fn,
            return_utt_id=False if args.utt2spk is None else True,
        )
    else:
        if args.target_feats != "feats":
            raise NotImplementedError("Not supported.")
        dataset = MelSCPDataset(
            args.feats_scp,
            return_utt_id=False if args.utt2spk is None else True,
        )
    logging.info(f"The number of files = {len(dataset)}.")

    if args.utt2spk is None:
        # calculate global statistics
        logging.info("Caluculate global statistics.")
        scaler = StandardScaler()
        for mel in tqdm(dataset):
            scaler.partial_fit(mel)

        if config["format"] == "hdf5":
            write_hdf5(
                os.path.join(args.dumpdir, "stats.h5"),
                "mean",
                scaler.mean_.astype(np.float32),
            )
            write_hdf5(
                os.path.join(args.dumpdir, "stats.h5"),
                "scale",
                scaler.scale_.astype(np.float32),
            )
        else:
            stats = np.stack([scaler.mean_, scaler.scale_], axis=0)
            np.save(
                os.path.join(args.dumpdir, "stats.npy"),
                stats.astype(np.float32),
                allow_pickle=False,
            )
    else:
        # calculate statistics of each speaker
        logging.info("Caluculate each speaker statistics.")
        with open(args.utt2spk) as f:
            lines = [line.replace("\n", "") for line in f.readlines()]
        utt2spk = {line.split()[0]: line.split()[1] for line in lines}
        spks = list(set(utt2spk.values()))
        spk2scaler = {spk: StandardScaler() for spk in spks}
        for utt_id, mel in tqdm(dataset):
            spk = utt2spk[utt_id]
            spk2scaler[spk].partial_fit(mel)

        for spk, scaler in spk2scaler.items():
            if config["format"] == "hdf5":
                write_hdf5(
                    os.path.join(args.dumpdir, "stats.h5"),
                    f"{spk}/mean",
                    scaler.mean_.astype(np.float32),
                )
                write_hdf5(
                    os.path.join(args.dumpdir, "stats.h5"),
                    f"{spk}/scale",
                    scaler.scale_.astype(np.float32),
                )
            else:
                stats = np.stack([scaler.mean_, scaler.scale_], axis=0)
                np.save(
                    os.path.join(args.dumpdir, f"stats-{spk}.npy"),
                    stats.astype(np.float32),
                    allow_pickle=False,
                )


if __name__ == "__main__":
    main()
