const cacheManager = CC_WORKER_ASSET_PIPELINE ? require('../worker/cache-manager-proxy') : require('../cache-manager');
const { fs, downloadFile, readText, readArrayBuffer, readJson, loadSubpackage, getUserDataPath, exists } = window.fsUtils;

const REGEX = /^https?:\/\/.*/;
const cachedSubpackageList = {};
const downloader = cc.assetManager.downloader;
const parser = cc.assetManager.parser;
const presets = cc.assetManager.presets;
const isSubDomain = __globalAdapter.isSubContext;
downloader.maxConcurrency = 8;
downloader.maxRequestsPerFrame = 64;
presets['scene'].maxConcurrency = 10;
presets['scene'].maxRequestsPerFrame = 64;

let SUBCONTEXT_ROOT, REMOTE_SERVER_ROOT;
let subpackages = {}, remoteBundles = {};

function downloadScript (url, options, onComplete) {
    if (typeof options === 'function') {
        onComplete = options;
        options = null;
    }
    if (REGEX.test(url)) {
        onComplete && onComplete(new Error('Can not load remote scripts'));
    }
    else {
        __cocos_require__(url);
        onComplete && onComplete(null);
    }
}

var handleZip = CC_WORKER_ASSET_PIPELINE
    ? function handleZip(url, options, onComplete) {
        cacheManager.handleZip(url, options, onComplete);
    }
    : function handleZip(url, options, onComplete) {
        let cachedUnzip = cacheManager.cachedFiles.get(url);
        if (cachedUnzip) {
            cacheManager.updateLastTime(url);
            onComplete && onComplete(null, cachedUnzip.url);
        }
        else if (REGEX.test(url)) {
            downloadFile(url, null, options.header, options.onFileProgress, function (err, downloadedZipPath) {
                if (err) {
                    onComplete && onComplete(err);
                    return;
                }
                cacheManager.unzipAndCacheBundle(url, downloadedZipPath, options.__cacheBundleRoot__, onComplete);
            });
        }
        else {
            cacheManager.unzipAndCacheBundle(url, url, options.__cacheBundleRoot__, onComplete);
        }
    }

function downloadDomAudio (url, options, onComplete) {
    if (typeof options === 'function') {
        onComplete = options;
        options = null;
    }
    
    let dom;
    let sys = cc.sys;
    if (sys.platform === sys.TAOBAO || sys.platform === sys.TAOBAO_MINIGAME) {
        dom = window.document.createElement('audio');
    } else {
        dom = document.createElement('audio');
    }
    dom.src = url;
    
    // HACK: wechat does not callback when load large number of assets
    onComplete && onComplete(null, dom);
}

var download = CC_WORKER_ASSET_PIPELINE
    ? function download(url, func, options, onFileProgress, onComplete) {
        cacheManager.download(url, func, options, onFileProgress, onComplete);
    }
    : function download(url, func, options, onFileProgress, onComplete) {
        var result = transformUrl(url, options);
        if (result.inLocal) {
            func(result.url, options, onComplete);
        }
        else if (result.inCache) {
            cacheManager.updateLastTime(url);
            func(result.url, options, function (err, data) {
                if (err) {
                    cacheManager.removeCache(url);
                }
                onComplete(err, data);
            });
        }
        else {
            downloadFile(url, null, options.header, onFileProgress, function (err, path) {
                if (err) {
                    onComplete(err, null);
                    return;
                }
                func(path, options, function (err, data) {
                    if (!err) {
                        cacheManager.tempFiles.add(url, path);
                        cacheManager.cacheFile(url, path, options.cacheEnabled, options.__cacheBundleRoot__, true);
                    }
                    onComplete(err, data);
                });
            });
        }
    }

function parseArrayBuffer (url, options, onComplete) {
    readArrayBuffer(url, onComplete);
}

function parseText (url, options, onComplete) {
    readText(url, onComplete);
}

function parseJson (url, options, onComplete) {
    readJson(url, onComplete);
}

function downloadText (url, options, onComplete) {
    download(url, parseText, options, options.onFileProgress, onComplete);
}

var downloadJson = !isSubDomain ? function (url, options, onComplete) {
    download(url, parseJson, options, options.onFileProgress, onComplete);
} : function (url, options, onComplete) {
    var { url } = transformUrl(url, options);
    url = url.slice(SUBCONTEXT_ROOT.length + 1);  // remove subcontext root in url
    var content = __cocos_require__(cc.path.changeExtname(url, '.js'));
    onComplete && onComplete(null, content);
}

var loadFont = !isSubDomain ? function (url, options, onComplete) {
    var fontFamily = __globalAdapter.loadFont(url);
    onComplete(null, fontFamily || 'Arial');
} : function (url, options, onComplete) {
    onComplete(null, 'Arial');
}

function doNothing(content, options, onComplete) {
    if (CC_WORKER_ASSET_PIPELINE) {
        onComplete(null, content); 
        return;
    }
    exists(content, (existence) => {
        if (existence) {
            onComplete(null, content); 
        } else {
            onComplete(new Error(`file ${content} does not exist!`));
        }
    });
}

function downloadAsset (url, options, onComplete) {
    download(url, doNothing, options, options.onFileProgress, onComplete);
}

function subdomainTransformUrl (url, options, onComplete) {
    var { url } = transformUrl(url, options);
    onComplete(null, url);
}

function downloadBundle (nameOrUrl, options, onComplete) {
    let bundleName = cc.path.basename(nameOrUrl);
    var version = options.version || cc.assetManager.downloader.bundleVers[bundleName];
    const suffix = version ? `${version}.` : '';

    function getConfigPathForSubPackage () {
        let sys = cc.sys;
        if (sys.platform === sys.TAOBAO_MINIGAME) {
            return `${bundleName}/config.${suffix}json`;
        }
        return `subpackages/${bundleName}/config.${suffix}json`;
    }

    function appendBaseToJsonData (data) {
        if (!data) return;

        let sys = cc.sys;
        if (sys.platform === sys.TAOBAO_MINIGAME) {
            data.base = `${bundleName}/`;
        } else {
            data.base = `subpackages/${bundleName}/`;
        }
    }

    if (subpackages[bundleName]) {
        var config = getConfigPathForSubPackage();
        let loadedCb = function () {
            downloadJson(config, options, function (err, data) {
                appendBaseToJsonData(data);
                onComplete(err, data);
            });
        };
        if (cachedSubpackageList[bundleName]) {
            return loadedCb();
        }
        loadSubpackage(bundleName, options.onFileProgress, function (err) {
            if (err) {
                onComplete(err, null);
                return;
            }
            cachedSubpackageList[bundleName] = true;
            loadedCb();
        });
    }
    else {
        let js, url;
        if (REGEX.test(nameOrUrl) || (!isSubDomain && nameOrUrl.startsWith(getUserDataPath()))) {
            url = nameOrUrl;
            js = `src/scripts/${bundleName}/index.js`;
            cacheManager.makeBundleFolder(bundleName);
        }
        else {
            if (remoteBundles[bundleName]) {
                url = `${REMOTE_SERVER_ROOT}remote/${bundleName}`;
                js = `src/scripts/${bundleName}/index.js`;
                cacheManager.makeBundleFolder(bundleName);
            }
            else {
                url = `assets/${bundleName}`;
                js = `assets/${bundleName}/index.js`;
            }
        }
        __cocos_require__(js);
        options.__cacheBundleRoot__ = bundleName;
        var config = `${url}/config.${version ? version + '.' : ''}json`;
        downloadJson(config, options, function (err, data) {
            if (err) {
                onComplete && onComplete(err);
                return;
            }
            if (data.isZip) {
                let zipVersion = data.zipVersion;
                let zipUrl = `${url}/res.${zipVersion ? zipVersion + '.' : ''}zip`;
                handleZip(zipUrl, options, function (err, unzipPath) {
                    if (err) {
                        onComplete && onComplete(err);
                        return;
                    }
                    data.base = unzipPath + '/res/';
                    // PATCH: for android alipay version before v10.1.95 (v10.1.95 included)
                    // to remove in the future
                    let sys = cc.sys;
                    if (sys.platform === sys.ALIPAY_GAME && sys.os === sys.OS_ANDROID) {
                        let resPath = unzipPath + 'res/';
                        if (fs.accessSync({path: resPath}).success) {
                            data.base = resPath;
                        }
                    }
                    onComplete && onComplete(null, data);
                });
            }
            else {
                data.base = url + '/';
                onComplete && onComplete(null, data);
            }
        });
    }
};

const originParsePVRTex = parser.parsePVRTex;
let parsePVRTex = function (file, options, onComplete) {
    readArrayBuffer(file, function (err, data) {
        if (err) return onComplete(err);
        originParsePVRTex(data, options, onComplete);
    });
};

const originParseASTCTex = parser.parseASTCTex;
let parseASTCTex = function (file, options, onComplete) {
    readArrayBuffer(file, function (err, data) {
        if (err) return onComplete(err);
        originParseASTCTex(data, options, onComplete);
    });
};

const originParsePKMTex = parser.parsePKMTex;
let parsePKMTex = function (file, options, onComplete) {
    readArrayBuffer(file, function (err, data) {
        if (err) return onComplete(err);
        originParsePKMTex(data, options, onComplete);
    });
};

function parsePlist (url, options, onComplete) {
    readText(url, function (err, file) {
        var result = null;
        if (!err) {
            result = cc.plistParser.parse(file);
            if (!result) err = new Error('parse failed');
        }
        onComplete && onComplete(err, result);
    });
}

let downloadImage = isSubDomain ? subdomainTransformUrl : downloadAsset;
downloader.downloadDomAudio = downloadDomAudio;
downloader.downloadScript = downloadScript;
parser.parsePVRTex = parsePVRTex;
parser.parsePKMTex = parsePKMTex;
parser.parseASTCTex = parseASTCTex;

downloader.register({
    '.js' : downloadScript,

    // Audio
    '.mp3' : downloadAsset,
    '.ogg' : downloadAsset,
    '.wav' : downloadAsset,
    '.m4a' : downloadAsset,

    // Image
    '.png' : downloadImage,
    '.jpg' : downloadImage,
    '.bmp' : downloadImage,
    '.jpeg' : downloadImage,
    '.gif' : downloadImage,
    '.ico' : downloadImage,
    '.tiff' : downloadImage,
    '.image' : downloadImage,
    '.webp' : downloadImage,
    '.pvr': downloadAsset,
    '.pkm': downloadAsset,
    '.astc': downloadAsset,

    '.font': downloadAsset,
    '.eot': downloadAsset,
    '.ttf': downloadAsset,
    '.woff': downloadAsset,
    '.svg': downloadAsset,
    '.ttc': downloadAsset,

    // Txt
    '.txt' : downloadAsset,
    '.xml' : downloadAsset,
    '.vsh' : downloadAsset,
    '.fsh' : downloadAsset,
    '.atlas' : downloadAsset,

    '.tmx' : downloadAsset,
    '.tsx' : downloadAsset,
    '.plist' : downloadAsset,
    '.fnt' : downloadAsset,

    '.json' : downloadJson,
    '.ExportJson' : downloadAsset,

    '.binary' : downloadAsset,
    '.bin': downloadAsset,
    '.dbbin': downloadAsset,
    '.skel': downloadAsset,

    '.mp4': downloadAsset,
    '.avi': downloadAsset,
    '.mov': downloadAsset,
    '.mpg': downloadAsset,
    '.mpeg': downloadAsset,
    '.rm': downloadAsset,
    '.rmvb': downloadAsset,

    'bundle': downloadBundle,

    'default': downloadText,
});

parser.register({
    '.png' : downloader.downloadDomImage,
    '.jpg' : downloader.downloadDomImage,
    '.bmp' : downloader.downloadDomImage,
    '.jpeg' : downloader.downloadDomImage,
    '.gif' : downloader.downloadDomImage,
    '.ico' : downloader.downloadDomImage,
    '.tiff' : downloader.downloadDomImage,
    '.image' : downloader.downloadDomImage,
    '.webp' : downloader.downloadDomImage,
    '.pvr': parsePVRTex,
    '.pkm': parsePKMTex,
    '.astc': parseASTCTex,

    '.font': loadFont,
    '.eot': loadFont,
    '.ttf': loadFont,
    '.woff': loadFont,
    '.svg': loadFont,
    '.ttc': loadFont,

    // Audio
    '.mp3' : downloadDomAudio,
    '.ogg' : downloadDomAudio,
    '.wav' : downloadDomAudio,
    '.m4a' : downloadDomAudio,

    // Txt
    '.txt' : parseText,
    '.xml' : parseText,
    '.vsh' : parseText,
    '.fsh' : parseText,
    '.atlas' : parseText,

    '.tmx' : parseText,
    '.tsx' : parseText,
    '.fnt' : parseText,
    '.plist' : parsePlist,

    '.binary' : parseArrayBuffer,
    '.bin': parseArrayBuffer,
    '.dbbin': parseArrayBuffer,
    '.skel': parseArrayBuffer,

    '.ExportJson' : parseJson,
});

var transformUrl = !isSubDomain ? function (url, options) {
    if (CC_WORKER_ASSET_PIPELINE) {
        console.error('transformUrl should not be called when the macro CC_WORKER_ASSET_PIPELINE is enabled.');
    }
    var inLocal = false;
    var inCache = false;
    var isInUserDataPath = url.startsWith(getUserDataPath());
    if (isInUserDataPath) {
        inLocal = true;
    }
    else if (REGEX.test(url)) {
        if (!options.reload) {
            var cache = cacheManager.cachedFiles.get(url);
            if (cache) {
                inCache = true;
                url = cache.url;
            }
            else {
                var tempUrl = cacheManager.tempFiles.get(url);
                if (tempUrl) { 
                    inLocal = true;
                    url = tempUrl;
                }
            }
        }
    }
    else {
        inLocal = true;
    }
    return { url, inLocal, inCache };
} : function (url, options) {
    if (!REGEX.test(url)) {
        url = SUBCONTEXT_ROOT + '/' + url;
    }
    return { url };
}

if (!isSubDomain) {
    cc.assetManager.transformPipeline.append(function (task) {
        var input = task.output = task.input;
        for (var i = 0, l = input.length; i < l; i++) {
            var item = input[i];
            var options = item.options;
            if (!item.config) {
                if (item.ext === 'bundle') continue;
                options.cacheEnabled = options.cacheEnabled !== undefined ? options.cacheEnabled : false;
            }
            else {
                options.__cacheBundleRoot__ = item.config.name;
            }
        }
    });

    var originInit = cc.assetManager.init;
    cc.assetManager.init = function (options) {
        originInit.call(cc.assetManager, options);
        options.subpackages && options.subpackages.forEach(x => subpackages[x] = 'subpackages/' + x);
        options.remoteBundles && options.remoteBundles.forEach(x => remoteBundles[x] = true);
        REMOTE_SERVER_ROOT = options.server || '';
        if (REMOTE_SERVER_ROOT && !REMOTE_SERVER_ROOT.endsWith('/')) REMOTE_SERVER_ROOT += '/';
        cacheManager.init();
    };
}
else {
    var originInit = cc.assetManager.init;
    cc.assetManager.init = function (options) {
        originInit.call(cc.assetManager, options);
        SUBCONTEXT_ROOT = options.subContextRoot || '';
    };
}