/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "WXPrerenderManager.h"
#import "WXConfigCenterProtocol.h"
#import "WXModuleMethod.h"
#import "WXBridgeManager.h"
#import "WXSDKInstance_private.h"
#import "WXBridgeManager.h"
#import "WXSDKEngine.h"
#import "WXUtility.h"

static NSString *const MSG_PRERENDER_INTERNAL_ERROR = @"internal_error";
static NSString *const MSG_PRERENDER_SUCCESS = @"success";

@interface WXPrerenderTask:NSObject

@property (nonatomic, strong) WXSDKInstance *instance;
@property (nonatomic, strong) UIView *view;
@property (nonatomic, copy) NSString *parentInstanceId;
@property (nonatomic, copy) NSString *url;
@property (nonatomic, strong) NSMutableArray *moduleTasks;
@property (nonatomic, strong) NSError *error;
@property (nonatomic, assign) WXState state;
@property (nonatomic, strong) NSDate *beginDate;
@property (nonatomic) long long cacheTime;
@property (nonatomic) BOOL isCache;  // if set cache , the cachetime is no use.

@end
@implementation WXPrerenderTask
@end


@interface WXPrerenderManager()

@property (nonatomic, strong) NSMutableArray *cachedUrlList;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) NSMutableDictionary<NSString *, WXPrerenderTask*> *prerenderTasks;
@property (nonatomic) NSInteger maxCacheNumber;

@end

@implementation WXPrerenderManager

+ (instancetype) sharedInstance{
    
    static WXPrerenderManager *instance = nil;
    static dispatch_once_t once;
    
    dispatch_once(&once, ^{
        instance = [[WXPrerenderManager alloc] initPrivate];
    });
    
    return instance;
}

- (instancetype) initPrivate{
    self = [super init];
    if(self){
        self.cachedUrlList = [[NSMutableArray alloc] init];
        self.queue = dispatch_queue_create("Prerender", DISPATCH_QUEUE_SERIAL);
        self.prerenderTasks = [[NSMutableDictionary alloc] init];
    }
    
    return self;
}

- (void) dealloc{
    self.cachedUrlList = nil;
    self.prerenderTasks = nil;
}

+ (void) addTask:(NSString *)url instanceId:(NSString *)instanceId callback:(WXModuleKeepAliveCallback)callback{
    NSURL *newUrl = [NSURL URLWithString:url];
    if(!newUrl){
        if(callback){
            callback(@{@"url":url,@"message":MSG_PRERENDER_INTERNAL_ERROR,@"result":@"error"}, NO);
        }
        return;
    }
    
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    __weak WXPrerenderManager *weakSelf = manager;
    dispatch_async(manager.queue, ^{
        [weakSelf prerender:newUrl instanceId:instanceId isCache:NO callback:callback];
    });
}

+ (void) addGlobalTask:(NSString *) url callback:(WXModuleKeepAliveCallback)callback
{
    NSURL *newUrl = [NSURL URLWithString:url];
    if(!newUrl){
        if(callback){
            callback(@{@"url":url,@"message":MSG_PRERENDER_INTERNAL_ERROR,@"result":@"error"}, NO);
        }
        return;
    }
    
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    __weak WXPrerenderManager *weakSelf = manager;
    dispatch_async(manager.queue, ^{
        [weakSelf prerender:newUrl instanceId:@"" isCache:YES callback:callback];
    });
}

-(BOOL)isSwitchOn
{
    BOOL switchOn = YES; // defautle YES
    id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)];
    if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) {
        id switchOnValue = [configCenter configForKey:@"iOS_weex_prerender_config.is_switch_on" defaultValue:@true isDefault:NULL];
        if(switchOnValue){
            switchOn = [switchOnValue boolValue];
        }
        if(!switchOn){
            return NO;
        }
    }
    return YES;
}

- (void) prerender:(NSURL *)url instanceId:(NSString *)instanceId isCache:(BOOL)isCache callback:(WXModuleKeepAliveCallback) callback{

    NSString *str = url.absoluteString;
    if(str.length==0){
        if(callback){
            callback(@{@"url":[url absoluteString],@"message":MSG_PRERENDER_INTERNAL_ERROR,@"result":@"error"}, NO);
        }
        return;
    }
    WXPrerenderTask *task = [WXPrerenderTask new];
    id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)];
    if(![self isSwitchOn]){
        if(callback){
            callback(@{@"url":[url absoluteString],@"message":MSG_PRERENDER_INTERNAL_ERROR,@"result":@"error"}, NO);
        }
        return;
    }
    task.beginDate = [NSDate date];
    task.cacheTime = 300000;
    if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) {
        long long time = [[configCenter configForKey:@"iOS_weex_prerender_config.cacheTime" defaultValue:@300000 isDefault:NULL] longLongValue];
        if(time){
            task.cacheTime = time;
        }
    }
    self.maxCacheNumber = 5;
    if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) {
        NSInteger max = [[configCenter configForKey:@"iOS_weex_prerender_config.max_cache_num" defaultValue:@5 isDefault:NULL] integerValue];
        if(max){
            self.maxCacheNumber = max;
        }
    }
    if(self.prerenderTasks && self.prerenderTasks.count<self.maxCacheNumber){
        [self.prerenderTasks setObject:task forKey:[WXPrerenderManager getTaskKeyFromUrl:url.absoluteString]];
        WXPerformBlockOnMainThread(^{
            WXSDKInstance *instance = [[WXSDKInstance alloc] init];
            instance.needPrerender = YES;
            task.instance = instance;
            task.parentInstanceId = instanceId;
            task.url = url.absoluteString;
            task.isCache = isCache;
            WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
            __weak typeof(self) weakSelf = manager;
            instance.onCreate = ^(UIView *view) {
                WXPrerenderTask *task = [weakSelf.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url.absoluteString]];
                task.view = view;
                if(task){
                    [weakSelf.prerenderTasks setObject:task forKey:[WXPrerenderManager getTaskKeyFromUrl:url.absoluteString]];
                }
            };
            
            instance.onFailed = ^(NSError *error) {
                WXPrerenderTask *task  = [weakSelf.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url.absoluteString]];
                task.error = error;
                if(task){
                    [weakSelf.prerenderTasks setObject:task forKey:[WXPrerenderManager getTaskKeyFromUrl:url.absoluteString]];
                }
            };
            [instance renderWithURL:url options:@{@"bundleUrl":url.absoluteString} data:nil];
        });
        if(callback){
            callback(@{@"url":url.absoluteString,@"message":MSG_PRERENDER_SUCCESS,@"result":@"success"}, NO);
        }
    }
}

+ (BOOL)isTaskReady:(NSString *)url{
    return [[WXPrerenderManager sharedInstance]isTaskReady:url];
}
- (BOOL)isTaskReady:(NSString *)url
{
    if( !url ||url.length == 0){
        return NO;
    }
    WXPrerenderTask *task  = [self.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
    if(!task ){
        return NO;
    }
    if(![self isSwitchOn]){
        return NO;
    }
    // compare cache time with begin time
    NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:task.beginDate];
    if(time > task.cacheTime && !task.isCache){
        return NO;
    }
    
    if(!task.view)  // view not exist ，not prerender
    {
        return NO;
    }
    if(task ){
        return YES;
    }
    return NO;
}

+ (BOOL)isTaskExist:(NSString *)url{
    if( !url ||url.length == 0){
        return NO;
    }
    id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)];
    if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) {
        BOOL switchOn = NO; // defautle NO
        id switchOnValue = [configCenter configForKey:@"iOS_weex_prerender_config.is_switch_on" defaultValue:@YES isDefault:NULL];
        if(switchOnValue){
            switchOn = [switchOnValue boolValue];
        }
        if(!switchOn){
            return NO;
        }
        
        id urlsValue = [configCenter configForKey:@"iOS_weex_prerender_config.urls" defaultValue:NULL isDefault:NULL];
        if(urlsValue){
            NSData *data = [urlsValue dataUsingEncoding:NSUTF8StringEncoding];
            NSError *error = nil;
            NSArray *urls = [WXUtility JSONObject:data error:&error];
            if(urls && [urls count]>0){
                for (NSString *configUrl in urls) {
                    if(configUrl && [[WXPrerenderManager getTaskKeyFromUrl:configUrl] isEqualToString:[WXPrerenderManager getTaskKeyFromUrl:url]]) {
                            return YES;
                        }
                }
                
            }
        }
        
    }
    
    return NO;
}

- (BOOL)isTaskExist:(NSString *)url
{
    if( !url ||url.length == 0){
        return NO;
    }
    WXPrerenderTask *task  = [self.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
    if(task ){
        return YES;
    }
    return NO;
}

+ (void)renderFromCache:(NSString *)url
{
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    if([manager isTaskReady:url])
    {
        WXPrerenderTask *task  = [manager.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
        
        UIView *view = [self viewFromUrl:url];
        NSError *error = [self errorFromUrl:url];
        if(task.instance.onCreate){
            task.instance.onCreate(view);
        }
        if(error && task.instance.onFailed){
            task.instance.onFailed(error);
        }
        WXPerformBlockOnComponentThread(^{
            [task.instance.componentManager startComponentTasks];
            [task.instance.componentManager executePrerenderUITask:url];
            task.instance.needPrerender = NO;
        });
        WXPerformBlockOnBridgeThreadForInstance(^{
            [WXPrerenderManager excuteModuleTasksForUrl:url];
        }, task.instance.instanceId);
    }
}

+ (UIView *)viewFromUrl:(NSString *)url
{
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    WXPrerenderTask *task  = [manager.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
    return task.view;
}

+ (NSError *)errorFromUrl:(NSString *)url
{
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    WXPrerenderTask *task  = [manager.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
    return task.error;
}

+ (id )instanceFromUrl:(NSString *)url
{
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    WXPrerenderTask *task  = [manager.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
    return task.instance;
}

+ (void)removePrerenderTaskforUrl:(NSString *)url
{
    if (url.length > 0) {
        WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
        if(manager.prerenderTasks && [manager.prerenderTasks count]>0){
            WXPrerenderTask *task  = [manager.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
            if(task){
                [manager.prerenderTasks removeObjectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
            }
        }
    }
}

+ (void)storePrerenderModuleTasks:(WXModuleMethod *)method forUrl:(NSString *)url
{
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    WXPrerenderTask *task = [manager.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
    if (!task.moduleTasks){
        task.moduleTasks = [NSMutableArray new];
    }
    [task.moduleTasks addObject:method];
    if(task){
        [manager.prerenderTasks setObject:task forKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
    }
}

+ (void)excuteModuleTasksForUrl:(NSString *)url
{
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    WXPrerenderTask *task = [manager.prerenderTasks objectForKey:[WXPrerenderManager getTaskKeyFromUrl:url]];
    
    if (task.moduleTasks && [task.moduleTasks count]>0){
        for (WXModuleMethod *method in task.moduleTasks) {
            [method invoke];
        }
    }
}

+ (NSString *)getTaskKeyFromUrl:(NSString *)url
{
    NSURL *newUrl = [NSURL URLWithString:url];
    NSString * newUrlStr = @"";
    if(url){
        newUrlStr =  [NSString stringWithFormat:@"%@/%@",newUrl.host,newUrl.path?:@""];
    }
    return newUrlStr;
}


+ (void)destroyTask:(NSString *)parentInstanceId
{
    WXPrerenderManager *manager = [WXPrerenderManager sharedInstance];
    if(!manager.prerenderTasks || [manager.prerenderTasks count] == 0){
        return;
    }
    
    [manager.prerenderTasks enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
        WXPrerenderTask *task = manager.prerenderTasks[key];
        if ([task.parentInstanceId isEqualToString:parentInstanceId]) {
            [manager removeTask:task];
        }
    }];
}

- (void)removeTask:(WXPrerenderTask *)task
{
    [task.instance destroyInstance];
    if(self.prerenderTasks && [self.prerenderTasks count] > 0 && task.url.length > 0){
        [self.prerenderTasks removeObjectForKey:[WXPrerenderManager getTaskKeyFromUrl:task.url]];
    }
}

@end
