
import { getError, gfx, SpriteFrame } from "cc";
import { XIAOMI } from "cc/env";

/**
 * @en The image source, can be HTML canvas, image type or image in memory data
 * @zh 图像资源的原始图像源。可以来源于 HTML 元素也可以来源于内存。
 */
type ImageSource = HTMLCanvasElement | HTMLImageElement | ImageBitmap; // 引擎解析到内存中可用的图像资源类型

/**
 * @en The task for decryption
 * @zh 解密任务
 */
type DecryptionTask = {
	name: string;
	image: HTMLImageElement;
	imgdata: ImageData;
	complete_call: (source: ImageSource)=>void;
};

const ccwindow: typeof window = typeof globalThis.jsb !== 'undefined' ? (typeof jsb.window !== 'undefined' ? jsb.window : globalThis) : globalThis;

/** 解密序列，要与加密脚本中的设置保持一致 */
const decryption_order = '3580419267'.split('').map(Number);

/**
 * 解析图片
 * @param file
 * @param options
 * @param onComplete
 */
export function parseImage(file: string|HTMLImageElement, options: any, onComplete: (err: any, img: ImageSource)=>void){
	if(task_list.length<=0){
		task_start_time = Date.now();
	}

	if(file){
		let start_time = Date.now();
		if(typeof file === 'string'){ // 一般原生环境中，并不直接传入图片资源，而是传入图片路径
			loadImage(file, (err, img)=>{
				let file_name = file.split('/').pop();
				logInfo('LoadImage', task_list.length, file_name, Date.now()-start_time);
				if(err){
					onComplete(err, null);
				}else{
					decryptionImage(file_name, img, source=>{
						onComplete(null, source);
					});
				}
			});
		}else if(file instanceof HTMLImageElement){ // 在浏览器环境中，直接传入图片资源
			let file_name = file.src.split('/').pop();
			logInfo('LoadImage', task_list.length, file_name, Date.now()-start_time);
			decryptionImage(file_name, file, source=>{
				onComplete(null, source);
			});
		}else{
			onComplete(getError(4930, file), null);
		}
	}else{
		// @ts-ignore
		onComplete(null, file);
	}
}

/**
 * 加载图片
 * @param url HTMLImageElement.src
 */
function loadImage(url: string, loaddone: (err: any, image: HTMLImageElement)=>void){
	let img = new ccwindow.Image();

    if (ccwindow.location.protocol !== 'file:' || XIAOMI) {
        img.crossOrigin = 'anonymous';
    }

	function loadCallback() {
	  img.removeEventListener('load', loadCallback);
	  img.removeEventListener('error', errorCallback);
	  loaddone(null, img);
	}

	function errorCallback() {
	  img.removeEventListener('load', loadCallback);
	  img.removeEventListener('error', errorCallback);
	  loaddone(new Error(getError(4930, url)), null);
	}

	img.addEventListener('load', loadCallback);
	img.addEventListener('error', errorCallback);
	img.src = url;
}

/** 获取图片数据的离屏画布上下文对象 */
var context2d_for_image_data: CanvasRenderingContext2D;
/**
 * 获取图片数据
 * @param image
 */
function getImageData(image: HTMLImageElement){
	if(!context2d_for_image_data){
		context2d_for_image_data = document.createElement('canvas').getContext('2d');
	}

	// 创建一个精灵帧
	let spf = SpriteFrame.createWithImage(image);
	// 创建一个缓冲区
	let buffer = new Uint8Array(image.width * image.height * 4);
	// 创建一个区域描述对象
	let region = new gfx.BufferTextureCopy();
	region.texOffset.x = 0;
	region.texOffset.y = 0;
	region.texExtent.width = image.width;
	region.texExtent.height = image.height;

	// 将纹理拷贝到缓冲区
	gfx.deviceManager.gfxDevice.copyTextureToBuffers(spf.getGFXTexture(), [buffer], [region]);

	// 创建一个ImageData对象
	let imageData = context2d_for_image_data.createImageData(image.width, image.height);
	// 将缓冲区数据拷贝到ImageData对象
	imageData.data.set(buffer);

	return imageData;
}

/**
 * 通过一个ImageData对象创建一个canvas资源
 * @param data
 * @param width
 * @param height
 */
function createCanvasSource(data: ImageData, width: number, height: number){
	let canvas = ccwindow.document.createElement('canvas');
	canvas.width = width;
	canvas.height = height;
	let ctx = canvas.getContext('2d');
	ctx.putImageData(data, 0, 0);

	return canvas;
}

/**
 * 解密图片
 * @param name
 * @param image
 * @param complete_call
 */
function decryptionImage(name: string, image: HTMLImageElement, complete_call: (source: ImageSource)=>void){
	runTask({ name, image, imgdata: null, complete_call });
}

/** 解密任务列表 */
const task_list: DecryptionTask[] = [];

/** 解析完成的任务数量 */
var idx_decrypt = 0;
/** 资源创建完成的任务数量 */
var idx_create = 0;

/** 解析任务执行状态 */
var ing_decrypt = false;
/** 创建资源任务状态 */
var ing_create = false;

var task_start_time = 0;

/**
 * 添加任务，并执行
 * @param task
 */
function runTask(task: DecryptionTask){
	task_list.push(task);
	let start_time = Date.now();
	task.imgdata = getImageData(task.image);
	logInfo('GetImageData', task_list.length-1, task.name, Date.now()-start_time);
	runDecryption();
}

/** 执行解密任务 */
function runDecryption(){
	if(ing_decrypt || idx_decrypt>=task_list.length){
		return void 0;
	}

	ing_decrypt = true;

	while(idx_decrypt < task_list.length){
		let task = task_list[idx_decrypt];
		let start_time = Date.now();
		let cut_step = Math.max(1, getUnderMaxPrime(Math.min(task.image.width, task.image.height)));
		decryptionBuffer(task.imgdata.data, decryption_order, cut_step);
		logInfo('Decryption', idx_decrypt, task.name, Date.now()-start_time);
		idx_decrypt++;
	}

	ing_decrypt = false;

	runCreateSource();
}

/** 执行创建资源任务 */
function runCreateSource(){
	if(ing_create || idx_create>=idx_decrypt){
		return void 0;
	}

	ing_create = true;

	while(idx_create < idx_decrypt){
		let task = task_list[idx_create];
		let start_time = Date.now();
		task.complete_call(createCanvasSource(task.imgdata, task.image.width, task.image.height));
		logInfo('CreateSource', idx_create, task.name, Date.now()-start_time);
		idx_create++;
	}

	ing_create = false;

	if(idx_create>=task_list.length){
		logInfo('CurrtPartTask', idx_create, 'Done', Date.now()-task_start_time);
	}
}

/** 解密缓冲区 */
var cache_buffer: Uint8Array|Uint8ClampedArray;
/**
 * 解密缓冲区
 * @param buffer
 * @param order
 * @param cut_length
 */
function decryptionBuffer(buffer: Uint8Array|Uint8ClampedArray, order: number[], cut_length: number){
	if(cut_length>=buffer.length || !(cut_length>0)){
		return buffer;
	}

	if(!cache_buffer || !(cache_buffer.length>=buffer.length)){
		cache_buffer = new Uint8Array(buffer.length);
	}

	let cut_count = Math.floor(buffer.length / cut_length);
	let idx_map_range: number;
	let idx_map: number[][];
	let offset = 0;

	do{
		idx_map = createDecryptionIdxMap(order, cut_count-offset);
		idx_map_range = idx_map[0].length * Math.pow(10, idx_map.length-1);

		for(let i = 0; i < idx_map_range; i++){
			let idx = (offset + i) * cut_length;
			let trans_idx = (offset + transformIndex(i, idx_map)) * cut_length;
			cache_buffer.set(buffer.slice(idx, idx+cut_length), trans_idx);
		}

		offset += idx_map_range;
	}while(offset < cut_count);

	buffer.set(cache_buffer.slice(0, cut_count * cut_length));

	return buffer;
}

/**
 * 转换索引
 * @param index
 * @param trans_map
 */
function transformIndex(index: number, trans_map: number[][]){
	let list = index.toString().padStart(trans_map.length, '0').split('').map(el=>parseInt(el));
	let str = list.map((el, i)=>trans_map[i][el]).join('');
	return parseInt(str);
}

/**
 * 创建解密索引映射
 * @param length
 * @param order
 */
function createDecryptionIdxMap(order: number[], length: number){
	let index_map = length.toString().split('')
	.map((el, idx)=>{
		let result: number[] = [];

		(new Array(idx==0 ? parseInt(el) : order.length))
		.fill(0).map((_, i)=>i)
		.sort((a, b)=>order.indexOf(a) - order.indexOf(b))
		.forEach((e, i)=> result[e] = i);

		return result;
	});

	return index_map;
}

/**
 * 获取小于指定值的最大素数
 * @param num
 */
function getUnderMaxPrime(num: number){
	let value = num;
	let prime = Math.min(value, findMaxPrime(value));
	while(prime >= num){
		value = value-1;
		prime = Math.min(value, findMaxPrime(value));
	}

	return prime;
}

/**
 * 查找最大素数
 * @param num
 */
function findMaxPrime(num: number){
	let max_prime = num;

	if(max_prime > 3){
		for(let i=max_prime; i>2; i--){
			let is_prime = true;
			for(let j=2; j*j<=i; j++){
				if(i % j == 0){
					is_prime = false;
					break;
				}
			}
			if(is_prime){
				max_prime = i;
				break;
			}
		}
	}else{
		max_prime = Math.max(2, max_prime);
	}

	return max_prime;
}

function logInfo(tag: string, index: number, name: string, time?: number){
	tag = '#' + index.toString().padStart(3, '0') + '@' + tag.padEnd(13, ' ') + '>>';
	if(time){
		tag += '|' + formatTime(time) + '|';
	}
	console.log(tag, name);
}

function formatTime(time: number){
	let s = Math.floor(time / 1000);
	let m = Math.floor(s / 60);
	let h = Math.floor(m / 60);

	return [h, m, s].map(v=>v.toString().padStart(2, '0')).join(':') + ' ' + (time % 1000).toString().padStart(3, '0') + 'ms';
}
