//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

#import "TSInteraction.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>

NS_ASSUME_NONNULL_BEGIN

NSString *NSStringFromOWSInteractionType(OWSInteractionType value)
{
    switch (value) {
        case OWSInteractionType_Unknown:
            return @"OWSInteractionType_Unknown";
        case OWSInteractionType_IncomingMessage:
            return @"OWSInteractionType_IncomingMessage";
        case OWSInteractionType_OutgoingMessage:
            return @"OWSInteractionType_OutgoingMessage";
        case OWSInteractionType_Error:
            return @"OWSInteractionType_Error";
        case OWSInteractionType_Call:
            return @"OWSInteractionType_Call";
        case OWSInteractionType_Info:
            return @"OWSInteractionType_Info";
        case OWSInteractionType_ThreadDetails:
            return @"OWSInteractionType_ThreadDetails";
        case OWSInteractionType_TypingIndicator:
            return @"OWSInteractionType_TypingIndicator";
        case OWSInteractionType_UnreadIndicator:
            return @"OWSInteractionType_UnreadIndicator";
        case OWSInteractionType_DateHeader:
            return @"OWSInteractionType_DateHeader";
        case OWSInteractionType_UnknownThreadWarning:
            return @"OWSInteractionType_UnknownThreadWarning";
        case OWSInteractionType_DefaultDisappearingMessageTimer:
            return @"OWSInteractionType_DefaultDisappearingMessageTimer";
        case OWSInteractionType_CollapseSet:
            return @"OWSInteractionType_CollapseSet";
    }
}

// MARK: -

@interface TSInteraction ()

@property (nonatomic) uint64_t sortId;
@property (nonatomic) uint64_t receivedAtTimestamp;
@property (nonatomic) uint64_t timestamp;

@end

// MARK: -

@implementation TSInteraction

- (instancetype)initWithCustomUniqueId:(NSString *)uniqueId
                             timestamp:(uint64_t)timestamp
                   receivedAtTimestamp:(uint64_t)receivedAtTimestamp
                                thread:(TSThread *)thread
{
    self = [super initWithUniqueId:uniqueId];

    if (!self) {
        return self;
    }

    _timestamp = timestamp;
    _receivedAtTimestamp = receivedAtTimestamp;
    _uniqueThreadId = thread.uniqueId;

    return self;
}

- (instancetype)initWithTimestamp:(uint64_t)timestamp
              receivedAtTimestamp:(uint64_t)receivedAtTimestamp
                           thread:(TSThread *)thread
{
    // Use a sequential UUID for interaction inserts, as an optimization for the
    // corresponding insert into the index on `uniqueId`. See comments about
    // UUIDv7 for more.
    NSString *uniqueId = [[NSUUID sequential] UUIDString];
    self = [super initWithUniqueId:uniqueId];

    if (!self) {
        return self;
    }

    _timestamp = timestamp;
    _receivedAtTimestamp = receivedAtTimestamp;
    _uniqueThreadId = thread.uniqueId;

    return self;
}

// --- CODE GENERATION MARKER

// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.

// clang-format off

- (instancetype)initWithGrdbId:(int64_t)grdbId
                      uniqueId:(NSString *)uniqueId
             receivedAtTimestamp:(uint64_t)receivedAtTimestamp
                          sortId:(uint64_t)sortId
                       timestamp:(uint64_t)timestamp
                  uniqueThreadId:(NSString *)uniqueThreadId
{
    self = [super initWithGrdbId:grdbId
                        uniqueId:uniqueId];

    if (!self) {
        return self;
    }

    _receivedAtTimestamp = receivedAtTimestamp;
    _sortId = sortId;
    _timestamp = timestamp;
    _uniqueThreadId = uniqueThreadId;

    return self;
}

// clang-format on

// --- CODE GENERATION MARKER

- (void)encodeWithCoder:(NSCoder *)coder
{
    [self encodeIdsWithCoder:coder];
    [coder encodeObject:[self valueForKey:@"receivedAtTimestamp"] forKey:@"receivedAtTimestamp"];
    [coder encodeObject:[self valueForKey:@"sortId"] forKey:@"sortId"];
    [coder encodeObject:[self valueForKey:@"timestamp"] forKey:@"timestamp"];
    NSString *uniqueThreadId = self.uniqueThreadId;
    if (uniqueThreadId != nil) {
        [coder encodeObject:uniqueThreadId forKey:@"uniqueThreadId"];
    }
}

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (!self) {
        return self;
    }
    self->_receivedAtTimestamp = [(NSNumber *)[coder decodeObjectOfClass:[NSNumber class]
                                                                  forKey:@"receivedAtTimestamp"] unsignedLongLongValue];
    self->_sortId = [(NSNumber *)[coder decodeObjectOfClass:[NSNumber class] forKey:@"sortId"] unsignedLongValue];
    self->_timestamp = [(NSNumber *)[coder decodeObjectOfClass:[NSNumber class]
                                                        forKey:@"timestamp"] unsignedLongLongValue];
    self->_uniqueThreadId = [coder decodeObjectOfClass:[NSString class] forKey:@"uniqueThreadId"];

    // Previously the receivedAtTimestamp field lived on TSMessage, but we've moved it up
    // to the TSInteraction superclass.
    if (_receivedAtTimestamp == 0) {
        // Upgrade from the older "TSMessage.receivedAtDate" and "TSMessage.receivedAt" properties if
        // necessary.
        NSDate *receivedAtDate = [coder decodeObjectOfClass:[NSDate class] forKey:@"receivedAtDate"];
        if (!receivedAtDate) {
            receivedAtDate = [coder decodeObjectOfClass:[NSDate class] forKey:@"receivedAt"];
        }

        if (receivedAtDate) {
            _receivedAtTimestamp = [NSDate ows_millisecondsSince1970ForDate:receivedAtDate];
        }

        // For TSInteractions which are not TSMessage's, the timestamp *is* the receivedAtTimestamp
        if (_receivedAtTimestamp == 0) {
            _receivedAtTimestamp = _timestamp;
        }
    }

    return self;
}

- (NSUInteger)hash
{
    NSUInteger result = [super hash];
    result ^= self.receivedAtTimestamp;
    result ^= self.sortId;
    result ^= self.timestamp;
    result ^= self.uniqueThreadId.hash;
    return result;
}

- (BOOL)isEqual:(id)other
{
    if (![super isEqual:other]) {
        return NO;
    }
    TSInteraction *typedOther = (TSInteraction *)other;
    if (self.receivedAtTimestamp != typedOther.receivedAtTimestamp) {
        return NO;
    }
    if (self.sortId != typedOther.sortId) {
        return NO;
    }
    if (self.timestamp != typedOther.timestamp) {
        return NO;
    }
    if (![NSObject isObject:self.uniqueThreadId equalToObject:typedOther.uniqueThreadId]) {
        return NO;
    }
    return YES;
}

#pragma mark Thread

- (nullable TSThread *)threadWithTx:(DBReadTransaction *)tx
{
    if (self.uniqueThreadId == nil) {
        // This might be true for a few legacy interactions enqueued in the message
        // sender. The message sender will handle this case.
        return nil;
    }

    // However, it's also possible that the thread doesn't exist.
    return [TSThread fetchViaCacheObjCWithUniqueId:self.uniqueThreadId transaction:tx];
}

#pragma mark Date operations

- (NSDate *)receivedAtDate
{
    return [NSDate ows_dateWithMillisecondsSince1970:self.receivedAtTimestamp];
}

- (NSDate *)timestampDate
{
    return [NSDate ows_dateWithMillisecondsSince1970:self.timestamp];
}

- (OWSInteractionType)interactionType
{
    OWSFailDebug(@"unknown interaction type.");

    return OWSInteractionType_Unknown;
}

- (NSString *)description
{
    return [NSString
        stringWithFormat:@"%@ in thread: %@ timestamp: %llu", [super description], self.uniqueThreadId, self.timestamp];
}

#pragma mark -

- (BOOL)isDynamicInteraction
{
    return NO;
}

#pragma mark - sorting migration

- (void)replaceSortId:(uint64_t)sortId
{
    _sortId = sortId;
}

#if TESTABLE_BUILD

- (void)replaceTimestamp:(uint64_t)timestamp transaction:(DBWriteTransaction *)transaction
{
    [self anyUpdateWithTransaction:transaction
                             block:^(TSInteraction *interaction) { interaction.timestamp = timestamp; }];
}

- (void)replaceReceivedAtTimestamp:(uint64_t)receivedAtTimestamp
{
    self.receivedAtTimestamp = receivedAtTimestamp;
}

- (void)replaceReceivedAtTimestamp:(uint64_t)receivedAtTimestamp transaction:(DBWriteTransaction *)transaction
{
    [self anyUpdateWithTransaction:transaction
                             block:^(TSInteraction *interaction) {
                                 interaction.receivedAtTimestamp = receivedAtTimestamp;
                             }];
}
#endif

@end

NS_ASSUME_NONNULL_END
