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

/*!
 * \file codegen_params.cc
 */

#include "codegen_params.h"

#include <dlpack/dlpack.h>

#include <cmath>
#include <iomanip>
#include <memory>
#include <string>

namespace tvm {
namespace codegen {

/*! \brief maximum line length of generated parameters, including indent. */
static constexpr const int kMaxLineLength = 80;

static int ComputeNumElementsPerRow(int one_element_size_bytes, int indent_chars) {
  if (one_element_size_bytes > kMaxLineLength - indent_chars) {
    return 1;
  }
  // When multiple elements fit per line, divide the available space by the size of one element,
  // and return the largest power of 2 less than the result. Using power-of-2-sized elements allows
  // for easily traversing the generated code.
  int elements_per_row = (kMaxLineLength - indent_chars) / one_element_size_bytes;

  // Implementation of fls. Iteratively clear the LSB until one bit remains.
  while ((elements_per_row & (elements_per_row - 1)) > 0) {
    elements_per_row &= elements_per_row - 1;
  }
  return elements_per_row;
}

template <typename T, typename Enable = std::enable_if<std::is_integral<T>::value>>
void PrintIntegralArray(void* data, size_t num_elements, int indent_chars, std::ostream& os,
                        const std::string& eol) {
  int one_element_size_bytes = (sizeof(T) / 4) + (2 /* "0x" */) + (2 /* ", " */);
  if (std::is_signed<T>::value) {
    one_element_size_bytes += 1;  // sign character
    if (sizeof(T) == 64 / 8) {
      one_element_size_bytes += 2;  // "LL"
    }
  } else {
    if (sizeof(T) == 64 / 8) {
      one_element_size_bytes += 3;  // "ULL"
    }
  }

  size_t elements_per_row = ComputeNumElementsPerRow(one_element_size_bytes, indent_chars);
  std::string indent_str(indent_chars, ' ');

  for (size_t i = 0; i < num_elements; i++) {
    if ((i % elements_per_row) == 0) {
      os << indent_str;
    }
    int64_t elem = static_cast<T*>(data)[i];
    if (std::is_signed<T>::value) {
      uint64_t to_print;
      if (elem < 0) {
        os << "-";
        to_print = -elem;
      } else {
        os << "+";
        to_print = elem;
      }
      os << "0x" << std::setw(sizeof(T) * 8 / 4) << static_cast<std::uint64_t>(to_print);
      if (sizeof(T) == 64 / 8) {
        os << "LL";
      }
    } else {
      os << "0x" << std::setw(sizeof(T) * 8 / 4) << static_cast<std::uint64_t>(elem);
      if (sizeof(T) == 64 / 8) {
        os << "ULL";
      }
    }
    if (i < num_elements - 1) {
      os << ", ";
    }
    if ((i % elements_per_row) == elements_per_row - 1) {
      os << eol;
    }
  }

  if ((num_elements % elements_per_row) != 0) {
    os << eol;
  }
}

template <typename T, typename Enable = std::enable_if<std::is_floating_point<T>::value>>
void PrintFloatingPointArray(void* data, size_t num_elements, int indent_chars, std::ostream& os,
                             const std::string& eol) {
  // Floats and doubles are printed as hex but casted.
  int one_element_size_bytes = (sizeof(T) / 4) + (2 /* "0x" */) + (2 /* ", " */) + 1 /* sign */ +
                               1 /* decimal point */ + 1 /* exponent sign */;
  if (sizeof(T) == 64 / 8) {
    one_element_size_bytes += 2; /* 4 decimal digits in exponent, relative to bits / 4 */
  } else if (sizeof(T) == 32 / 8) {
    one_element_size_bytes += 1; /* extra decimal digit in exponent, relative to bits / 4 */
  }

  size_t elements_per_row = ComputeNumElementsPerRow(one_element_size_bytes, indent_chars);
  std::string indent_str(indent_chars, ' ');

  std::stringstream ss;
  if (std::is_signed<T>::value) {
    ss.setf(std::ios::hex | std::ios::showbase | std::ios::fixed | std::ios::scientific,
            std::ios::basefield | std::ios::showbase | std::ios::floatfield);
  } else {
    ss.setf(std::ios::hex | std::ios::fixed | std::ios::scientific,
            std::ios::basefield | std::ios::showbase | std::ios::floatfield);
  }
  for (size_t i = 0; i < num_elements; i++) {
    if ((i % elements_per_row) == 0) {
      os << indent_str;
    }

    T elem = static_cast<T*>(data)[i];
    if (std::isinf(elem)) {
      // C99 standard.
      os << (elem < 0 ? "-" : " ") << std::setw(one_element_size_bytes - 1) << "INFINITY";
    } else if (std::isnan(elem)) {
      // GNU extension, implemenatation-dependent.
      os << std::setw(one_element_size_bytes) << "NAN";
    } else {
      ss << elem;
      os << std::setw(one_element_size_bytes) << ss.str();
      ss.str("");
    }
    if (i < num_elements - 1) {
      os << ", ";
    }
    if ((i % elements_per_row) == elements_per_row - 1) {
      os << eol;
    }
  }

  if ((num_elements % elements_per_row) != 0) {
    os << eol;
  }
}

void TensorDataToC(::tvm::runtime::Tensor arr, int indent_chars, std::ostream& os,
                   const std::string& eol) {
  auto arr_type = arr.DataType();
  TVM_FFI_ICHECK_EQ(arr_type.lanes, 1)
      << "CodegenParams: only support generating 1-lane parameters; saw " << arr_type.lanes;

  auto shape = arr.Shape();
  int num_elements = 1;
  for (auto shape_elem : shape) {
    num_elements *= shape_elem;
  }

  auto old_fmtflags = os.flags();
  os.setf(std::ios::internal | std::ios::hex,
          std::ios::adjustfield | std::ios::basefield | std::ios::showbase);
  os.fill('0');
  switch (static_cast<DLDataTypeCode>(arr_type.code)) {
    case DLDataTypeCode::kDLInt:
      TVM_FFI_ICHECK(arr_type.bits == 8 || arr_type.bits == 16 || arr_type.bits == 32 ||
                     arr_type.bits == 64)
          << "CodegenParams: only support generating 8-, 16-, 32-, or 64-bit integer params; saw "
          << arr_type.bits << "-bit array";
      if (arr_type.bits == 8) {
        PrintIntegralArray<int8_t>(arr->data, num_elements, indent_chars, os, eol);
      } else if (arr_type.bits == 16) {
        PrintIntegralArray<int16_t>(arr->data, num_elements, indent_chars, os, eol);
      } else if (arr_type.bits == 32) {
        PrintIntegralArray<int32_t>(arr->data, num_elements, indent_chars, os, eol);
      } else if (arr_type.bits == 64) {
        PrintIntegralArray<int64_t>(arr->data, num_elements, indent_chars, os, eol);
      } else {
        TVM_FFI_ICHECK(false) << "should not get here";
      }
      break;

    case DLDataTypeCode::kDLUInt:
      TVM_FFI_ICHECK(arr_type.bits == 8 || arr_type.bits == 16 || arr_type.bits == 32 ||
                     arr_type.bits == 64)
          << "CodegenParams: only support generating 8-, 16-, 32-, or 64-bit integer params; saw "
          << arr_type.bits << "-bit array";

      if (arr_type.bits == 8) {
        PrintIntegralArray<uint8_t>(arr->data, num_elements, indent_chars, os, eol);
      } else if (arr_type.bits == 16) {
        PrintIntegralArray<uint16_t>(arr->data, num_elements, indent_chars, os, eol);
      } else if (arr_type.bits == 32) {
        PrintIntegralArray<uint32_t>(arr->data, num_elements, indent_chars, os, eol);
      } else if (arr_type.bits == 64) {
        PrintIntegralArray<uint64_t>(arr->data, num_elements, indent_chars, os, eol);
      } else {
        TVM_FFI_ICHECK(false) << "should not get here";
      }
      break;

    case DLDataTypeCode::kDLFloat: {
      os.fill(' ');
      os.setf(std::ios::left, std::ios::adjustfield);
      if (arr_type.bits == 16) {
        // NOTE: print types not widely supported by C as uint16_t.
        PrintIntegralArray<uint16_t>(arr->data, num_elements, indent_chars, os, eol);
      } else if (arr_type.bits == 32) {
        PrintFloatingPointArray<float>(arr->data, num_elements, indent_chars, os, eol);
      } else if (arr_type.bits == 64) {
        PrintFloatingPointArray<double>(arr->data, num_elements, indent_chars, os, eol);
      } else {
        TVM_FFI_ICHECK(false) << "CodegenParams: only support 32- or 64-bit floating point; saw "
                              << arr_type.bits << "-bit array";
      }
      break;
    }

    case DLDataTypeCode::kDLBfloat: {
      // NOTE: print types not widely supported by C as uint16_t.
      TVM_FFI_ICHECK(arr_type.bits == 16)
          << "CodegenParams: only support generating 16-bit bfloat params; saw " << arr_type.bits
          << "-bit array";
      PrintIntegralArray<uint16_t>(arr->data, num_elements, indent_chars, os, eol);
      break;
    }

    default:
      TVM_FFI_ICHECK(false) << "Data type '" << ffi::DLDataTypeToString(arr_type)
                            << "' not supported";
  }

  os.flags(old_fmtflags);
}

}  // namespace codegen
}  // namespace tvm
