import paddle.fluid as fluid
import math


def ctr_xdeepfm_model(embedding_size,
                      num_field,
                      num_feat,
                      layer_sizes_dnn,
                      act,
                      reg,
                      layer_sizes_cin,
                      is_sparse=False):
    init_value_ = 0.1
    initer = fluid.initializer.TruncatedNormalInitializer(
        loc=0.0, scale=init_value_)

    raw_feat_idx = fluid.data(
        name='feat_idx', shape=[None, num_field], dtype='int64')
    raw_feat_value = fluid.data(
        name='feat_value', shape=[None, num_field], dtype='float32')
    label = fluid.data(
        name='label', shape=[None, 1], dtype='float32')  # None * 1
    feat_idx = fluid.layers.reshape(raw_feat_idx,
                                    [-1, 1])  # (None * num_field) * 1
    feat_value = fluid.layers.reshape(
        raw_feat_value, [-1, num_field, 1])  # None * num_field * 1

    feat_embeddings = fluid.embedding(
        input=feat_idx,
        is_sparse=is_sparse,
        dtype='float32',
        size=[num_feat + 1, embedding_size],
        padding_idx=0,
        param_attr=fluid.ParamAttr(initializer=initer))
    feat_embeddings = fluid.layers.reshape(
        feat_embeddings,
        [-1, num_field, embedding_size])  # None * num_field * embedding_size
    feat_embeddings = feat_embeddings * feat_value  # None * num_field * embedding_size

    # -------------------- linear  --------------------

    weights_linear = fluid.embedding(
        input=feat_idx,
        is_sparse=is_sparse,
        dtype='float32',
        size=[num_feat + 1, 1],
        padding_idx=0,
        param_attr=fluid.ParamAttr(initializer=initer))
    weights_linear = fluid.layers.reshape(
        weights_linear, [-1, num_field, 1])  # None * num_field * 1
    b_linear = fluid.layers.create_parameter(
        shape=[1],
        dtype='float32',
        default_initializer=fluid.initializer.ConstantInitializer(value=0))
    y_linear = fluid.layers.reduce_sum(
        (weights_linear * feat_value), 1) + b_linear

    # -------------------- CIN  --------------------

    Xs = [feat_embeddings]
    last_s = num_field
    for s in layer_sizes_cin:
        # calculate Z^(k+1) with X^k and X^0
        X_0 = fluid.layers.reshape(
            fluid.layers.transpose(Xs[0], [0, 2, 1]),
            [-1, embedding_size, num_field,
             1])  # None, embedding_size, num_field, 1
        X_k = fluid.layers.reshape(
            fluid.layers.transpose(Xs[-1], [0, 2, 1]),
            [-1, embedding_size, 1, last_s])  # None, embedding_size, 1, last_s
        Z_k_1 = fluid.layers.matmul(
            X_0, X_k)  # None, embedding_size, num_field, last_s

        # compresses Z^(k+1) to X^(k+1)
        Z_k_1 = fluid.layers.reshape(Z_k_1, [
            -1, embedding_size, last_s * num_field
        ])  # None, embedding_size, last_s*num_field
        Z_k_1 = fluid.layers.transpose(
            Z_k_1, [0, 2, 1])  # None, s*num_field, embedding_size
        Z_k_1 = fluid.layers.reshape(
            Z_k_1, [-1, last_s * num_field, 1, embedding_size]
        )  # None, last_s*num_field, 1, embedding_size  (None, channal_in, h, w) 
        X_k_1 = fluid.layers.conv2d(
            Z_k_1,
            num_filters=s,
            filter_size=(1, 1),
            act=None,
            bias_attr=False,
            param_attr=fluid.ParamAttr(
                initializer=initer))  # None, s, 1, embedding_size
        X_k_1 = fluid.layers.reshape(
            X_k_1, [-1, s, embedding_size])  # None, s, embedding_size

        Xs.append(X_k_1)
        last_s = s

    # sum pooling
    y_cin = fluid.layers.concat(Xs[1:],
                                1)  # None, (num_field++), embedding_size
    y_cin = fluid.layers.reduce_sum(y_cin, -1)  # None, (num_field++)
    y_cin = fluid.layers.fc(input=y_cin,
                            size=1,
                            act=None,
                            param_attr=fluid.ParamAttr(initializer=initer),
                            bias_attr=None)
    y_cin = fluid.layers.reduce_sum(y_cin, dim=-1, keep_dim=True)

    # -------------------- DNN --------------------

    y_dnn = fluid.layers.reshape(feat_embeddings,
                                 [-1, num_field * embedding_size])
    for s in layer_sizes_dnn:
        y_dnn = fluid.layers.fc(input=y_dnn,
                                size=s,
                                act=act,
                                param_attr=fluid.ParamAttr(initializer=initer),
                                bias_attr=None)
    y_dnn = fluid.layers.fc(input=y_dnn,
                            size=1,
                            act=None,
                            param_attr=fluid.ParamAttr(initializer=initer),
                            bias_attr=None)

    # ------------------- xDeepFM ------------------

    predict = fluid.layers.sigmoid(y_linear + y_cin + y_dnn)
    cost = fluid.layers.log_loss(input=predict, label=label, epsilon=0.0000001)
    batch_cost = fluid.layers.reduce_mean(cost)

    # for auc
    predict_2d = fluid.layers.concat([1 - predict, predict], 1)
    label_int = fluid.layers.cast(label, 'int64')
    auc_var, batch_auc_var, auc_states = fluid.layers.auc(input=predict_2d,
                                                          label=label_int,
                                                          slide_steps=0)

    return batch_cost, auc_var, [raw_feat_idx, raw_feat_value, label]
