//
//  ICRDataBaseController.m
//  XFFruit
//
//  Created by Xummer on 4/5/15.
//  Copyright (c) 2015 Xummer. All rights reserved.
//

#import "ICRDataBaseController.h"

#import "ICRStore.h"
#import "ICRTask.h"
#import "ICRPostTask.h"
#import "ICRAttachment.h"
#import "ICRAnnouncement.h"
#import "ICRPatrolPlan.h"
#import "ICRPost.h"
#import "ICRQuestion.h"
#import "ICRStoreResult.h"
#import "ICRAnswer.h"
#import "ICRAnswerDetail.h"

#import "Product.h"
#import "User.h"
#import "Survey.h"
#import "Vendor.h"
#import "Warehouse.h"
#import "GXFProductUnit.h"
#import "Accounttitle.h"

#define ICR_DB_ERROR_PARAMETER              @"Parse Error: Bad Parameter(s)"

NSString * const ICRDataBaseErrorDomain = @"ICRDataBaseErrorDomain";
static NSString *ICRDataBasePath = @"";

@interface ICRDataBaseController ()

@property (strong, nonatomic) FMDatabaseQueue *m_dbQueue;

@end

@implementation ICRDataBaseController

#pragma mark - Class Method
+ (instancetype)sharedController {
    static ICRDataBaseController *_sharedController = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedController = [[self alloc] init];
    });
    
    return _sharedController;
}

+ (NSString *)GetDataBasePath {
    if (ICRDataBasePath.length == 0) {
        NSString *archivePath = [IBTCommon archivePathForCurrentUser];
        if (!archivePath.length) {
            return ICRDataBasePath;
        }
        
        NSString *dbName = @"db";
        NSString *sqliteName = [dbName stringByAppendingPathExtension:@"sqlite"];
        ICRDataBasePath = [archivePath stringByAppendingPathComponent:sqliteName];
        
        return ICRDataBasePath;
    }
    else {
        return ICRDataBasePath;
    }
}

+ (void)CleanUpDBPath {
    ICRDataBasePath = @"";
}

+ (NSError *)ErrorWithMsg:(NSString *)nsErrorMsg code:(NSInteger)uiCode {
    NSDictionary *userInfo =
    nsErrorMsg ? @{ NSLocalizedFailureReasonErrorKey : nsErrorMsg } : nil;
    
    NSError *error =
    [[NSError alloc] initWithDomain:ICRDataBaseErrorDomain
                               code:uiCode userInfo:userInfo];
    
    return error;
}

#pragma mark - Life Cycle
- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    
    NSString *nsDBPath = [[self class] GetDataBasePath];
    self.m_dbQueue = [FMDatabaseQueue databaseQueueWithPath:nsDBPath];
    
    [_m_dbQueue inDatabase:^(FMDatabase *db) {
        
        NSArray *tableNameArr =
        @[ [Product class],[User class],[Survey class],[Vendor class],[Warehouse class],[GXFProductUnit class],[Accounttitle class]];
        
        NSMutableArray *sqlBatch = [NSMutableArray array];
        NSString *sql = nil;
        
        for (id <IBTDatabaseObject> cls in tableNameArr) {
            sql = [cls SQLForCreateTable];
            [sqlBatch safeAddObject:sql];
        }
        
        NSMutableString *nsSQLBatch = [[NSMutableString alloc] initWithString:@""];
        for (NSString *aSQL in sqlBatch) {
            [nsSQLBatch appendFormat:@"%@;", aSQL];
        }
        
        BOOL res = [db executeStatements:nsSQLBatch];
        if (!res) {
            CLog(@"error to run SQL: %@", sql);
        }
    }];
    
    return self;
}

#pragma mark - Storage
- (void)storageEntities:(NSArray *)arrObjects
            objectClass:(Class<IBTDatabaseObject>)DBObjClass
            deleteLocal:(BOOL)bDeleteLocal
             handleData:(void (^)(id <IBTDatabaseObject>))handleDataBlock
               complete:(void (^)(void))complete
                   fail:(void (^)(NSError *))fail
{
    [self storageEntities:arrObjects objectClass:DBObjClass updateNil:YES deleteLocal:bDeleteLocal handleData:handleDataBlock complete:complete fail:fail];
}

- (void)storageEntities:(NSArray *)arrObjects
            objectClass:(Class<IBTDatabaseObject>)DBObjClass
              updateNil:(BOOL)bUpdateNil
            deleteLocal:(BOOL)bDeleteLocal
             handleData:(void (^)(id <IBTDatabaseObject>))handleDataBlock
               complete:(void (^)(void))complete
                   fail:(void (^)(NSError *))fail
{
    if (!IsArrayObject(arrObjects)) {
        if (fail) {
            fail( [[self class] ErrorWithMsg:ICR_DB_ERROR_PARAMETER code:0] );
        }
    }
    
    NSString *nsTableName = [DBObjClass TableName];
    NSString *nsSortKey = [DBObjClass PrimaryKey];
    NSString *nsDBSortKey = nsSortKey;
    
    NSArray *arrSortedObjects = [arrObjects sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
        id first = a[ nsSortKey ];
        id second = b[ nsSortKey ];
        return [first compare:second];
    }];
    
    NSArray *arrIDs = [arrSortedObjects valueForKeyPath:nsSortKey];
    NSMutableArray *arrModObjs = [NSMutableArray array];
    
    for (id obj in arrObjects) {
        id<IBTDatabaseObject> dbObj = [DBObjClass DBObject];
        
        [dbObj praseFromJsonDict:obj isNilLeague:bUpdateNil];
    
        [arrModObjs addObject:dbObj];
    }
    
    [_m_dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        if (bDeleteLocal) {
            NSMutableString *sql = [NSMutableString stringWithFormat:@"DELETE FROM %@ ", nsTableName];
            NSUInteger uiRecordCount = [arrIDs count];
            if (uiRecordCount > 0) {
                [sql appendFormat:@"WHERE %@ NOT IN %@", nsDBSortKey, [IBTModel ValuePlaceholdersWithCount:uiRecordCount]];
            }
            CLog(@"%@", sql);
            
            [db executeUpdate:sql withArgumentsInArray:arrIDs];
        }
        
        @autoreleasepool {
            NSString *nsSql = nil;
            NSArray *arrValues = nil;
            for (id<IBTDatabaseObject> dbObj in arrModObjs) {
                
                if (handleDataBlock) {
                    handleDataBlock( dbObj );
                }
                
                nsSql = [dbObj SQLForInsertOrUpdate:&arrValues];
                [db executeUpdate:nsSql withArgumentsInArray:arrValues];
                for (id<IBTDatabaseObject> subObj in dbObj.arrSubModels) {
                    nsSql = [subObj SQLForInsertOrUpdate:&arrValues];
                    [db executeUpdate:nsSql withArgumentsInArray:arrValues];
                    
                }
            }
        }
    }];
    
    if (complete) {
        complete();
    }
}

- (void)storageEntity:(NSDictionary *)dictObject
          objectClass:(Class<IBTDatabaseObject>)DBObjClass
           handleData:(void (^)(id <IBTDatabaseObject>))handleDataBlock
             complete:(void (^)(void))complete
                 fail:(void (^)(NSError *))fail
{
    if (!IsDictObject(dictObject)) {
        if (fail) {
            fail( [[self class] ErrorWithMsg:ICR_DB_ERROR_PARAMETER code:0] );
        }
    }
    
    __block id<IBTDatabaseObject> dbObj = [DBObjClass DBObject];
    [dbObj praseFromJsonDict:dictObject];
    
    [_m_dbQueue inDatabase:^(FMDatabase *db) {
        
        if (handleDataBlock) {
            handleDataBlock( dbObj );
        }
        
        NSString *nsSql = nil;
        NSArray *arrValues = nil;
        
        nsSql = [dbObj SQLForInsertOrUpdate:&arrValues];
        
        [db executeUpdate:nsSql withArgumentsInArray:arrValues];
        
        for (id<IBTDatabaseObject> subObj in dbObj.arrSubModels) {
            nsSql = [subObj SQLForInsertOrUpdate:&arrValues];
            [db executeUpdate:nsSql withArgumentsInArray:arrValues];
            
        }
    }];
    
    if (complete) {
        complete();
    }
}

- (void)storageDBObject:(id<IBTDatabaseObject>)dbObj
           handleData:(void (^)(id <IBTDatabaseObject>))handleDataBlock
             complete:(void (^)(void))complete
                 fail:(void (^)(NSError *))fail
{
    if (!dbObj) {
        if (fail) {
            fail( [[self class] ErrorWithMsg:ICR_DB_ERROR_PARAMETER code:0] );
        }
    }
    
    [_m_dbQueue inDatabase:^(FMDatabase *db) {
        
        if (handleDataBlock) {
            handleDataBlock( dbObj );
        }
        
        NSString *nsSql = nil;
        NSArray *arrValues = nil;
        
        nsSql = [dbObj SQLForInsertOrUpdate:&arrValues];
        
        [db executeUpdate:nsSql withArgumentsInArray:arrValues];
        
        for (id<IBTDatabaseObject> subObj in dbObj.arrSubModels) {
            nsSql = [subObj SQLForInsertOrUpdate:&arrValues];
            [db executeUpdate:nsSql withArgumentsInArray:arrValues];
            
        }
    }];
    
    if (complete) {
        complete();
    }
}

#pragma mark - Query

- (void)runFetchForClass:(Class<IBTDatabaseObject>)dbObjClass
              fetchBlock:(ICRDatabaseFetchBlock)fetchBlock
       fetchResultsBlock:(ICRDatabaseFetchResultsBlock)fetchResultBlock
{
    NSParameterAssert(dbObjClass); NSParameterAssert(fetchBlock); NSParameterAssert(fetchResultBlock);
    
    [_m_dbQueue inDatabase:^(FMDatabase *db) {
        FMResultSet *rs = fetchBlock(db);
        
        if (rs == nil && db.lastError != nil) {
            [NSException raise:[NSString stringWithFormat:@"%@Exception", self.class]
                        format:@"Invalid querry: %@", db.lastError];
            fetchResultBlock(nil);
            return;
        }
        
        NSArray *fetchedObjects = [self databaseObjectsWithResultSet:rs
                                                               class:dbObjClass];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            fetchResultBlock(fetchedObjects);
        });
        
    }];
}

- (NSArray *)databaseObjectsWithResultSet:(FMResultSet *)resultSet
                                    class:(Class<IBTDatabaseObject>)dbObjClass
{
    NSMutableArray *arrObjs = [NSMutableArray array];
    
    while ([resultSet next]) {
        id obj = [dbObjClass objectFromFetchResult:resultSet];
        if (obj) {
            [arrObjs addObject:obj];
        }
    }
    
    return [arrObjs count] > 0 ? arrObjs : nil;
}

@end
