// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Author: kenton@google.com (Kenton Varda)
//  Based on original Protocol Buffers design by
//  Sanjay Ghemawat, Jeff Dean, and others.

#include <herogoogle/protobuf/io/printer.h>

#include <cctype>

#include <herogoogle/protobuf/stubs/logging.h>
#include <herogoogle/protobuf/stubs/common.h>
#include <herogoogle/protobuf/io/zero_copy_stream.h>

namespace herogoogle {
namespace protobuf {
namespace io {

Printer::Printer(ZeroCopyOutputStream* output, char variable_delimiter)
    : variable_delimiter_(variable_delimiter),
      output_(output),
      buffer_(NULL),
      buffer_size_(0),
      offset_(0),
      at_start_of_line_(true),
      failed_(false),
      annotation_collector_(NULL) {}

Printer::Printer(ZeroCopyOutputStream* output, char variable_delimiter,
                 AnnotationCollector* annotation_collector)
    : variable_delimiter_(variable_delimiter),
      output_(output),
      buffer_(NULL),
      buffer_size_(0),
      offset_(0),
      at_start_of_line_(true),
      failed_(false),
      annotation_collector_(annotation_collector) {}

Printer::~Printer() {
  // Only BackUp() if we have called Next() at least once and never failed.
  if (buffer_size_ > 0 && !failed_) {
    output_->BackUp(buffer_size_);
  }
}

bool Printer::GetSubstitutionRange(const char* varname,
                                   std::pair<size_t, size_t>* range) {
  std::map<std::string, std::pair<size_t, size_t> >::const_iterator iter =
      substitutions_.find(varname);
  if (iter == substitutions_.end()) {
    GOOGLE_LOG(DFATAL) << " Undefined variable in annotation: " << varname;
    return false;
  }
  if (iter->second.first > iter->second.second) {
    GOOGLE_LOG(DFATAL) << " Variable used for annotation used multiple times: "
                << varname;
    return false;
  }
  *range = iter->second;
  return true;
}

void Printer::Annotate(const char* begin_varname, const char* end_varname,
                       const std::string& file_path,
                       const std::vector<int>& path) {
  if (annotation_collector_ == NULL) {
    // Can't generate signatures with this Printer.
    return;
  }
  std::pair<size_t, size_t> begin, end;
  if (!GetSubstitutionRange(begin_varname, &begin) ||
      !GetSubstitutionRange(end_varname, &end)) {
    return;
  }
  if (begin.first > end.second) {
    GOOGLE_LOG(DFATAL) << "  Annotation has negative length from " << begin_varname
                << " to " << end_varname;
  } else {
    annotation_collector_->AddAnnotation(begin.first, end.second, file_path,
                                         path);
  }
}

void Printer::Print(const std::map<std::string, std::string>& variables,
                    const char* text) {
  int size = strlen(text);
  int pos = 0;  // The number of bytes we've written so far.
  substitutions_.clear();
  line_start_variables_.clear();

  for (int i = 0; i < size; i++) {
    if (text[i] == '\n') {
      // Saw newline.  If there is more text, we may need to insert an indent
      // here.  So, write what we have so far, including the '\n'.
      WriteRaw(text + pos, i - pos + 1);
      pos = i + 1;

      // Setting this true will cause the next WriteRaw() to insert an indent
      // first.
      at_start_of_line_ = true;
      line_start_variables_.clear();

    } else if (text[i] == variable_delimiter_) {
      // Saw the start of a variable name.

      // Write what we have so far.
      WriteRaw(text + pos, i - pos);
      pos = i + 1;

      // Find closing delimiter.
      const char* end = strchr(text + pos, variable_delimiter_);
      if (end == NULL) {
        GOOGLE_LOG(DFATAL) << " Unclosed variable name.";
        end = text + pos;
      }
      int endpos = end - text;

      std::string varname(text + pos, endpos - pos);
      if (varname.empty()) {
        // Two delimiters in a row reduce to a literal delimiter character.
        WriteRaw(&variable_delimiter_, 1);
      } else {
        // Replace with the variable's value.
        std::map<std::string, std::string>::const_iterator iter =
            variables.find(varname);
        if (iter == variables.end()) {
          GOOGLE_LOG(DFATAL) << " Undefined variable: " << varname;
        } else {
          if (at_start_of_line_ && iter->second.empty()) {
            line_start_variables_.push_back(varname);
          }
          WriteRaw(iter->second.data(), iter->second.size());
          std::pair<std::map<std::string, std::pair<size_t, size_t> >::iterator,
                    bool>
              inserted = substitutions_.insert(std::make_pair(
                  varname,
                  std::make_pair(offset_ - iter->second.size(), offset_)));
          if (!inserted.second) {
            // This variable was used multiple times.  Make its span have
            // negative length so we can detect it if it gets used in an
            // annotation.
            inserted.first->second = std::make_pair(1, 0);
          }
        }
      }

      // Advance past this variable.
      i = endpos;
      pos = endpos + 1;
    }
  }

  // Write the rest.
  WriteRaw(text + pos, size - pos);
}

void Printer::Indent() { indent_ += "  "; }

void Printer::Outdent() {
  if (indent_.empty()) {
    GOOGLE_LOG(DFATAL) << " Outdent() without matching Indent().";
    return;
  }

  indent_.resize(indent_.size() - 2);
}

void Printer::PrintRaw(const std::string& data) {
  WriteRaw(data.data(), data.size());
}

void Printer::PrintRaw(const char* data) {
  if (failed_) return;
  WriteRaw(data, strlen(data));
}

void Printer::WriteRaw(const char* data, int size) {
  if (failed_) return;
  if (size == 0) return;

  if (at_start_of_line_ && (size > 0) && (data[0] != '\n')) {
    // Insert an indent.
    at_start_of_line_ = false;
    CopyToBuffer(indent_.data(), indent_.size());
    if (failed_) return;
    // Fix up empty variables (e.g., "{") that should be annotated as
    // coming after the indent.
    for (std::vector<std::string>::iterator i = line_start_variables_.begin();
         i != line_start_variables_.end(); ++i) {
      substitutions_[*i].first += indent_.size();
      substitutions_[*i].second += indent_.size();
    }
  }

  // If we're going to write any data, clear line_start_variables_, since
  // we've either updated them in the block above or they no longer refer to
  // the current line.
  line_start_variables_.clear();

  CopyToBuffer(data, size);
}

bool Printer::Next() {
  do {
    void* void_buffer;
    if (!output_->Next(&void_buffer, &buffer_size_)) {
      failed_ = true;
      return false;
    }
    buffer_ = reinterpret_cast<char*>(void_buffer);
  } while (buffer_size_ == 0);
  return true;
}

void Printer::CopyToBuffer(const char* data, int size) {
  if (failed_) return;
  if (size == 0) return;

  while (size > buffer_size_) {
    // Data exceeds space in the buffer.  Copy what we can and request a
    // new buffer.
    if (buffer_size_ > 0) {
      memcpy(buffer_, data, buffer_size_);
      offset_ += buffer_size_;
      data += buffer_size_;
      size -= buffer_size_;
    }
    void* void_buffer;
    failed_ = !output_->Next(&void_buffer, &buffer_size_);
    if (failed_) return;
    buffer_ = reinterpret_cast<char*>(void_buffer);
  }

  // Buffer is big enough to receive the data; copy it.
  memcpy(buffer_, data, size);
  buffer_ += size;
  buffer_size_ -= size;
  offset_ += size;
}

void Printer::IndentIfAtStart() {
  if (at_start_of_line_) {
    CopyToBuffer(indent_.data(), indent_.size());
    at_start_of_line_ = false;
  }
}

void Printer::FormatInternal(const std::vector<std::string>& args,
                             const std::map<std::string, std::string>& vars,
                             const char* format) {
  auto save = format;
  int arg_index = 0;
  std::vector<AnnotationCollector::Annotation> annotations;
  while (*format) {
    char c = *format++;
    switch (c) {
      case '$':
        format = WriteVariable(args, vars, format, &arg_index, &annotations);
        continue;
      case '\n':
        at_start_of_line_ = true;
        line_start_variables_.clear();
        break;
      default:
        IndentIfAtStart();
        break;
    }
    push_back(c);
  }
  if (arg_index != static_cast<int>(args.size())) {
    GOOGLE_LOG(FATAL) << " Unused arguments. " << save;
  }
  if (!annotations.empty()) {
    GOOGLE_LOG(FATAL) << " Annotation range is not-closed, expect $}$. " << save;
  }
}

const char* Printer::WriteVariable(
    const std::vector<std::string>& args,
    const std::map<std::string, std::string>& vars, const char* format,
    int* arg_index, std::vector<AnnotationCollector::Annotation>* annotations) {
  auto start = format;
  auto end = strchr(format, '$');
  if (!end) {
    GOOGLE_LOG(FATAL) << " Unclosed variable name.";
  }
  format = end + 1;
  if (end == start) {
    // "$$" is an escape for just '$'
    IndentIfAtStart();
    push_back('$');
    return format;
  }
  if (*start == '{') {
    GOOGLE_CHECK(std::isdigit(start[1]));
    GOOGLE_CHECK_EQ(end - start, 2);
    int idx = start[1] - '1';
    if (idx < 0 || static_cast<size_t>(idx) >= args.size()) {
      GOOGLE_LOG(FATAL) << "Annotation ${" << idx + 1 << "$ is out of bounds.";
    }
    if (idx > *arg_index) {
      GOOGLE_LOG(FATAL) << "Annotation arg must be in correct order as given. Expected"
                 << " ${" << (*arg_index) + 1 << "$ got ${" << idx + 1 << "$.";
    } else if (idx == *arg_index) {
      (*arg_index)++;
    }
    IndentIfAtStart();
    annotations->push_back({{offset_, 0}, args[idx]});
    return format;
  } else if (*start == '}') {
    GOOGLE_CHECK(annotations);
    if (annotations->empty()) {
      GOOGLE_LOG(FATAL) << "Unexpected end of annotation found.";
    }
    auto& a = annotations->back();
    a.first.second = offset_;
    if (annotation_collector_) annotation_collector_->AddAnnotationNew(a);
    annotations->pop_back();
    return format;
  }
  auto start_var = start;
  while (start_var < end && *start_var == ' ') start_var++;
  if (start_var == end) {
    GOOGLE_LOG(FATAL) << " Empty variable.";
  }
  auto end_var = end;
  while (start_var < end_var && *(end_var - 1) == ' ') end_var--;
  std::string var_name{
      start_var, static_cast<std::string::size_type>(end_var - start_var)};
  std::string sub;
  if (std::isdigit(var_name[0])) {
    GOOGLE_CHECK_EQ(var_name.size(), 1U);  // No need for multi-digits
    int idx = var_name[0] - '1';   // Start counting at 1
    GOOGLE_CHECK_GE(idx, 0);
    if (static_cast<size_t>(idx) >= args.size()) {
      GOOGLE_LOG(FATAL) << "Argument $" << idx + 1 << "$ is out of bounds.";
    }
    if (idx > *arg_index) {
      GOOGLE_LOG(FATAL) << "Arguments must be used in same order as given. Expected $"
                 << (*arg_index) + 1 << "$ got $" << idx + 1 << "$.";
    } else if (idx == *arg_index) {
      (*arg_index)++;
    }
    sub = args[idx];
  } else {
    auto it = vars.find(var_name);
    if (it == vars.end()) {
      GOOGLE_LOG(FATAL) << " Unknown variable: " << var_name << ".";
    }
    sub = it->second;
  }

  // By returning here in case of empty we also skip possible spaces inside
  // the $...$, i.e. "void$ dllexpor$ f();" -> "void f();" in the empty case.
  if (sub.empty()) return format;

  // We're going to write something non-empty so we need a possible indent.
  IndentIfAtStart();

  // Write the possible spaces in front.
  CopyToBuffer(start, start_var - start);
  // Write a non-empty substituted variable.
  CopyToBuffer(sub.c_str(), sub.size());
  // Finish off with writing possible trailing spaces.
  CopyToBuffer(end_var, end - end_var);
  return format;
}

}  // namespace io
}  // namespace protobuf
}  // namespace google
