/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
#ifndef TVM_SCRIPT_PRINTER_UTILS_H_
#define TVM_SCRIPT_PRINTER_UTILS_H_

#include <tvm/ffi/cast.h>
#include <tvm/ffi/extra/dataclass.h>
#include <tvm/ffi/extra/json.h>
#include <tvm/ffi/extra/serialization.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/base.h>
#include <tvm/runtime/logging.h>
#include <tvm/script/printer/ir_docsifier.h>
#include <tvm/script/printer/printer.h>

#include <sstream>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

#include "../../support/str_escape.h"

namespace tvm {
namespace script {
namespace printer {

// Note: the `TVM_SCRIPT_REPR` macro is intentionally duplicated in each
// dialect-local `src/<dialect>/script/printer/utils.h`. Keeping a single
// definition here would force the dialect headers to depend on this shared
// header, which the per-dialect restructure aims to avoid for cross-directory
// references. See each `<dialect>/script/printer/utils.h` for the macro.

inline std::string Docsify(const ffi::ObjectRef& obj, const IRDocsifier& d, const Frame& f,
                           const PrinterConfig& cfg) {
  Doc doc = d->AsDoc(obj, AccessPath::Root());
  bool move_source_paths = false;
  if (const auto* expr_doc = doc.as<ExprDocNode>()) {
    if (!cfg->verbose_expr) {
      f->stmts.clear();
    }
    f->stmts.push_back(ExprStmtDoc(ffi::GetRef<ExprDoc>(expr_doc)));
  } else if (const auto* stmt_doc = doc.as<StmtDocNode>()) {
    f->stmts.push_back(ffi::GetRef<StmtDoc>(stmt_doc));
  } else if (const auto* stmt_block = doc.as<StmtBlockDocNode>()) {
    for (const StmtDoc& d : stmt_block->stmts) {
      f->stmts.push_back(d);
    }
    move_source_paths = true;
  } else {
    TVM_FFI_THROW(TypeError) << "Unexpected doc type: " << doc->GetTypeKey();
  }
  std::ostringstream os;
  if (!d->metadata.empty()) {
    if (d->cfg->show_meta) {
      os << "metadata = tvm.ir.load_json(\"\"\""
         << support::StrEscape(
                ffi::json::Stringify(
                    ffi::ToJSONGraph(
                        ffi::Map<ffi::String, ffi::Any>(d->metadata.begin(), d->metadata.end()),
                        ffi::json::Object{{"tvm_version", TVM_VERSION}}),
                    2),
                false, false)
         << "\"\"\")\n";
    } else {
      f->stmts.push_back(
          CommentDoc("Metadata omitted. Use show_meta=True in script() method to show it."));
    }
  }
  if (move_source_paths) {
    StmtBlockDoc new_doc(f->stmts);
    new_doc->source_paths = std::move(doc->source_paths);
    os << DocToPythonScript(new_doc, cfg);
  } else {
    os << DocToPythonScript(StmtBlockDoc(f->stmts), cfg);
  }
  return os.str();
}

/*! \brief Creates the IR common prefix, which is by default `I` */
inline ExprDoc IR(const IRDocsifier& d, const ffi::String& attr) {
  d->ir_usage.insert("ir");
  return IdDoc(d->cfg->ir_prefix)->Attr(attr);
}

/*! \brief Creates the TIR common prefix, which is by default `T` */
inline ExprDoc TIR(const IRDocsifier& d, const ffi::String& attr) {
  d->ir_usage.insert("tirx");
  return IdDoc(d->cfg->GetExtraConfig<ffi::String>("tirx.prefix", "T"))->Attr(attr);
}

/*! \brief Alias for TIR — historical TIRx name used by tirx printer code */
inline ExprDoc TIRx(const IRDocsifier& d, const ffi::String& attr) { return TIR(d, attr); }

/*! \brief Creates the Relax common prefix, which is by default `R` */
inline ExprDoc Relax(const IRDocsifier& d, const ffi::String& attr) {
  d->ir_usage.insert("relax");
  return IdDoc(d->cfg->GetExtraConfig<ffi::String>("relax.prefix", "R"))->Attr(attr);
}

inline std::string DType2Str(DLDataType dtype) {
  return (((dtype).code == kDLOpaqueHandle) && ((dtype).bits == 0) && ((dtype).lanes == 0))
             ? "void"
             : ffi::DLDataTypeToString(dtype);
}

/*! \brief Add headers as comments to doc if needed */
inline Doc HeaderWrapper(const IRDocsifier& d, const Doc& doc) {
  if (d->ir_usage.size()) {
    ffi::Array<StmtDoc> stmts;
    if (d->ir_usage.count("ir")) {
      stmts.push_back(CommentDoc("from tvm.script import ir as " + d->cfg->ir_prefix));
    }
    if (d->ir_usage.count("tirx")) {
      stmts.push_back(CommentDoc("from tvm.script import tirx as " +
                                 d->cfg->GetExtraConfig<ffi::String>("tirx.prefix", "T")));
      // Layout sugar like `4 @ Axis.laneid` references registered axes via the
      // `Axis` class attribute. Mirror the `Axis` injection in `_default_globals`
      // so readers see the dependency. Decorative only.
      stmts.push_back(CommentDoc("from tvm.tirx.layout import Axis"));
    }
    if (d->ir_usage.count("relax")) {
      stmts.push_back(CommentDoc("from tvm.script import relax as " +
                                 d->cfg->GetExtraConfig<ffi::String>("relax.prefix", "R")));
    }
    stmts.push_back(CommentDoc(""));
    stmts.push_back(doc.as_or_throw<StmtDoc>());
    return StmtBlockDoc(stmts);
  }
  return doc;
}

/*! \brief Check if a string has multiple lines. */
inline bool HasMultipleLines(const std::string& str) {
  return str.find_first_of('\n') != std::string::npos;
}

inline ffi::Optional<ffi::String> GetBindingName(const IRDocsifier& d) {
  return d->cfg->binding_names.empty() ? ffi::Optional<ffi::String>(std::nullopt)
                                       : d->cfg->binding_names.back();
}

inline ffi::Optional<ffi::String> FindFunctionName(const IRDocsifier& d, const BaseFunc& f) {
  if (ffi::Optional<ffi::String> name = GetBindingName(d)) {
    return name.value();
  }
  if (ffi::Optional<ffi::String> sym = f->GetAttr<ffi::String>(tvm::attr::kGlobalSymbol)) {
    return sym.value();
  }
  return std::nullopt;
}

inline ffi::String GenerateUniqueName(std::string name_hint,
                                      const std::unordered_set<ffi::String>& defined_names) {
  for (char& c : name_hint) {
    if (c != '_' && !std::isalnum(c)) {
      c = '_';
    }
  }
  std::string name = name_hint;
  for (int i = 1; defined_names.count(name) > 0; ++i) {
    name = name_hint + "_" + std::to_string(i);
  }
  return name;
}

}  // namespace printer
}  // namespace script
}  // namespace tvm

#endif  // TVM_SCRIPT_PRINTER_UTILS_H_
