// Copyright (c) 2013-2020 Sandstorm Development Group, Inc. and contributors
// 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.

#pragma once

#include <capnp/orphan.h>
#include <capnp/compiler/grammar.capnp.h>
#include <capnp/schema.capnp.h>
#include <capnp/dynamic.h>
#include <kj/vector.h>
#include <kj/one-of.h>
#include "error-reporter.h"
#include "resolver.h"

CAPNP_BEGIN_HEADER

namespace capnp {
namespace compiler {

class BrandedDecl;
class BrandScope;

struct ImplicitParams {
  // Represents a set of implicit brand parameters visible in the current context.
  //
  // As of this writing, implicit parameters occur only in the context of RPC methods. That is,
  // like this:
  //
  //     makeBox @0 [T] (value :T) -> Box(T);
  //
  // Here, `T` is an implicit parameter.

  uint64_t scopeId;
  // If zero, then any reference to an implicit param in this context should be compiled to a
  // `implicitMethodParam` AnyPointer. If non-zero, it should be compiled to a `parameter`
  // AnyPointer using this scopeId. This comes into play when compiling the implicitly-generated
  // struct types corresponding to a method's params or results; these implicitly-generated types
  // themselves have *explicit* brand parameters corresponding to the *implicit* brand parameters
  // of the method.
  //
  // TODO(cleanup): Unclear why ImplicitParams is even used when compiling the implicit structs
  //   with explicit params. Missing abstraction?

  List<Declaration::BrandParameter>::Reader params;
  // Name and metadata about the parameter declaration.

  static inline ImplicitParams none() {
    // Convenience helper to create an empty `ImplicitParams`.
    return { 0, List<Declaration::BrandParameter>::Reader() };
  }
};

class BrandedDecl {
  // Represents a declaration possibly with generic parameter bindings.

public:
  inline BrandedDecl(Resolver::ResolvedDecl decl,
                     kj::Own<BrandScope>&& brand,
                     Expression::Reader source)
      : brand(kj::mv(brand)), source(source) {
    // `source`, is the expression which specified this branded decl. It is provided so that errors
    // can be reported against it. It is acceptable to pass a default-initialized reader if there's
    // no source expression; errors will then be reported at 0, 0.

    body.init<Resolver::ResolvedDecl>(kj::mv(decl));
  }
  inline BrandedDecl(Resolver::ResolvedParameter variable, Expression::Reader source)
      : source(source) {
    body.init<Resolver::ResolvedParameter>(kj::mv(variable));
  }
  inline BrandedDecl(decltype(nullptr)) {}
  inline BrandedDecl() {}  // exists only for ExternalMutexGuarded<BrandedDecl> to work...

  static BrandedDecl implicitMethodParam(uint index) {
    // Get a BrandedDecl referring to an implicit method parameter.
    // (As a hack, we internally represent this as a ResolvedParameter. Sorry.)
    return BrandedDecl(Resolver::ResolvedParameter { 0, index }, Expression::Reader());
  }

  BrandedDecl(BrandedDecl& other);
  BrandedDecl(BrandedDecl&& other) = default;

  BrandedDecl& operator=(BrandedDecl& other);
  BrandedDecl& operator=(BrandedDecl&& other) = default;

  kj::Maybe<BrandedDecl> applyParams(kj::Array<BrandedDecl> params, Expression::Reader subSource);
  // Treat the declaration as a generic and apply it to the given parameter list.

  kj::Maybe<BrandedDecl> getMember(kj::StringPtr memberName, Expression::Reader subSource);
  // Get a member of this declaration.

  kj::Maybe<Declaration::Which> getKind();
  // Returns the kind of declaration, or null if this is an unbound generic variable.

  template <typename InitBrandFunc>
  uint64_t getIdAndFillBrand(InitBrandFunc&& initBrand);
  // Returns the type ID of this node. `initBrand` is a zero-arg functor which returns
  // schema::Brand::Builder; this will be called if this decl has brand bindings, and
  // the returned builder filled in to reflect those bindings.
  //
  // It is an error to call this when `getKind()` returns null.

  kj::Maybe<BrandedDecl&> getListParam();
  // Only if the kind is BUILTIN_LIST: Get the list's type parameter.

  Resolver::ResolvedParameter asVariable();
  // If this is an unbound generic variable (i.e. `getKind()` returns null), return information
  // about the variable.
  //
  // It is an error to call this when `getKind()` does not return null.

  bool compileAsType(ErrorReporter& errorReporter, schema::Type::Builder target);
  // Compile this decl to a schema::Type.

  inline void addError(ErrorReporter& errorReporter, kj::StringPtr message) {
    errorReporter.addErrorOn(source, message);
  }

  Resolver::ResolveResult asResolveResult(uint64_t scopeId, schema::Brand::Builder brandBuilder);
  // Reverse this into a ResolveResult. If necessary, use `brandBuilder` to fill in
  // ResolvedDecl.brand.

  kj::String toString();
  kj::String toDebugString();

  kj::Maybe<uint64_t> getGenericTypeId() {
    // If this declaration points to a type, gets its type ID. Keep in mind that this
    // drops the brand, that is, if the type is the result of applying a generic to
    // some type parameters, this returns only the ID of the underlying generic type,
    // which loses information about the type parameters.
    if (body.is<Resolver::ResolvedDecl>()) {
      return body.get<Resolver::ResolvedDecl>().id;
    }
    return nullptr;
  }

private:
  Resolver::ResolveResult body;
  kj::Own<BrandScope> brand;  // null if parameter
  Expression::Reader source;
};

class BrandScope: public kj::Refcounted {
  // Tracks the brand parameter bindings affecting the scope specified by some expression. For
  // example, if we are interpreting the type expression "Foo(Text).Bar", we would start with the
  // current scope's BrandScope, create a new child BrandScope representing "Foo", add the "(Text)"
  // parameter bindings to it, then create a further child scope for "Bar". Thus the BrandScope for
  // Bar knows that Foo's parameter list has been bound to "(Text)".

public:
  BrandScope(ErrorReporter& errorReporter, uint64_t startingScopeId,
             uint startingScopeParamCount, Resolver& startingScope);
  // TODO(bug): Passing an `errorReporter` to the constructor of `BrandScope` turns out not to
  //   make a ton of sense, as an `errorReporter` is meant to report errors in a specific module,
  //   but `BrandScope` might be constructed while compiling one module but then used when
  //   compiling a different module, or not compiling a module at all. Note, though, that it DOES
  //   make sense for BrandedDecl to have an ErrorReporter, specifically associated with its
  //   `source` expression.

  bool isGeneric();
  // Returns true if this scope or any parent scope is a generic (has brand parameters).

  kj::Own<BrandScope> push(uint64_t typeId, uint paramCount);
  // Creates a new child scope with the given type ID and number of brand parameters.

  kj::Maybe<kj::Own<BrandScope>> setParams(
      kj::Array<BrandedDecl> params, Declaration::Which genericType, Expression::Reader source);
  // Create a new BrandScope representing the same scope, but with parameters filled in.
  //
  // This should only be called on the generic version of the scope. If called on a branded
  // version, an error will be reported.
  //
  // Returns null if an error occurred that prevented creating the BrandScope; the error will have
  // been reported to the ErrorReporter.

  kj::Own<BrandScope> pop(uint64_t newLeafId);
  // Return the parent scope.

  kj::Maybe<BrandedDecl> lookupParameter(Resolver& resolver, uint64_t scopeId, uint index);
  // Search up the scope chain for the scope matching `scopeId`, and return its `index`th parameter
  // binding. Returns null if the parameter is from a scope that we are currently compiling, and
  // hasn't otherwise been bound to any argument (see Brand.Scope.inherit in schema.capnp).
  //
  // In the case that a parameter wasn't specified, but isn't part of the current scope, this
  // returns the declaration for `AnyPointer`.
  //
  // TODO(cleanup): Should be called lookupArgument()?

  kj::Maybe<kj::ArrayPtr<BrandedDecl>> getParams(uint64_t scopeId);
  // Get the whole list of parameter bindings at the given scope. Returns null if the scope is
  // currently be compiled and the parameters are unbound.
  //
  // Note that it's possible that not all declared parameters were actually specified for a given
  // scope. For example, if you declare a generic `Foo(T, U)`, and then you intiantiate it
  // somewhere as `Foo(Text)`, then `U` is unspecified -- this is not an error, because Cap'n
  // Proto allows new type parameters to be added over time. `U` should be treated as `AnyPointer`
  // in this case, but `getParams()` doesn't know how many parameters are expected, so it will
  // return an array that only contains one item. Use `lookupParameter()` if you want unspecified
  // parameters to be filled in with `AnyPointer` automatically.
  //
  // TODO(cleanup): Should be called getArguments()?

  template <typename InitBrandFunc>
  void compile(InitBrandFunc&& initBrand);
  // Constructs the schema::Brand corresponding to this brand scope.
  //
  // `initBrand` is a zero-arg functor which returns an empty schema::Brand::Builder, into which
  // the brand is constructed. If no generics are present, then `initBrand` is never called.
  //
  // TODO(cleanup): Should this return Maybe<Orphan<schema::Brand>> instead?

  kj::Maybe<BrandedDecl> compileDeclExpression(
      Expression::Reader source, Resolver& resolver,
      ImplicitParams implicitMethodParams);
  // Interpret a type expression within this branded scope.

  BrandedDecl interpretResolve(
      Resolver& resolver, Resolver::ResolveResult& result, Expression::Reader source);
  // After using a Resolver to resolve a symbol, call interpretResolve() to interpret the result
  // within the current brand scope. For example, if a name resolved to a brand parameter, this
  // replaces it with the appropriate argument from the scope.

  inline uint64_t getScopeId() { return leafId; }

private:
  ErrorReporter& errorReporter;
  kj::Maybe<kj::Own<BrandScope>> parent;
  uint64_t leafId;                     // zero = this is the root
  uint leafParamCount;                 // number of generic parameters on this leaf
  bool inherited;
  kj::Array<BrandedDecl> params;

  BrandScope(kj::Own<BrandScope> parent, uint64_t leafId, uint leafParamCount)
      : errorReporter(parent->errorReporter),
        parent(kj::mv(parent)), leafId(leafId), leafParamCount(leafParamCount),
        inherited(false) {}
  BrandScope(BrandScope& base, kj::Array<BrandedDecl> params)
      : errorReporter(base.errorReporter),
        leafId(base.leafId), leafParamCount(base.leafParamCount),
        inherited(false), params(kj::mv(params)) {
    KJ_IF_MAYBE(p, base.parent) {
      parent = kj::addRef(**p);
    }
  }
  BrandScope(ErrorReporter& errorReporter, uint64_t scopeId)
      : errorReporter(errorReporter), leafId(scopeId), leafParamCount(0), inherited(false) {}

  kj::Own<BrandScope> evaluateBrand(
      Resolver& resolver, Resolver::ResolvedDecl decl,
      List<schema::Brand::Scope>::Reader brand, uint index = 0);

  BrandedDecl decompileType(Resolver& resolver, schema::Type::Reader type);

  template <typename T, typename... Params>
  friend kj::Own<T> kj::refcounted(Params&&... params);
  friend class BrandedDecl;
};

template <typename InitBrandFunc>
uint64_t BrandedDecl::getIdAndFillBrand(InitBrandFunc&& initBrand) {
  KJ_REQUIRE(body.is<Resolver::ResolvedDecl>());

  brand->compile(kj::fwd<InitBrandFunc>(initBrand));
  return body.get<Resolver::ResolvedDecl>().id;
}

template <typename InitBrandFunc>
void BrandScope::compile(InitBrandFunc&& initBrand) {
  kj::Vector<BrandScope*> levels;
  BrandScope* ptr = this;
  for (;;) {
    if (ptr->params.size() > 0 || (ptr->inherited && ptr->leafParamCount > 0)) {
      levels.add(ptr);
    }
    KJ_IF_MAYBE(p, ptr->parent) {
      ptr = *p;
    } else {
      break;
    }
  }

  if (levels.size() > 0) {
    auto scopes = initBrand().initScopes(levels.size());
    for (uint i: kj::indices(levels)) {
      auto scope = scopes[i];
      scope.setScopeId(levels[i]->leafId);

      if (levels[i]->inherited) {
        scope.setInherit();
      } else {
        auto bindings = scope.initBind(levels[i]->params.size());
        for (uint j: kj::indices(bindings)) {
          levels[i]->params[j].compileAsType(errorReporter, bindings[j].initType());
        }
      }
    }
  }
}

}  // namespace compiler
}  // namespace capnp

CAPNP_END_HEADER
