// Copyright (c) 2013-2014 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

#if defined(__GNUC__) && !defined(CAPNP_HEADER_WARNINGS)
#pragma GCC system_header
#endif

#include <capnp/compiler/grammar.capnp.h>
#include <capnp/schema.capnp.h>
#include <capnp/schema-loader.h>
#include "error-reporter.h"

namespace capnp {
namespace compiler {

class Module: public ErrorReporter {
public:
  virtual kj::StringPtr getSourceName() = 0;
  // The name of the module file relative to the source tree.  Used to decide where to output
  // generated code and to form the `displayName` in the schema.

  virtual Orphan<ParsedFile> loadContent(Orphanage orphanage) = 0;
  // Loads the module content, using the given orphanage to allocate objects if necessary.

  virtual kj::Maybe<Module&> importRelative(kj::StringPtr importPath) = 0;
  // Find another module, relative to this one.  Importing the same logical module twice should
  // produce the exact same object, comparable by identity.  These objects are owned by some
  // outside pool that outlives the Compiler instance.

  virtual kj::Maybe<kj::Array<const byte>> embedRelative(kj::StringPtr embedPath) = 0;
  // Read and return the content of a file specified using `embed`.
};

class Compiler final: private SchemaLoader::LazyLoadCallback {
  // Cross-links separate modules (schema files) and translates them into schema nodes.
  //
  // This class is thread-safe, hence all its methods are const.

public:
  enum AnnotationFlag {
    COMPILE_ANNOTATIONS,
    // Compile annotations normally.

    DROP_ANNOTATIONS
    // Do not compile any annotations, eagerly or lazily.  All "annotations" fields in the schema
    // will be left empty.  This is useful to avoid parsing imports that are used only for
    // annotations which you don't intend to use anyway.
    //
    // Unfortunately annotations cannot simply be compiled lazily because filling in the
    // "annotations" field at the usage site requires knowing the annotation's type, which requires
    // compiling the annotation, and the schema API has no particular way to detect when you
    // try to access the "annotations" field in order to lazily compile the annotations at that
    // point.
  };

  explicit Compiler(AnnotationFlag annotationFlag = COMPILE_ANNOTATIONS);
  ~Compiler() noexcept(false);
  KJ_DISALLOW_COPY(Compiler);

  uint64_t add(Module& module) const;
  // Add a module to the Compiler, returning the module's file ID.  The ID can then be looked up in
  // the `SchemaLoader` returned by `getLoader()`.  However, the SchemaLoader may behave as if the
  // schema node doesn't exist if any compilation errors occur (reported via the module's
  // ErrorReporter).  The module is parsed at the time `add()` is called, but not fully compiled --
  // individual schema nodes are compiled lazily.  If you want to force eager compilation,
  // see `eagerlyCompile()`, below.

  kj::Maybe<uint64_t> lookup(uint64_t parent, kj::StringPtr childName) const;
  // Given the type ID of a schema node, find the ID of a node nested within it.  Throws an
  // exception if the parent ID is not recognized; returns null if the parent has no child of the
  // given name.  Neither the parent nor the child schema node is actually compiled.

  kj::Maybe<schema::Node::SourceInfo::Reader> getSourceInfo(uint64_t id) const;
  // Get the SourceInfo for the given type ID, if available.

  Orphan<List<schema::CodeGeneratorRequest::RequestedFile::Import>>
      getFileImportTable(Module& module, Orphanage orphanage) const;
  // Build the import table for the CodeGeneratorRequest for the given module.

  Orphan<List<schema::Node::SourceInfo>> getAllSourceInfo(Orphanage orphanage) const;
  // Gets the SourceInfo structs for all nodes parsed by the compiler.

  enum Eagerness: uint32_t {
    // Flags specifying how eager to be about compilation.  These are intended to be bitwise OR'd.
    // Used with the method `eagerlyCompile()`.
    //
    // Schema declarations can be compiled upfront, or they can be compiled lazily as they are
    // needed.  Usually, the difference is not observable, but it is not a perfect abstraction.
    // The difference has the following effects:
    // * `getLoader().getAllLoaded()` only returns the schema nodes which have been compiled so
    //   far.
    // * `getLoader().get()` (i.e. searching for a schema by ID) can only find schema nodes that
    //   have either been compiled already, or which are referenced by schema nodes which have been
    //   compiled already.  This means that if the ID you pass in came from another schema node
    //   compiled with the same compiler, there should be no observable difference, but if you
    //   have an ID from elsewhere which you _a priori_ expect is defined in a particular schema
    //   file, you will need to compile that file eagerly before you look up the node by ID.
    // * Errors are reported when they are encountered, so some errors will not be reported until
    //   the node is actually compiled.
    // * If an imported file is not needed, it will never even be read from disk.
    //
    // The last point is the main reason why you might want to prefer lazy compilation:  it allows
    // you to use a schema file with missing imports, so long as those missing imports are not
    // actually needed.
    //
    // For example, the flag combo:
    //     EAGER_NODE | EAGER_CHILDREN | EAGER_DEPENDENCIES | EAGER_DEPENDENCY_PARENTS
    // will compile the entire given module, plus all direct dependencies of anything in that
    // module, plus all lexical ancestors of those dependencies.  This is what the Cap'n Proto
    // compiler uses when building initial code generator requests.

    ALL_RELATED_NODES = ~0u,
    // Compile everything that is in any way related to the target node, including its entire
    // containing file and everything transitively imported by it.

    NODE = 1 << 0,
    // Eagerly compile the requested node, but not necessarily any of its parents, children, or
    // dependencies.

    PARENTS = 1 << 1,
    // Eagerly compile all lexical parents of the requested node.  Only meaningful in conjuction
    // with NODE.

    CHILDREN = 1 << 2,
    // Eagerly compile all of the node's lexically nested nodes.  Only meaningful in conjuction
    // with NODE.

    DEPENDENCIES = NODE << 15,
    // For all nodes compiled as a result of the above flags, also compile their direct
    // dependencies.  E.g. if Foo is a struct which contains a field of type Bar, and Foo is
    // compiled, then also compile Bar.  "Dependencies" are defined as field types, method
    // parameter and return types, and annotation types.  Nested types and outer types are not
    // considered dependencies.

    DEPENDENCY_PARENTS = PARENTS * DEPENDENCIES,
    DEPENDENCY_CHILDREN = CHILDREN * DEPENDENCIES,
    DEPENDENCY_DEPENDENCIES = DEPENDENCIES * DEPENDENCIES,
    // Like PARENTS, CHILDREN, and DEPENDENCIES, but applies relative to dependency nodes rather
    // than the original requested node.  Note that DEPENDENCY_DEPENDENCIES causes all transitive
    // dependencies of the requested node to be compiled.
    //
    // These flags are defined as multiples of the original flag and DEPENDENCIES so that we
    // can form the flags to use when traversing a dependency by shifting bits.
  };

  void eagerlyCompile(uint64_t id, uint eagerness) const;
  // Force eager compilation of schema nodes related to the given ID.  `eagerness` specifies which
  // related nodes should be compiled before returning.  It is a bitwise OR of the possible values
  // of the `Eagerness` enum.
  //
  // If this returns and no errors have been reported, then it is guaranteed that the compiled
  // nodes can be found in the SchemaLoader returned by `getLoader()`.

  const SchemaLoader& getLoader() const { return loader; }
  SchemaLoader& getLoader() { return loader; }
  // Get a SchemaLoader backed by this compiler.  Schema nodes will be lazily constructed as you
  // traverse them using this loader.

  void clearWorkspace() const;
  // The compiler builds a lot of temporary tables and data structures while it works.  It's
  // useful to keep these around if more work is expected (especially if you are using lazy
  // compilation and plan to look up Schema nodes that haven't already been seen), but once
  // the SchemaLoader has everything you need, you can call clearWorkspace() to free up the
  // temporary space.  Note that it's safe to call clearWorkspace() even if you do expect to
  // compile more nodes in the future; it may simply lead to redundant work if the discarded
  // structures are needed again.

private:
  class Impl;
  kj::MutexGuarded<kj::Own<Impl>> impl;
  SchemaLoader loader;

  class CompiledModule;
  class Node;
  class Alias;

  void load(const SchemaLoader& loader, uint64_t id) const override;
};

}  // namespace compiler
}  // namespace capnp
