/**
 * 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.
 */

#include "frontend/parallel/tensor_layout/arrangement.h"
#include <algorithm>
#include <iostream>
#include <utility>
#include "common/utils.h"
#include "frontend/parallel/status.h"
#include "frontend/parallel/tensor_layout/shape_util.h"
#include "utils/convert_utils.h"
#include "utils/log_adapter.h"

namespace mindspore {
namespace parallel {
Status Arrangement::Init(const std::vector<int32_t> &array) {
  Status status = Array::Init(array);
  if (status != Status::SUCCESS) {
    return Status::FAILED;
  }
  if (!IsValidArrangement()) {
    MS_LOG(ERROR) << "invalid arrangement " << this->ToString();
    return Status::FAILED;
  }
  ComputeSize();
  return Status::SUCCESS;
}

bool Arrangement::IsValidArrangement() {
  return !std::any_of(array_.begin(), array_.end(), [](int32_t value) { return value <= 0; });
}

void Arrangement::ComputeSize() {
  size_ = 1;
  for (auto &value : array_) {
    size_ *= value;
  }
}

/*
 * if GetDimSize() = 0, return []
 * if value <= array_[0], return [value]
 * if array_[0] < value <= size_[i], return [shape[0], shape[1], ..., shape[i-1], value/size_[i-1]],
 * where size_[i-1] = shape[0] * shape[1] * ... * shape[i-1],
 * if value > size_, return []
 */
std::vector<int32_t> Arrangement::GetFrontElementByValue(int32_t value) const {
  std::vector<int32_t> out;
  if (GetDimSize() == 0) {
    return out;
  }
  if (value <= size_) {
    int32_t size = 1;
    uint32_t shape_list_idx = 0;
    while (size < value) {
      size *= array_[shape_list_idx];
      if (size <= value) {
        out.push_back(array_[shape_list_idx]);
      } else {
        if (size == 0) {
          MS_LOG(ERROR) << "The size is 0";
          out.clear();
          return out;
        }
        out.push_back(value * array_[shape_list_idx] / size);
      }
      shape_list_idx++;
    }
  }
  return out;
}

std::shared_ptr<Arrangement> Arrangement::GetExpandedShapeByExpandListRemoveLeft(
  const std::vector<Arrangement> &expand_list) const {
  if (expand_list.size() != GetDimSize()) {
    return nullptr;
  }
  std::vector<int32_t> new_shape;
  for (uint32_t i = 0; i < expand_list.size(); i++) {
    std::vector<int32_t> expand_shape = expand_list[i].GetFrontElementByValue(GetDimByIdx(i));
    if (expand_shape.empty()) {
      new_shape.push_back(GetDimByIdx(i));
    } else {
      (void)new_shape.insert(new_shape.end(), expand_shape.begin(), expand_shape.end());
    }
  }
  Arrangement arrangement_new;
  (void)arrangement_new.Init(new_shape);
  return std::make_shared<Arrangement>(arrangement_new);
}

/*
 *  example:
 *    expand_shape = [4, 2, 2, 2]
 *    array_ = [8, 4],
 *    arrangement_list = [[4, 2], [2, 2]]
 */
std::shared_ptr<std::vector<Arrangement>> Arrangement::GetExpandShapeList(const Arrangement &expand_shape) const {
  int32_t size = 1;
  uint32_t ind = 0;
  std::vector<Arrangement> arrangement_list;
  std::vector<int32_t> shape;
  for (uint32_t i = 0; i < expand_shape.GetDimSize(); i++) {
    size *= expand_shape.GetDimByIdx(i);
    if (size > GetDimByIdx(ind)) {
      MS_LOG(ERROR) << "invalid expand_shape";
      return nullptr;
    } else if (size < GetDimByIdx(ind)) {
      shape.push_back(expand_shape.GetDimByIdx(i));
      continue;
    } else {
      shape.push_back(expand_shape.GetDimByIdx(i));
      Arrangement arrangement;
      (void)arrangement.Init(shape);
      arrangement_list.push_back(arrangement);
      shape.clear();
      ind++;
      size = 1;
    }
  }
  if (ind != GetDimSize()) {
    MS_LOG(ERROR) << "invalid expand_shape";
    return nullptr;
  }
  auto arrangement_new = std::make_shared<std::vector<Arrangement>>(arrangement_list);
  return arrangement_new;
}

std::shared_ptr<std::pair<std::vector<Arrangement>, Arrangement>> Arrangement::GetExpandShapeListPair(
  const Arrangement &expand_shape) const {
  std::shared_ptr<std::vector<Arrangement>> expand_shape_list_ptr = GetExpandShapeList(expand_shape);
  if (expand_shape_list_ptr == nullptr) {
    return nullptr;
  }
  std::vector<int32_t> expand_num_list_shape;
  (void)std::transform(expand_shape_list_ptr->begin(), expand_shape_list_ptr->end(),
                       std::back_inserter(expand_num_list_shape),
                       [](const Arrangement &arr) { return SizeToInt(arr.GetDimSize()); });
  Arrangement expand_num_list;
  Status status = expand_num_list.Init(expand_num_list_shape);
  if (status != Status::SUCCESS) {
    return nullptr;
  }
  auto out_value = std::make_pair(*expand_shape_list_ptr, expand_num_list);
  return std::make_shared<std::pair<std::vector<Arrangement>, Arrangement>>(out_value);
}

std::vector<int32_t> Arrangement::ComputeReverseAccumulateSumInReverseOrder() const {
  std::vector<int32_t> shape_accum;
  int32_t size = 0;
  for (auto iter = array_.end() - 1; iter >= array_.begin(); --iter) {
    shape_accum.push_back(size);
    size += *iter;
  }
  return shape_accum;
}

std::shared_ptr<Arrangement> Arrangement::GetExpandedShapeByExpandListReserveLeft(
  const std::vector<Arrangement> &expand_list) const {
  if (expand_list.size() != GetDimSize()) {
    return nullptr;
  }
  std::vector<int32_t> new_shape;
  for (uint32_t i = 0; i < expand_list.size(); i++) {
    if (expand_list[i].GetDimSize() >= 1) {
      int32_t size = 1;
      for (uint32_t k = 0; k < expand_list[i].GetDimSize() - 1; k++) {
        new_shape.push_back(expand_list[i].GetDimByIdx(k));
        size *= expand_list[i].GetDimByIdx(k);
      }
      new_shape.push_back(GetDimByIdx(i) / size);
    } else {
      new_shape.push_back(GetDimByIdx(i));
    }
  }
  Arrangement arrangement_new;
  (void)arrangement_new.Init(new_shape);
  return std::make_shared<Arrangement>(arrangement_new);
}

std::shared_ptr<Arrangement> Arrangement::GetUnifiedShape(const Arrangement &in2) const {
  std::vector<int64_t> in1_accum;
  Status status = ShapeToAccumulateProduct(array_, &in1_accum);
  if (status != Status::SUCCESS) {
    return nullptr;
  }
  std::vector<int64_t> in2_accum;
  status = ShapeToAccumulateProduct(in2.array(), &in2_accum);
  if (status != Status::SUCCESS) {
    return nullptr;
  }
  std::vector<int64_t> out_accum;
  status = UnifyAccumulateProduct(in1_accum, in2_accum, &out_accum);
  if (status != Status::SUCCESS) {
    return nullptr;
  }
  std::vector<int32_t> out_shape;
  status = AccumulateProductToShape(out_accum, &out_shape);
  if (status != Status::SUCCESS) {
    return nullptr;
  }
  Arrangement out;
  status = out.Init(out_shape);
  if (status != Status::SUCCESS) {
    return nullptr;
  }
  return std::make_shared<Arrangement>(out);
}

std::vector<size_t> Arrangement::GetSqueezeIdx() const {
  std::vector<size_t> out;
  for (size_t i = 0; i < GetDimSize(); i++) {
    if (GetDimByIdx(SizeToUint(i)) == 1) {
      out.push_back(i);
    }
  }
  return out;
}

Arrangement Arrangement::GetSqueezeArrangement() const {
  std::vector<int32_t> out_shape(array_.size());
  auto it = std::copy_if(array_.begin(), array_.end(), out_shape.begin(), [](int32_t value) { return value != 1; });
  out_shape.resize(LongToSize(std::distance(out_shape.begin(), it)));

  // if all elements are 1, out_shape = {1}
  if (out_shape.empty()) {
    MS_LOG(ERROR) << "out_shape size is 0, this may not happen under current situation";
    out_shape.push_back(1);
  }
  Arrangement out;
  (void)out.Init(out_shape);
  return out;
}
}  // namespace parallel
}  // namespace mindspore
