/**
 * Copyright 2020 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.
 */
#include <map>
#include <string>
#include <vector>
#include "backend/kernel_compiler/cpu/reduce_cpu_kernel.h"
#include "runtime/device/cpu/cpu_device_address.h"

namespace mindspore {
namespace kernel {
const size_t kReduceTypeMax = 0;
const size_t kReduceTypeMean = 1;
const size_t kReduceTypeSum = 2;
const size_t kMaxDim = 100;
void ReduceCPUKernel::InitKernel(const CNodePtr &kernel_node) {
  MS_EXCEPTION_IF_NULL(kernel_node);
  std::string kernel_name = AnfAlgo::GetCNodeName(kernel_node);
  if (kernel_name == "ReduceMax") {
    reduce_type_ = kReduceTypeMax;
  } else if (kernel_name == "ReduceMean") {
    reduce_type_ = kReduceTypeMean;
  } else if (kernel_name == "ReduceSum") {
    reduce_type_ = kReduceTypeSum;
  } else {
    MS_LOG(EXCEPTION) << "Array reduce kernel type " << kernel_name << " is not supported.";
  }
  shape_ = AnfAlgo::GetInputDeviceShape(kernel_node, 0);
  auto axis_addr = AnfAlgo::GetCNodePrimitive(kernel_node)->GetAttr(AXIS);
  if (axis_addr->isa<ValueTuple>()) {
    auto attr_axis = AnfAlgo::GetNodeAttr<std::vector<int>>(kernel_node, AXIS);
    if (attr_axis.size() > shape_.size()) {
      MS_LOG(EXCEPTION) << "invalid axis size: " << axis_.size();
    } else if (attr_axis.empty()) {
      axis_.push_back(shape_.size() - 1);
    } else {
      for (auto axis : attr_axis) {
        if (IntToSize(axis) >= (shape_.size())) {
          MS_LOG(EXCEPTION) << "axis value is oversize.";
        }
        axis < 0 ? axis_.push_back(axis + shape_.size()) : axis_.push_back(axis);
      }
    }
  } else if (axis_addr->isa<Int32Imm>()) {
    int axis = AnfAlgo::GetNodeAttr<int>(kernel_node, AXIS);
    if (axis >= 0 && IntToSize(axis) >= shape_.size()) {
      MS_LOG(EXCEPTION) << "axis value is oversize.";
    }
    axis < 0 ? axis_.push_back(axis + shape_.size()) : axis_.push_back(axis);
  } else {
    MS_LOG(EXCEPTION) << "Attribute axis type is invalid.";
  }
  for (size_t i = 0; i < shape_.size(); ++i) {
    if (shape_[i] <= 0) {
      MS_LOG(EXCEPTION) << "shape value is invalid.";
    }
    left_dims_ *= shape_[i];
  }
  for (size_t i = 0; i < axis_.size(); ++i) {
    stride_ *= shape_[axis_[i]];
  }
  if (stride_ <= 0) {
    MS_LOG(EXCEPTION) << "stride_ must greater than zero.";
  }
  left_dims_ = left_dims_ / stride_;
}
bool ReduceCPUKernel::Launch(const std::vector<kernel::AddressPtr> &inputs,
                             const std::vector<kernel::AddressPtr> & /*workspaces*/,
                             const std::vector<kernel::AddressPtr> &outputs) {
  size_t out_float_size = left_dims_ * sizeof(float);
  size_t in_float_size = stride_ * out_float_size;
  if (inputs[0]->size != in_float_size || outputs[0]->size != out_float_size) {
    MS_LOG(EXCEPTION) << "invalid input or output data size!";
  }
  auto input = reinterpret_cast<float *>(inputs[0]->addr);
  auto output = reinterpret_cast<float *>(outputs[0]->addr);
  int size = inputs[0]->size / sizeof(float);
  std::vector<float> new_input(IntToSize(size), 0.0);
  std::vector<size_t> transpose_axis;
  for (size_t i = 0; i < shape_.size(); ++i) {
    bool insert = true;
    for (size_t j = 0; j < axis_.size(); ++j) {
      if (axis_[j] == i) {
        insert = false;
        break;
      }
    }
    if (insert) {
      transpose_axis.push_back(i);
    }
  }
  (void)transpose_axis.insert(transpose_axis.end(), axis_.begin(), axis_.end());
  Transpose(size, input, shape_, transpose_axis, SizeToInt(shape_.size()), &new_input[0]);
  ConvertDataToOutput(&new_input[0], output);
  return true;
}

void ReduceCPUKernel::ConvertDataToOutput(const float *new_input, float *output) {
  if (reduce_type_ == kReduceTypeMax) {
    for (size_t i = 0; i < left_dims_; ++i) {
      float value = new_input[i * stride_];
      for (size_t k = 0; k < stride_; ++k) {
        if (value < new_input[i * stride_ + k]) {
          value = new_input[i * stride_ + k];
        }
      }
      output[i] = value;
    }
  } else {
    for (size_t i = 0; i < left_dims_; ++i) {
      float value = 0.0;
      for (size_t k = 0; k < stride_; ++k) {
        value += new_input[i * stride_ + k];
      }
      if (reduce_type_ == kReduceTypeMean) {
        output[i] = value / stride_;
      } else {
        output[i] = value;
      }
    }
  }
}
void ReduceCPUKernel::Transpose(const int size, const float *input, const std::vector<size_t> &input_shape,
                                const std::vector<size_t> &input_axis, const int shape_size, float *output) {
  int pos_array[kMaxDim];
  int size_offset[kMaxDim];
  size_offset[0] = size / SizeToInt(input_shape[0]);
  for (int i = 1; i < shape_size; i++) {
    size_offset[i] = size_offset[i - 1] / SizeToInt(input_shape[i]);
  }
  for (int position = 0; position < size; position += 1) {
    int temp_position = position;
    pos_array[0] = temp_position / size_offset[0];
    for (int i = 1; i < shape_size; i++) {
      temp_position -= pos_array[i - 1] * size_offset[i - 1];
      pos_array[i] = temp_position / size_offset[i];
    }
    int new_position = pos_array[SizeToInt(input_axis[shape_size - 1])];
    int new_position_size = 1;
    for (int j = shape_size - 2; j >= 0; j--) {
      new_position_size *= SizeToInt(input_shape[SizeToInt(input_axis[j + 1])]);
      new_position += pos_array[SizeToInt(input_axis[j])] * new_position_size;
    }
    output[new_position] = input[position];
  }
  return;
}
}  // namespace kernel
}  // namespace mindspore
