// Copyright (c) 2021 CINN Authors. All Rights Reserved.
//
// 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 "paddle/cinn/runtime/flags.h"

#include <gflags/gflags.h>
#include <glog/logging.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <unordered_set>

#include "paddle/cinn/common/target.h"

#ifdef CINN_WITH_CUDNN
DEFINE_bool(cinn_cudnn_deterministic,
            false,
            "Whether allow using an autotuning algorithm for convolution "
            "operator. The autotuning algorithm may be non-deterministic. If "
            "true, the algorithm is deterministic.");
#endif

using ::GFLAGS_NAMESPACE::BoolFromEnv;
using ::GFLAGS_NAMESPACE::Int32FromEnv;
using ::GFLAGS_NAMESPACE::Int64FromEnv;
using ::GFLAGS_NAMESPACE::StringFromEnv;

DEFINE_string(cinn_x86_builtin_code_root, StringFromEnv("FLAGS_cinn_x86_builtin_code_root", ""), "");
DEFINE_string(cinn_nvcc_cmd_path,
              StringFromEnv("FLAGS_cinn_nvcc_cmd_path", "/usr/local/cuda/bin"),
              "Setting nvcc default path!");

DEFINE_int32(cinn_parallel_compile_size,
             Int32FromEnv("FLAGS_cinn_parallel_compile_size", 16),
             "When use parallel compile, set the number of group compiled by each thread.");

DEFINE_int32(cinn_parallel_compile_thread,
             Int32FromEnv("FLAGS_cinn_parallel_compile_thread", -1),
             "How much thread the parallel compile used.");

DEFINE_bool(cinn_use_op_fusion, BoolFromEnv("FLAGS_cinn_use_op_fusion", true), "Whether to use op fusion pass.");

DEFINE_bool(cinn_use_common_subexpression_elimination,
            BoolFromEnv("FLAGS_cinn_use_common_subexpression_elimination", false),
            "Whether to use common subexpression elimination pass.");

DEFINE_string(cinn_custom_call_deny_ops,
              StringFromEnv("FLAGS_cinn_custom_call_deny_ops", ""),
              "a blacklist of op are denied by MarkCustomCallOps pass, separated by ;");

DEFINE_bool(cinn_use_custom_call,
            BoolFromEnv("FLAGS_cinn_use_custom_call", true),
            "Whether to use custom_call for ops with external_api registered");

DEFINE_bool(cinn_use_fill_constant_folding,
            BoolFromEnv("FLAGS_cinn_use_fill_constant_folding", false),
            "Whether use the FillConstantFolding pass.");

DEFINE_string(cinn_check_fusion_accuracy_pass,
              StringFromEnv("FLAGS_cinn_check_fusion_accuracy_pass", ""),
              "Check the correct of fusion kernels, if the results not satisfied 'allclose(rtol=1e-05f, atol=1e-08f)', "
              "report error and exited.");

DEFINE_bool(cinn_use_cuda_vectorize,
            BoolFromEnv("FLAGS_cinn_use_cuda_vectorize", false),
            "Whether use cuda vectroize on schedule config");

DEFINE_bool(cinn_ir_schedule,
            BoolFromEnv("FLAGS_cinn_ir_schedule", true),
            "Whether use reconstructed schedule primitives.");

DEFINE_bool(use_reduce_split_pass, BoolFromEnv("FLAGS_use_reduce_split_pass", false), "Whether use reduce split pass.");

DEFINE_bool(cinn_use_dense_merge_pass,
            BoolFromEnv("FLAGS_cinn_use_dense_merge_pass", false),
            "Whether use dense merge pass.");

DEFINE_bool(nvrtc_compile_to_cubin,
            BoolFromEnv("FLAGS_nvrtc_compile_to_cubin", false),
            "Whether nvrtc compile cuda source into cubin instead of ptx (only works after cuda-11.1).");

DEFINE_bool(cinn_compile_with_nvrtc,
            BoolFromEnv("FLAGS_cinn_compile_with_nvrtc", true),
            "Whether nvrtc compile cuda source with nvrtc(default nvcc).");

// FLAGS for performance analysis and accuracy debug
DEFINE_bool(cinn_sync_run,
            BoolFromEnv("FLAGS_cinn_sync_run", false),
            "Whether sync all devices after each instruction run, which is used for debug.");

DEFINE_string(cinn_self_check_accuracy,
              StringFromEnv("FLAGS_cinn_self_check_accuracy", ""),
              "Whether self-check accuracy after each instruction run, which is used for debug.");

DEFINE_int64(cinn_self_check_accuracy_num,
             Int64FromEnv("FLAGS_cinn_self_check_accuracy_num", 0L),
             "Set self-check accuracy print numel, which is used for debug.");

DEFINE_string(cinn_fusion_groups_graphviz_dir,
              StringFromEnv("FLAGS_cinn_fusion_groups_graphviz_dir", ""),
              "Specify the directory path of dot file of graph, which is used for debug.");

DEFINE_string(cinn_source_code_save_path,
              StringFromEnv("FLAGS_cinn_source_code_save_path", ""),
              "Specify the directory path of generated source code, which is used for debug.");

DEFINE_string(cinn_pass_visualize_dir,
              StringFromEnv("FLAGS_cinn_pass_visualize_dir", ""),
              "Specify the directory path of pass visualize file of graph, which is used for debug.");

DEFINE_bool(enable_auto_tuner, BoolFromEnv("FLAGS_enable_auto_tuner", false), "Whether enable auto tuner.");

DEFINE_bool(auto_schedule_use_cost_model,
            BoolFromEnv("FLAGS_auto_schedule_use_cost_model", true),
            "Whether to use cost model in auto schedule, this is an on-developing flag and it will be removed when "
            "cost model is stable.");

DEFINE_bool(enhance_vertical_fusion_with_recompute,
            BoolFromEnv("FLAGS_enhance_vertical_fusion_with_recompute", true),
            "Whether to enhance check logic on vertical fusion with recompute");

DEFINE_bool(verbose_function_register,
            BoolFromEnv("FLAGS_verbose_function_register", false),
            "Whether to verbose function regist log. This will only work if CINN build with flag -DWITH_DEBUG=ON.");

DEFINE_int32(
    cinn_profiler_state,
    Int32FromEnv("FLAGS_cinn_profiler_state", -1),
    "Specify the ProfilerState by Int in CINN, 0 for kDisabled, 1 for kCPU, 2 for kCUDA, 3 for kAll, default 0.");

namespace cinn {
namespace runtime {

bool CheckStringFlagTrue(const std::string& flag) {
  // from gflag FlagValue::ParseFrom:
  // https://github.com/gflags/gflags/blob/master/src/gflags.cc#L292
  static const std::unordered_set<std::string> kTrue = {"1", "t", "true", "y", "yes", "T", "True", "TRUE", "Y", "yes"};
  return kTrue.count(flag);
}

bool CheckStringFlagFalse(const std::string& flag) {
  // from gflag FlagValue::ParseFrom:
  // https://github.com/gflags/gflags/blob/master/src/gflags.cc#L292
  static const std::unordered_set<std::string> kFalse = {
      "0", "f", "false", "n", "no", "F", "False", "FALSE", "N", "No", "NO"};
  return flag.empty() || kFalse.count(flag);
}

void SetCinnCudnnDeterministic(bool state) {
#ifdef CINN_WITH_CUDNN
  FLAGS_cinn_cudnn_deterministic = state;
#else
  LOG(WARNING) << "CINN is compiled without cuDNN, this api is invalid!";
#endif
}

bool GetCinnCudnnDeterministic() {
#ifdef CINN_WITH_CUDNN
  return FLAGS_cinn_cudnn_deterministic;
#else
  LOG(FATAL) << "CINN is compiled without cuDNN, this api is invalid!";
  return false;
#endif
}

unsigned long long RandomSeed::seed_ = 0ULL;

unsigned long long RandomSeed::GetOrSet(unsigned long long seed) {
  if (seed != 0ULL) {
    seed_ = seed;
  }
  return seed_;
}

unsigned long long RandomSeed::Clear() {
  auto old_seed = seed_;
  seed_         = 0ULL;
  return old_seed;
}

bool CanUseNvccCompiler() {
  std::string nvcc_dir = FLAGS_cinn_nvcc_cmd_path + "/nvcc";
  return (access(nvcc_dir.c_str(), 0) == -1 ? false : true) && (!FLAGS_cinn_compile_with_nvrtc);
}

bool IsCompiledWithCUDA() {
#if !defined(CINN_WITH_CUDA)
  return false;
#else
  return true;
#endif
}

bool IsCompiledWithCUDNN() {
#if !defined(CINN_WITH_CUDNN)
  return false;
#else
  return true;
#endif
}

common::Target CurrentTarget::target_ = common::DefaultTarget();

void CurrentTarget::SetCurrentTarget(const common::Target& target) {
  if (!IsCompiledWithCUDA() && target.arch == common::Target::Arch::NVGPU) {
    LOG(FATAL) << "Current CINN version does not support NVGPU, please try to recompile with -DWITH_CUDA.";
  } else {
    target_ = target;
  }
}

common::Target& CurrentTarget::GetCurrentTarget() { return target_; }

}  // namespace runtime
}  // namespace cinn
