#!/usr/bin/env python3
# coding: utf-8
# Copyright 2019 Huawei Technologies Co., Ltd
#
# 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.

"""operator dsl function: mean"""
import akg.topi
import akg.tvm
from akg.ops.math import sum
from akg.utils import format_transform as ft_util
from akg.utils import validation_check as vc_util
from akg.utils import custom_tiling as ct_util
from akg.utils.dynamic_shape import shape_is_dynamic


INT16_MAX = 65536


def get_attrs(tensor):
    """generate default attrs."""
    if shape_is_dynamic(tensor):
        return {"enable_double_buffer": 0, "enable_divide_var": 1}

    return {}


def mean_dynamic_tiling_strategy(tensor, axis):
    """custom tiling for mean with dynamic shape"""
    strategy = list()

    inner_most_to_full = True
    resnet_inner_most_axis_pos = 4

    reduce_axis_to_1 = True
    reduce_axis_to_no_iso = False

    multicore_axis_to_1 = True
    resnet_outer_most_axis_pos = 0

    if inner_most_to_full:
        strategy += ct_util.create_constraint_on_tensor(tensor=tensor,
                                                        values="FULL",
                                                        constraints=ct_util.TileConstraint.MAX,
                                                        tensor_pos=resnet_inner_most_axis_pos)
    if reduce_axis_to_1:
        strategy += ct_util.create_constraint_on_tensor(tensor=tensor,
                                                        values=[1 for _ in axis],
                                                        constraints=ct_util.TileConstraint.FACTOR,
                                                        tensor_pos=axis)
    elif reduce_axis_to_no_iso:
        strategy += ct_util.create_constraint_on_tensor(tensor=tensor,
                                                        values=[1 for _ in axis],
                                                        constraints=ct_util.TileConstraint.FORBID_ISOLATE,
                                                        tensor_pos=axis)
    if multicore_axis_to_1:
        strategy += ct_util.create_constraint_on_tensor(tensor=tensor,
                                                        values=1,
                                                        constraints=ct_util.TileConstraint.FACTOR,
                                                        tensor_pos=resnet_outer_most_axis_pos)
    return strategy


@vc_util.check_input_type(akg.tvm.tensor.Tensor, (list, tuple, int, type(None)), (bool, type(None)))
def mean(data, axis=None, keepdims=False):
    """
    Computes the mean of the values of a Tensor over the whole dataset.

    Note:
        If the tuple's elements are unsorted, this function will call preprocess_axis firstly to let these elements
        sorted. if tuple is empty, this function will compute all elements' sum.
        if the data type is folat 16 and the whole dim not less than 65536, this function will compute the mean by
        divide 65535 first to avoid whole dim too large.

    Args:
        data (tvm.tensor.Tensor): Tensor of type float16, float32.
        axis (Union[list, tuple, int, None]): If the tuple is empty, the axis equal to None.
        keepdims (bool): If keepdims equal to True, the result shape length is same to input shape length.

    Returns:
            tvm.tensor.Tensor, has the same type as data. If keepdims equal to True, all reduced dimensions are
            retained with length 1. else these reduced axis will be eliminate.
    """
    # Check types
    vc_util.ops_dtype_check(data.dtype, vc_util.DtypeForDavinci.ALL_FLOAT)

    # Check shape
    shape = ft_util.get_shape(data)
    vc_util.reduce_axis_check(shape, axis)
    axis = ft_util.refine_reduce_axis(data, axis)

    count = 1
    for i in axis:
        count *= shape[i]
    output, _ = sum.sum_value(data, axis, keepdims)

    if shape_is_dynamic(data):
        res = akg.tvm.compute(output.shape, lambda *i: akg.lang.cce.divide_var(output(*i), count), name="res")
    else:
        res = akg.topi.divide(output, count)

    attrs = get_attrs(data)
    if shape_is_dynamic(data):
        attrs["custom_tiling"] = mean_dynamic_tiling_strategy(data, axis)
    return res, attrs


@vc_util.check_input_type(akg.tvm.tensor.Tensor, (list, tuple, int, type(None)), (bool, type(None)))
def mean_v2(data, axis=None, keepdims=False):
    """Simple implementation of mean."""
    # Check types
    vc_util.ops_dtype_check(data.dtype, vc_util.DtypeForDavinci.ALL_FLOAT)

    # Check shape
    shape = [x.value for x in data.shape]
    vc_util.reduce_axis_check(shape, axis)
    axis = ft_util.refine_reduce_axis(data, axis)

    dtype = data.dtype
    count = 1
    for i in axis:
        count *= shape[i]

    count_rec = 1 / count
    output, _ = sum.sum_v2(data, axis, keepdims)
    res = output * akg.tvm.const(count_rec, dtype)
    attrs = get_attrs(data)
    if shape_is_dynamic(data):
        attrs["custom_tiling"] = mean_dynamic_tiling_strategy(data, axis)
    return res, attrs
