# The MIT License (MIT)
#
# Copyright (c) 2019 Mirco Ravanelli
# Copyright (c) 2019-2020 CNRS
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# AUTHOR
# Hervé Bredin - http://herve.niderb.fr

# Part of this code was taken from https://github.com/mravanelli/SincNet
# (see above license terms).

# Please give proper credit to the authors if you are using SincNet-based
# models  by citing their paper:

# Mirco Ravanelli, Yoshua Bengio.
# "Speaker Recognition from raw waveform with SincNet".
# SLT 2018. https://arxiv.org/abs/1808.00158

from typing import List, Tuple, Optional
import numpy as np
import torch
import torch.nn.functional as F
import torch.nn as nn
import math
from pyannote.core import SlidingWindow
from pyannote.audio.train.task import Task

import copy

class SincConv1d(nn.Module):
    """Sinc-based 1D convolution

    Parameters
    ----------
    in_channels : `int`
        Should be 1.
    out_channels : `int`
        Number of filters.
    kernel_size : `int`
        Filter length.
    stride : `int`, optional
        Defaults to 1.
    sample_rate : `int`, optional
        Sample rate. Defaults to 16000.
    min_low_hz: `int`, optional
        Defaults to 50.
    min_band_hz: `int`, optional
        Defaults to 50.

    Usage
    -----
    Same as `torch.nn.Conv1d`

    Reference
    ---------
    Mirco Ravanelli, Yoshua Bengio. "Speaker Recognition from raw waveform with
    SincNet". SLT 2018. https://arxiv.org/abs/1808.00158
    """

    @staticmethod
    def to_mel(hz):
        return 2595 * np.log10(1 + hz / 700)

    @staticmethod
    def to_hz(mel):
        return 700 * (10 ** (mel / 2595) - 1)

    def __init__(
        self,
        in_channels,
        out_channels,
        kernel_size,
        sample_rate=16000,
        min_low_hz=50,
        min_band_hz=50,
        stride=1,
        padding=0,
        dilation=1,
        bias=False,
        groups=1,
    ):

        super().__init__()

        if in_channels != 1:
            msg = (
                f"SincConv1d only supports one input channel. "
                f"Here, in_channels = {in_channels}."
            )
            raise ValueError(msg)
        self.in_channels = in_channels

        self.out_channels = out_channels

        if kernel_size % 2 == 0:
            msg = (
                f"SincConv1d only support odd kernel size. "
                f"Here, kernel_size = {kernel_size}."
            )
            raise ValueError(msg)
        self.kernel_size = kernel_size

        self.stride = stride
        self.padding = padding
        self.dilation = dilation

        if bias:
            raise ValueError("SincConv1d does not support bias.")
        if groups > 1:
            raise ValueError("SincConv1d does not support groups.")

        self.sample_rate = sample_rate
        self.min_low_hz = float(min_low_hz)
        self.min_band_hz = min_band_hz

        # initialize filterbanks such that they are equally spaced in Mel scale
        low_hz = 30
        high_hz = self.sample_rate / 2 - (self.min_low_hz + self.min_band_hz)

        mel = np.linspace(
            self.to_mel(low_hz), self.to_mel(high_hz), self.out_channels + 1
        )
        hz = self.to_hz(mel)

        # filter lower frequency (out_channels, 1)
        self.low_hz_ = nn.Parameter(torch.Tensor(hz[:-1]).view(-1, 1))

        # filter frequency band (out_channels, 1)
        self.band_hz_ = nn.Parameter(torch.Tensor(np.diff(hz)).view(-1, 1))

        # Half Hamming half window
        n_lin_1 = torch.linspace(
            0, self.kernel_size / 2 - 1, steps=int((self.kernel_size / 2))
        )
        n_lin_2 = torch.linspace(
            self.kernel_size / 2 - 1, 0, steps=int((self.kernel_size / 2))
        )
        self.window_1 = 0.54 - 0.46 * torch.cos(2 * math.pi * n_lin_1 / self.kernel_size)
        self.window_2 = 0.54 - 0.46 * torch.cos(2 * math.pi * n_lin_2 / self.kernel_size)

        # (kernel_size, 1)
        # Due to symmetry, I only need half of the time axes
        n = (self.kernel_size - 1) / 2.0
        self.n_1 = 2 * math.pi * torch.arange(-n, 0).view(1, -1) / self.sample_rate
        self.n_2 = 2 * math.pi * torch.arange(-1, -n-1, -1).view(1, -1) / self.sample_rate


    def forward(self, waveforms):
        """Get sinc filters activations

        Parameters
        ----------
        waveforms : `torch.Tensor` (batch_size, 1, n_samples)
            Batch of waveforms.

        Returns
        -------
        features : `torch.Tensor` (batch_size, out_channels, n_samples_out)
            Batch of sinc filters activations.
        """

        n_1 = self.n_1.to(waveforms.device)
        n_2 = self.n_2.to(waveforms.device)
        window_1 = self.window_1.to(waveforms.device)
        window_2 = self.window_2.to(waveforms.device)

        low = self.min_low_hz + torch.abs(self.low_hz_)

        high = torch.clamp(
            low + self.min_band_hz + torch.abs(self.band_hz_),
            self.min_low_hz,
            self.sample_rate / 2,
        )
        band = (high - low)[:, 0]

        f_times_t_low_1 = torch.matmul(low, n_1)
        f_times_t_high_1 = torch.matmul(high, n_1)

        f_times_t_low_2 = torch.matmul(low, n_2)
        f_times_t_high_2 = torch.matmul(high, n_2)

        # Equivalent to Eq.4 of the reference paper
        # I just have expanded the sinc and simplified the terms.
        # This way I avoid several useless computations.
        band_pass_left = (
            (torch.sin(f_times_t_high_1) - torch.sin(f_times_t_low_1)) / (n_1 / 2)
        ) * window_1
        band_pass_center = 2 * band.view(-1, 1)
        band_pass_right = (
            (torch.sin(f_times_t_high_2) - torch.sin(f_times_t_low_2)) / (n_2 / 2)
        ) * window_2

        band_pass = torch.cat(
            [band_pass_left, band_pass_center, band_pass_right], dim=1
        )

        band_pass = band_pass / (2 * band[:, None])

        filters = (band_pass).view(self.out_channels, 1, self.kernel_size)

        return F.conv1d(
            waveforms,
            filters,
            stride=self.stride,
            padding=self.padding,
            dilation=self.dilation,
            bias=None,
            groups=1,
        )


class SincNet(nn.Module):
    """SincNet (learnable) feature extraction

    Parameters
    ----------
    waveform_normalize : `bool`, optional
        Standardize waveforms (to zero mean and unit standard deviation) and
        apply (learnable) affine transform. Defaults to True.
    instance_normalize : `bool`, optional
        Standardize internal representation (to zero mean and unit standard
        deviation) and apply (learnable) affine transform. Defaults to True.


    Reference
    ---------
    Mirco Ravanelli, Yoshua Bengio. "Speaker Recognition from raw waveform with
    SincNet". SLT 2018. https://arxiv.org/abs/1808.00158

    """

    @staticmethod
    def get_alignment(task: Task, **kwargs):
        """Get frame alignment"""
        return "strict"

    @staticmethod
    def get_resolution(
        task: Task,
        sample_rate: int = 16000,
        kernel_size: List[int] = [251, 5, 5],
        stride: List[int] = [1, 1, 1],
        max_pool: List[int] = [3, 3, 3],
        **kwargs,
    ) -> SlidingWindow:
        """Get frame resolution

        Parameters
        ----------
        task : Task
        sample_rate : int, optional
        kerne_size : list of int, optional
        stride : list of int, optional
        max_pool : list of int, optional

        Returns
        -------
        resolution : SlidingWindow
            Frame resolution.
        """

        # https://medium.com/mlreview/a-guide-to-receptive-field-arithmetic-for-convolutional-neural-networks-e0f514068807
        padding = 0
        receptive_field, jump, start = 1, 1, 0.5
        for ks, s, mp in zip(kernel_size, stride, max_pool):
            # increase due to (Sinc)Conv1d
            receptive_field += (ks - 1) * jump
            start += ((ks - 1) / 2 - padding) * jump
            jump *= s
            # increase in receptive field due to MaxPool1d
            receptive_field += (mp - 1) * jump
            start += ((mp - 1) / 2 - padding) * jump
            jump *= mp

        return SlidingWindow(
            duration=receptive_field / sample_rate, step=jump / sample_rate, start=0.0
        )

    def __init__(
        self,
        waveform_normalize=True,
        sample_rate=16000,
        min_low_hz=50,
        min_band_hz=50,
        out_channels=[80, 60, 60],
        kernel_size: List[int] = [251, 5, 5],
        stride=[1, 1, 1],
        max_pool=[3, 3, 3],
        instance_normalize=True,
        activation="leaky_relu",
        dropout=0.0,
        history_length=5.0,
        use_calculated_stats=True,
    ):
        super().__init__()

        # check parameters values
        n_layers = len(out_channels)
        if len(kernel_size) != n_layers:
            msg = (
                f"out_channels ({len(out_channels):d}) and kernel_size "
                f"({len(kernel_size):d}) should have the same length."
            )
            raise ValueError(msg)
        if len(stride) != n_layers:
            msg = (
                f"out_channels ({len(out_channels):d}) and stride "
                f"({len(stride):d}) should have the same length."
            )
            raise ValueError(msg)
        if len(max_pool) != n_layers:
            msg = (
                f"out_channels ({len(out_channels):d}) and max_pool "
                f"({len(max_pool):d}) should have the same length."
            )
            raise ValueError(msg)

        # Waveform normalization
        self.waveform_normalize = waveform_normalize
        if self.waveform_normalize:
            self.waveform_normalize_ = torch.nn.InstanceNorm1d(1, affine=True)

        # SincNet-specific parameters
        self.sample_rate = sample_rate
        self.min_low_hz = min_low_hz
        self.min_band_hz = min_band_hz

        # Conv1D parameters
        self.out_channels = out_channels
        self.kernel_size = kernel_size
        self.stride = stride
        self.conv1d_ = nn.ModuleList([])

        # Max-pooling parameters
        self.max_pool = max_pool
        self.max_pool1d_ = nn.ModuleList([])

        # Instance normalization
        self.instance_normalize = instance_normalize
        # if self.instance_normalize:
        #     self.instance_norm1d_ = nn.ModuleList([])
        self.instance_norm1d_ = nn.ModuleList([])

        config = zip(self.out_channels, self.kernel_size, self.stride, self.max_pool)

        in_channels = None
        self.conv_layers = 0
        paddings = []
        strides = []
        pools = []
        self.history_counts = [torch.zeros((1,), dtype=torch.int64)]
        self.history_sums = [torch.zeros((1,), dtype=torch.float64)]
        self.history_ssums = [torch.zeros((1,), dtype=torch.float64)]
        for i, (out_channels, kernel_size, stride, max_pool) in enumerate(config):

            # 1D convolution
            if i > 0:
                conv1d = nn.Conv1d(
                    in_channels,
                    out_channels,
                    kernel_size,
                    stride=stride,
                    padding=0,
                    dilation=1,
                    groups=1,
                    bias=True,
                )
            else:
                conv1d = SincConv1d(
                    1,
                    out_channels,
                    kernel_size,
                    sample_rate=self.sample_rate,
                    min_low_hz=self.min_low_hz,
                    min_band_hz=self.min_band_hz,
                    stride=stride,
                    padding=0,
                    dilation=1,
                    bias=False,
                    groups=1,
                )
            self.conv1d_.append(conv1d)

            # 1D max-pooling
            max_pool1d = nn.MaxPool1d(max_pool, stride=max_pool, padding=0, dilation=1)
            self.max_pool1d_.append(max_pool1d)

            # 1D instance normalization
            if self.instance_normalize:
                instance_norm1d = nn.InstanceNorm1d(out_channels, affine=True)
                self.instance_norm1d_.append(instance_norm1d)

            in_channels = out_channels

            paddings.append((kernel_size - 1) // 2)
            strides.append(stride)
            pools.append(max_pool)
            self.history_counts.append(torch.zeros((1,), dtype=torch.int64))
            self.history_sums.append(torch.zeros(out_channels, dtype=torch.float64))
            self.history_ssums.append(torch.zeros(out_channels, dtype=torch.float64))

            self.conv_layers += 1
        # print(list(zip(self.paddings, self.strides, self.pools)))

        self.paddings = []
        self.strides  = []
        # self.history_counts = []
        _pd, _st = 0, 1
        for pd, st, po in list(zip(paddings, strides, pools))[::-1]:
            _pd *= po
            _pd += pd
            _st *= st * po
            # print((_pd, _st))
            self.paddings.append(_pd)
            self.strides.append(_st)
        self.paddings = self.paddings[::-1]
        self.strides = self.strides[::-1]
        # print(list(zip(self.paddings, self.strides)))
        # print(self.history_window_count_)

        # Activation function
        self.activation = activation
        if self.activation == "leaky_relu":
            self.activation_ = nn.LeakyReLU(negative_slope=0.2)
        else:
            msg = f'Only "leaky_relu" activation is supported.'
            raise ValueError(msg)

        # Dropout
        self.dropout = dropout
        if self.dropout:
            self.dropout_ = nn.Dropout(p=self.dropout)

        self.use_calculated_stats = use_calculated_stats
        self.set_history_length(history_length)
        self.reset()

        self.saved_status = (torch.zeros((0,)), [torch.zeros((0,))], 0, [0], 0, [0], [torch.zeros((0,))], [torch.zeros((0,))], [torch.zeros((0,))])

    def set_history_length(self, history_length):
        self.history_length_ = history_length
        self.history_window_count_ = int(math.ceil(history_length * self.sample_rate / self.strides[0]))

    def push_status(self):
        self.saved_status = (
            self.waveform_history_buffer_.clone(),
            [v.clone() for v in self.instance_history_buffers_],
            self.waveform_history_offset_,
            [v for v in self.instance_history_offsets_],
            self.waveform_comsumed_count_,
            [v for v in self.instance_comsumed_counts_],
            [v.clone() for v in self.history_counts],
            [v.clone() for v in self.history_sums],
            [v.clone() for v in self.history_ssums],
        )

    def pop_status(self):
        self.waveform_history_buffer_  = self.saved_status[0]
        self.instance_history_buffers_ = self.saved_status[1]
        self.waveform_history_offset_  = self.saved_status[2]
        self.instance_history_offsets_ = self.saved_status[3]
        self.waveform_comsumed_count_  = self.saved_status[4]
        self.instance_comsumed_counts_ = self.saved_status[5]
        self.history_counts = self.saved_status[6]
        self.history_sums   = self.saved_status[7]
        self.history_ssums  = self.saved_status[8]

    # def reset(self, means:Optional[Tuple[torch.Tensor]]=None, sigmas:Optional[Tuple[torch.Tensor]]=None, counts:Optional[Tuple[torch.Tensor]]=None):
    def reset(self, means:Optional[List[torch.Tensor]]=None, sigmas:Optional[List[torch.Tensor]]=None, counts:Optional[List[torch.Tensor]]=None):
    # def reset(self, means:Optional[Tuple]=None, sigmas:Optional[Tuple]=None, counts:Optional[Tuple]=None):
        self.waveform_history_buffer_ = torch.zeros((0,))
        self.instance_history_buffers_ = [torch.zeros((0,))] * self.conv_layers
        self.waveform_history_offset_ = 0
        self.instance_history_offsets_ = [0] * self.conv_layers
        self.waveform_comsumed_count_ = 0
        self.instance_comsumed_counts_ = [0] * self.conv_layers

        if means is None and sigmas is None and counts is None: # len(means) == 0 and len(sigmas) == 0 and len(counts) == 0:
        # if len(means) == 0 and len(sigmas) == 0 and len(counts) == 0:
            self.history_counts = [torch.zeros_like(z) for z in self.history_counts]
            self.history_sums = [torch.zeros_like(z) for z in self.history_sums]
            self.history_ssums = [torch.zeros_like(z) for z in self.history_ssums]
        else:
            assert means is not None
            assert sigmas is not None
            assert counts is not None
            cs = []
            ss = []
            sss = []
            for i, (m, s, c) in enumerate(zip(means, sigmas, counts)):
                hc = self.history_counts[i]
                hs = self.history_sums[i]
                c = c.expand(hc.shape)
                s = s.expand(hs.shape)
                m = m.expand(hs.shape)
                cs.append(c)
                ss.append(m * c)
                sss.append((torch.pow(s, 2) + torch.pow(m, 2)) * c)
            self.history_counts = cs
            self.history_sums = ss
            self.history_ssums = sss
        # print('means: {}'.format(means))
        # print('sigmas: {}'.format(sigmas))
        # print('counts: {}'.format(counts))
        # print('counts: {}'.format(self.history_counts))
        # print('sums: {}'.format(self.history_sums))
        # print('ssums: {}'.format(self.history_ssums))

    def dump_stats(self):
        means =  [s / c for (s, c) in zip(self.history_sums, self.history_counts)]
        vars =   [ss / c - torch.pow(m, 2) for ss, c, m in zip(self.history_ssums, self.history_counts,means)]
        sigmas = [torch.sqrt(v + e) for v, e in zip(vars, [self.waveform_normalize_.eps] + [i.eps for i in self.instance_norm1d_])]

        return {
            'counts': self.history_counts,
            'means': means,
            'sigmas': sigmas,
        }

    def forward(self, waveforms):
        """Extract SincNet features

        Parameters
        ----------
        waveforms : (batch_size, n_samples, 1)
            Batch of waveforms

        Returns
        -------
        features : (batch_size, n_frames, out_channels[-1])
        """

        output = waveforms.to(torch.float32).transpose(1, 2) # shape = (batch_size, 1, wav_sample_count)

        # WAVデータを蓄積
        if self.waveform_history_buffer_.numel() == 0:
            self.waveform_history_buffer_ = torch.zeros((output.shape[0], 1, 0))
        self.waveform_history_buffer_ = torch.cat((self.waveform_history_buffer_, output), dim=2)

        # n = output.numel()
        # s = output.sum()
        # ss = torch.pow(output, 2).sum()
        # print('n={}, s={}, ss={}'.format(n, s, ss))
        self.history_counts[0] += output.numel()
        self.history_sums[0] += output.sum()
        self.history_ssums[0] += torch.pow(output, 2).sum()

        # conv 層にピッタリ収まる window をいくつ取れる？
        window_count = \
            max(0, (self.waveform_history_offset_ + self.waveform_history_buffer_.shape[2]) - 2 * self.paddings[0]) // self.strides[0] - \
            self.waveform_comsumed_count_ // self.strides[0]
        if window_count < 1:
            return torch.zeros((output.shape[0], 0, self.out_channels[-1]), dtype=torch.float32)

        # 次段に回すサンプルの取得
        offset = self.waveform_comsumed_count_ - self.waveform_history_offset_
        length = window_count * self.strides[0] + 2 * self.paddings[0]

        # standardize waveforms
        if self.waveform_normalize:
            if self.use_calculated_stats:
                m = self.history_sums[0] / self.history_counts[0]
                v = self.history_ssums[0] / self.history_counts[0] - torch.pow(m, 2)
                s = torch.sqrt(v + self.waveform_normalize_.eps)
                w = self.waveform_normalize_.weight
                b = self.waveform_normalize_.bias
                x = self.waveform_history_buffer_[:, :, offset:(offset+length)]
                m = m.to(torch.float32)
                s = s.to(torch.float32)
                # # print(x.shape)
                # print(self.history_counts[0], self.history_sums[0], self.history_ssums[0])
                # print(m.shape, m)
                # print((self.history_ssums[0] / self.history_counts[0]).shape, (self.history_ssums[0] / self.history_counts[0]))
                # print(m.shape, torch.pow(m, 2))
                # print(v.shape, v)
                # print(s.shape, s)
                # # print(w.shape, w)
                # # print(b.shape, b)
                output = (w * (x - m) / s + b)
            else:
                output = self.waveform_normalize_(self.waveform_history_buffer_)[:, :, offset:(offset+length)]
        else:
            output = self.waveform_history_buffer_[:, :, offset:(offset+length)]

        # waveform_buffer の更新
        to_discard = max(0, self.waveform_history_offset_ + window_count * self.strides[0] - self.history_window_count_ * self.strides[0]) // self.strides[0] * self.strides[0]
        self.waveform_comsumed_count_ += window_count * self.strides[0]
        self.waveform_history_offset_ += to_discard
        self.waveform_history_buffer_ = self.waveform_history_buffer_[:, :, to_discard:]
        # print('buffer: shape={}'.format(self.waveform_history_buffer_.shape))

        # print('output: shape={}, window_count={}, offset={}, length={}'.format(output.shape, window_count, offset, length))
        layers = zip(self.conv1d_, self.max_pool1d_, self.instance_norm1d_)
        for i, (conv1d, max_pool1d, instance_norm1d) in enumerate(layers):

            # print('op[{}]-1: {}, {}'.format(i, output.shape, output[:, :, :10]))
            # print('op[{}]-1: {}'.format(i, output.shape))
            output = conv1d(output)
            if i == 0:
                output = torch.abs(output)

            # print('op[{}]-2: {}, {}'.format(i, output.shape, output[:, :, :10]))
            output = max_pool1d(output)
            # print('op[{}]-3: {}, {}'.format(i, output.shape, output[:, :, :10]))

            # print('op[{}]-2: {}'.format(i, output.shape))
            if self.use_calculated_stats:
                self.history_counts[i + 1] += output.numel() // output.shape[1]
                self.history_sums[i + 1] += output.sum(dim=(0, 2))
                self.history_ssums[i + 1] += torch.pow(output, 2).sum(dim=(0, 2))
            else:
                if self.instance_history_buffers_[i].numel() == 0:
                    self.instance_history_buffers_[i] = torch.clone(output)
                else:
                    self.instance_history_buffers_[i] = torch.cat((self.instance_history_buffers_[i], output), dim=2)
                self.instance_history_buffers_[i] = self.instance_history_buffers_[i][:, :, -max(output.shape[2], self.history_window_count_ * self.strides[i] + 2 * self.paddings[i]):]
            # print(output.shape)

            # print('op[{}]-3: output={}, buffer={}, offset={}, length={}'.format(i, output.shape, self.instance_history_buffers_[i].shape, offset, length))
            if self.instance_normalize:
                if self.use_calculated_stats:
                    m = self.history_sums[i + 1] / self.history_counts[i + 1]
                    v = self.history_ssums[i + 1] / self.history_counts[i + 1] - torch.pow(m, 2)
                    s = torch.sqrt(v + instance_norm1d.eps)
                    w = instance_norm1d.weight
                    b = instance_norm1d.bias
                    x = output
                    m = m.to(torch.float32)
                    s = s.to(torch.float32)
                    # # print(x.shape)
                    # print(m.shape, m)
                    # print(s.shape, s)
                    # # print(w.shape, w)
                    # # print(b.shape, b)
                    output = (w.unsqueeze(-1) * (x - m.unsqueeze(-1)) / s.unsqueeze(-1) + b.unsqueeze(-1))
                else:
                    # print('instance-normalize: {}: {}'.format(i, self.instance_history_buffers_[i].shape))
                    # print(instance_norm1d)
                    xx = self.instance_history_buffers_[i]
                    # print(xx.shape)
                    # print(xx.mean().shape)
                    # print(xx.std().shape)
                    # print(instance_norm1d.gamma)
                    # print(instance_norm1d.beta)
                    # print(instance_norm1d.bias.shape)
                    # print(instance_norm1d.weight.unsqueeze(1).shape)
                    # print(instance_norm1d.bias.unsqueeze(1).shape)
                    # print('mean: {}'.format(xx.mean(dim=2)))
                    # print('std: {}'.format(xx.std(dim=2)))
                    # print('mean-c: {}'.format(self.history_sums[i + 1] / self.history_counts[i + 1]))
                    # print('std-c: {}'.format(torch.sqrt(self.history_ssums[i + 1] / self.history_counts[i + 1] - torch.pow(self.history_sums[i + 1] / self.history_counts[i + 1], 2))))
                    yy = (instance_norm1d.weight.unsqueeze(1) * (xx - xx.mean(dim=2).unsqueeze(2)) / torch.sqrt(xx.var(dim=2, unbiased=False).unsqueeze(2) + instance_norm1d.eps) + instance_norm1d.bias.unsqueeze(1))[:, :, -(output.shape[2]):]
                    output = instance_norm1d(self.instance_history_buffers_[i])[:, :, -(output.shape[2]):]
                    # print(torch.stack([yy, output]))
            else:
                output = self.instance_history_buffers_[i][:, :, -(output.shape[2]):]

            # print('op[{}]-4: {}, {}, {}'.format(i, output.shape, output.mean(), output.std()))
            output = self.activation_(output)
            # print('op[{}]-5: {}, {}'.format(i, output.shape, output[:, :, :10]))

            # if i + 1 < self.conv_layers:
            #     # instance_buffer の更新
            #     to_discard = max(0, self.instance_history_offsets_[i] + window_count * self.strides[i + 1] - self.instance_comsumed_counts_[i] * self.strides[i + 1]) // self.strides[i + 1] * self.strides[i + 1]
            #     self.instance_comsumed_counts_[i] += window_count * self.strides[i + 1]
            #     self.instance_history_offsets_[i] += to_discard
            #     self.instance_history_buffers_[i] = self.instance_history_buffers_[i][:, :, to_discard:]

            # print('buffer: shape={}'.format(self.instance_history_buffers_[i].shape))

        return output.transpose(1, 2)

    def dimension():
        doc = "Output features dimension."

        def fget(self):
            return self.out_channels[-1]

        return locals()

    dimension = property(**dimension())
