/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#import "BridgeSerializer.h"
#import "BridgeArray.h"
#import "BridgeCodecUtil.h"
#include "base/log/log.h"

uint8_t GetSizeForBridgeType(BridgeArrayType type) {
    switch (type) {
        case BridgeArrayTypeBooL:
            return 1;
        case BridgeArrayTypeInt32:
            return 4;
        case BridgeArrayTypeInt64:
            return 8;
        case BridgeArrayTypeDouble:
            return 8;
        default:
            return 1;
    }
}

BridgeCodecType BridgeCodecTypeForBridgeType(BridgeArrayType type) {
    switch (type) {
        case BridgeArrayTypeBooL:
            return T_LIST_BOOL;
        case BridgeArrayTypeInt32:
            return T_LIST_INT32;
        case BridgeArrayTypeInt64:
            return T_LIST_INT64;
        case BridgeArrayTypeDouble:
            return T_LIST_DOUBLE;
        case BridgeArrayTypeString:
            return T_LIST_STRING;
        default:
            return T_LIST_STRING;
    }
}

BridgeCodecObjectType BridgeCodecObjectTypeForBridge(BridgeArray* data) {
    if (data.arrayType != 0) {
        if (data.arrayType == BridgeArrayTypeBooL) {
            return BridgeCodecObjectTypeBoolList;
        } else if (data.arrayType == BridgeArrayTypeString) {
            return BridgeCodecObjectTypeStringList;
        } else {
            return BridgeCodecObjectTypeNumberListData;
        }
    }
    return BridgeCodecObjectTypeUnknown;
}

@implementation BridgeSerializer
- (BridgeStreamWriter*)writerWithData:(NSMutableData*)data {
    return [[BridgeStreamWriter alloc] initWithData:data];
}

- (BridgeStreamReader*)readerWithData:(NSData*)data {
    return [[BridgeStreamReader alloc] initWithData:data];
}
@end

/// BridgeStreamWriter
@interface BridgeStreamWriter()
{
    NSMutableData *_data;
}
@end

@implementation BridgeStreamWriter

- (instancetype)initWithData:(NSMutableData*)data {
    self = [super self];
    if (self) {
        _data = data;
    }
    return self;
}

- (bool)writeValue:(id)value {
    BridgeCodecObjectType type = GetWriteType(value);
    return WriteValueOfType((__bridge CFTypeRef)self,
                (__bridge CFMutableDataRef)_data, type, (__bridge CFTypeRef)value);
}

#pragma marked private

static BridgeCodecObjectType GetWriteType(id value) {
    if (value == nil || (__bridge CFNullRef)value == kCFNull || [value isKindOfClass:[NSNull class]]) {
        return BridgeCodecObjectTypeNil;
    } else if ([value isKindOfClass:[NSNumber class]]) {
        return BridgeCodecObjectTypeNSNumber;
    } else if ([value isKindOfClass:[NSString class]]) {
        return BridgeCodecObjectTypeNSString;
    } else if ([value isKindOfClass:[BridgeArray class]]) {
        return BridgeCodecObjectTypeForBridge(value);
    } else if ([value isKindOfClass:[NSData class]]) {
        return BridgeCodecObjectTypeNSData;
    } else if ([value isKindOfClass:[NSArray class]]) {
        return BridgeCodecObjectTypeNSArray;
    } else if ([value isKindOfClass:[NSDictionary class]]) {
        return BridgeCodecObjectTypeNSDictionary;
    }
    return BridgeCodecObjectTypeUnknown;
}

static bool WriteValueOfType(CFTypeRef writer,
                            CFMutableDataRef data,
                            BridgeCodecObjectType type,
                            CFTypeRef value) {
    switch (type) {
        case BridgeCodecObjectTypeNil:
            BridgeCodecUtilWriteByte(data, T_NULL);
            return true;
        case BridgeCodecObjectTypeNSNumber: {
            CFNumberRef number = (CFNumberRef)value;
            return BridgeCodecUtilWriteNumber(data, number);
        }
        case BridgeCodecObjectTypeNSString: {
            CFStringRef string = (CFStringRef)value;
            BridgeCodecUtilWriteByte(data, T_STRING);
            BridgeCodecUtilWriteUTF8(data, string);
            return true;
        }
        case BridgeCodecObjectTypeNumberListData:
            return WriteValueOfNumber(data, value);
        case BridgeCodecObjectTypeNSData: {
            WriteValueOfNSData(data, value);
            return true;
        } 
        case BridgeCodecObjectTypeBoolList:
            return WriteValueOfBoolList(data, value);
        case BridgeCodecObjectTypeStringList:
            WriteValueOfStringList(data, value);
            return true;
        case BridgeCodecObjectTypeNSArray:
            WriteValueOfNSArray(writer, data, value);
            return true;
        case BridgeCodecObjectTypeNSDictionary:
            WriteValueOfNSDictionary(writer, data, value);
            return true;
        case BridgeCodecObjectTypeUnknown:{
            LOGE("BridgeStreamWriter::Unsupported value of type %{public}s",
                 NSStringFromClass([(__bridge id)value class]).UTF8String);
            return false;
        }
        default:
            return false;
        break;
    }
}

static bool WriteValueOfNumber(CFMutableDataRef data, CFTypeRef value) {
    BridgeArray* bridgeData = (__bridge BridgeArray*)value;
    BridgeCodecUtilWriteByte(data, BridgeCodecTypeForBridgeType(bridgeData.arrayType));
    CFArrayRef array = (__bridge CFArrayRef)bridgeData.array;
    CFIndex size = CFArrayGetCount(array);
    uint8_t byteSize = GetSizeForBridgeType(bridgeData.arrayType);

    BridgeCodecUtilWriteSize(data, (uint32_t)size);
    BridgeCodecUtilWriteAlignment(data, byteSize);
    bool success = true;
    for (CFIndex i = 0; i < size; ++i) {
        CFNumberRef number = (CFNumberRef)CFArrayGetValueAtIndex(array, i);
        success = BridgeCodecUtilWriteNumberWithSize(data, number, byteSize);
        if (!success) {
            LOGE("BridgeStreamWriter::codec Unsupported value: %{public}d  number type: %{public}ld", (int)bridgeData.arrayType,
                 (long)CFNumberGetType(number));
            break;
        }
    }
    return success;
}

static void WriteValueOfNSData(CFMutableDataRef data, CFTypeRef value) {
        NSData* ocData = (__bridge NSData*)value;
        BridgeCodecUtilWriteByte(data, T_LIST_UINT8);
        BridgeCodecUtilWriteSize(data, (uint32_t)ocData.length);
        BridgeCodecUtilWriteAlignment(data, 1);
        BridgeCodecUtilWriteData(data, (__bridge CFDataRef)ocData);
}

static bool WriteValueOfBoolList(CFMutableDataRef data, CFTypeRef value) {
    BridgeCodecUtilWriteByte(data, T_LIST_BOOL);
    BridgeArray* bridgeData = (__bridge BridgeArray*)value;
    CFArrayRef array = (__bridge CFArrayRef)bridgeData.array;
    CFIndex size = CFArrayGetCount(array);
    BridgeCodecUtilWriteSize(data, (uint32_t)size);
    bool success = true;
    for (CFIndex i = 0; i < size; ++i) {
        CFNumberRef number = (CFNumberRef)CFArrayGetValueAtIndex(array, i);
        success = BridgeCodecUtilWriteNumber(data, number);
        if (!success) {
            LOGE("BridgeStreamWriter::codec Unsupported value:%{public}d  number type %{public}ld",
                   (int)bridgeData.arrayType, (long)CFNumberGetType(number));
            break;
        }
    }
    return success;
}

static void WriteValueOfStringList(CFMutableDataRef data, CFTypeRef value) {
    BridgeCodecUtilWriteByte(data, T_LIST_STRING);
    BridgeArray* bridgeData = (__bridge BridgeArray*)value;
    CFArrayRef array = (__bridge CFArrayRef)bridgeData.array;
    CFIndex size = CFArrayGetCount(array);
    BridgeCodecUtilWriteSize(data, (uint32_t)size);
    for (CFIndex i = 0; i < size; ++i) {
        CFStringRef string = (CFStringRef)CFArrayGetValueAtIndex(array, i);
        BridgeCodecUtilWriteUTF8(data, string);
    }
}

static void WriteValueOfNSArray(CFTypeRef writer, CFMutableDataRef data, CFTypeRef value) {
    BridgeCodecUtilWriteByte(data, T_COMPOSITE_LIST);
    CFArrayRef array = (CFArrayRef)value;
    CFIndex size = CFArrayGetCount(array);
    BridgeCodecUtilWriteSize(data, (uint32_t)size);
    for (CFIndex i = 0; i < size; ++i) {
        RecursivelyWriteValueOfType(writer, data, CFArrayGetValueAtIndex(array, i));
    }
}

static void WriteValueOfNSDictionary(CFTypeRef writer, CFMutableDataRef data, CFTypeRef value) {
    CFDictionaryRef dict = (CFDictionaryRef)value;
    BridgeCodecUtilWriteByte(data, T_MAP);
    CFIndex size = CFDictionaryGetCount(dict);
    BridgeCodecUtilWriteSize(data, (UInt32)size);
    DicWriteKeyValuesInfo info = {
        .writer = writer,
        .data = data,
    };
    CFDictionaryApplyFunction(dict, DicWriteKeyValues, (void*)&info);
}

static void RecursivelyWriteValueOfType(CFTypeRef writer, CFMutableDataRef data, CFTypeRef value) {
    BridgeCodecObjectType type = GetWriteType((__bridge id)value);
    if (type != BridgeCodecObjectTypeUnknown) {
        WriteValueOfType(writer, data, type, value);
    } else {
        LOGE("BridgeStreamWriter::List Unsupported value: %{public}u", (unsigned)type);
    }
}

struct DicWriteKeyValuesInfo {
    CFTypeRef writer;
    CFMutableDataRef data;
};

static void DicWriteKeyValues(CFTypeRef key, CFTypeRef value, void* context) {
    DicWriteKeyValuesInfo* info = (DicWriteKeyValuesInfo*)context;
    RecursivelyWriteValueOfType(info->writer, info->data, key);
    RecursivelyWriteValueOfType(info->writer, info->data, value);
}

@end

/// BridgeStreamReader
@interface BridgeStreamReader () {
    NSData* _data;
    NSRange _dataRange;
}
@end
@implementation BridgeStreamReader

- (instancetype)initWithData:(NSData*)data {
    self = [super self];
    if (self) {
        _data = data;
        _dataRange = NSMakeRange(0, 0);
    }
    return self;
}

- (BOOL)isNoMoreData {
    return _dataRange.location < _data.length;
}

- (uint8_t)readUnitByte {
    return BridgeCodecUtilReadByte(&_dataRange.location, (__bridge CFDataRef)_data);
}

- (void)readBytes:(void*)destination length:(NSUInteger)length {
    BridgeCodecUtilReadBytes(&_dataRange.location, length, destination, (__bridge CFDataRef)_data);
}

- (NSString*)readUTF8 {
    return (__bridge NSString*)BridgeCodecUtilReadUTF8(&_dataRange.location, (__bridge CFDataRef)_data);
}

- (nullable id)readValue {
    return (__bridge id)InnerReadValue((__bridge CFTypeRef)self);
}

- (nullable id)readValueWithType:(uint8_t)type {
    return (__bridge id)BridgeCodecUtilReadValueOfType(
        &_dataRange.location, (__bridge CFDataRef)_data, 
        type, InnerReadValue, (__bridge CFTypeRef)self);
}

static CFTypeRef InnerReadValue(CFTypeRef user_data) {
    BridgeStreamReader* reader = (__bridge BridgeStreamReader*)user_data;
    uint8_t type = BridgeCodecUtilReadByte(&reader->_dataRange.location,
                                           (__bridge CFDataRef)reader->_data);
    return (__bridge CFTypeRef)[reader readValueWithType:type];
}
@end