//===--- NSAPI.cpp - NSFoundation APIs ------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "clang/AST/NSAPI.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "llvm/ADT/StringSwitch.h"

using namespace clang;

NSAPI::NSAPI(ASTContext &ctx)
  : Ctx(ctx), ClassIds(), BOOLId(nullptr), NSIntegerId(nullptr),
    NSUIntegerId(nullptr), NSASCIIStringEncodingId(nullptr),
    NSUTF8StringEncodingId(nullptr) {}

IdentifierInfo *NSAPI::getNSClassId(NSClassIdKindKind K) const {
  static const char *ClassName[NumClassIds] = {
    "NSObject",
    "NSString",
    "NSArray",
    "NSMutableArray",
    "NSDictionary",
    "NSMutableDictionary",
    "NSNumber",
    "NSMutableSet",
    "NSMutableOrderedSet",
    "NSValue"
  };

  if (!ClassIds[K])
    return (ClassIds[K] = &Ctx.Idents.get(ClassName[K]));

  return ClassIds[K];
}

Selector NSAPI::getNSStringSelector(NSStringMethodKind MK) const {
  if (NSStringSelectors[MK].isNull()) {
    Selector Sel;
    switch (MK) {
    case NSStr_stringWithString:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("stringWithString"));
      break;
    case NSStr_stringWithUTF8String:
      Sel = Ctx.Selectors.getUnarySelector(
                                       &Ctx.Idents.get("stringWithUTF8String"));
      break;
    case NSStr_initWithUTF8String:
      Sel = Ctx.Selectors.getUnarySelector(
                                       &Ctx.Idents.get("initWithUTF8String"));
      break;
    case NSStr_stringWithCStringEncoding: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("stringWithCString"),
        &Ctx.Idents.get("encoding")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSStr_stringWithCString:
      Sel= Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("stringWithCString"));
      break;
    case NSStr_initWithString:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("initWithString"));
      break;
    }
    return (NSStringSelectors[MK] = Sel);
  }

  return NSStringSelectors[MK];
}

Optional<NSAPI::NSStringMethodKind>
NSAPI::getNSStringMethodKind(Selector Sel) const {
  for (unsigned i = 0; i != NumNSStringMethods; ++i) {
    NSStringMethodKind MK = NSStringMethodKind(i);
    if (Sel == getNSStringSelector(MK))
      return MK;
  }

  return None;
}

Selector NSAPI::getNSArraySelector(NSArrayMethodKind MK) const {
  if (NSArraySelectors[MK].isNull()) {
    Selector Sel;
    switch (MK) {
    case NSArr_array:
      Sel = Ctx.Selectors.getNullarySelector(&Ctx.Idents.get("array"));
      break;
    case NSArr_arrayWithArray:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("arrayWithArray"));
      break;
    case NSArr_arrayWithObject:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("arrayWithObject"));
      break;
    case NSArr_arrayWithObjects:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("arrayWithObjects"));
      break;
    case NSArr_arrayWithObjectsCount: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("arrayWithObjects"),
        &Ctx.Idents.get("count")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSArr_initWithArray:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("initWithArray"));
      break;
    case NSArr_initWithObjects:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("initWithObjects"));
      break;
    case NSArr_objectAtIndex:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("objectAtIndex"));
      break;
    case NSMutableArr_replaceObjectAtIndex: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("replaceObjectAtIndex"),
        &Ctx.Idents.get("withObject")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSMutableArr_addObject:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("addObject"));
      break;
    case NSMutableArr_insertObjectAtIndex: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("insertObject"),
        &Ctx.Idents.get("atIndex")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSMutableArr_setObjectAtIndexedSubscript: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("setObject"),
        &Ctx.Idents.get("atIndexedSubscript")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    }
    return (NSArraySelectors[MK] = Sel);
  }

  return NSArraySelectors[MK];
}

Optional<NSAPI::NSArrayMethodKind> NSAPI::getNSArrayMethodKind(Selector Sel) {
  for (unsigned i = 0; i != NumNSArrayMethods; ++i) {
    NSArrayMethodKind MK = NSArrayMethodKind(i);
    if (Sel == getNSArraySelector(MK))
      return MK;
  }

  return None;
}

Selector NSAPI::getNSDictionarySelector(
                                       NSDictionaryMethodKind MK) const {
  if (NSDictionarySelectors[MK].isNull()) {
    Selector Sel;
    switch (MK) {
    case NSDict_dictionary:
      Sel = Ctx.Selectors.getNullarySelector(&Ctx.Idents.get("dictionary"));
      break;
    case NSDict_dictionaryWithDictionary:
      Sel = Ctx.Selectors.getUnarySelector(
                                   &Ctx.Idents.get("dictionaryWithDictionary"));
      break;
    case NSDict_dictionaryWithObjectForKey: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("dictionaryWithObject"),
        &Ctx.Idents.get("forKey")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSDict_dictionaryWithObjectsForKeys: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("dictionaryWithObjects"),
        &Ctx.Idents.get("forKeys")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSDict_dictionaryWithObjectsForKeysCount: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("dictionaryWithObjects"),
        &Ctx.Idents.get("forKeys"),
        &Ctx.Idents.get("count")
      };
      Sel = Ctx.Selectors.getSelector(3, KeyIdents);
      break;
    }
    case NSDict_dictionaryWithObjectsAndKeys:
      Sel = Ctx.Selectors.getUnarySelector(
                               &Ctx.Idents.get("dictionaryWithObjectsAndKeys"));
      break;
    case NSDict_initWithDictionary:
      Sel = Ctx.Selectors.getUnarySelector(
                                         &Ctx.Idents.get("initWithDictionary"));
      break;
    case NSDict_initWithObjectsAndKeys:
      Sel = Ctx.Selectors.getUnarySelector(
                                     &Ctx.Idents.get("initWithObjectsAndKeys"));
      break;
    case NSDict_initWithObjectsForKeys: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("initWithObjects"),
        &Ctx.Idents.get("forKeys")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSDict_objectForKey:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("objectForKey"));
      break;
    case NSMutableDict_setObjectForKey: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("setObject"),
        &Ctx.Idents.get("forKey")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSMutableDict_setObjectForKeyedSubscript: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("setObject"),
        &Ctx.Idents.get("forKeyedSubscript")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSMutableDict_setValueForKey: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("setValue"),
        &Ctx.Idents.get("forKey")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    }
    return (NSDictionarySelectors[MK] = Sel);
  }

  return NSDictionarySelectors[MK];
}

Optional<NSAPI::NSDictionaryMethodKind>
NSAPI::getNSDictionaryMethodKind(Selector Sel) {
  for (unsigned i = 0; i != NumNSDictionaryMethods; ++i) {
    NSDictionaryMethodKind MK = NSDictionaryMethodKind(i);
    if (Sel == getNSDictionarySelector(MK))
      return MK;
  }

  return None;
}

Selector NSAPI::getNSSetSelector(NSSetMethodKind MK) const {
  if (NSSetSelectors[MK].isNull()) {
    Selector Sel;
    switch (MK) {
    case NSMutableSet_addObject:
      Sel = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("addObject"));
      break;
    case NSOrderedSet_insertObjectAtIndex: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("insertObject"),
        &Ctx.Idents.get("atIndex")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSOrderedSet_setObjectAtIndex: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("setObject"),
        &Ctx.Idents.get("atIndex")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSOrderedSet_setObjectAtIndexedSubscript: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("setObject"),
        &Ctx.Idents.get("atIndexedSubscript")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    case NSOrderedSet_replaceObjectAtIndexWithObject: {
      IdentifierInfo *KeyIdents[] = {
        &Ctx.Idents.get("replaceObjectAtIndex"),
        &Ctx.Idents.get("withObject")
      };
      Sel = Ctx.Selectors.getSelector(2, KeyIdents);
      break;
    }
    }
    return (NSSetSelectors[MK] = Sel);
  }

  return NSSetSelectors[MK];
}

Optional<NSAPI::NSSetMethodKind>
NSAPI::getNSSetMethodKind(Selector Sel) {
  for (unsigned i = 0; i != NumNSSetMethods; ++i) {
    NSSetMethodKind MK = NSSetMethodKind(i);
    if (Sel == getNSSetSelector(MK))
      return MK;
  }

  return None;
}

Selector NSAPI::getNSNumberLiteralSelector(NSNumberLiteralMethodKind MK,
                                           bool Instance) const {
  static const char *ClassSelectorName[NumNSNumberLiteralMethods] = {
    "numberWithChar",
    "numberWithUnsignedChar",
    "numberWithShort",
    "numberWithUnsignedShort",
    "numberWithInt",
    "numberWithUnsignedInt",
    "numberWithLong",
    "numberWithUnsignedLong",
    "numberWithLongLong",
    "numberWithUnsignedLongLong",
    "numberWithFloat",
    "numberWithDouble",
    "numberWithBool",
    "numberWithInteger",
    "numberWithUnsignedInteger"
  };
  static const char *InstanceSelectorName[NumNSNumberLiteralMethods] = {
    "initWithChar",
    "initWithUnsignedChar",
    "initWithShort",
    "initWithUnsignedShort",
    "initWithInt",
    "initWithUnsignedInt",
    "initWithLong",
    "initWithUnsignedLong",
    "initWithLongLong",
    "initWithUnsignedLongLong",
    "initWithFloat",
    "initWithDouble",
    "initWithBool",
    "initWithInteger",
    "initWithUnsignedInteger"
  };

  Selector *Sels;
  const char **Names;
  if (Instance) {
    Sels = NSNumberInstanceSelectors;
    Names = InstanceSelectorName;
  } else {
    Sels = NSNumberClassSelectors;
    Names = ClassSelectorName;
  }

  if (Sels[MK].isNull())
    Sels[MK] = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(Names[MK]));
  return Sels[MK];
}

Optional<NSAPI::NSNumberLiteralMethodKind>
NSAPI::getNSNumberLiteralMethodKind(Selector Sel) const {
  for (unsigned i = 0; i != NumNSNumberLiteralMethods; ++i) {
    NSNumberLiteralMethodKind MK = NSNumberLiteralMethodKind(i);
    if (isNSNumberLiteralSelector(MK, Sel))
      return MK;
  }

  return None;
}

Optional<NSAPI::NSNumberLiteralMethodKind>
NSAPI::getNSNumberFactoryMethodKind(QualType T) const {
  const BuiltinType *BT = T->getAs<BuiltinType>();
  if (!BT)
    return None;

  const TypedefType *TDT = T->getAs<TypedefType>();
  if (TDT) {
    QualType TDTTy = QualType(TDT, 0);
    if (isObjCBOOLType(TDTTy))
      return NSAPI::NSNumberWithBool;
    if (isObjCNSIntegerType(TDTTy))
      return NSAPI::NSNumberWithInteger;
    if (isObjCNSUIntegerType(TDTTy))
      return NSAPI::NSNumberWithUnsignedInteger;
  }

  switch (BT->getKind()) {
  case BuiltinType::Char_S:
  case BuiltinType::SChar:
    return NSAPI::NSNumberWithChar;
  case BuiltinType::Char_U:
  case BuiltinType::UChar:
    return NSAPI::NSNumberWithUnsignedChar;
  case BuiltinType::Short:
    return NSAPI::NSNumberWithShort;
  case BuiltinType::UShort:
    return NSAPI::NSNumberWithUnsignedShort;
  case BuiltinType::Int:
    return NSAPI::NSNumberWithInt;
  case BuiltinType::UInt:
    return NSAPI::NSNumberWithUnsignedInt;
  case BuiltinType::Long:
    return NSAPI::NSNumberWithLong;
  case BuiltinType::ULong:
    return NSAPI::NSNumberWithUnsignedLong;
  case BuiltinType::LongLong:
    return NSAPI::NSNumberWithLongLong;
  case BuiltinType::ULongLong:
    return NSAPI::NSNumberWithUnsignedLongLong;
  case BuiltinType::Float:
    return NSAPI::NSNumberWithFloat;
  case BuiltinType::Double:
    return NSAPI::NSNumberWithDouble;
  case BuiltinType::Bool:
    return NSAPI::NSNumberWithBool;

  case BuiltinType::Void:
  case BuiltinType::WChar_U:
  case BuiltinType::WChar_S:
  case BuiltinType::Char8:
  case BuiltinType::Char16:
  case BuiltinType::Char32:
  case BuiltinType::Int128:
  case BuiltinType::LongDouble:
  case BuiltinType::ShortAccum:
  case BuiltinType::Accum:
  case BuiltinType::LongAccum:
  case BuiltinType::UShortAccum:
  case BuiltinType::UAccum:
  case BuiltinType::ULongAccum:
  case BuiltinType::ShortFract:
  case BuiltinType::Fract:
  case BuiltinType::LongFract:
  case BuiltinType::UShortFract:
  case BuiltinType::UFract:
  case BuiltinType::ULongFract:
  case BuiltinType::SatShortAccum:
  case BuiltinType::SatAccum:
  case BuiltinType::SatLongAccum:
  case BuiltinType::SatUShortAccum:
  case BuiltinType::SatUAccum:
  case BuiltinType::SatULongAccum:
  case BuiltinType::SatShortFract:
  case BuiltinType::SatFract:
  case BuiltinType::SatLongFract:
  case BuiltinType::SatUShortFract:
  case BuiltinType::SatUFract:
  case BuiltinType::SatULongFract:
  case BuiltinType::UInt128:
  case BuiltinType::Float16:
  case BuiltinType::Float128:
  case BuiltinType::NullPtr:
  case BuiltinType::ObjCClass:
  case BuiltinType::ObjCId:
  case BuiltinType::ObjCSel:
#define IMAGE_TYPE(ImgType, Id, SingletonId, Access, Suffix) \
  case BuiltinType::Id:
#include "clang/Basic/OpenCLImageTypes.def"
#define EXT_OPAQUE_TYPE(ExtType, Id, Ext) \
  case BuiltinType::Id:
#include "clang/Basic/OpenCLExtensionTypes.def"
  case BuiltinType::OCLSampler:
  case BuiltinType::OCLEvent:
  case BuiltinType::OCLClkEvent:
  case BuiltinType::OCLQueue:
  case BuiltinType::OCLReserveID:
  case BuiltinType::BoundMember:
  case BuiltinType::Dependent:
  case BuiltinType::Overload:
  case BuiltinType::UnknownAny:
  case BuiltinType::ARCUnbridgedCast:
  case BuiltinType::Half:
  case BuiltinType::PseudoObject:
  case BuiltinType::BuiltinFn:
  case BuiltinType::OMPArraySection:
    break;
  }

  return None;
}

/// Returns true if \param T is a typedef of "BOOL" in objective-c.
bool NSAPI::isObjCBOOLType(QualType T) const {
  return isObjCTypedef(T, "BOOL", BOOLId);
}
/// Returns true if \param T is a typedef of "NSInteger" in objective-c.
bool NSAPI::isObjCNSIntegerType(QualType T) const {
  return isObjCTypedef(T, "NSInteger", NSIntegerId);
}
/// Returns true if \param T is a typedef of "NSUInteger" in objective-c.
bool NSAPI::isObjCNSUIntegerType(QualType T) const {
  return isObjCTypedef(T, "NSUInteger", NSUIntegerId);
}

StringRef NSAPI::GetNSIntegralKind(QualType T) const {
  if (!Ctx.getLangOpts().ObjC || T.isNull())
    return StringRef();

  while (const TypedefType *TDT = T->getAs<TypedefType>()) {
    StringRef NSIntegralResust =
      llvm::StringSwitch<StringRef>(
        TDT->getDecl()->getDeclName().getAsIdentifierInfo()->getName())
    .Case("int8_t", "int8_t")
    .Case("int16_t", "int16_t")
    .Case("int32_t", "int32_t")
    .Case("NSInteger", "NSInteger")
    .Case("int64_t", "int64_t")
    .Case("uint8_t", "uint8_t")
    .Case("uint16_t", "uint16_t")
    .Case("uint32_t", "uint32_t")
    .Case("NSUInteger", "NSUInteger")
    .Case("uint64_t", "uint64_t")
    .Default(StringRef());
    if (!NSIntegralResust.empty())
      return NSIntegralResust;
    T = TDT->desugar();
  }
  return StringRef();
}

bool NSAPI::isMacroDefined(StringRef Id) const {
  // FIXME: Check whether the relevant module macros are visible.
  return Ctx.Idents.get(Id).hasMacroDefinition();
}

bool NSAPI::isSubclassOfNSClass(ObjCInterfaceDecl *InterfaceDecl,
                                NSClassIdKindKind NSClassKind) const {
  if (!InterfaceDecl) {
    return false;
  }

  IdentifierInfo *NSClassID = getNSClassId(NSClassKind);

  bool IsSubclass = false;
  do {
    IsSubclass = NSClassID == InterfaceDecl->getIdentifier();

    if (IsSubclass) {
      break;
    }
  } while ((InterfaceDecl = InterfaceDecl->getSuperClass()));

  return IsSubclass;
}

bool NSAPI::isObjCTypedef(QualType T,
                          StringRef name, IdentifierInfo *&II) const {
  if (!Ctx.getLangOpts().ObjC)
    return false;
  if (T.isNull())
    return false;

  if (!II)
    II = &Ctx.Idents.get(name);

  while (const TypedefType *TDT = T->getAs<TypedefType>()) {
    if (TDT->getDecl()->getDeclName().getAsIdentifierInfo() == II)
      return true;
    T = TDT->desugar();
  }

  return false;
}

bool NSAPI::isObjCEnumerator(const Expr *E,
                             StringRef name, IdentifierInfo *&II) const {
  if (!Ctx.getLangOpts().ObjC)
    return false;
  if (!E)
    return false;

  if (!II)
    II = &Ctx.Idents.get(name);

  if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParenImpCasts()))
    if (const EnumConstantDecl *
          EnumD = dyn_cast_or_null<EnumConstantDecl>(DRE->getDecl()))
      return EnumD->getIdentifier() == II;

  return false;
}

Selector NSAPI::getOrInitSelector(ArrayRef<StringRef> Ids,
                                  Selector &Sel) const {
  if (Sel.isNull()) {
    SmallVector<IdentifierInfo *, 4> Idents;
    for (ArrayRef<StringRef>::const_iterator
           I = Ids.begin(), E = Ids.end(); I != E; ++I)
      Idents.push_back(&Ctx.Idents.get(*I));
    Sel = Ctx.Selectors.getSelector(Idents.size(), Idents.data());
  }
  return Sel;
}

Selector NSAPI::getOrInitNullarySelector(StringRef Id, Selector &Sel) const {
  if (Sel.isNull()) {
    IdentifierInfo *Ident = &Ctx.Idents.get(Id);
    Sel = Ctx.Selectors.getSelector(0, &Ident);
  }
  return Sel;
}
