//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>

#include <swift/Runtime/Debug.h>
#include <cstdint>
#include "swift/Basic/Lazy.h"

// This file assumes that `sizeof(NSInteger) == Swift.Int` and
// `sizeof(NSUInteger) == sizeof(Swift.UInt)`.

@interface _SwiftTypePreservingNSNumber : NSNumber
@end

// This enum has a matching counterpart in Foundation.swift, please
// update both copies when changing it.
enum _SwiftTypePreservingNSNumberTag: uint8_t {
  SwiftInt     =  0,
  SwiftInt64   =  1,
  SwiftInt32   =  2,
  SwiftInt16   =  3,
  SwiftInt8    =  4,
  SwiftUInt    =  5,
  SwiftUInt64  =  6,
  SwiftUInt32  =  7,
  SwiftUInt16  =  8,
  SwiftUInt8   =  9,
  SwiftFloat   = 10,
  SwiftDouble  = 11,
  SwiftCGFloat = 12,
  SwiftBool    = 13,
  /// A non-type-preserving NSNumber from Cocoa.
  ///
  /// Note that this value is *not* in the corresponding Swift enum in
  /// Foundation.swift. It should never be used as the tag of a valid
  /// _SwiftTypePreservingNSNumber instance; it is used as a return value
  /// for _swift_Foundation_TypePreservingNSNumberGetKind to indicate that
  /// an NSNumber instance does not know its originating Swift type.
  ///
  /// The numbering scheme here, with `NonSwift` coming after the valid values
  /// for the enum, matches Swift's tag assignment scheme for single-payload
  /// enums. For an Optional<_SwiftTypePreservingNSNumberTag>, Swift uses the
  /// first invalid integer value to represent `.none`. This representation on
  /// the C side allows _SwiftTypePreservingNSNumberTag(rawValue:) on the Swift
  /// side to compile down to a no-op.
  NonSwift     = 14,
};

static const unsigned StorageSize = 8;

@implementation _SwiftTypePreservingNSNumber {
  char storage[StorageSize];
  _SwiftTypePreservingNSNumberTag tag;
}

- (id)init {
  self = [super init];
  if (self) {
    self->tag = SwiftInt;
    memset(self->storage, 0, StorageSize);
  }
  return self;
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wobjc-designated-initializers"
- (id)initWithCoder:(NSCoder *)coder {
  swift::swift_reportError(
      /* flags = */ 0,
      "_SwiftTypePreservingNSNumber should not be archived.\n");
  abort();
}
#pragma GCC diagnostic pop

- (id)copyWithZone:(NSZone *)zone {
  return [self retain];
}

// When returning the `objCType` encoding, use the traditional C names,
// because these are what are documented as corresponding to specific
// encode strings, and `int` vs `long` or `long` vs `long long` may have
// different encodings. Ensure the traditional C types have the sizes we
// expect, though.
static_assert(sizeof(long long) == 8, "long long should be 64 bit");
static_assert(sizeof(int) == 4, "int should be 32 bit");
static_assert(sizeof(short) == 2, "short should be 16 bit");

- (const char *)objCType {
  // When changing this method, make sure to keep the `getValue:` method in
  // sync (it should copy as many bytes as this property promises).
  switch(tag) {
  case SwiftInt:
    return @encode(NSInteger);
  case SwiftInt64:
    return @encode(long long);
  case SwiftInt32:
    return @encode(int);
  case SwiftInt16:
    return @encode(short);
  case SwiftInt8:
    return @encode(signed char);
  case SwiftUInt:
    return @encode(NSUInteger);
  case SwiftUInt64:
    return @encode(unsigned long long);
  case SwiftUInt32:
    return @encode(unsigned);
  case SwiftUInt16:
    return @encode(unsigned short);
  case SwiftUInt8:
    return @encode(unsigned char);
  case SwiftFloat:
    return @encode(float);
  case SwiftDouble:
    return @encode(double);
  case SwiftCGFloat:
    return @encode(CGFloat);
  case NonSwift:
  case SwiftBool:
    // Bool is represented by CFBoolean.
    break;
  }
  swift::swift_reportError(
      /* flags = */ 0,
      "_SwiftTypePreservingNSNumber.tag is corrupted.\n");
  abort();
}

- (void)getValue:(void *)value {
  // This method should copy as many bytes as the `objCType` property promises.
  switch(tag) {
  case SwiftInt:
    memcpy(value, self->storage, sizeof(NSInteger));
    return;
  case SwiftInt64:
    memcpy(value, self->storage, sizeof(int64_t));
    return;
  case SwiftInt32:
    memcpy(value, self->storage, sizeof(int32_t));
    return;
  case SwiftInt16:
    memcpy(value, self->storage, sizeof(int16_t));
    return;
  case SwiftInt8:
    memcpy(value, self->storage, sizeof(int8_t));
    return;
  case SwiftUInt:
    memcpy(value, self->storage, sizeof(NSUInteger));
    return;
  case SwiftUInt64:
    memcpy(value, self->storage, sizeof(uint64_t));
    return;
  case SwiftUInt32:
    memcpy(value, self->storage, sizeof(uint32_t));
    return;
  case SwiftUInt16:
    memcpy(value, self->storage, sizeof(uint16_t));
    return;
  case SwiftUInt8:
    memcpy(value, self->storage, sizeof(uint8_t));
    return;
  case SwiftFloat:
    memcpy(value, self->storage, sizeof(float));
    return;
  case SwiftDouble:
    memcpy(value, self->storage, sizeof(double));
    return;
  case SwiftCGFloat:
    memcpy(value, self->storage, sizeof(CGFloat));
    return;
  case NonSwift:
  case SwiftBool:
    // Bool is represented by CFBoolean.
    break;
  }
  swift::swift_reportError(
      /* flags = */ 0,
      "_SwiftTypePreservingNSNumber.tag is corrupted.\n");
  abort();
}

#define DEFINE_ACCESSOR(C_TYPE, METHOD_NAME) \
  - (C_TYPE)METHOD_NAME { \
    switch(tag) { \
    case SwiftInt: { \
      NSInteger result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftInt64: { \
      int64_t result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftInt32: { \
      int32_t result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftInt16: { \
      int16_t result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftInt8: { \
      int8_t result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftUInt: { \
      NSUInteger result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftUInt64: { \
      uint64_t result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftUInt32: { \
      uint32_t result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftUInt16: { \
      uint16_t result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftUInt8: { \
      uint8_t result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftFloat: { \
      float result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftDouble: { \
      double result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case SwiftCGFloat: { \
      CGFloat result; \
      memcpy(&result, self->storage, sizeof(result)); \
      return result; \
    } \
    case NonSwift: \
    case SwiftBool: { \
      /* Bool is represented by CFBoolean. */ \
      break; \
    } \
    } \
    swift::swift_reportError( \
        /* flags = */ 0, \
        "_SwiftTypePreservingNSNumber.tag is corrupted.\n"); \
    abort(); \
  }

DEFINE_ACCESSOR(char, charValue)
DEFINE_ACCESSOR(unsigned char, unsignedCharValue)
DEFINE_ACCESSOR(short, shortValue)
DEFINE_ACCESSOR(unsigned short, unsignedShortValue)
DEFINE_ACCESSOR(int, intValue)
DEFINE_ACCESSOR(unsigned int, unsignedIntValue)
DEFINE_ACCESSOR(long long, longLongValue)
DEFINE_ACCESSOR(unsigned long long, unsignedLongLongValue)
DEFINE_ACCESSOR(float, floatValue)
DEFINE_ACCESSOR(double, doubleValue)
DEFINE_ACCESSOR(NSInteger, integerValue)
DEFINE_ACCESSOR(NSUInteger, unsignedIntegerValue)

#undef DEFINE_ACCESSOR

- (Class)classForCoder {
  return [NSNumber class];
}

#define DEFINE_INIT(C_TYPE, FUNCTION_NAME) \
  SWIFT_CC(swift) extern "C" NS_RETURNS_RETAINED NSNumber * \
  _swift_Foundation_TypePreservingNSNumberWith ## FUNCTION_NAME(C_TYPE value) { \
    _SwiftTypePreservingNSNumber *result = \
      [[_SwiftTypePreservingNSNumber alloc] init]; \
    result->tag = Swift ## FUNCTION_NAME; \
    memcpy(result->storage, &value, sizeof(value)); \
    return result; \
  }

DEFINE_INIT(NSInteger, Int)
DEFINE_INIT(int64_t, Int64)
DEFINE_INIT(int32_t, Int32)
DEFINE_INIT(int16_t, Int16)
DEFINE_INIT(int8_t,  Int8)
DEFINE_INIT(NSUInteger, UInt)
DEFINE_INIT(uint64_t, UInt64)
DEFINE_INIT(uint32_t, UInt32)
DEFINE_INIT(uint16_t, UInt16)
DEFINE_INIT(uint8_t,  UInt8)
DEFINE_INIT(float, Float)
DEFINE_INIT(double, Double)
DEFINE_INIT(CGFloat, CGFloat)

#undef DEFINE_INIT

SWIFT_CC(swift) extern "C" uint8_t
_swift_Foundation_TypePreservingNSNumberGetKind(
  NSNumber *NS_RELEASES_ARGUMENT _Nonnull self_) {
  uint8_t result = NonSwift;
  if ([self_ isKindOfClass:
                   SWIFT_LAZY_CONSTANT([_SwiftTypePreservingNSNumber class])]) {
    result = ((_SwiftTypePreservingNSNumber *) self_)->tag;
  } else if (CFGetTypeID(self_) == CFBooleanGetTypeID()) {
    result = SwiftBool;
  }
  [self_ release];
  return result;
}

#define DEFINE_GETTER(C_TYPE, FUNCTION_NAME) \
  SWIFT_CC(swift) extern "C" C_TYPE \
  _swift_Foundation_TypePreservingNSNumberGetAs ## FUNCTION_NAME( \
      _SwiftTypePreservingNSNumber *NS_RELEASES_ARGUMENT _Nonnull self_) { \
    if (self_->tag != Swift ## FUNCTION_NAME) { \
      swift::swift_reportError( \
        /* flags = */ 0, "Incorrect tag.\n"); \
    } \
    C_TYPE result; \
    memcpy(&result, self_->storage, sizeof(result)); \
    [self_ release]; \
    return result; \
  }

DEFINE_GETTER(NSInteger, Int)
DEFINE_GETTER(int64_t, Int64)
DEFINE_GETTER(int32_t, Int32)
DEFINE_GETTER(int16_t, Int16)
DEFINE_GETTER(int8_t,  Int8)
DEFINE_GETTER(NSUInteger, UInt)
DEFINE_GETTER(uint64_t, UInt64)
DEFINE_GETTER(uint32_t, UInt32)
DEFINE_GETTER(uint16_t, UInt16)
DEFINE_GETTER(uint8_t,  UInt8)
DEFINE_GETTER(float, Float)
DEFINE_GETTER(double, Double)
DEFINE_GETTER(CGFloat, CGFloat)

#undef DEFINE_GETTER

@end

