//===--- AvailabilityMixin.cpp - Symbol Graph Symbol Availability ---------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "AvailabilityMixin.h"
#include "JSON.h"
#include "swift/Basic/Assertions.h"

using namespace swift;
using namespace symbolgraphgen;

namespace {
StringRef getDomain(const SemanticAvailableAttr &AvAttr) {
  // FIXME: [avalailability] Move the definition of these strings into
  // AvailabilityDomain so that new domains are handled automatically.

  if (AvAttr.getDomain().isPackageDescription())
    return { "SwiftPM" };

  if (AvAttr.getDomain().isSwiftLanguageMode())
    return { "Swift" };

  // Platform-specific availability.
  switch (AvAttr.getPlatform()) {
    case swift::PlatformKind::iOS:
      return { "iOS" };
    case swift::PlatformKind::macCatalyst:
      return { "macCatalyst" };
    case swift::PlatformKind::macOS:
      return { "macOS" };
    case swift::PlatformKind::tvOS:
      return { "tvOS" };
    case swift::PlatformKind::watchOS:
      return { "watchOS" };
    case swift::PlatformKind::visionOS:
      return { "visionOS" };
    case swift::PlatformKind::iOSApplicationExtension:
      return { "iOSAppExtension" };
    case swift::PlatformKind::macCatalystApplicationExtension:
      return { "macCatalystAppExtension" };
    case swift::PlatformKind::macOSApplicationExtension:
      return { "macOSAppExtension" };
    case swift::PlatformKind::tvOSApplicationExtension:
      return { "tvOSAppExtension" };
    case swift::PlatformKind::watchOSApplicationExtension:
      return { "watchOSAppExtension" };
    case swift::PlatformKind::visionOSApplicationExtension:
      return { "visionOSAppExtension" };
    case PlatformKind::DriverKit:
      return { "DriverKit" };
    case swift::PlatformKind::Swift:
      return { "Swift" };
    case PlatformKind::anyAppleOS:
      return { "Any Apple OS" };
    case swift::PlatformKind::FreeBSD:
      return { "FreeBSD" };
    case swift::PlatformKind::OpenBSD:
      return { "OpenBSD" };
    case swift::PlatformKind::Windows:
      return { "Windows" };
    case swift::PlatformKind::Android:
      return { "Android" };
    case swift::PlatformKind::none:
      return { "*" };
  }
  llvm_unreachable("invalid platform kind");
}
} // end anonymous namespace

Availability::Availability(const SemanticAvailableAttr &AvAttr)
    : Domain(getDomain(AvAttr)), Introduced(AvAttr.getIntroduced()),
      Deprecated(AvAttr.getDeprecated()), Obsoleted(AvAttr.getObsoleted()),
      Message(AvAttr.getMessage()), Renamed(AvAttr.getRename()),
      IsUnconditionallyDeprecated(AvAttr.isUnconditionallyDeprecated()),
      IsUnconditionallyUnavailable(AvAttr.isUnconditionallyUnavailable()) {
  assert(!Domain.empty());
}

void
Availability::updateFromDuplicate(const Availability &Other) {
  assert(Domain == Other.Domain);

  // The highest `introduced` version always wins
  // regardless of the order in which they appeared in the source.
  if (!Introduced) {
    Introduced = Other.Introduced;
  } else if (Other.Introduced && *Other.Introduced > *Introduced) {
    Introduced = Other.Introduced;
  }

  // The `deprecated` version that appears last in the source always wins,
  // allowing even to erase a previous deprecated.
  Deprecated = Other.Deprecated;

  // Same for `deprecated` with no version.
  IsUnconditionallyDeprecated = Other.IsUnconditionallyDeprecated;

  // Same for `unavailable` with no version.
  IsUnconditionallyUnavailable = Other.IsUnconditionallyUnavailable;

  // Same for `obsoleted`.
  Obsoleted = Other.Obsoleted;

  // The `message` that appears last in the source always wins.
  Message = Other.Message;

  // Same for `renamed`.
  Renamed = Other.Renamed;
}

void
Availability::updateFromParent(const Availability &Parent) {
  assert(Domain == Parent.Domain);

  // Allow filling, but never replace a child's existing `introduced`
  // availability because it can never be less available than the parent anyway.
  //
  // e.g. you cannot write this:
  // @available(macos, introduced: 10.15)
  // struct S {
  //   @available(macos, introduced: 10.14)
  //   func foo() {}
  // }
  //
  // So the child's `introduced` availability will always
  // be >= the parent's.
  if (!Introduced) {
    Introduced = Parent.Introduced;
  }

  // Allow filling from the parent.
  // For replacement, we will consider a parent's
  // earlier deprecation to supersede a child's later deprecation.
  if (!Deprecated) {
    Deprecated = Parent.Deprecated;
  } else if (Parent.Deprecated && *Parent.Deprecated < *Deprecated) {
    Deprecated = Parent.Deprecated;
  }

  // The above regarding `deprecated` also will apply to `obsoleted`.
  if (!Obsoleted) {
    Obsoleted = Parent.Obsoleted;
  } else if (Parent.Obsoleted && *Parent.Obsoleted < *Obsoleted) {
    Obsoleted = Parent.Obsoleted;
  }

  // Never replace or fill a child's `message` with a parent's because
  // there may be context at the parent that doesn't apply at the child,
  // i.e. it might not always make sense.

  // Never replace or fill a child's `renamed` field because it
  // doesn't make sense. Just because a parent is renamed doesn't
  // mean its child is renamed to the same thing.

  // If a parent is unconditionally deprecated, then so are all
  // of its children.
  IsUnconditionallyDeprecated |= Parent.IsUnconditionallyDeprecated;

  // If a parent is unconditionally unavailable, then so are all
  // of its children.
  IsUnconditionallyUnavailable |= Parent.IsUnconditionallyUnavailable;
}

void Availability::serialize(llvm::json::OStream &OS) const {
  OS.object([&](){
    OS.attribute("domain", Domain);
    if (Introduced) {
      AttributeRAII IntroducedAttribute("introduced", OS);
      symbolgraphgen::serialize(*Introduced, OS);
    }
    if (Deprecated) {
      AttributeRAII DeprecatedAttribute("deprecated", OS);
      symbolgraphgen::serialize(*Deprecated, OS);
    }
    if (Obsoleted) {
      AttributeRAII ObsoletedAttribute("obsoleted", OS);
      symbolgraphgen::serialize(*Obsoleted, OS);
    }
    if (!Message.empty()) {
      OS.attribute("message", Message);
    }
    if (!Renamed.empty()) {
      OS.attribute("renamed", Renamed);
    }
    if (IsUnconditionallyDeprecated) {
      OS.attribute("isUnconditionallyDeprecated", true);
    }
    if (IsUnconditionallyUnavailable) {
      OS.attribute("isUnconditionallyUnavailable", true);
    }
  }); // end availability object
}

bool Availability::empty() const {
  return !Introduced.has_value() &&
         !Deprecated.has_value() &&
         !Obsoleted.has_value() &&
         !IsUnconditionallyDeprecated &&
         !IsUnconditionallyUnavailable;
}
