# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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 logging

import tensorflow as tf

from submarine.ml.tensorflow_v2.layers.core import (
    KMaxPooling,
    dnn_layer,
    embedding_layer,
    linear_layer,
)
from submarine.ml.tensorflow_v2.model.base_tf_model import BaseTFModel
from submarine.utils.tf_utils_v2 import get_estimator_spec

logger = logging.getLogger(__name__)


class CCPM(BaseTFModel):
    def model_fn(self, features, labels, mode, params):
        super().model_fn(features, labels, mode, params)

        if len(params["training"]["conv_kernel_width"]) != len(params["training"]["conv_filters"]):
            raise ValueError("conv_kernel_width must have same element with conv_filters")

        linear_logit = linear_layer(features, **params["training"])
        embedding_outputs = embedding_layer(features, **params["training"])
        conv_filters = params["training"]["conv_filters"]
        conv_kernel_width = params["training"]["conv_kernel_width"]

        n = params["training"]["embedding_size"]
        conv_filters_len = len(conv_filters)
        conv_input = tf.concat(embedding_outputs, axis=1)

        pooling_result = tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=3))(conv_input)

        for i in range(1, conv_filters_len + 1):
            filters = conv_filters[i - 1]
            width = conv_kernel_width[i - 1]
            p = pow(i / conv_filters_len, conv_filters_len - i)
            k = max(1, int((1 - p) * n)) if i < conv_filters_len else 3

            conv_result = tf.keras.layers.Conv2D(
                filters=filters,
                kernel_size=(width, 1),
                strides=(1, 1),
                padding="same",
                activation="tanh",
                use_bias=True,
            )(pooling_result)

            pooling_result = KMaxPooling(k=min(k, int(conv_result.shape[1])), axis=1)(conv_result)

        flatten_result = tf.keras.layers.Flatten()(pooling_result)
        deep_logit = dnn_layer(flatten_result, mode, **params["training"])

        with tf.compat.v1.variable_scope("CCPM_out"):
            logit = linear_logit + deep_logit

        return get_estimator_spec(logit, labels, mode, params)
