# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import paddle.fluid as fluid
import math

user_profile_dim = 65
dense_feature_dim = 3

def ctr_deepfm_dataset(dense_feature, context_feature, context_feature_fm, label,
                       embedding_size, sparse_feature_dim):
    def dense_fm_layer(input, emb_dict_size, factor_size, fm_param_attr):

        first_order = fluid.layers.fc(input=input, size=1)
        emb_table = fluid.layers.create_parameter(shape=[emb_dict_size, factor_size],
                                                  dtype='float32', attr=fm_param_attr)

        input_mul_factor = fluid.layers.matmul(input, emb_table)
        input_mul_factor_square = fluid.layers.square(input_mul_factor)
        input_square = fluid.layers.square(input)
        factor_square = fluid.layers.square(emb_table)
        input_square_mul_factor_square = fluid.layers.matmul(input_square, factor_square)

        second_order = 0.5 * (input_mul_factor_square - input_square_mul_factor_square)
        return first_order, second_order


    dense_fm_param_attr = fluid.param_attr.ParamAttr(name="DenseFeatFactors",
                                                     initializer=fluid.initializer.Normal(
                                                         scale=1 / math.sqrt(dense_feature_dim)))
    dense_fm_first, dense_fm_second = dense_fm_layer(
        dense_feature, dense_feature_dim, 16, dense_fm_param_attr)


    def sparse_fm_layer(input, emb_dict_size, factor_size, fm_param_attr):

        first_embeddings = fluid.layers.embedding(
            input=input, dtype='float32', size=[emb_dict_size, 1], is_sparse=True)
        first_order = fluid.layers.sequence_pool(input=first_embeddings, pool_type='sum')

        nonzero_embeddings = fluid.layers.embedding(
            input=input, dtype='float32', size=[emb_dict_size, factor_size],
            param_attr=fm_param_attr, is_sparse=True)
        summed_features_emb = fluid.layers.sequence_pool(input=nonzero_embeddings, pool_type='sum')
        summed_features_emb_square = fluid.layers.square(summed_features_emb)

        squared_features_emb = fluid.layers.square(nonzero_embeddings)
        squared_sum_features_emb = fluid.layers.sequence_pool(
            input=squared_features_emb, pool_type='sum')

        second_order = 0.5 * (summed_features_emb_square - squared_sum_features_emb)
        return first_order, second_order

    sparse_fm_param_attr = fluid.param_attr.ParamAttr(name="SparseFeatFactors",
                                                      initializer=fluid.initializer.Normal(
                                                          scale=1 / math.sqrt(sparse_feature_dim)))

    #data = fluid.layers.data(name='ids', shape=[1], dtype='float32')
    sparse_fm_first, sparse_fm_second = sparse_fm_layer(
        context_feature_fm, sparse_feature_dim, 16, sparse_fm_param_attr)

    def embedding_layer(input):
        return fluid.layers.embedding(
            input=input,
            is_sparse=True,
            # you need to patch https://github.com/PaddlePaddle/Paddle/pull/14190
            # if you want to set is_distributed to True
            is_distributed=False,
            size=[sparse_feature_dim, embedding_size],
            param_attr=fluid.ParamAttr(name="SparseFeatFactors",
                                       initializer=fluid.initializer.Uniform()))

    sparse_embed_seq = list(map(embedding_layer, context_feature))

    concated_ori = fluid.layers.concat(sparse_embed_seq + [dense_feature], axis=1)
    concated = fluid.layers.batch_norm(input=concated_ori, name="bn", epsilon=1e-4)

    deep = deep_net(concated)

    predict = fluid.layers.fc(input=[deep, sparse_fm_first, sparse_fm_second, dense_fm_first, dense_fm_second], size=2, act="softmax",
                              param_attr=fluid.ParamAttr(initializer=fluid.initializer.Normal(
                                  scale=1 / math.sqrt(deep.shape[1])), learning_rate=0.01))

    #similarity_norm = fluid.layers.sigmoid(fluid.layers.clip(predict, min=-15.0, max=15.0), name="similarity_norm")

    cost = fluid.layers.cross_entropy(input=predict, label=label)

    avg_cost = fluid.layers.reduce_sum(cost)
    accuracy = fluid.layers.accuracy(input=predict, label=label)
    auc_var, batch_auc_var, auc_states = \
        fluid.layers.auc(input=predict, label=label, num_thresholds=2 ** 12, slide_steps=20)
    return avg_cost, auc_var, batch_auc_var, accuracy, predict


def deep_net(concated, lr_x=0.0001):
    fc_layers_input = [concated]
    fc_layers_size = [400, 400, 400]
    fc_layers_act = ["relu"] * (len(fc_layers_size))

    for i in range(len(fc_layers_size)):
        fc = fluid.layers.fc(
            input=fc_layers_input[-1],
            size=fc_layers_size[i],
            act=fc_layers_act[i],
            param_attr=fluid.ParamAttr(learning_rate=lr_x * 0.5))

        fc_layers_input.append(fc)
    #w_res = fluid.layers.create_parameter(shape=[353, 16], dtype='float32', name="w_res")
    #high_path = fluid.layers.matmul(concated, w_res)

    #return fluid.layers.elementwise_add(high_path, fc_layers_input[-1])
    return fc_layers_input[-1]
