//
//  CWSqliteModelTool.m
//  CWDB
//
//  Created by 陈旺 on 2017/12/3.
//  Copyright © 2017年 Chavez. All rights reserved.
//

#import "CWSqliteModelTool.h"
#import "CWModelTool.h"
#import "CWDatabase.h"
#import "CWSqliteTableTool.h"

@interface CWSqliteModelTool ()

@property (nonatomic,strong)dispatch_semaphore_t dsema;

@end

@implementation CWSqliteModelTool

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.dsema = dispatch_semaphore_create(1);
    }
    return self;
}

static CWSqliteModelTool * instance = nil;
+ (instancetype)shareInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[CWSqliteModelTool alloc] init];
    });
    return instance;
}


+ (BOOL)createSQLTable:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId {
   
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    
    if (![cls respondsToSelector:@selector(primaryKey)]) {
        return NO;
    }
   
    NSString *primaryKey = [cls primaryKey];
    if (!primaryKey) {
        return NO;
    }
    
    NSString *createTableSql = [NSString stringWithFormat:@"create table if not exists %@(%@, primary key(%@))",tableName,[CWModelTool sqlColumnNamesAndTypesStr:cls],primaryKey];
   
    BOOL result = [CWDatabase execSQL:createTableSql uid:uid];
  
    
    return result;
}




+ (BOOL)insertOrUpdateModel:(id)model {
    return [self insertOrUpdateModels:@[model] uid:nil targetId:nil];
}

+ (BOOL)insertOrUpdateModels:(NSArray<id> *)modelsArray {
    return [self insertOrUpdateModels:modelsArray uid:nil targetId:nil];
}


+ (BOOL)insertOrUpdateModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId {
    return [self insertOrUpdateModels:@[model] uid:uid targetId:targetId];
}


+ (BOOL)insertOrUpdateModels:(NSArray<id> *)modelsArray uid:(NSString *)uid targetId:(NSString *)targetId {
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);
    id modelF = modelsArray.firstObject;
    Class cls = [modelF class];
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    
    if (![CWSqliteTableTool isTableExists:tableName uid:uid]) {
        BOOL r = [self createSQLTable:cls uid:uid targetId:targetId];
        if (!r) {
            dispatch_semaphore_signal([[self shareInstance] dsema]);
            return NO;
        }
    }else {
        if (!targetId) targetId = @"";
        NSString *cacheKey = [NSString stringWithFormat:@"%@%@CWUpdated",NSStringFromClass(cls),targetId];
        BOOL updated = [[[CWCache shareInstance] objectForKey:cacheKey] boolValue];
        if (!updated) {
            if ([CWSqliteTableTool isTableNeedUpdate:cls uid:uid targetId:targetId] ) {
                dispatch_semaphore_signal([[self shareInstance] dsema]);
                BOOL result = [self updateTable:cls uid:uid targetId:targetId];
                dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);
                if (!result) {
                    [[CWCache shareInstance] setObject:@(NO) forKey:cacheKey];
                    NSLog(@"更新数据库表结构失败!插入或更新数据失败!");
                    dispatch_semaphore_signal([[self shareInstance] dsema]);
                    return NO;
                }
                [[CWCache shareInstance] setObject:@(YES) forKey:cacheKey];
            }else {
                [[CWCache shareInstance] setObject:@(YES) forKey:cacheKey];
            }
        }
    }
    

    if (![cls respondsToSelector:@selector(primaryKey)]) {
        dispatch_semaphore_signal([[self shareInstance] dsema]);
        return NO;
    }
    NSString *primaryKey = [cls primaryKey];
    if (!primaryKey) {
        dispatch_semaphore_signal([[self shareInstance] dsema]);
        return NO;
    }
    [CWDatabase beginTransaction:uid];
    for (id model in modelsArray) {
        @autoreleasepool {
        id primaryValue = [model valueForKeyPath:primaryKey];
        NSString * checkSql = [NSString stringWithFormat:@"select * from %@ where %@ = '%@'",tableName,primaryKey,primaryValue];
        
        NSArray *result = [CWDatabase querySql:checkSql uid:uid];
        NSDictionary *nameTypeDict = [CWModelTool classIvarNameAndTypeDic:cls];
        NSArray *allIvarNames = nameTypeDict.allKeys;
        NSMutableArray *allIvarValues = [NSMutableArray array];
        for (NSString *ivarName in allIvarNames) {
            @autoreleasepool {
                id value = [model valueForKeyPath:ivarName];
                
                NSString *type = nameTypeDict[ivarName];
                
                value = [CWModelTool formatModelValue:value type:type isEncode:YES];
                
                [allIvarValues addObject:value];
            }
        }
        NSMutableArray *ivarNameValueArray = [NSMutableArray array];
        
        [allIvarNames enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSString *name = obj;
            id value = allIvarValues[idx];
            NSString *ivarNameValue = [NSString stringWithFormat:@"%@='%@'",name,value];
            [ivarNameValueArray addObject:ivarNameValue];
        }];
        
        NSString *execSql = @"";
        if (result.count > 0) {
            execSql = [NSString stringWithFormat:@"update %@ set %@ where %@ = '%@'",tableName,[ivarNameValueArray componentsJoinedByString:@","],primaryKey,primaryValue];
        }else {
            execSql = [NSString stringWithFormat:@"insert into %@(%@) values('%@')",tableName,[allIvarNames componentsJoinedByString:@","],[allIvarValues componentsJoinedByString:@"','"]];
        }
        BOOL ret = [CWDatabase execSQL:execSql uid:uid];
        if (ret == NO) {
            [CWDatabase rollBackTransaction:uid];
            dispatch_semaphore_signal([[self shareInstance] dsema]);
            return NO;
        }
        }
    }
    [CWDatabase commitTransaction:uid];
    [CWDatabase closeDB];
    
    dispatch_semaphore_signal([[self shareInstance] dsema]);
    return YES;
}



+ (NSArray *)queryAllModels:(Class)cls {
    return [self queryAllModels:cls uid:nil targetId:nil];
}

+ (NSArray *)queryModels:(Class)cls name:(NSString *)name relation:(CWDBRelationType)relation value:(id)value {
    return [self queryModels:cls name:name relation:relation value:value uid:nil targetId:nil];
}

+ (NSArray *)queryModels:(Class)cls columnNames:(NSArray <NSString *>*)columnNames relations:(NSArray <NSNumber *>*)relations values:(NSArray *)values isAnd:(BOOL)isAnd {
    return [self queryModels:cls columnNames:columnNames relations:relations values:values isAnd:isAnd uid:nil targetId:nil];
}


+ (NSArray *)queryAllModels:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId {
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    NSString *sql = [NSString stringWithFormat:@"select * from %@", tableName];
    
    NSArray <NSDictionary *>*results = [CWDatabase querySql:sql uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);
    
    return [self parseResults:results withClass:cls];
}

+ (NSArray *)queryModels:(Class)cls Sql:(NSString *)sql uid:(NSString *)uid {
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);
    NSArray <NSDictionary *>*results = [CWDatabase querySql:sql uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);

    return [self parseResults:results withClass:cls];
}

+ (NSArray *)queryModels:(Class)cls name:(NSString *)name relation:(CWDBRelationType)relation value:(id)value uid:(NSString *)uid targetId:(NSString *)targetId {
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);

    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    NSString *sql = [NSString stringWithFormat:@"select * from %@ where %@ %@ '%@'", tableName,name,self.CWDBNameToValueRelationTypeDic[@(relation)],value];
    
    NSArray <NSDictionary *>*results = [CWDatabase querySql:sql uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);

    return [self parseResults:results withClass:cls];
}

+ (NSArray *)queryModels:(Class)cls columnNames:(NSArray <NSString *>*)columnNames relations:(NSArray <NSNumber *>*)relations values:(NSArray *)values isAnd:(BOOL)isAnd uid:(NSString *)uid targetId:(NSString *)targetId {
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);

    if (!(columnNames.count == relations.count && relations.count == values.count)) {
        NSLog(@"columnNames、relations、values元素个数请保持一致!");
        dispatch_semaphore_signal([[self shareInstance] dsema]);
        return nil;
    }
    
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    NSString *appendStr = isAnd ? @"and" : @"or" ;
    NSMutableString *sql = [NSMutableString stringWithFormat:@"select * from %@ where",tableName];
    for (int i = 0; i < columnNames.count; i++) {
        NSString *columnName = columnNames[i];
        NSString *relation = self.CWDBNameToValueRelationTypeDic[relations[i]];
        id value = values[i];
        NSString *nameValueStr = [NSString stringWithFormat:@" %@ %@ '%@' ",columnName,relation,value];
        [sql appendString:nameValueStr];
        if (i != columnNames.count - 1) {
            [sql appendString:appendStr];
        }
    }
    
    NSArray <NSDictionary *>*results = [CWDatabase querySql:sql uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);

    return [self parseResults:results withClass:cls];
}

+ (NSArray *)parseResults:(NSArray <NSDictionary *>*)results withClass:(Class)cls  {
    
    NSMutableArray *models = [NSMutableArray array];
    // {字段名称 : 值}
    for (NSDictionary *dict in results) {
        
        id model = [CWModelTool model:cls Dict:dict];
        [models addObject:model];
    }
    return models;
}



+ (BOOL)deleteModel:(id)model {
    return [self deleteModel:model uid:nil targetId:nil];
}

+ (BOOL)deleteTableAllData:(Class)cls isKeepTable:(BOOL)isKeep {
    return [self deleteTableAllData:cls uid:nil targetId:nil isKeepTable:isKeep];
}

+ (BOOL)deleteModels:(Class)cls columnName:(NSString *)name relation:(CWDBRelationType)relation value:(id)value {
    return [self deleteModels:cls columnName:name relation:relation value:value uid:nil targetId:nil];
}

+ (BOOL)deleteModels:(Class)cls columnNames:(NSArray <NSString *>*)columnNames relations:(NSArray <NSNumber *>*)relations values:(NSArray *)values isAnd:(BOOL)isAnd {
    return [self deleteModels:cls columnNames:columnNames relations:relations values:values isAnd:isAnd uid:nil targetId:nil];
}

+ (BOOL)deleteTableAllData:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId isKeepTable:(BOOL)isKeep {
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    NSString *deleteSql ;
    if (isKeep) {
        deleteSql = [NSString stringWithFormat:@"delete from %@",tableName];
    }else {
        deleteSql = [NSString stringWithFormat:@"drop table if exists %@",tableName];
    }
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);
    BOOL result = [CWDatabase execSQL:deleteSql uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);

    return result;
}

+ (BOOL)deleteModel:(id)model uid:(NSString *)uid targetId:(NSString *)targetId {
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);

    Class cls = [model class];
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    if (![cls respondsToSelector:@selector(primaryKey)]) {
        NSLog(@"如果想要操作这个模型，必须要实现+ (NSString *)primaryKey;这个方法，来告诉我主键信息");
        dispatch_semaphore_signal([[self shareInstance] dsema]);
        return NO;
    }
    NSString *primaryKey = [cls primaryKey];
    id primaryValue = [model valueForKeyPath:primaryKey];
    NSString *deleteSql = [NSString stringWithFormat:@"delete from %@ where %@ = '%@'",tableName,primaryKey,primaryValue];
    
    BOOL result = [CWDatabase execSQL:deleteSql uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);

    return result;
}

+ (BOOL)deleteModelWithSql:(NSString *)deleteSql uid:(NSString *)uid{
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);
    
    BOOL result = [CWDatabase execSQL:deleteSql uid:uid];
    
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);
    
    return result;
}

+ (BOOL)deleteModels:(Class)cls columnName:(NSString *)name relation:(CWDBRelationType)relation value:(id)value uid:(NSString *)uid targetId:(NSString *)targetId {
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);
    
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    NSString *deleteSql = [NSString stringWithFormat:@"delete from %@ where %@ %@ '%@'",tableName,name,self.CWDBNameToValueRelationTypeDic[@(relation)],value];
    
    BOOL result = [CWDatabase execSQL:deleteSql uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);

    return result;
}

+ (BOOL)deleteModels:(Class)cls columnNames:(NSArray <NSString *>*)columnNames relations:(NSArray <NSNumber *>*)relations values:(NSArray *)values isAnd:(BOOL)isAnd uid:(NSString *)uid targetId:(NSString *)targetId {
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);

    if (!(columnNames.count == relations.count && relations.count == values.count)) {
        NSLog(@"columnNames、relations、values元素个数请保持一致!");
        dispatch_semaphore_signal([[self shareInstance] dsema]);
        return NO;
    }
    
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    NSString *appendStr = isAnd ? @"and" : @"or" ;

    NSMutableString *deleteSql = [NSMutableString stringWithFormat:@"delete from %@ where",tableName];
    
    [columnNames enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSString *columnName = obj;
        NSString *relation = self.CWDBNameToValueRelationTypeDic[relations[idx]];
        id value = values[idx];
        NSString *nameValueStr = [NSString stringWithFormat:@" %@ %@ '%@' ",columnName,relation,value];
        [deleteSql appendString:nameValueStr];
        if (idx != columnNames.count - 1) {
            [deleteSql appendString:appendStr];
        }
    }];
    
    BOOL result = [CWDatabase execSQL:deleteSql uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);
    
    return result;
}

+ (BOOL)updateTable:(Class)cls uid:(NSString *)uid targetId:(NSString *)targetId{
    
    dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER);
    NSString *tmpTableName = [CWModelTool tmpTableName:cls targetId:targetId];
    NSString *tableName = [CWModelTool tableName:cls targetId:targetId];
    
    if (![cls respondsToSelector:@selector(primaryKey)]) {
        NSLog(@"如果想要操作这个模型，必须要实现+ (NSString *)primaryKey;这个方法，来告诉我主键信息");
        dispatch_semaphore_signal([[self shareInstance] dsema]);
        return NO;
    }
    
    NSMutableArray *execSqls = [NSMutableArray array];
    
    NSString *primaryKey = [cls primaryKey];
    NSString *createTableSql = [NSString stringWithFormat:@"create table if not exists %@(%@, primary key(%@))",tmpTableName,[CWModelTool sqlColumnNamesAndTypesStr:cls],primaryKey];
    
    [execSqls addObject:createTableSql];
    
    NSString *inserPrimaryKeyData = [NSString stringWithFormat:@"insert into %@(%@) select %@ from %@",tmpTableName,primaryKey,primaryKey,tableName];
    
    [execSqls addObject:inserPrimaryKeyData];
    
    NSArray *oldNames = [CWSqliteTableTool allTableColumnNames:tableName uid:uid];
    NSArray *newNames = [CWModelTool allIvarNames:cls];
        NSDictionary *newNameToOldNameDic = @{};
    if ([cls respondsToSelector:@selector(newNameToOldNameDic)]) {
        newNameToOldNameDic = [cls newNameToOldNameDic];
    }
    
    for (NSString *columnName in newNames) {
        NSString *oldName = columnName;
        if ([newNameToOldNameDic[columnName] length] != 0) {
            if ([oldNames containsObject:newNameToOldNameDic[columnName]]) {
                oldName = newNameToOldNameDic[columnName];
            }
        }
        if ((![oldNames containsObject:columnName] && [columnName isEqualToString:oldName]) ) {
            continue;
        }
        NSString *updateSql = [NSString stringWithFormat:@"update %@ set %@ = (select %@ from %@ where %@.%@ = %@.%@)",tmpTableName,columnName,oldName,tableName,tmpTableName,primaryKey,tableName,primaryKey];
        
        [execSqls addObject:updateSql];
        
    }
    
    NSString *deleteOldTable = [NSString stringWithFormat:@"drop table if exists %@",tableName];
    [execSqls addObject:deleteOldTable];
    
    NSString *renameTableName = [NSString stringWithFormat:@"alter table %@ rename to %@",tmpTableName,tableName];
    [execSqls addObject:renameTableName];
    
    BOOL result = [CWDatabase execSqls:execSqls uid:uid];
    [CWDatabase closeDB];
    dispatch_semaphore_signal([[self shareInstance] dsema]);

    return result;
}

+ (NSDictionary *)CWDBNameToValueRelationTypeDic {
    return @{@(CWDBRelationTypeMore):@">",
             @(CWDBRelationTypeLess):@"<",
             @(CWDBRelationTypeEqual):@"=",
             @(CWDBRelationTypeMoreEqual):@">=",
             @(CWDBRelationTypeLessEqual):@"<="
             };
}

@end
