// Copyright (c) 2016 Google Inc.
//
// 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 "util/parse_number.h"

#include <functional>
#include <iomanip>
#include <memory>
#include <sstream>
#include <string>
#include <tuple>

#include "util/hex_float.h"

namespace spvutils {

namespace {
// A helper class that temporarily stores error messages and dump the messages
// to a string which given as as pointer when it is destructed. If the given
// pointer is a nullptr, this class does not store error message.
class ErrorMsgStream {
 public:
  explicit ErrorMsgStream(std::string* error_msg_sink)
      : error_msg_sink_(error_msg_sink) {
    if (error_msg_sink_) stream_.reset(new std::ostringstream());
  }
  ~ErrorMsgStream() {
    if (error_msg_sink_ && stream_) *error_msg_sink_ = stream_->str();
  }
  template <typename T>
  ErrorMsgStream& operator<<(T val) {
    if (stream_) *stream_ << val;
    return *this;
  }

 private:
  std::unique_ptr<std::ostringstream> stream_;
  // The destination string to which this class dump the error message when
  // destructor is called.
  std::string* error_msg_sink_;
};
}

EncodeNumberStatus ParseAndEncodeIntegerNumber(
    const char* text, const NumberType& type,
    std::function<void(uint32_t)> emit, std::string* error_msg) {
  if (!text) {
    ErrorMsgStream(error_msg) << "The given text is a nullptr";
    return EncodeNumberStatus::kInvalidText;
  }

  if (!IsIntegral(type)) {
    ErrorMsgStream(error_msg) << "The expected type is not a integer type";
    return EncodeNumberStatus::kInvalidUsage;
  }

  const uint32_t bit_width = AssumedBitWidth(type);

  if (bit_width > 64) {
    ErrorMsgStream(error_msg) << "Unsupported " << bit_width
                              << "-bit integer literals";
    return EncodeNumberStatus::kUnsupported;
  }

  // Either we are expecting anything or integer.
  bool is_negative = text[0] == '-';
  bool can_be_signed = IsSigned(type);

  if (is_negative && !can_be_signed) {
    ErrorMsgStream(error_msg)
        << "Cannot put a negative number in an unsigned literal";
    return EncodeNumberStatus::kInvalidUsage;
  }

  const bool is_hex = text[0] == '0' && (text[1] == 'x' || text[1] == 'X');

  uint64_t decoded_bits;
  if (is_negative) {
    int64_t decoded_signed = 0;

    if (!ParseNumber(text, &decoded_signed)) {
      ErrorMsgStream(error_msg) << "Invalid signed integer literal: " << text;
      return EncodeNumberStatus::kInvalidText;
    }

    if (!CheckRangeAndIfHexThenSignExtend(decoded_signed, type, is_hex,
                                          &decoded_signed)) {
      ErrorMsgStream(error_msg)
          << "Integer " << (is_hex ? std::hex : std::dec) << std::showbase
          << decoded_signed << " does not fit in a " << std::dec << bit_width
          << "-bit " << (IsSigned(type) ? "signed" : "unsigned") << " integer";
      return EncodeNumberStatus::kInvalidText;
    }
    decoded_bits = decoded_signed;
  } else {
    // There's no leading minus sign, so parse it as an unsigned integer.
    if (!ParseNumber(text, &decoded_bits)) {
      ErrorMsgStream(error_msg) << "Invalid unsigned integer literal: " << text;
      return EncodeNumberStatus::kInvalidText;
    }
    if (!CheckRangeAndIfHexThenSignExtend(decoded_bits, type, is_hex,
                                          &decoded_bits)) {
      ErrorMsgStream(error_msg)
          << "Integer " << (is_hex ? std::hex : std::dec) << std::showbase
          << decoded_bits << " does not fit in a " << std::dec << bit_width
          << "-bit " << (IsSigned(type) ? "signed" : "unsigned") << " integer";
      return EncodeNumberStatus::kInvalidText;
    }
  }
  if (bit_width > 32) {
    uint32_t low = uint32_t(0x00000000ffffffff & decoded_bits);
    uint32_t high = uint32_t((0xffffffff00000000 & decoded_bits) >> 32);
    emit(low);
    emit(high);
  } else {
    emit(uint32_t(decoded_bits));
  }
  return EncodeNumberStatus::kSuccess;
}

EncodeNumberStatus ParseAndEncodeFloatingPointNumber(
    const char* text, const NumberType& type,
    std::function<void(uint32_t)> emit, std::string* error_msg) {
  if (!text) {
    ErrorMsgStream(error_msg) << "The given text is a nullptr";
    return EncodeNumberStatus::kInvalidText;
  }

  if (!IsFloating(type)) {
    ErrorMsgStream(error_msg) << "The expected type is not a float type";
    return EncodeNumberStatus::kInvalidUsage;
  }

  const auto bit_width = AssumedBitWidth(type);
  switch (bit_width) {
    case 16: {
      HexFloat<FloatProxy<spvutils::Float16>> hVal(0);
      if (!ParseNumber(text, &hVal)) {
        ErrorMsgStream(error_msg) << "Invalid 16-bit float literal: " << text;
        return EncodeNumberStatus::kInvalidText;
      }
      // getAsFloat will return the spvutils::Float16 value, and get_value
      // will return a uint16_t representing the bits of the float.
      // The encoding is therefore correct from the perspective of the SPIR-V
      // spec since the top 16 bits will be 0.
      emit(static_cast<uint32_t>(hVal.value().getAsFloat().get_value()));
      return EncodeNumberStatus::kSuccess;
    } break;
    case 32: {
      HexFloat<FloatProxy<float>> fVal(0.0f);
      if (!ParseNumber(text, &fVal)) {
        ErrorMsgStream(error_msg) << "Invalid 32-bit float literal: " << text;
        return EncodeNumberStatus::kInvalidText;
      }
      emit(BitwiseCast<uint32_t>(fVal));
      return EncodeNumberStatus::kSuccess;
    } break;
    case 64: {
      HexFloat<FloatProxy<double>> dVal(0.0);
      if (!ParseNumber(text, &dVal)) {
        ErrorMsgStream(error_msg) << "Invalid 64-bit float literal: " << text;
        return EncodeNumberStatus::kInvalidText;
      }
      uint64_t decoded_val = BitwiseCast<uint64_t>(dVal);
      uint32_t low = uint32_t(0x00000000ffffffff & decoded_val);
      uint32_t high = uint32_t((0xffffffff00000000 & decoded_val) >> 32);
      emit(low);
      emit(high);
      return EncodeNumberStatus::kSuccess;
    } break;
    default:
      break;
  }
  ErrorMsgStream(error_msg) << "Unsupported " << bit_width
                            << "-bit float literals";
  return EncodeNumberStatus::kUnsupported;
}

EncodeNumberStatus ParseAndEncodeNumber(const char* text,
                                        const NumberType& type,
                                        std::function<void(uint32_t)> emit,
                                        std::string* error_msg) {
  if (!text) {
    ErrorMsgStream(error_msg) << "The given text is a nullptr";
    return EncodeNumberStatus::kInvalidText;
  }

  if (IsUnknown(type)) {
    ErrorMsgStream(error_msg)
        << "The expected type is not a integer or float type";
    return EncodeNumberStatus::kInvalidUsage;
  }

  // If we explicitly expect a floating-point number, we should handle that
  // first.
  if (IsFloating(type)) {
    return ParseAndEncodeFloatingPointNumber(text, type, emit, error_msg);
  }

  return ParseAndEncodeIntegerNumber(text, type, emit, error_msg);
}

}  // namespace spvutils
