/**
 * 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 "predict/converter/kernel2ms.h"
#include <algorithm>
#include "ir/anf.h"
#include "predict/converter/lite_model/op_attr_packer.h"
#include "mindspore/ccsrc/operator/ops.h"

namespace mindspore {
namespace executor {
Kernel2Ms &Kernel2Ms::GetInstance() {
  static Kernel2Ms instance;
  return instance;
}

bool Kernel2Ms::SetMemResue() const {
  MS_LOG(INFO) << "MemResue start";
  return true;
}

bool Kernel2Ms::SetAllTensors(const TensorCachePtr &tensor_cache, SubGraphDefT *ms_graph) {
  if (tensor_cache == nullptr || ms_graph == nullptr) {
    return false;
  }
  const std::unordered_map<int, std::vector<ExTensorPtr>> &cachedTensors = tensor_cache->GetCachedTensor();
  size_t total_size = 0;
  if (cachedTensors.empty()) {
    return false;
  }
  for (auto &iter : cachedTensors) {
    auto ex_tensors = iter.second;
    total_size += ex_tensors.size();
  }
  ms_graph->allTensors.resize(total_size);
  for (auto &iter : cachedTensors) {
    for (auto &ex_tensor : iter.second) {
      std::unique_ptr<TensorDefT> ms_tensor(new TensorDefT());
      auto device_tensor_tmp = ex_tensor->device_tensor_ptr_;
      auto device_d_type = device_tensor_tmp->data_type();
      ms_tensor->dataType = predict::utils::GetMSDataType(device_d_type);
      auto device_shape = device_tensor_tmp->shape();
      ms_tensor->dims.clear();
      if (device_shape.empty()) {
        ms_tensor->dims.push_back(1);
      } else {
        ms_tensor->dims.assign(device_shape.begin(), device_shape.end());
      }
      std::string format_str = device_tensor_tmp->device_info().format_;
      ms_tensor->format = predict::utils::GetMsFormat(format_str);
      ms_tensor->offset = 0;
      auto stable = ex_tensor->stable_;
      if (stable == INPUTDATA || stable == CONSTANT || stable == WEIGHTS) {
        ms_tensor->refCount = MS_MAX_REFCOUNT;
      } else {
        ms_tensor->refCount = 0;
      }
      ms_graph->allTensors[IntToSize(ex_tensor->index_)] = std::move(ms_tensor);
    }
  }
  return true;
}

bool Kernel2Ms::SetGraphOutputIdx(const KernelGraphPtr &kernel_graph_ptr, const TensorCachePtr &tensor_cache,
                                  SubGraphDefT *ms_graph, AllOutputTensors *all_output_tensors) {
  MS_EXCEPTION_IF_NULL(tensor_cache);
  MS_EXCEPTION_IF_NULL(ms_graph);
  MS_EXCEPTION_IF_NULL(all_output_tensors);
  auto out_nodes = kernel_graph_ptr->outputs();
  if (out_nodes.empty()) {
    return false;
  }
  // maybe need to judge out_nodes is real && output must be CNode
  for (size_t i = 0; i < out_nodes.size(); ++i) {
    std::vector<AnfNodePtr> real_inputs_link;
    std::vector<size_t> real_output_idx_link;
    GetRealInpoutsPtr(out_nodes[i], &real_inputs_link, &real_output_idx_link);
    if (real_inputs_link.empty()) {
      MS_LOG(INFO) << "this graph output node is vitural node, has no real input";
      continue;
    }
    for (size_t k = 0; k < real_inputs_link.size(); ++k) {
      int key = node_indexs_[out_nodes[i].get()];
      auto ex_tensor_list = tensor_cache->findTensor(key);
      if (ex_tensor_list.empty()) {
        MS_LOG(INFO) << "SetGraphOutputIdx do not add Extensor ";
        continue;
      }
      auto ex_tensor = ex_tensor_list[real_output_idx_link[k]];
      ex_tensor_list.clear();
      ms_graph->outputIndex.push_back(ex_tensor->index_);
    }
  }
  return true;
}

bool Kernel2Ms::SetOpOutputIdx(const CNodePtr &c_node_ptr, const TensorPtr &output_tensor,
                               const TensorCachePtr &tensor_cache, int ref_count, size_t order_index,
                               NodeDef *ms_node) {
  MS_EXCEPTION_IF_NULL(c_node_ptr);
  MS_EXCEPTION_IF_NULL(output_tensor);
  MS_EXCEPTION_IF_NULL(ms_node);
  MS_EXCEPTION_IF_NULL(tensor_cache);
  if (!predict::utils::FindNodeInMap(node_indexs_, c_node_ptr)) {
    MS_LOG(ERROR) << "can not find any pk_key in inited node_indexs map";
    return false;
  }
  int tensor_key = node_indexs_[c_node_ptr.get()];
  auto host_shape = AnfAlgo::GetOutputInferShape(c_node_ptr, order_index);
  std::vector<int> tensor_shape;
  (void)std::transform(host_shape.begin(), host_shape.end(), std::back_inserter(tensor_shape), SizeToInt);
  int outputIndex = tensor_cache->addExTensor(tensor_key, output_tensor, ref_count, tensor_shape, KERNEL);
  ms_node->opDef->outputIndex.push_back(outputIndex);
  return true;
}

void Kernel2Ms::GetRealInpoutsPtr(const AnfNodePtr &node, std::vector<AnfNodePtr> *real_inputs,
                                  std::vector<size_t> *real_output_idx) {
  MS_EXCEPTION_IF_NULL(real_inputs);
  MS_EXCEPTION_IF_NULL(real_output_idx);
  size_t default_idx = 0;
  if (node->isa<CNode>()) {
    auto c_node = node->cast<CNodePtr>();
    MS_EXCEPTION_IF_NULL(c_node);
    std::string c_node_name = GetCNodeFuncName(c_node);
    if (c_node_name == prim::kPrimTupleGetItem->name()) {
      auto v_node = c_node->inputs()[kTupleGetItemIndex]->cast<ValueNodePtr>();
      MS_EXCEPTION_IF_NULL(v_node);
      default_idx = IntToSize(GetValue<int>(v_node->value()));
      real_inputs->push_back(c_node->inputs()[1]);
      real_output_idx->push_back(default_idx);
      return;
    } else if (c_node_name == prim::kPrimDepend->name()) {
      GetRealInpoutsPtr(c_node->inputs()[1], real_inputs, real_output_idx);
      return;
    } else if (c_node_name == prim::kPrimMakeTuple->name()) {
      for (auto &in : c_node->inputs()) {
        GetRealInpoutsPtr(in, real_inputs, real_output_idx);
      }
      return;
    } else {
      real_inputs->push_back(node);
      real_output_idx->push_back(default_idx);
    }
  } else if (node->isa<Parameter>()) {
    real_inputs->push_back(node);
    real_output_idx->push_back(default_idx);
  } else if (node->isa<ValueNode>()) {
    real_inputs->push_back(node);
    real_output_idx->push_back(default_idx);
  }
}

bool Kernel2Ms::SetOpInputIdx(const CNodePtr &c_node_ptr, const TensorCachePtr &tensor_cache, NodeDef *ms_node) {
  MS_EXCEPTION_IF_NULL(c_node_ptr);
  MS_EXCEPTION_IF_NULL(tensor_cache);
  MS_EXCEPTION_IF_NULL(ms_node);
  for (size_t i = 1; i < c_node_ptr->inputs().size(); ++i) {
    std::vector<AnfNodePtr> real_inputs;
    std::vector<size_t> real_output_idx;
    GetRealInpoutsPtr(c_node_ptr->inputs()[i], &real_inputs, &real_output_idx);
    if (real_inputs.empty()) {
      MS_LOG(INFO) << "kernel has no inputs: " << c_node_ptr.get() << " input size[%lu]" << c_node_ptr->inputs().size();
      continue;
    }
    for (size_t j = 0; j < real_inputs.size(); ++j) {
      int key = node_indexs_[real_inputs[j].get()];
      std::vector<ExTensorPtr> ex_tensor_list = tensor_cache->findTensor(key);
      if (ex_tensor_list.empty()) {
        continue;
      }
      ExTensorPtr ex_tensor_ptr = ex_tensor_list[real_output_idx[j]];
      ex_tensor_list.clear();
      ms_node->opDef->inputIndex.push_back(ex_tensor_ptr->index_);
    }
  }
  return true;
}

void Kernel2Ms::TransformGraphIndx() {
  // transform index && anfnodeptr
  if (node_indexs_.empty()) {
    MS_LOG(EXCEPTION) << "node_indexs_ not ininted";
  }
  for (auto &item : node_indexs_) {
    index_nodes_[item.second] = item.first;
  }
}

bool Kernel2Ms::InitGraphInputsIndx(const KernelGraphPtr &kernel_graph_ptr) {
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  auto input_nodes = kernel_graph_ptr->inputs();
  if (input_nodes.empty()) {
    return false;
  }
  for (const auto &input_node : input_nodes) {
    if (input_node->isa<Parameter>()) {
      if (!predict::utils::FindNodeInMap(node_indexs_, input_node)) {
        // init every parameter node
        node_indexs_[input_node.get()] = graph_index_;
        graph_index_++;
      }
    } else {
      MS_LOG(INFO) << "This node is anfnode, no need to handle, continue. node info: " << input_node->ToString();
      continue;
    }
  }
  MS_LOG(DEBUG) << "inputs GraphIndex: " << graph_index_;
  return true;
}

bool Kernel2Ms::InitGraphValueNodesIndx(const KernelGraphPtr &kernel_graph_ptr) {
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  if (kernel_graph_ptr->value_nodes().empty()) {
    return false;
  }
  for (auto &item : kernel_graph_ptr->value_nodes()) {
    if (item.first->isa<ValueNode>()) {
      auto value_node = item.first->cast<ValueNodePtr>();
      MS_EXCEPTION_IF_NULL(value_node);
      if (value_node == nullptr) {
        MS_LOG(WARNING) << "value_node is nullptr";
        return false;
      }
      if (value_node->value() == nullptr) {
        MS_LOG(ERROR) << "Constant value is  null.";
        return false;
      }
      if (!value_node->value()->isa<tensor::Tensor>()) {
        continue;
      }
      if (!predict::utils::FindNodeInMap(node_indexs_, item.first)) {
        // init node
        auto node_ptr = item.first;
        node_indexs_[node_ptr.get()] = graph_index_;
        graph_index_++;
      }
    }
  }
  return true;
}

bool Kernel2Ms::InitGraphOpsIndx(const KernelGraphPtr &kernel_graph_ptr) {
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  auto kernels = kernel_graph_ptr->execution_order();
  if (kernels.empty()) {
    MS_LOG(WARNING) << "this graph has no kernel";
    return false;
  }
  for (size_t i = 0; i < kernels.size(); ++i) {
    // for each kernel's inputs foreach real_input
    if (kernels[i]->isa<CNode>()) {
      if (!predict::utils::FindNodeInMap(node_indexs_, kernels[i])) {
        // init node
        node_indexs_[kernels[i].get()] = graph_index_;
        graph_index_++;
      }
    }
  }
  return true;
}

bool Kernel2Ms::InitGraphOutputsIndx(const KernelGraphPtr &kernel_graph_ptr) {
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  // graph output && their inputs should link together
  auto out_nodes = kernel_graph_ptr->outputs();
  if (out_nodes.empty()) {
    MS_LOG(ERROR) << "this graph has no outputs";
    return false;
  }
  for (auto &item : out_nodes) {
    if (!predict::utils::FindNodeInMap(node_indexs_, item)) {
      node_indexs_[item.get()] = graph_index_;
      graph_index_++;
    }
  }
  return true;
}

bool Kernel2Ms::InitGraphIndx(const KernelGraphPtr &kernel_graph_ptr) {
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  // only parameter
  if (!InitGraphInputsIndx(kernel_graph_ptr)) {
    return false;
  }
  // init value node
  if (!InitGraphValueNodesIndx(kernel_graph_ptr)) {
    return false;
  }
  // init op
  if (!InitGraphOpsIndx(kernel_graph_ptr)) {
    return false;
  }
  // init Graphoutput attention: out_put nodes have inputs
  return InitGraphOutputsIndx(kernel_graph_ptr);
}

bool Kernel2Ms::SetGraphInputTensors(const KernelGraphPtr &kernel_graph_ptr, const TensorCachePtr &tensor_cache,
                                     SubGraphDefT *ms_graph) {
  MS_EXCEPTION_IF_NULL(tensor_cache);
  MS_EXCEPTION_IF_NULL(ms_graph);
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  if (convert_mode_ == kConvertUnused) {
    return false;
  }
  if (kernel_graph_ptr->inputs().empty()) {
    return false;
  }
  for (const auto &input_node : kernel_graph_ptr->inputs()) {
    if (input_node->isa<Parameter>()) {
      ParameterPtr pk_node = std::dynamic_pointer_cast<Parameter>(input_node);
      TensorPtr device_tensor;
      if (convert_mode_ == kConvertCpuMode) {
        device_tensor = predict::utils::GetParaCpuTensor(input_node);
      } else {
        device_tensor = predict::utils::GetParaAscendTensor(input_node);
      }
      if (device_tensor == nullptr) {
        return false;
      }
      ExTensorType node_type;
      if (AnfAlgo::IsParameterWeight(pk_node)) {
        node_type = WEIGHTS;
      } else {
        node_type = INPUTDATA;
      }
      if (!predict::utils::FindNodeInMap(node_indexs_, input_node)) {
        MS_LOG(WARNING) << "can not find any pk_key in inited node_indexs map";
        return false;
      }
      auto pk_key = node_indexs_[input_node.get()];
      all_output_tensors_[pk_key].push_back(device_tensor);
      int nodeRefCount = SizeToInt(AnfAlgo::GetOutputTensorNum(input_node));
      int nodeInputIdx =
        tensor_cache->addExTensor(pk_key, device_tensor, nodeRefCount, device_tensor->shape(), node_type);
      if (!AnfAlgo::IsParameterWeight(pk_node)) {
        ms_graph->inputIndex.push_back(nodeInputIdx);
        all_input_idxs_.push_back(nodeInputIdx);
      } else {
        input_weight_idxs_.push_back(nodeInputIdx);
        all_input_idxs_.push_back(nodeInputIdx);
      }
    }
  }
  return true;
}

bool Kernel2Ms::SetGraphValueTensors(const KernelGraphPtr &kernel_graph_ptr, const TensorCachePtr &tensor_cache) {
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  MS_EXCEPTION_IF_NULL(tensor_cache);
  for (auto &item : kernel_graph_ptr->value_nodes()) {
    if (item.first->isa<ValueNode>()) {
      auto const_node = item.first->cast<ValueNodePtr>();
      auto tensor_constant = predict::utils::GetValueTensor(const_node);
      if (tensor_constant == nullptr) {
        continue;
      }
      if (!predict::utils::FindNodeInMap(node_indexs_, item.first)) {
        MS_LOG(WARNING) << "can not find any pk_key in inited node_indexs map";
        return false;
      }
      int constant_key = node_indexs_[(item.first).get()];
      all_output_tensors_[constant_key].push_back(tensor_constant);
      auto shape = tensor_constant->shape();
      (void)tensor_cache->addExTensor(constant_key, tensor_constant, 0, shape, CONSTANT);
    }
  }
  return true;
}

bool Kernel2Ms::SetGraphOpTensors(const KernelGraphPtr &kernel_graph_ptr, const TensorCachePtr &tensor_cache,
                                  SubGraphDefT *ms_graph) {
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  MS_EXCEPTION_IF_NULL(tensor_cache);
  MS_EXCEPTION_IF_NULL(ms_graph);
  auto kernels = kernel_graph_ptr->execution_order();
  if (kernels.empty()) {
    MS_LOG(ERROR) << "this graph has no kernels";
    return false;
  }
  for (auto &kernel : kernels) {
    if (!predict::utils::FindNodeInMap(node_indexs_, kernel)) {
      MS_LOG(ERROR) << "can not find any pk_key in inited node_indexs map";
      return false;
    }
    auto kernel_key = node_indexs_[kernel.get()];
    std::unique_ptr<NodeDef> ms_node(new NodeDef);
    ms_node->fmkType = mindspore::predict::FmkType_CAFFE;
    std::unique_ptr<OpDefT> ms_op(new OpDefT());
    auto c_name = AnfAlgo::GetCNodeName(kernel);
    auto fun = predict::convert::OpAttrFactory::GetInstance()->GetPackFun(c_name);
    if (fun == nullptr) {
      MS_LOG(ERROR) << "get node [" << kernel->fullname_with_scope() << "] attr failed.";
      return false;
    } else if (!fun(kernel, ms_op.get())) {
      MS_LOG(ERROR) << "set node [" << kernel->fullname_with_scope() << "] attr failed.";
      return false;
    }
    ms_node->opDef = std::move(ms_op);
    auto output_size = AnfAlgo::GetOutputTensorNum(kernel);
    int nodeRefCount = SizeToInt(output_size);
    for (size_t j = 0; j < output_size; ++j) {
      TensorPtr device_tensor;
      if (convert_mode_ == kConvertCpuMode) {
        device_tensor = predict::utils::GetKernelCpuTensor(kernel, j);
      } else if (convert_mode_ == kConvertAscendMode) {
        device_tensor = predict::utils::GetKernelAscendTensor(kernel, j);
      }
      if (device_tensor == nullptr) {
        return false;
      }
      all_output_tensors_[kernel_key].push_back(device_tensor);
      if (!SetOpOutputIdx(kernel, device_tensor, tensor_cache, nodeRefCount, j, ms_node.get())) {
        return false;
      }
    }
    tmp_op_nodes_.emplace_back(ms_node.release());
  }
  return true;
}

bool Kernel2Ms::KernelGraph2MsGraph(const KernelGraphPtr &kernel_graph_ptr) {
  MS_EXCEPTION_IF_NULL(kernel_graph_ptr);
  graph_index_ = 0;
  all_output_tensors_.clear();
  node_indexs_.clear();
  index_nodes_.clear();
  std::unique_ptr<SubGraphDefT> sub_ms_graph(new SubGraphDefT());
  if (!InitGraphIndx(kernel_graph_ptr)) {
    return false;
  }
  TransformGraphIndx();
  tensor_cache_ptr_ = std::make_shared<TensorCache>();
  // foreach node to init it's real output tensor
  if (!SetGraphInputTensors(kernel_graph_ptr, tensor_cache_ptr_, sub_ms_graph.get())) {
    return false;
  }
  // Get KernelGraph value node
  if (!SetGraphValueTensors(kernel_graph_ptr, tensor_cache_ptr_)) {
    return false;
  }
  // Get KernelGraph apply_kernel && add opNode
  if (!SetGraphOpTensors(kernel_graph_ptr, tensor_cache_ptr_, sub_ms_graph.get())) {
    return false;
  }
  // Get KernelGraph outputs
  if (!SetGraphOutputIdx(kernel_graph_ptr, tensor_cache_ptr_, sub_ms_graph.get(), &all_output_tensors_)) {
    return false;
  }
  auto kernels = kernel_graph_ptr->execution_order();
  for (size_t i = 0; i < kernels.size(); ++i) {
    auto ms_node = tmp_op_nodes_[i];
    if (!SetOpInputIdx(kernels[i], tensor_cache_ptr_, ms_node)) {
      return false;
    }
    std::unique_ptr<NodeDef> ms_node_tmp(ms_node);
    sub_ms_graph->nodes.emplace_back(std::move(ms_node_tmp));
  }
  if (!SetAllTensors(tensor_cache_ptr_, sub_ms_graph.get())) {
    return false;
  }
  if (!SetMemResue()) {
    return false;
  }
  sub_ms_graph_ = std::move(sub_ms_graph);
  sub_ms_graph_->name = "default_sub_graph";
  return true;
}

bool Kernel2Ms::CheckInputSizes(const std::vector<TensorPtr> &input_tensors,
                                const std::vector<uint32_t> &all_input_idxs) {
  if (input_tensors.size() != all_input_idxs.size()) {
    MS_LOG(EXCEPTION) << "real input tensors size:" << input_tensors.size()
                      << "not equal converted tesnors size:" << all_input_idxs.size() << "the graph has changed";
  }
  for (auto in : all_input_idxs) {
    if (in < sub_ms_graph_->allTensors.size()) {
      auto real_tensor = input_tensors[in];
      auto convert_dims = sub_ms_graph_->allTensors[in]->dims;
      auto real_dims = real_tensor->shape();
      if (real_dims.size() != convert_dims.size()) {
        return false;
      } else {
        for (size_t i = 0; i < convert_dims.size(); ++i) {
          if (convert_dims[i] != real_dims[i]) {
            return false;
          }
        }
      }
    } else {
      MS_LOG(EXCEPTION) << "index: " << in << "in all_input_idxs is valid";
    }
  }
  return true;
}

void Kernel2Ms::ReleaseContextRes() {
  tmp_op_nodes_.clear();
  node_indexs_.clear();
  index_nodes_.clear();
  tensor_cache_ptr_ = nullptr;
  all_output_tensors_.clear();
}

bool Kernel2Ms::KernelInput2MS(const std::vector<TensorPtr> &input_tensors) {
  const std::unordered_map<int, std::vector<ExTensorPtr>> &cache_tensors = tensor_cache_ptr_->GetCachedTensor();
  if (cache_tensors.empty()) {
    return false;
  }
  auto all_weights_idxs = GetAllInputWeightIdxs();
  auto all_input_idxs = GetAllInputIdxs();
  auto real_input_size = input_tensors.size();
  // check tensor size
  bool ret = CheckInputSizes(input_tensors, all_input_idxs);
  std::vector<uint32_t> match_to_rel_idxs;
  // indx order not matched,macth to it
  if (!ret) {
    for (auto idx : all_weights_idxs) {
      auto macth_idx = real_input_size - idx;
      match_to_rel_idxs.push_back(macth_idx);
    }
  } else {
    match_to_rel_idxs = all_weights_idxs;
  }
  if (match_to_rel_idxs.size() == all_weights_idxs.size()) {
    for (size_t j = 0; j < all_weights_idxs.size(); ++j) {
      auto cache_idx = all_weights_idxs[j];
      auto match_idx = match_to_rel_idxs[j];
      auto real_tensor = input_tensors[match_idx];
      auto real_size = LongToSize(real_tensor->data().nbytes());
      auto real_data = real_tensor->data_c(false);
      MS_EXCEPTION_IF_NULL(real_data);
      if (sub_ms_graph_->allTensors[cache_idx] != nullptr) {
        sub_ms_graph_->allTensors[cache_idx]->data.resize(real_size);
      }
      if (memcpy_s(sub_ms_graph_->allTensors[cache_idx]->data.data(), real_size, real_data, real_size) != 0) {
        MS_LOG(ERROR) << "KernelInput2MS memcpy_s failed";
        return false;
      }
    }
  }
  ReleaseContextRes();
  return true;
}

bool Kernel2Ms::SaveDeviceModel(const std::shared_ptr<GraphDefT> &new_ms_graph_ptr, const std::string &save_path_name) {
  MS_EXCEPTION_IF_NULL(new_ms_graph_ptr);
  return predict::utils::SaveDeviceModelUtil(new_ms_graph_ptr, save_path_name, sub_ms_graph_.release());
}
}  // namespace executor
}  // namespace mindspore
