// Copyright (c) 2015 Philip Quinn.
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "serialize-text.h"

#include <kj/debug.h>

#include "pretty-print.h"
#include "compiler/lexer.capnp.h"
#include "compiler/lexer.h"
#include "compiler/node-translator.h"
#include "compiler/parser.h"

namespace capnp {

namespace {

class ThrowingErrorReporter final: public capnp::compiler::ErrorReporter {
  // Throws all errors as assertion failures.
public:
  ThrowingErrorReporter(kj::StringPtr input): input(input) {}

  void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override {
    // Note: Line and column numbers are usually 1-based.
    uint line = 1;
    uint32_t lineStart = 0;
    for (auto i: kj::zeroTo(startByte)) {
      if (input[i] == '\n') {
        ++line;
        lineStart = i;  // Omit +1 so that column is 1-based.
      }
    }

    kj::throwRecoverableException(kj::Exception(
      kj::Exception::Type::FAILED, "(capnp text input)", line,
      kj::str(startByte - lineStart, "-", endByte - lineStart, ": ", message)
    ));
  }

  bool hadErrors() override { return false; }

private:
  kj::StringPtr input;
};

class ExternalResolver final: public capnp::compiler::ValueTranslator::Resolver {
  // Throws all external resolution requests as assertion failures.
public:
  kj::Maybe<capnp::DynamicValue::Reader>
  resolveConstant(capnp::compiler::Expression::Reader name) override {
    KJ_FAIL_REQUIRE("External constants not allowed.");
  }

  kj::Maybe<kj::Array<const capnp::byte>>
  readEmbed(capnp::compiler::LocatedText::Reader filename) override {
    KJ_FAIL_REQUIRE("External embeds not allowed.");
  }
};

template <typename Function>
void lexAndParseExpression(kj::StringPtr input, Function f) {
  // Parses a single expression from the input and calls `f(expression)`.

  ThrowingErrorReporter errorReporter(input);

  capnp::MallocMessageBuilder tokenArena;
  auto lexedTokens = tokenArena.initRoot<capnp::compiler::LexedTokens>();
  capnp::compiler::lex(input, lexedTokens, errorReporter);

  capnp::compiler::CapnpParser parser(tokenArena.getOrphanage(), errorReporter);
  auto tokens = lexedTokens.asReader().getTokens();
  capnp::compiler::CapnpParser::ParserInput parserInput(tokens.begin(), tokens.end());

  if (parserInput.getPosition() != tokens.end()) {
    KJ_IF_MAYBE(expression, parser.getParsers().expression(parserInput)) {
      // The input is expected to contain a *single* message.
      KJ_REQUIRE(parserInput.getPosition() == tokens.end(), "Extra tokens in input.");

      f(expression->getReader());
    } else {
      auto best = parserInput.getBest();
      if (best == tokens.end()) {
        KJ_FAIL_REQUIRE("Premature end of input.");
      } else {
        errorReporter.addErrorOn(*best, "Parse error");
      }
    }
  } else {
    KJ_FAIL_REQUIRE("Failed to read input.");
  }
}

}  // namespace

TextCodec::TextCodec() : prettyPrint(false) {}
TextCodec::~TextCodec() noexcept(true) {}

void TextCodec::setPrettyPrint(bool enabled) { prettyPrint = enabled; }

kj::String TextCodec::encode(DynamicValue::Reader value) const {
  if (!prettyPrint) {
    return kj::str(value);
  } else {
    if (value.getType() == DynamicValue::Type::STRUCT) {
      return capnp::prettyPrint(value.as<DynamicStruct>()).flatten();
    } else if (value.getType() == DynamicValue::Type::LIST) {
      return capnp::prettyPrint(value.as<DynamicList>()).flatten();
    } else {
      return kj::str(value);
    }
  }
}

void TextCodec::decode(kj::StringPtr input, DynamicStruct::Builder output) const {
  lexAndParseExpression(input, [&](compiler::Expression::Reader expression) {
    KJ_REQUIRE(expression.isTuple(), "Input does not contain a struct.") { return; }

    ThrowingErrorReporter errorReporter(input);
    ExternalResolver nullResolver;

    Orphanage orphanage = Orphanage::getForMessageContaining(output);
    compiler::ValueTranslator translator(nullResolver, errorReporter, orphanage);
    translator.fillStructValue(output, expression.getTuple());
  });
}

Orphan<DynamicValue> TextCodec::decode(kj::StringPtr input, Type type, Orphanage orphanage) const {
  Orphan<DynamicValue> output;

  lexAndParseExpression(input, [&](compiler::Expression::Reader expression) {
    ThrowingErrorReporter errorReporter(input);
    ExternalResolver nullResolver;

    compiler::ValueTranslator translator(nullResolver, errorReporter, orphanage);
    KJ_IF_MAYBE(value, translator.compileValue(expression, type)) {
      output = *kj::mv(value);
    } else {
      // An error should have already been given to the errorReporter.
    }
  });

  return output;
}

}  // namespace capnp
