//
//  IBTBaseEntity.m
//  
//
//  Created by Xummer on 15/4/7.
//  Copyright (c) 2015年 Xummer. All rights reserved.
//

#import "IBTModel.h"
#import "FMDB.h"
#import "ICRUtilsMacro.h"
#import "ICRDataBaseController.h"
#import "NSMutableArray+SafeInsert.h"
#import <objc/runtime.h>

@implementation IBTModel

@synthesize arrSubModels;

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    
    self.arrSubModels = [NSMutableArray array];
    
    return self;
}

#pragma mark - Setting
+ (NSDictionary *)specialKeysAndReplaceKeys {
    return nil;
}

+ (NSArray *)customAcitonKeys {
    return nil;
}

+ (NSArray *)localKeys {
    return nil;
}

#pragma mark - ICRDatabaseObject
+ (id)DBObject {
    return [[[self class] alloc] init];
}

+ (id)objectFromFetchResult:(FMResultSet *)result {
    id<IBTDatabaseObject> obj = [[[self class] alloc] init];
    
    [obj updateFromFetchResult:result];
    
    return obj;
}

- (void)updateFromFetchResult:(FMResultSet *)result {
    NSDictionary *dictSpKeyDict = [[self class] specialKeysAndReplaceKeys];
    NSArray *arrSpecialKeys = [dictSpKeyDict allKeys];
    NSArray *arrCustomKeys = [[self class] customAcitonKeys];
    
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
    NSString *objectType = [NSString stringWithUTF8String:@encode(id)];
    
    for (unsigned int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        NSString *keyInJson = key;
        if ([arrCustomKeys containsObject:key]) {
            continue;
        }
        else if ([arrSpecialKeys containsObject:key]) {
            keyInJson = dictSpKeyDict[ key ];
        }
        
        NSString *typeAttribute =
        [[[NSString stringWithUTF8String:property_getAttributes(property)] componentsSeparatedByString:@","] firstObject];
        NSString *nsPropertyType = [typeAttribute substringFromIndex:1];
        const char *propertyType = [nsPropertyType UTF8String];
        
//        CLog(@"Key %@, type %@", key, typeAttribute);
        
        if ([nsPropertyType hasPrefix:objectType]) {
            NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length] - 4)];  //turns @"NSString" into NSString
            Class typeClass = NSClassFromString(typeClassName);
            
            if (typeClass == [NSString class]) {
                [self setValue:[result stringForColumn:keyInJson] forKey:key];
            }
            else if (typeClass == [NSDate class]) {
                [self setValue:[result dateForColumn:keyInJson] forKey:key];
            }
            else if (typeClass == [NSArray class] ||
                     typeClass == [NSDictionary class])
            {
                NSData *data = [result dataForColumn:keyInJson];
                [self setValue:[NSKeyedUnarchiver unarchiveObjectWithData:data] forKey:keyInJson];
            }
            else {
                [self setValue:[result objectForColumnName:keyInJson] forKey:key];
            }
        }
        else if (strcmp(propertyType, @encode(BOOL)) == 0) {
            [self setValue:@( [result boolForColumn:keyInJson] ) forKey:key];
        }
        else if (strcmp(propertyType, @encode(unsigned long long)) == 0 ||
                 strcmp(propertyType, @encode(long long)) == 0)
        {
            [self setValue:@( [result longLongIntForColumn:keyInJson] ) forKey:key];
        }
        else if (strcmp(propertyType, @encode(double)) == 0 ||
            strcmp(propertyType, @encode(float)) == 0)
        {
            [self setValue:@( [result doubleForColumn:keyInJson] ) forKey:key];
        }
        else if (strcmp(propertyType, @encode(long)) == 0 ||
                 strcmp(propertyType, @encode(unsigned long)) == 0)
        {
            [self setValue:@( [result longForColumn:keyInJson] ) forKey:key];
        }
        else if (strcmp(propertyType, @encode(int)) == 0 ||
                 strcmp(propertyType, @encode(unsigned int)) == 0 ||
                 strcmp(propertyType, @encode(short)) == 0 ||
                 strcmp(propertyType, @encode(unsigned short)) == 0)
        {
            [self setValue:@( [result intForColumn:keyInJson] ) forKey:key];
        }
        else if (strcmp(propertyType, @encode(char)) == 0 ||
                 strcmp(propertyType, @encode(unsigned char)) == 0)
        {
            [self setValue:[NSString stringWithUTF8String:(const char *)[result UTF8StringForColumnName:keyInJson]]
                    forKey:key];
        }


        /*
         TODO
         dataNoCopyForColumn:
         */
    }
    
    free(properties);
}

#pragma mark - SQLite
+ (NSString *)TableName {
    return NSStringFromClass([self class]);
}

+ (NSString *)PrimaryKey {
    return nil;
}

+ (NSArray *)PrimaryKeys {
    NSString *priKey = [[self class] PrimaryKey];
    if (priKey) {
        return @[ priKey ];
    }
    else {
        return nil;
    }
}

- (NSString *)SQLForInsertOrUpdate:(NSArray * __autoreleasing *)pValues {
    
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    if (count == 0) {
        free(properties);
        return nil;
    }
    
    NSDictionary *dictSpKeyDict = [[self class] specialKeysAndReplaceKeys];
    NSArray *arrSpecialKeys = [dictSpKeyDict allKeys];
    NSArray *arrCustomKeys = [[self class] customAcitonKeys];
    
    NSMutableArray *arrValues = [NSMutableArray array];
    NSMutableArray *arrKeys = [NSMutableArray array];
    
    for (unsigned int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        
        id obj = [self valueForKey:key];
        
        if (!obj) {
            continue;
        }
        
        NSString *keyInJson = key;
        if ([arrCustomKeys containsObject:key]) {
            continue;
        }
        else if ([arrSpecialKeys containsObject:key]) {
            keyInJson = dictSpKeyDict[ key ];
        }
        
        [arrKeys addObject:keyInJson];
        
        if ([obj isKindOfClass:[NSArray class]] ||
            [obj isKindOfClass:[NSDictionary class]])
        {
            obj = [NSKeyedArchiver archivedDataWithRootObject:obj];
        }
        
        [arrValues addObject:obj];
    }
    
    free(properties);
    
    if (pValues) {
        *pValues = arrValues;
    }
    
    return [NSString stringWithFormat:@"REPLACE INTO '%@' (%@) VALUES %@",
            [[self class] TableName],
            [arrKeys componentsJoinedByString:@","],
            [[self class] ValuePlaceholdersWithCount:[arrValues count]]];
}

+ (NSString *)SQLForCreateTable {
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    if (count == 0) {
        free(properties);
        return nil;
    }
    
    NSDictionary *dictSpKeyDict = [[self class] specialKeysAndReplaceKeys];
    NSArray *arrSpecialKeys = [dictSpKeyDict allKeys];
    NSArray *arrCustomKeys = [[self class] customAcitonKeys];
    NSArray *arrPrimaryKeys = [[self class] PrimaryKeys];
    
    // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
    NSString *objectType = [NSString stringWithUTF8String:@encode(id)];
    NSString *nsTypeStr = nil;
    
    NSMutableString *nsTmp = nil;
    NSMutableArray *arrColNameAndTypes = [NSMutableArray array];
    
    for (unsigned int i = 0; i < count; i++) {
        nsTypeStr = @"TEXT";
        nsTmp = [NSMutableString stringWithString:@""];
        
        objc_property_t property = properties[ i ];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        NSString *keyInJson = key;
        if ([arrCustomKeys containsObject:key]) {
            continue;
        }
        else if ([arrSpecialKeys containsObject:key]) {
            keyInJson = dictSpKeyDict[ key ];
        }
        
        NSString *typeAttribute =
        [[[NSString stringWithUTF8String:property_getAttributes(property)] componentsSeparatedByString:@","] firstObject];
        NSString *nsPropertyType = [typeAttribute substringFromIndex:1];
        const char *propertyType = [nsPropertyType UTF8String];
        
        if ([nsPropertyType hasPrefix:objectType]) {
            NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length] - 4)];  //turns @"NSString" into NSString
            Class typeClass = NSClassFromString(typeClassName);
            
            if (typeClass == [NSString class]) {
                nsTypeStr = @"TEXT";
            }
            else if (typeClass == [NSDate class]) {
                nsTypeStr = @"TEXT";
            }
            else if (typeClass == [NSArray class] ||
                     typeClass == [NSDictionary class])
            {
                nsTypeStr = @"BLOB";
            }
            else {
                nsTypeStr = @"TEXT";
            }
        }
        else if (strcmp(propertyType, @encode(BOOL)) == 0) {
            nsTypeStr = @"INTEGER";
        }
        else if (strcmp(propertyType, @encode(unsigned long long)) == 0 ||
                 strcmp(propertyType, @encode(long long)) == 0)
        {
            nsTypeStr = @"INTEGER";
        }
        else if (strcmp(propertyType, @encode(double)) == 0 ||
                 strcmp(propertyType, @encode(float)) == 0)
        {
            nsTypeStr = @"REAL";
        }
        else if (strcmp(propertyType, @encode(long)) == 0 ||
                 strcmp(propertyType, @encode(unsigned long)) == 0)
        {
            nsTypeStr = @"INTEGER";
        }
        else if (strcmp(propertyType, @encode(int)) == 0 ||
                 strcmp(propertyType, @encode(unsigned int)) == 0 ||
                 strcmp(propertyType, @encode(short)) == 0 ||
                 strcmp(propertyType, @encode(unsigned short)) == 0)
        {
            nsTypeStr = @"INTEGER";
        }
        else if (strcmp(propertyType, @encode(char)) == 0 ||
                 strcmp(propertyType, @encode(unsigned char)) == 0)
        {
            nsTypeStr = @"TEXT";
        }
        else {
            nsTypeStr = @"TEXT";
        }
        
        [nsTmp appendFormat:@"'%@' %@", keyInJson, nsTypeStr];
        
        [arrColNameAndTypes addObject:nsTmp];
    }
    
    if ([arrPrimaryKeys count] > 0) {
        NSString *nsPriKeys = [NSString stringWithFormat:@"PRIMARY KEY (%@)", [arrPrimaryKeys componentsJoinedByString:@","]];
        [arrColNameAndTypes addObject:nsPriKeys];
    }
    
    free(properties);
    
    return [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS '%@' (%@)", [[self class] TableName], [arrColNameAndTypes componentsJoinedByString:@","]];
}

+ (NSString *)SQLForUpdateKeyValueDict:(NSDictionary *)kvDict
                          WhereInfoStr:(NSString *)nsWhereInfoStr
{
    if ([kvDict isKindOfClass:[NSDictionary class]] && [kvDict count] > 0) {
        NSMutableArray *arrKeyValues = [NSMutableArray array];
        for (NSString *key in [kvDict allKeys]) {
            NSString *nsTmp = [NSString stringWithFormat:@"%@=%@", key, kvDict[ key ]];
            [arrKeyValues safeAddObject:nsTmp];
        }
        
        return [NSString stringWithFormat:@"UPDATE '%@' SET %@ WHERE %@",
                [[self class] TableName],
                [arrKeyValues componentsJoinedByString:@","],
                nsWhereInfoStr ];
    }
    else {
        return nil;
    }
}

+ (NSString *)SQLForUpdateKeyValueDict:(NSDictionary *)kvDict
                             WhereInfo:(NSDictionary *)dictWhere
{
    if ([dictWhere isKindOfClass:[NSDictionary class]] && [dictWhere count])
    {
        NSMutableArray *arrWhereInfo = [NSMutableArray array];
        for (NSString *key in [dictWhere allKeys]) {
            NSString *nsTmp = [NSString stringWithFormat:@"%@=%@", key, dictWhere[ key ]];
            [arrWhereInfo safeAddObject:nsTmp];
        }
        
        return [[self class] SQLForUpdateKeyValueDict:kvDict WhereInfoStr:[arrWhereInfo componentsJoinedByString:@" AND "]];
    }
    else {
        return nil;
    }
}

- (void)saveToDBWithHandleData:(void (^)(id <IBTDatabaseObject>))handleDataBlock
                      complete:(void (^)(void))complete
                          fail:(void (^)(NSError *))fail
{
    ICRDataBaseController *dbCtrl = [ICRDataBaseController sharedController];
    [dbCtrl storageDBObject:self
                 handleData:handleDataBlock
                   complete:complete fail:fail];
}

#pragma mark - Functions
- (NSDictionary *)dictForCommit {
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    if (count == 0) {
        free(properties);
        return nil;
    }
    
    NSMutableDictionary *mDict = [NSMutableDictionary dictionary];
    
    NSDictionary *dictSpKeyDict = [[self class] specialKeysAndReplaceKeys];
    NSArray *arrSpecialKeys = [dictSpKeyDict allKeys];
    NSArray *arrCustomKeys = [[self class] customAcitonKeys];
    NSArray *arrLocalKeys = [[self class] localKeys];
    
    for (unsigned int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        
        id value = [self valueForKey:key];
        
        if (value) {
            if ([arrCustomKeys containsObject:key] ||
                [arrLocalKeys containsObject:key])
            {
                continue;
            }
            else if ([arrSpecialKeys containsObject:key]) {
                [mDict addEntriesFromDictionary:@{ dictSpKeyDict[ key ] : value }];
            }
            else {
                [mDict addEntriesFromDictionary:@{ key : value }];
            }
        }
    }
    free(properties);
    
    return mDict;
}

- (NSDictionary *)dictForLocalSave {
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    if (count == 0) {
        free(properties);
        return nil;
    }
    
    NSMutableDictionary *mDict = [NSMutableDictionary dictionary];
    
    NSDictionary *dictSpKeyDict = [[self class] specialKeysAndReplaceKeys];
    NSArray *arrSpecialKeys = [dictSpKeyDict allKeys];
    NSArray *arrCustomKeys = [[self class] customAcitonKeys];
    
    for (unsigned int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        
        id value = [self valueForKey:key];
        
        if (value) {
            if ([arrCustomKeys containsObject:key])
            {
                continue;
            }
            else if ([arrSpecialKeys containsObject:key]) {
                [mDict addEntriesFromDictionary:@{ dictSpKeyDict[ key ] : value }];
            }
            else {
                [mDict addEntriesFromDictionary:@{ key : value }];
            }
        }
    }
    
    free(properties);
    
    return mDict;
}

- (void)praseFromJsonDict:(NSDictionary *)dict {
    [self praseFromJsonDict:dict isNilLeague:YES];
}

- (void)praseFromLocalDict:(NSDictionary *)dict {
    [self praseFromLocalDict:dict isNilLeague:YES];
}

- (void)praseFromJsonDict:(NSDictionary *)dict isNilLeague:(BOOL)bIsNilLeague {
    if (!IsDictObject(dict)) {
        return;
    }
    
    NSDictionary *dictSpKeyDict = [[self class] specialKeysAndReplaceKeys];
    NSArray *arrSpecialKeys = [dictSpKeyDict allKeys];
    NSArray *arrCustomKeys = [[self class] customAcitonKeys];
    NSArray *arrLocalKeys = [[self class] localKeys];
    
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    for (unsigned int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        NSString *keyInJson = key;
        if ([arrCustomKeys containsObject:key] ||
            [arrLocalKeys containsObject:key])
        {
            continue;
        }
        else if ([arrSpecialKeys containsObject:key]) {
            keyInJson = dictSpKeyDict[ key ];
        }
        
        id obj = dict[ keyInJson ];
        obj = obj != [NSNull null] ? obj : nil;
        
        if (bIsNilLeague) {
            if (!obj) {
                NSString *typeAttribute =
                [[[NSString stringWithUTF8String:property_getAttributes(property)] componentsSeparatedByString:@","] firstObject];
                NSString *nsPropertyType = [typeAttribute substringFromIndex:1];
                const char *propertyType = [nsPropertyType UTF8String];
                
                if (strcmp(propertyType, @encode(BOOL)) == 0) {
                    obj = @( NO );
                }
                else if (strcmp(propertyType, @encode(unsigned long long)) == 0 ||
                         strcmp(propertyType, @encode(long long)) == 0 ||
                         strcmp(propertyType, @encode(double)) == 0 ||
                         strcmp(propertyType, @encode(float)) == 0 ||
                         strcmp(propertyType, @encode(long)) == 0 ||
                         strcmp(propertyType, @encode(unsigned long)) == 0 ||
                         strcmp(propertyType, @encode(int)) == 0 ||
                         strcmp(propertyType, @encode(unsigned int)) == 0 ||
                         strcmp(propertyType, @encode(short)) == 0 ||
                         strcmp(propertyType, @encode(unsigned short)) == 0)
                {
                    obj = @( 0 );
                }
            }
            [self setValue:obj forKey:key];
        }
        else {
            if (obj) {
                [self setValue:obj forKey:key];
            }
        }
    }
    
    free(properties);
}

- (void)praseFromLocalDict:(NSDictionary *)dict isNilLeague:(BOOL)bIsNilLeague {
    if (!IsDictObject(dict)) {
        return;
    }
    
    NSDictionary *dictSpKeyDict = [[self class] specialKeysAndReplaceKeys];
    NSArray *arrSpecialKeys = [dictSpKeyDict allKeys];
    NSArray *arrCustomKeys = [[self class] customAcitonKeys];
    
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    for (unsigned int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];
        NSString *keyInJson = key;
        if ([arrCustomKeys containsObject:key])
        {
            continue;
        }
        else if ([arrSpecialKeys containsObject:key]) {
            keyInJson = dictSpKeyDict[ key ];
        }
        
        id obj = dict[ keyInJson ];
        obj = obj != [NSNull null] ? obj : nil;
        
        if (bIsNilLeague) {
            if (!obj) {
                NSString *typeAttribute =
                [[[NSString stringWithUTF8String:property_getAttributes(property)] componentsSeparatedByString:@","] firstObject];
                NSString *nsPropertyType = [typeAttribute substringFromIndex:1];
                const char *propertyType = [nsPropertyType UTF8String];
                
                if (strcmp(propertyType, @encode(BOOL)) == 0) {
                    obj = @( NO );
                }
                else if (strcmp(propertyType, @encode(unsigned long long)) == 0 ||
                         strcmp(propertyType, @encode(long long)) == 0 ||
                         strcmp(propertyType, @encode(double)) == 0 ||
                         strcmp(propertyType, @encode(float)) == 0 ||
                         strcmp(propertyType, @encode(long)) == 0 ||
                         strcmp(propertyType, @encode(unsigned long)) == 0 ||
                         strcmp(propertyType, @encode(int)) == 0 ||
                         strcmp(propertyType, @encode(unsigned int)) == 0 ||
                         strcmp(propertyType, @encode(short)) == 0 ||
                         strcmp(propertyType, @encode(unsigned short)) == 0)
                {
                    obj = @( 0 );
                }
            }
            [self setValue:obj forKey:key];
        }
        else {
            if (obj) {
                [self setValue:obj forKey:key];
            }
        }
    }
    
    free(properties);
}

#pragma mark - Helper
+ (NSString *)ValuePlaceholdersWithCount:(NSUInteger)uiCount {
    
    NSMutableArray *arrTmp = [NSMutableArray array];
    for (NSUInteger i = 0; i < uiCount; i++) {
        [arrTmp addObject:@"?"];
    }
    
    return [NSString stringWithFormat:@"(%@)", [arrTmp componentsJoinedByString:@","]];
}

@end
