# coding: utf8
from __future__ import unicode_literals

import numpy

from ..api import layerize
from .ops import NumpyOps, CupyOps
from ._classes.model import Model


try:
    from cupy import get_array_module
except ImportError:
    get_array_module = lambda arr: numpy


def Pooling(*funcs, **kwargs):
    ops = kwargs["ops"] if "ops" in kwargs else Model.ops
    F = len(funcs)
    drop_factor = kwargs.get("drop_factor", 1.0)

    def begin_update(X_lengths, drop=0.0):
        if drop is not None:
            drop *= drop_factor
        X, lengths = X_lengths
        T, O = X.shape  # noqa: E741
        pooled = ops.allocate((len(lengths), F * O))
        bp_funcs = [None] * F
        for i, func in enumerate(funcs):
            res, bp_res = func.begin_update((X, lengths))
            pooled[:, i * O : i * O + O] = res
            bp_funcs[i] = bp_res
        pooled, bp_dropout = ops.dropout(pooled, drop)

        def finish_update(d_pooled, sgd=None):
            dX = ops.allocate(X.shape)
            for i, bp_func in enumerate(bp_funcs):
                dX += bp_func(d_pooled[:, i * O : i * O + O])
            return dX

        return pooled, bp_dropout(finish_update)

    return layerize(begin_update)


@layerize
def mean_pool(X_lengths, drop=0.0):
    X, lengths = X_lengths
    if isinstance(X, numpy.ndarray):
        ops = NumpyOps()
    else:
        ops = CupyOps()
    output = ops.mean_pool(X, lengths)

    def finish_update(d_output, sgd=None):
        d_output = ops.xp.ascontiguousarray(d_output)
        return ops.backprop_mean_pool(d_output, lengths)

    return output, finish_update


@layerize
def sum_pool(X_lengths, drop=0.0):
    X, lengths = X_lengths
    if isinstance(X, numpy.ndarray):
        ops = NumpyOps()
    else:
        ops = CupyOps()

    output = ops.sum_pool(X, lengths)

    def finish_update(d_output, sgd=None):
        d_output = ops.xp.ascontiguousarray(d_output)
        return ops.backprop_sum_pool(d_output, lengths)

    return output, finish_update


@layerize
def max_pool(X_lengths, drop=0.0):
    X, lengths = X_lengths
    ops = Model.ops

    best, which = ops.max_pool(X, lengths)

    def finish_update(d_output, sgd=None):
        d_output = ops.xp.ascontiguousarray(d_output)
        return ops.backprop_max_pool(d_output, which, lengths)

    return best, finish_update
