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

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

import logging

import numpy as np
import pytest
import torch

from parallel_wavegan.layers import CausalConv1d
from parallel_wavegan.layers import CausalConvTranspose1d
from parallel_wavegan.layers import Conv1d
from parallel_wavegan.layers import Conv1d1x1
from parallel_wavegan.layers import Conv2d
from parallel_wavegan.layers import ConvInUpsampleNetwork
from parallel_wavegan.layers import PQMF
from parallel_wavegan.layers import UpsampleNetwork

logging.basicConfig(
    level=logging.WARN, format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s")


def test_conv_initialization():
    conv = Conv1d(10, 10, 3, bias=True)
    np.testing.assert_array_equal(conv.bias.data.numpy(),
                                  np.zeros_like(conv.bias.data.numpy()))
    conv1x1 = Conv1d1x1(10, 10, bias=True)
    np.testing.assert_array_equal(conv1x1.bias.data.numpy(),
                                  np.zeros_like(conv1x1.bias.data.numpy()))
    kernel_size = (10, 10)
    conv2d = Conv2d(10, 10, kernel_size, bias=True)
    np.testing.assert_array_equal(conv2d.weight.data.numpy(),
                                  np.ones_like(conv2d.weight.data.numpy()) / np.prod(kernel_size))
    np.testing.assert_array_equal(conv2d.bias.data.numpy(),
                                  np.zeros_like(conv2d.bias.data.numpy()))
    kernel_size = (1, 10)
    conv2d = Conv2d(10, 10, kernel_size, bias=True)
    np.testing.assert_array_equal(conv2d.weight.data.numpy(),
                                  np.ones_like(conv2d.weight.data.numpy()) / np.prod(kernel_size))
    np.testing.assert_array_equal(conv2d.bias.data.numpy(),
                                  np.zeros_like(conv2d.bias.data.numpy()))


@pytest.mark.parametrize(
    "use_causal_conv", [
        (False),
        (True),
    ])
def test_upsample(use_causal_conv):
    length = 10
    scales = [4, 4]
    x = torch.randn(1, 10, length)
    upsample = UpsampleNetwork(scales)
    y = upsample(x)
    assert x.size(-1) * np.prod(scales) == y.size(-1)

    for aux_context_window in [0, 1, 2, 3]:
        conv_upsample = ConvInUpsampleNetwork(scales,
                                              aux_channels=x.size(1),
                                              aux_context_window=aux_context_window,
                                              use_causal_conv=use_causal_conv)
        y = conv_upsample(x)
        assert (x.size(-1) - 2 * aux_context_window) * np.prod(scales) == y.size(-1)


@torch.no_grad()
@pytest.mark.parametrize(
    "kernel_size, dilation, pad, pad_params", [
        (3, 1, "ConstantPad1d", {"value": 0.0}),
        (3, 3, "ConstantPad1d", {"value": 0.0}),
        (2, 1, "ConstantPad1d", {"value": 0.0}),
        (2, 3, "ConstantPad1d", {"value": 0.0}),
        (5, 1, "ConstantPad1d", {"value": 0.0}),
        (5, 3, "ConstantPad1d", {"value": 0.0}),
        (3, 3, "ReflectionPad1d", {}),
        (2, 1, "ReflectionPad1d", {}),
        (2, 3, "ReflectionPad1d", {}),
        (5, 1, "ReflectionPad1d", {}),
        (5, 3, "ReflectionPad1d", {}),
    ])
def test_causal_conv(kernel_size, dilation, pad, pad_params):
    x = torch.randn(1, 1, 32)
    conv = CausalConv1d(1, 1, kernel_size, dilation,
                        pad=pad, pad_params=pad_params)
    y1 = conv(x)
    x[:, :, 16:] += torch.randn(1, 1, 16)
    y2 = conv(x)
    assert x.size(2) == y1.size(2)
    np.testing.assert_array_equal(
        y1[:, :, :16].cpu().numpy(),
        y2[:, :, :16].cpu().numpy(),
    )


@torch.no_grad()
@pytest.mark.parametrize(
    "kernel_size, stride", [
        (4, 2),
        (6, 3),
        (10, 5),
    ])
def test_causal_conv_transpose(kernel_size, stride):
    deconv = CausalConvTranspose1d(1, 1, kernel_size, stride)
    x = torch.randn(1, 1, 32)
    y1 = deconv(x)
    x[:, :, 19:] += torch.randn(1, 1, 32 - 19)
    y2 = deconv(x)
    assert x.size(2) * stride == y1.size(2)
    np.testing.assert_array_equal(
        y1[:, :, :19 * stride].cpu().numpy(),
        y2[:, :, :19 * stride].cpu().numpy(),
    )


@pytest.mark.parametrize(
    "subbands", [
        (3),
        (4),
    ])
def test_pqmf(subbands):
    pqmf = PQMF(subbands)
    x = torch.randn(1, 1, subbands * 32)
    y = pqmf.analysis(x)
    assert y.shape[2] * subbands == x.shape[2]
    x_hat = pqmf.synthesis(y)
    assert x.shape[2] == x_hat.shape[2]
