//===--- APIGen.cpp - Swift API Generation --------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file implements the entrypoints into API file generation.
//
//===----------------------------------------------------------------------===//

#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"

#include "APIGen.h"

namespace swift {
namespace apigen {

void API::addSymbol(StringRef symbol, APILoc loc, APILinkage linkage,
                    APIFlags flags, APIAccess access,
                    APIAvailability availability) {
  auto *global = new (allocator) GlobalRecord(
      symbol, loc, linkage, flags, access, GVKind::Function, availability);
  globals.push_back(global);
}

ObjCInterfaceRecord *API::addObjCClass(StringRef name, APILinkage linkage,
                                       APILoc loc, APIAccess access,
                                       APIAvailability availability,
                                       StringRef superClassName) {
  auto *interface = new (allocator) ObjCInterfaceRecord(
      name, linkage, loc, access, availability, superClassName);
  interfaces.push_back(interface);
  return interface;
}

ObjCCategoryRecord *API::addObjCCategory(StringRef name, APILinkage linkage,
                                         APILoc loc, APIAccess access,
                                         APIAvailability availability,
                                         StringRef interface) {
  auto *category = new (allocator)
      ObjCCategoryRecord(name, linkage, loc, access, availability, interface);
  categories.push_back(category);
  return category;
}

void API::addObjCMethod(ObjCContainerRecord *record, StringRef name, APILoc loc,
                        APIAccess access, bool isInstanceMethod,
                        bool isOptional, APIAvailability availability) {
  auto method = new (allocator) ObjCMethodRecord(
      name, loc, access, isInstanceMethod, isOptional, availability);
  record->methods.push_back(method);
}

static void serialize(llvm::json::OStream &OS, APIAccess access) {
  switch (access) {
  case APIAccess::Public:
    OS.attribute("access", "public");
    break;
  case APIAccess::Private:
    OS.attribute("access", "private");
    break;
  case APIAccess::Project:
    OS.attribute("access", "project");
    break;
  case APIAccess::Unknown:
    break;
  }
}

static void serialize(llvm::json::OStream &OS, APIAvailability availability) {
  if (availability.empty())
    return;
  if (!availability.introduced.empty())
    OS.attribute("introduced", availability.introduced);
  if (!availability.obsoleted.empty())
    OS.attribute("obsoleted", availability.obsoleted);
  if (availability.unavailable)
    OS.attribute("unavailable", availability.unavailable);
}

static void serialize(llvm::json::OStream &OS, APILinkage linkage) {
  switch (linkage) {
  case APILinkage::Exported:
    OS.attribute("linkage", "exported");
    break;
  case APILinkage::Reexported:
    OS.attribute("linkage", "re-exported");
    break;
  case APILinkage::Internal:
    OS.attribute("linkage", "internal");
    break;
  case APILinkage::External:
    OS.attribute("linkage", "external");
    break;
  case APILinkage::Unknown:
    // do nothing;
    break;
  }
}

static void serialize(llvm::json::OStream &OS, APILoc loc) {
  OS.attribute("file", loc.getFilename());
}

static void serialize(llvm::json::OStream &OS, const GlobalRecord &record) {
  OS.object([&]() {
    OS.attribute("name", record.name);
    serialize(OS, record.access);
    serialize(OS, record.loc);
    serialize(OS, record.linkage);
    serialize(OS, record.availability);
  });
}

static void serialize(llvm::json::OStream &OS, const ObjCMethodRecord &record) {
  OS.object([&]() {
    OS.attribute("name", record.name);
    serialize(OS, record.access);
    serialize(OS, record.loc);
    serialize(OS, record.linkage);
    serialize(OS, record.availability);
    if (record.isOptional)
      OS.attribute("optional", record.isOptional);
  });
}

static bool sortAPIRecords(const APIRecord *base, const APIRecord *compare) {
  return base->name < compare->name;
}

static void serialize(llvm::json::OStream &OS,
                      const ObjCInterfaceRecord &record) {
  OS.object([&]() {
    OS.attribute("name", record.name);
    serialize(OS, record.access);
    serialize(OS, record.loc);
    serialize(OS, record.linkage);
    serialize(OS, record.availability);
    OS.attribute("super", record.superClassName);
    OS.attributeArray("instanceMethods", [&]() {
      for (auto &method : record.methods) {
        if (method->isInstanceMethod)
          serialize(OS, *method);
      }
    });
    OS.attributeArray("classMethods", [&]() {
      for (auto &method : record.methods) {
        if (!method->isInstanceMethod)
          serialize(OS, *method);
      }
    });
  });
}

static void serialize(llvm::json::OStream &OS,
                      const ObjCCategoryRecord &record) {
  OS.object([&]() {
    OS.attribute("name", record.name);
    serialize(OS, record.access);
    serialize(OS, record.loc);
    serialize(OS, record.linkage);
    serialize(OS, record.availability);
    OS.attribute("interface", record.interface);
    OS.attributeArray("instanceMethods", [&]() {
      for (auto &method : record.methods) {
        if (method->isInstanceMethod)
          serialize(OS, *method);
      }
    });
    OS.attributeArray("classMethods", [&]() {
      for (auto &method : record.methods) {
        if (!method->isInstanceMethod)
          serialize(OS, *method);
      }
    });
  });
}

void API::writeAPIJSONFile(llvm::raw_ostream &os, bool PrettyPrint) {
  unsigned indentSize = PrettyPrint ? 2 : 0;
  llvm::json::OStream JSON(os, indentSize);

  JSON.object([&]() {
    JSON.attribute("target", target.str());
    JSON.attributeArray("globals", [&]() {
      llvm::sort(globals, sortAPIRecords);
      for (const auto *g : globals)
        serialize(JSON, *g);
    });
    JSON.attributeArray("interfaces", [&]() {
      llvm::sort(interfaces, sortAPIRecords);
      for (const auto *i : interfaces)
        serialize(JSON, *i);
    });
    JSON.attributeArray("categories", [&]() {
      llvm::sort(categories, sortAPIRecords);
      for (const auto *c : categories)
        serialize(JSON, *c);
    });
    JSON.attribute("version", "1.0");
  });
}

} // end namespace apigen
} // end namespace swift