import paddle.v2 as paddle
from paddle.v2.layer import parse_network

__all__ = ["encoder_decoder_network"]


def _bidirect_lstm_encoder(input, hidden_dim, depth):
    lstm_last = []
    for dirt in ["fwd", "bwd"]:
        for i in range(depth):
            input_proj = paddle.layer.mixed(
                name="__in_proj_%0d_%s__" % (i, dirt),
                size=hidden_dim * 4,
                bias_attr=True,
                input=[
                    paddle.layer.full_matrix_projection(input_proj),
                    paddle.layer.full_matrix_projection(
                        lstm, param_attr=paddle.attr.Param(initial_std=5e-4)),
                ] if i else [paddle.layer.full_matrix_projection(input)])
            lstm = paddle.layer.lstmemory(
                input=input_proj,
                bias_attr=paddle.attr.Param(initial_std=0.),
                param_attr=paddle.attr.Param(initial_std=5e-4),
                reverse=i % 2 if dirt == "fwd" else not i % 2)
        lstm_last.append(lstm)
    return paddle.layer.concat(input=lstm_last)


def _attended_decoder_step(word_count, enc_out, enc_out_proj,
                           decoder_hidden_dim, depth, trg_emb):
    decoder_memory = paddle.layer.memory(
        name="__decoder_0__", size=decoder_hidden_dim, boot_layer=None)

    context = paddle.networks.simple_attention(
        encoded_sequence=enc_out,
        encoded_proj=enc_out_proj,
        decoder_state=decoder_memory)

    for i in range(depth):
        input_proj = paddle.layer.mixed(
            act=paddle.activation.Linear(),
            size=decoder_hidden_dim * 4,
            bias_attr=False,
            input=[
                paddle.layer.full_matrix_projection(input_proj),
                paddle.layer.full_matrix_projection(lstm)
            ] if i else [
                paddle.layer.full_matrix_projection(context),
                paddle.layer.full_matrix_projection(trg_emb)
            ])
        lstm = paddle.networks.lstmemory_unit(
            input=input_proj,
            input_proj_layer_attr=paddle.attr.ExtraLayerAttribute(
                error_clipping_threshold=25.),
            out_memory=decoder_memory if not i else None,
            name="__decoder_%d__" % (i),
            size=decoder_hidden_dim,
            act=paddle.activation.Tanh(),
            gate_act=paddle.activation.Sigmoid(),
            state_act=paddle.activation.Tanh())

    next_word = paddle.layer.fc(
        size=word_count,
        bias_attr=True,
        act=paddle.activation.Softmax(),
        input=lstm)
    return next_word


def encoder_decoder_network(word_count,
                            emb_dim,
                            encoder_depth,
                            encoder_hidden_dim,
                            decoder_depth,
                            decoder_hidden_dim,
                            beam_size=10,
                            max_length=15,
                            is_generating=False):
    src_emb = paddle.layer.embedding(
        input=paddle.layer.data(
            name="src_word_id",
            type=paddle.data_type.integer_value_sequence(word_count)),
        size=emb_dim,
        param_attr=paddle.attr.ParamAttr(name="__embedding__"))
    enc_out = _bidirect_lstm_encoder(
        input=src_emb, hidden_dim=encoder_hidden_dim, depth=encoder_depth)
    enc_out_proj = paddle.layer.fc(
        act=paddle.activation.Linear(),
        size=encoder_hidden_dim,
        bias_attr=False,
        input=enc_out)

    decoder_group_name = "decoder_group"
    group_inputs = [
        word_count, paddle.layer.StaticInput(input=enc_out),
        paddle.layer.StaticInput(input=enc_out_proj), decoder_hidden_dim,
        decoder_depth
    ]

    if is_generating:
        gen_trg_emb = paddle.layer.GeneratedInput(
            size=word_count,
            embedding_name="__embedding__",
            embedding_size=emb_dim)
        return paddle.layer.beam_search(
            name=decoder_group_name,
            step=_attended_decoder_step,
            input=group_inputs + [gen_trg_emb],
            bos_id=0,
            eos_id=1,
            beam_size=beam_size,
            max_length=max_length)

    else:
        trg_emb = paddle.layer.embedding(
            input=paddle.layer.data(
                name="trg_word_id",
                type=paddle.data_type.integer_value_sequence(word_count)),
            size=emb_dim,
            param_attr=paddle.attr.ParamAttr(name="__embedding__"))
        lbl = paddle.layer.data(
            name="trg_next_word",
            type=paddle.data_type.integer_value_sequence(word_count))
        next_word = paddle.layer.recurrent_group(
            name=decoder_group_name,
            step=_attended_decoder_step,
            input=group_inputs + [trg_emb])
        return paddle.layer.classification_cost(input=next_word, label=lbl)
