/*
 * 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 "WXApmForInstance.h"
#import "WXApmProtocol.h"
#import "WXHandlerFactory.h"
#import "WXSDKManager.h"
#import "WXAppConfiguration.h"
#import "WXUtility.h"
#import "WXComponentManager.h"
#import "WXConfigCenterProtocol.h"
#import "WXSDKEngine.h"
#import "WXSDKError.h"
#import "WXExceptionUtils.h"
#import "WXSDKInstance_performance.h"
#import "WXAnalyzerCenter+Transfer.h"


#pragma mark - const static string

NSString* const WEEX_PAGE_TOPIC = @"weex_page";

/************** properties *****************/
NSString* const KEY_PROPERTIES_ERROR_CODE = @"wxErrorCode";
NSString* const KEY_PAGE_PROPERTIES_BIZ_ID = @"wxBizID";
NSString* const KEY_PAGE_PROPERTIES_BUBDLE_URL = @"wxBundleUrl";
NSString* const KEY_PAGE_PROPERTIES_JSLIB_VERSION  = @"wxJSLibVersion";
NSString* const KEY_PAGE_PROPERTIES_WEEX_VERSION  = @"wxSDKVersion";
NSString* const KEY_PAGE_PROPERTIES_REQUEST_TYPE  = @"wxRequestType";
NSString* const KEY_PAGE_PROPERTIES_Z_CACHE_INFO  = @"wxZCacheInfo";
NSString* const KEY_PAGE_PROPERTIES_GREY_BUNDLE = @"wxGreyBundle";
NSString* const KEY_PAGE_PROPERTIES_JS_FM_INIT  = @"wxJsFrameworkInit";
NSString* const KEY_PAGE_PROPERTIES_BUNDLE_TYPE = @"wxBundleType";
NSString* const KEY_PAGE_PROPERTIES_CONTAINER_NAME = @"wxContainerName";
NSString* const KEY_PAGE_PROPERTIES_INSTANCE_TYPE = @"wxInstanceType";
NSString* const KEY_PAGE_PROPERTIES_PARENT_PAGE = @"wxParentPage";
NSString* const KEY_PAGE_PROPERTIES_RENDER_TYPE = @"wxRenderType";
NSString* const KEY_PAGE_PROPERTIES_UIKIT_TYPE = @"wxUIKitType";



///************** stages *****************/
NSString* const KEY_PAGE_STAGES_START = @"wxRecordStart";
NSString* const KEY_PAGE_STAGES_DOWN_BUNDLE_START  = @"wxStartDownLoadBundle";
NSString* const KEY_PAGE_STAGES_DOWN_BUNDLE_END  = @"wxEndDownLoadBundle";
NSString* const KEY_PAGE_STAGES_DOWN_JS_START  = @"wxStartDownLoadJS";
NSString* const KEY_PAGE_STAGES_DOWN_JS_END  = @"wxEndDownLoadJS";
NSString* const KEY_PAGE_STAGES_CUSTOM_PREPROCESS_START  = @"wxCustomPreprocessStart";
NSString* const KEY_PAGE_STAGES_CUSTOM_PREPROCESS_END  = @"wxCustomPreprocessEnd";
NSString* const KEY_PAGE_STAGES_RENDER_ORGIGIN  = @"wxRenderTimeOrigin";
NSString* const KEY_PAGE_STAGES_LOAD_BUNDLE_START  = @"wxStartLoadBundle";
NSString* const KEY_PAGE_STAGES_LOAD_BUNDLE_END  = @"wxEndLoadBundle";
NSString* const KEY_PAGE_STAGES_EXECUTE_BUNDLE_END  = @"wxEndExecuteBundle";
NSString* const KEY_PAGE_STAGES_EXECUTE_JSON_START = @"wxStartExecuteJson";
NSString* const KEY_PAGE_STAGES_EXECUTE_JSON_END = @"wxEndExecuteJson";
NSString* const KEY_PAGE_STAGES_CREATE_FINISH = @"wxJSBundleCreateFinish";
NSString* const KEY_PAGE_STAGES_FSRENDER  = @"wxFsRender";
NSString* const KEY_PAGE_STAGES_NEW_FSRENDER = @"wxNewFsRender";
NSString* const KEY_PAGE_STAGES_INTERACTION  = @"wxInteraction";
NSString* const KEY_PAGE_STAGES_DESTROY  = @"wxDestroy";

///************** stats *****************/
NSString* const KEY_PAGE_STATS_BUNDLE_SIZE  = @"wxBundleSize";
NSString* const KEY_PAGE_STATS_FS_CALL_JS_TIME  = @"wxFSCallJsTotalTime";
NSString* const KEY_PAGE_STATS_FS_CALL_JS_NUM  = @"wxFSCallJsTotalNum";
NSString* const KEY_PAGE_STATS_EXECUTE_JS_TIME = @"wxExecJsCallBack";
NSString* const KEY_PAGE_STATS_CREATE_COMPONENT_TIME = @"wxComponentCost";
NSString* const KEY_PAGE_STATS_CREATE_VIEW_TIME = @"wxViewCost";
NSString* const KEY_PAGE_STATS_LAYOUT_TIME = @"wxLayoutTime";
NSString* const KEY_PAGE_STATS_FS_TIMER_NUM = @"wxFSTimerCount";
NSString* const KEY_PAGE_STATS_FS_CALL_NATIVE_TIME = @"wxFSCallNativeTotalTime";
NSString* const KEY_PAGE_STATS_FS_CALL_NATIVE_NUM = @"wxFSCallNativeTotalNum";
NSString* const KEY_PAGE_STATS_FS_CALL_EVENT_NUM = @"wxFSCallEventTotalNum";
NSString* const KEY_PAGE_STATS_FS_REQUEST_NUM = @"wxFSRequestNum";

NSString* const KEY_PAGE_STATS_SCROLLER_NUM = @"wxScrollerCount";
NSString* const KEY_PAGE_STATS_CELL_EXCEED_NUM = @"wxCellExceedNum";
NSString* const KEY_PAGE_STATS_CELL_UN_RE_USE_NUM = @"wxCellUnReUseCount";
NSString* const KEY_PAGE_STATS_CELL_DATA_UN_RECYCLE_NUM = @"wxCellDataUnRecycleCount";
NSString* const KEY_PAGE_STATS_EMBED_COUNT=@"wxEmbedCount";
NSString* const KEY_PAGE_STATS_LARGE_IMG_COUNT=@"wxLargeImgMaxCount";

NSString* const KEY_PAGE_STATS_MAX_DEEP_VIEW = @"wxMaxDeepViewLayer";
NSString* const KEY_PAGE_STATS_MAX_DEEP_DOM = @"wxMaxDeepVDomLayer";
NSString* const KEY_PAGE_STATS_MAX_COMPONENT_NUM = @"wxMaxComponentCount";
NSString* const KEY_PAGE_STATS_WRONG_IMG_SIZE_COUNT = @"wxWrongImgSizeCount";
NSString* const KEY_PAGE_STATS_IMG_UN_RECYCLE_NUM = @"wxImgUnRecycleCount";

NSString* const KEY_PAGE_STATS_I_SCREEN_VIEW_COUNT = @"wxInteractionScreenViewCount";
NSString* const KEY_PAGE_STATS_I_ALL_VIEW_COUNT = @"wxInteractionAllViewCount";

NSString* const KEY_PAGE_STATS_I_COMPONENT_CREATE_COUNT = @"wxInteractionComponentCreateCount";
NSString* const KEY_PAGE_ANIM_BACK_NUM = @"wxAnimationInBackCount";
NSString* const KEY_PAGE_TIMER_BACK_NUM = @"wxTimerInBackCount";
NSString* const KEY_PAGE_STATS_ACTUAL_DOWNLOAD_TIME = @"wxActualNetworkTime";

NSString* const KEY_PAGE_STATS_IMG_LOAD_NUM = @"wxImgLoadCount";
NSString* const KEY_PAGE_STATS_IMG_LOAD_SUCCESS_NUM = @"wxImgLoadSuccessCount";
NSString* const KEY_PAGE_STATS_IMG_LOAD_FAIL_NUM = @"wxImgLoadFailCount";
NSString* const KEY_PAGE_STATS_NET_NUM  = @"wxNetworkRequestCount";
NSString* const KEY_PAGE_STATS_NET_SUCCESS_NUM = @"wxNetworkRequestSuccessCount";
NSString* const KEY_PAGE_STATS_NET_FAIL_NUM = @"wxNetworkRequestFailCount";
NSString* const KEY_PAGE_STAGES_FIRST_INTERACTION_VIEW = @"wxFirstInteractionView";
NSString* const KEY_PAGE_STATS_BODY_RATIO = @"wxBodyRatio";

///************** value *****************/
NSString* const VALUE_ERROR_CODE_DEFAULT = @"0";

@interface WXApmForInstance ()
{
    BOOL _isRecord;
    BOOL _isEnd;
    NSDictionary* _responseHeader;
    BOOL _hasRecordInteractionTime;
    BOOL _hasRecordDownLoadStart;
    BOOL _hasRecordDownLoadEnd;
}

@property (nonatomic,strong) id<WXApmProtocol> apmProtocolInstance;
@property (nonatomic,strong) NSString* instanceId;
@property (nonatomic,strong) NSMutableArray<NSNumber*>* recordUpdateComponentDataTimestamp;
@property (nonatomic,strong) NSMutableArray<NSNumber*>* recordUpdateComponentDataTime;

@property (nonatomic,strong) NSMutableArray<WXJSExceptionInfo*>* errorList;
@end

@implementation WXApmForInstance


- (instancetype) init
{
    self = [super init];
    if (self) {
        _recordStatsMap = [[NSMutableDictionary alloc] init];
        _recordStageMap = [[NSMutableDictionary alloc] init];
        _recordUpdateComponentDataTimestamp = [[NSMutableArray alloc] init];
        _recordUpdateComponentDataTime = [[NSMutableArray alloc] init];
        _errorList = [[NSMutableArray alloc] init];
        _isOpenApm = [self _loadApmSwitch];
        if (_isOpenApm) {
            id<WXApmGeneratorProtocol> generater = [WXHandlerFactory handlerForProtocol:@protocol(WXApmGeneratorProtocol)];
            _apmProtocolInstance = [generater gengratorApmInstance:WEEX_PAGE_TOPIC];
        }
    }
    return self;
}

- (void) onEvent:(NSString *)name withValue:(id)value
{
    if (nil == _apmProtocolInstance || _isEnd) {
        return;
    }
    [self.apmProtocolInstance onEvent:name withValue:value];
}

- (long) onStage:(NSString *)name
{
    if(_isEnd){
        return 0;
    }
    if ([name isEqualToString:KEY_PAGE_STAGES_RENDER_ORGIGIN]) {
        __weak typeof(self) weakSelf = self;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [weakSelf printRecordStageMap];
        });
    }
    
    long result = [WXUtility getUnixFixTimeMillis];
    [self onStageWithTime:name time:result];
    return result;
}

- (void) onStageWithTime:(NSString*)name time:(long)unixTime
{
    if(_isEnd){
        return;
    }
    if ([WXAnalyzerCenter isOpen]) {
        [WXAnalyzerCenter transferPerformance:self.instanceId withType:@"stage" andKey:name andValue:@(unixTime)];
    }

    if (nil == _apmProtocolInstance) {
        return;
    }
    if ([KEY_PAGE_STAGES_DOWN_BUNDLE_START isEqualToString:name]) {
        if (_hasRecordDownLoadStart) {
            return;
        }
        _hasRecordDownLoadStart = YES;
    }
    if ([KEY_PAGE_STAGES_DOWN_BUNDLE_END isEqualToString:name]) {
        if (_hasRecordDownLoadEnd) {
            return;
        }
        _hasRecordDownLoadEnd = YES;
    }
    
    if ([KEY_PAGE_STAGES_INTERACTION isEqualToString:name]) {
        _hasRecordInteractionTime = YES;
        if (self.forceStopRecordInteractionTime) {
            return;
        }
    }
    [self.apmProtocolInstance onStage:name withValue:unixTime];
    __weak typeof(self) weakSelf = self;
    WXPerformBlockOnComponentThread(^{
        [weakSelf.recordStageMap setObject:@(unixTime) forKey:name];
    });
}

- (void) setProperty:(NSString *)name withValue:(id)value
{
    if(_isEnd){
        return;
    }
    
    if ([WXAnalyzerCenter isOpen]) {
        [WXAnalyzerCenter transferPerformance:self.instanceId withType:@"properties" andKey:name andValue:value];
    }
    
    if (nil == _apmProtocolInstance) {
        return;
    }
   
    [self.apmProtocolInstance addProperty:name withValue:value];
}

- (void) setStatistic:(NSString *)name withValue:(double)value
{
    if(_isEnd){
        return;
    }
    if ([WXAnalyzerCenter isOpen]) {
        [WXAnalyzerCenter transferPerformance:self.instanceId withType:@"stats" andKey:name andValue:@(value)];
    }
    if (nil == _apmProtocolInstance) {
        return;
    }
  
    [self.apmProtocolInstance addStatistic:name withValue:value];
}

- (void)printRecordStageMap {
    // These logs are for performance auto-test platforms.
    __weak typeof(self) weakSelf = self;
    WXPerformBlockOnComponentThread(^{
        if (!weakSelf) {
            return;
        }
        WXSDKInstance *instance = [WXSDKManager instanceForID:weakSelf.instanceId];
        if (!instance) {
            return;
        }

        NSNumber* downloadBundleStart = weakSelf.recordStageMap[KEY_PAGE_STAGES_DOWN_BUNDLE_START];
        NSNumber* downloadBundleEnd = weakSelf.recordStageMap[KEY_PAGE_STAGES_DOWN_BUNDLE_END];
        if (downloadBundleStart) {
            WXLogInfo(@"pageid: %@ start download url: %@  at timestamp: %lld", weakSelf.instanceId, instance.scriptURL.absoluteString, [downloadBundleStart longLongValue]);
        } else {
            NSNumber* renderTimeOrigin = weakSelf.recordStageMap[KEY_PAGE_STAGES_RENDER_ORGIGIN];
            WXLogInfo(@"pageid: %@ start render view at timestamp: %lld", weakSelf.instanceId, [renderTimeOrigin longLongValue]);
        }

        if (downloadBundleStart && downloadBundleEnd) {
            WXLogInfo(@"pageid: %@ download bundle time(ms): %lld", weakSelf.instanceId, [downloadBundleEnd longLongValue] - [downloadBundleStart longLongValue]);
        }

        NSNumber* executeJsonStart = weakSelf.recordStageMap[KEY_PAGE_STAGES_EXECUTE_JSON_START];
        NSNumber* executeJsonEnd = weakSelf.recordStageMap[KEY_PAGE_STAGES_EXECUTE_JSON_END];
        if (executeJsonStart && executeJsonEnd) {
            WXLogInfo(@"pageid: %@ execute json time(ms): %lld", weakSelf.instanceId, [executeJsonEnd longLongValue] - [executeJsonStart longLongValue]);
        }

        NSNumber* downloadJsStart = weakSelf.recordStageMap[KEY_PAGE_STAGES_DOWN_JS_START];
        NSNumber* downloadJsEnd = weakSelf.recordStageMap[KEY_PAGE_STAGES_DOWN_JS_END];
        if (downloadJsStart && downloadJsEnd) {
            WXLogInfo(@"pageid: %@ download javascript time(ms): %lld", weakSelf.instanceId, [downloadJsEnd longLongValue] - [downloadJsStart longLongValue]);
        }

        NSNumber* loadBundleEnd = weakSelf.recordStageMap[KEY_PAGE_STAGES_LOAD_BUNDLE_END];
        NSNumber* executeBundleEnd = weakSelf.recordStageMap[KEY_PAGE_STAGES_EXECUTE_BUNDLE_END];
        if (loadBundleEnd && executeBundleEnd) {
            WXLogInfo(@"pageid: %@ execute js bundle time(ms): %lld", weakSelf.instanceId, [executeBundleEnd longLongValue] - [loadBundleEnd longLongValue]);
        }

        for (int i=0; i<weakSelf.recordUpdateComponentDataTimestamp.count; i++) {
            NSNumber* timestamp = weakSelf.recordUpdateComponentDataTimestamp[i];
            WXLogInfo(@"pageid: %@ updateComponentData at: %lld cost time(ms): %lld", weakSelf.instanceId, [timestamp longLongValue], [weakSelf.recordUpdateComponentDataTime[i] longLongValue]);
        }

        NSNumber* executeJsCallbackTime = weakSelf.recordStatsMap[KEY_PAGE_STATS_EXECUTE_JS_TIME];
        if (executeJsCallbackTime) {
            WXLogInfo(@"pageid: %@ execute js callback time(ms): %f", weakSelf.instanceId, [executeJsCallbackTime doubleValue]);
        }

        NSNumber* createComponentTime = weakSelf.recordStatsMap[KEY_PAGE_STATS_CREATE_COMPONENT_TIME];
        if (createComponentTime) {
            WXLogInfo(@"pageid: %@ create components time(ms): %f", weakSelf.instanceId, [createComponentTime doubleValue]);
        }

        NSNumber* createViewTime = weakSelf.recordStatsMap[KEY_PAGE_STATS_CREATE_VIEW_TIME];
        if (createViewTime) {
            WXLogInfo(@"pageid: %@ create views time(ms): %f", weakSelf.instanceId, [createViewTime doubleValue]);
        }

        NSNumber* layoutTime = weakSelf.recordStatsMap[KEY_PAGE_STATS_LAYOUT_TIME];
        if (layoutTime) {
            WXLogInfo(@"pageid: %@ layout time(ms): %f", weakSelf.instanceId, [layoutTime doubleValue]);
        }

        NSNumber* renderOrgin = weakSelf.recordStageMap[KEY_PAGE_STAGES_RENDER_ORGIGIN];
        NSNumber* interactionTime = weakSelf.recordStageMap[KEY_PAGE_STAGES_INTERACTION];
        if (renderOrgin && interactionTime) {
            WXLogInfo(@"pageid: %@ interaction time(ms): %lld", weakSelf.instanceId, [interactionTime longLongValue] - [renderOrgin longLongValue]);
        }
    });
}

#pragma mark - instance record

- (void) startRecord:(NSString*) instanceId
{
    if (_isRecord || ![self _shouldRecordInfo]) {
        return;
    }
  
    _isRecord = YES;
    _instanceId = instanceId;
    
    if (nil != _apmProtocolInstance) {
        [self.apmProtocolInstance onStart:instanceId topic:WEEX_PAGE_TOPIC];
    }
    [self onStage:KEY_PAGE_STAGES_START];
    WXSDKInstance* instance = [WXSDKManager instanceForID:instanceId];
    if (nil == instance) {
        return;
    }

    for (NSString* key in instance.continerInfo) {
        id value = [instance.continerInfo objectForKey:key];
        [self setProperty:key withValue:value];
    }

    [self setProperty:KEY_PAGE_PROPERTIES_CONTAINER_NAME withValue:NSStringFromClass(instance.viewController.class)?:@"unknownVCName"];
    [self setProperty:KEY_PAGE_PROPERTIES_INSTANCE_TYPE withValue:@"page"];
    [self setProperty:KEY_PAGE_PROPERTIES_BIZ_ID withValue: instance.pageName?:@"unknownPageName"];
    [self setProperty:KEY_PAGE_PROPERTIES_BUBDLE_URL withValue:instance.scriptURL.absoluteString?:@"unknownUrl"];
    [self setProperty:KEY_PROPERTIES_ERROR_CODE withValue:VALUE_ERROR_CODE_DEFAULT];
    [self setProperty:KEY_PAGE_PROPERTIES_JSLIB_VERSION withValue:[WXAppConfiguration JSFrameworkVersion]?:@"unknownJSFrameworkVersion"];
    [self setProperty:KEY_PAGE_PROPERTIES_WEEX_VERSION withValue:WX_SDK_VERSION];
    if (self.pageRatio >0) {
        [self setStatistic:KEY_PAGE_STATS_BODY_RATIO withValue:self.pageRatio];
    }
    
    //for apm protocl
    //iOS/Android we default recycle img when imgView disapper form screen
    //but in Android ,js can switch recycle or not
    [self updateDiffStats:KEY_PAGE_STATS_IMG_UN_RECYCLE_NUM withDiffValue:0];
}

- (void) endRecord
{
    if (_isEnd) {
        return;
    }
    _isEnd = YES;
    [self onStage:KEY_PAGE_STAGES_DESTROY];
    if (nil != _apmProtocolInstance) {
         [self.apmProtocolInstance onEnd];
    }
    
    WXPerformBlockOnComponentThread(^{
        WXLogInfo(@"APM data of instance: %@, %@", self.instanceId, self.recordStageMap);
        NSNumber* stageRenderOrigin = self.recordStageMap[KEY_PAGE_STAGES_RENDER_ORGIGIN];
        NSNumber* stageInteraction = self.recordStageMap[KEY_PAGE_STAGES_INTERACTION];
        if (stageRenderOrigin && stageInteraction) {
            WXLogInfo(@"APM interaction time(ms): %lld", [stageInteraction longLongValue] - [stageRenderOrigin longLongValue]);
        }
    });
}

- (void) updateFSDiffStats:(NSString *)name withDiffValue:(double)diff
{
    [self updateDiffStats:name withDiffValue:diff];
}

- (void) updateDiffStats:(NSString *)name withDiffValue:(double)diff
{
    if (![self _shouldRecordInfo]) {
        return;
    }
    __weak typeof(self) weakSelf = self;
    WXPerformBlockOnComponentThread(^{
        NSNumber* preNumber = [weakSelf.recordStatsMap objectForKey:name];
        double preVal = nil == preNumber?0:preNumber.doubleValue;
        double currentVal = preVal + diff;
        [weakSelf.recordStatsMap setObject:@(currentVal) forKey:name];
        [weakSelf setStatistic:name withValue:currentVal];
    });
}

- (void) updateMaxStats:(NSString *)name curMaxValue:(double)currentValue
{
    if (![self _shouldRecordInfo]) {
        return;
    }
    __weak typeof(self) weakSelf = self;
    WXPerformBlockOnComponentThread(^{
        NSNumber* maxNumber = [weakSelf.recordStatsMap objectForKey:name];
        double maxVal = nil == maxNumber?0:maxNumber.doubleValue;
        
        if (maxVal < currentValue) {
            maxVal = currentValue;
            [weakSelf.recordStatsMap setObject:@(maxVal) forKey:name];
            [weakSelf setStatistic:name withValue:maxVal];
        }
    });
}

- (void) updateExtInfoFromResponseHeader:(NSDictionary*) extInfo
{
    _responseHeader = extInfo;
    
    if (![self _shouldRecordInfo] || nil == extInfo) {
        return;
    }
    
    id wxGreyBundle = [extInfo objectForKey:KEY_PAGE_PROPERTIES_GREY_BUNDLE];
    if (nil != wxGreyBundle && [wxGreyBundle isKindOfClass:NSString.class]) {
        [self setProperty:KEY_PAGE_PROPERTIES_GREY_BUNDLE withValue:wxGreyBundle];
    }

    id wxRequestType = [extInfo objectForKey:KEY_PAGE_PROPERTIES_REQUEST_TYPE];
    if (nil != wxRequestType && [wxRequestType isKindOfClass: NSString.class]) {
        [self setProperty:KEY_PAGE_PROPERTIES_REQUEST_TYPE withValue:wxRequestType];
    }
    
    id wxNetLibDownBundleTime = [extInfo objectForKey:KEY_PAGE_STATS_ACTUAL_DOWNLOAD_TIME];
    if (nil != wxNetLibDownBundleTime && [wxNetLibDownBundleTime isKindOfClass: NSNumber.class]) {
        double value = ((NSNumber *)wxNetLibDownBundleTime).doubleValue;
        [self  setStatistic:KEY_PAGE_STATS_ACTUAL_DOWNLOAD_TIME withValue:value];
    }
    
    id wxZcacheInfo = [extInfo objectForKey:@"X-ZCache-Info"];
    if (nil !=wxZcacheInfo && [wxZcacheInfo isKindOfClass: NSString.class]) {
        [self setProperty:KEY_PAGE_PROPERTIES_Z_CACHE_INFO withValue:wxZcacheInfo];
    }
}

#pragma mark - called by IWXHttpAdapter implementer

- (void) actionNetRequest
{
    if (![self _shouldRecordInfo]) {
        return;
    }
    if (!self.isFSEnd) {
        [self updateFSDiffStats:KEY_PAGE_STATS_FS_REQUEST_NUM withDiffValue:1];
    }
    [self updateDiffStats:KEY_PAGE_STATS_NET_NUM withDiffValue:1];
}

- (void) actionNetRequestResult:(bool)succeed withErrorCode:(NSString*)errorCode
{
    if (![self _shouldRecordInfo]) {
        return;
    }
    if (succeed) {
        [self updateDiffStats:KEY_PAGE_STATS_NET_SUCCESS_NUM withDiffValue:1];
    } else {
        [self updateDiffStats:KEY_PAGE_STATS_NET_FAIL_NUM withDiffValue:1];
    }
}

#pragma mark - called by IWXImgLoaderAdapter implementer

- (void) actionImgLoad
{
    if (![self _shouldRecordInfo]) {
        return;
    }
    [self updateDiffStats:KEY_PAGE_STATS_IMG_LOAD_NUM withDiffValue:1];
}

- (void) actionImgLoadResult:(bool)succeed withErrorCode:(NSString*)errorCode
{
    if (![self _shouldRecordInfo]) {
        return;
    }
    if (succeed) {
        [self updateDiffStats:KEY_PAGE_STATS_IMG_LOAD_SUCCESS_NUM withDiffValue:1];
    } else {
        [self updateDiffStats:KEY_PAGE_STATS_IMG_LOAD_FAIL_NUM withDiffValue:1];
    }
}

- (BOOL) _shouldRecordInfo
{
    if (_isEnd) {
        return NO;
    }
    return self.apmProtocolInstance != nil || [WXAnalyzerCenter isOpen];
}

- (void) recordErrorMsg:(WXJSExceptionInfo *)exception
{
    if (nil == exception || !self.isOpenApm) {
        return;
    }
    __weak typeof(self) weakSelf = self;
    WXPerformBlockOnComponentThread(^{
        if (weakSelf.errorList.count > 5) {
            [weakSelf.errorList removeObjectAtIndex:0];
            [weakSelf.errorList addObject:exception];
        }
    });
}

- (NSArray<WXJSExceptionInfo*>*) topExceptionList
{
    return self.errorList;
}

- (void) _checkScreenEmptyAndReport
{
//    if(self.isDownLoadFailed || self.hasAddView || !self.isStartRender || self.isDegrade){
//        return;
//    }
//    if (![self _isReportEmptyScreenError]) {
//        return;
//    }
//    __weak WXApmForInstance* weakSelf = self;
//    WXPerformBlockOnComponentThread(^{
//        __strong WXApmForInstance* strongSelf = weakSelf;
//        if (nil == strongSelf) {
//            return;
//        }
//        if (nil == [strongSelf.stageDic objectForKey:KEY_PAGE_STAGES_CREATE_FINISH]) {
//            return;
//        }
//        long curTime = [WXUtility getUnixFixTimeMillis];
//        NSNumber* startExecJsTime = [strongSelf.stageDic objectForKey:KEY_PAGE_STAGES_LOAD_BUNDLE_END];
//        if (nil == startExecJsTime) {
//            return;
//        }
//        long jsExecTime = curTime - startExecJsTime.longValue;
//        if (jsExecTime < 4000) {
//            return;
//        }
//
//        NSString *codeStr = [NSString stringWithFormat:@"%d",WX_KEY_EXCEPTION_EMPTY_SCREEN_JS];
//        NSDictionary* extInfo = @{
//            @"wxBeginRender":@(strongSelf.isStartRender),
//            @"wxHasAddView":@(strongSelf.hasAddView),
//            @"wxHasDegrade":@(strongSelf.isDegrade),
//            @"wxJSExecTime":@(jsExecTime)
//            };
//        NSString* errorMsg;
//        if(self.errorList.count<=0){
//            errorMsg = @"whiteScreen :never add view until page destroy(js has execute > 3s)";
//        }else {
//            errorMsg = [NSString stringWithFormat:@"writeScreen :history exception :%@",[strongSelf _convertTopExceptionListToStr]];
//        }
//        [WXExceptionUtils commitCriticalExceptionRT:strongSelf.instanceId errCode:codeStr function:@"_checkScreenEmptyAndReport"
//                                          exception:errorMsg extParams:extInfo];
//    });
}

- (NSString *)_convertTopExceptionListToStr
{
    if (!self.isOpenApm) {
        return @"";
    }
   
    NSString* errorStr = @"";
    for (WXJSExceptionInfo* exception in self.errorList) {
        errorStr = [errorStr stringByAppendingFormat:
                    @"%@ -> code:%@,func:%@,exception:%@ ========>",
                    errorStr,exception.errorCode,exception.functionName,exception.exception
                    ];
    }
    return errorStr;
}

- (NSDictionary<NSString*,NSNumber*>*) stageDic
{
    return self.recordStageMap;
}

- (BOOL) _loadApmSwitch
{
    BOOL openApm = YES;
    id<WXConfigCenterProtocol> configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)];
    
    if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) {
        openApm = [[configCenter configForKey:@"iOS_weex_ext_config.open_apm" defaultValue:@(YES) isDefault:NULL] boolValue];
    }
    return openApm;
}

- (BOOL) _isReportEmptyScreenError
{
    BOOL report = YES;
    id<WXConfigCenterProtocol> configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)];
    
    if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) {
        report = [[configCenter configForKey:@"iOS_weex_ext_config.report_write_screen" defaultValue:@(YES) isDefault:NULL] boolValue];
    }
    return report;
}

- (NSString*) templateInfo
{
    
    NSString* headerStr = [WXUtility JSONString:_responseHeader];
    NSString* bundleVerfiInfo = nil;
    WXSDKInstance* instance = [WXSDKManager instanceForID:self.instanceId];
    if (nil != instance) {
        bundleVerfiInfo = [WXUtility JSONString:instance.userInfo];
    }
    NSString* info = [NSString stringWithFormat:@"bundleVerfiInfo :%@, httpHeaderInfo:%@",bundleVerfiInfo?:@"unSetVerfiInfo",headerStr?:@"unSetHeader"];
    return info;
}

- (void) forceSetInteractionTime:(long) unixTime
{
    [self onStageWithTime:KEY_PAGE_STAGES_INTERACTION time:unixTime];
    _forceStopRecordInteractionTime = YES;
}

- (void) addUpdateComponentDataTimestamp:(long)unixTime {
    [_recordUpdateComponentDataTimestamp addObject:@(unixTime)];
}

- (void) addUpdateComponentDataTime:(long)unixTime {
    [_recordUpdateComponentDataTime addObject:@(unixTime)];
}

@end

