var Detector = require('./lib/Detector');
var Decoder = require('./lib/Decoder');
var debug = false;


/**
 * 返回获取指定位置的灰度的函数
 * @param {ImageData} data 
 * @param {Object} base 一个有width,height信息
 */
var Pixel = function (data,base) {
	return function(x,y){
		if (base.width < x || base.height < y) {
			throw "point error";
		}
		var point = (x * 4) + (y * base.width * 4)
		// 透明/半透明像素按白色背景处理，避免透明区域被误判为黑色模块
		if (data[point + 3] < 128) {
			return 255;
		}
		return (data[point] * 33.33 + data[point + 1] * 33.33 + data[point + 2] * 33.33) / 100;
	}
}

var binarize = function(data,base,th){
	var ret = new Array(base.width * base.height);
	var getPixel = Pixel(data,base); //
    for (var y = 0; y < base.height; y++) {
        for (var x = 0; x < base.width; x++) {
            var gray = getPixel(x, y);
            ret[x + y * base.width] = gray <= th ? true : false;
        }
    }
    return ret;
}

var grayscale = function(data,base) {
    var buff = new ArrayBuffer(base.width * base.height);
    var ret = new Uint8Array(buff);
    //var ret = new Array(base.width*base.height);
	var getPixel = Pixel(data,base);
    for (var y = 0; y < base.height; y++) {
        for (var x = 0; x < base.width; x++) {
            var gray = getPixel(x, y);
            ret[x + y * base.width] = gray;
        }
    }
    return ret;
}

var getMiddleBrightnessPerArea = function (image,base) {
    var numSqrtArea = 4;
    //obtain middle brightness((min + max) / 2) per area
    var areaWidth = Math.floor(base.width / numSqrtArea);
    var areaHeight = Math.floor(base.height / numSqrtArea);
    var minmax = new Array(numSqrtArea);
    for (var i = 0; i < numSqrtArea; i++) {
        minmax[i] = new Array(numSqrtArea);
        for (var i2 = 0; i2 < numSqrtArea; i2++) {
            minmax[i][i2] = new Array(0, 0);
        }
    }
    for (var ay = 0; ay < numSqrtArea; ay++) {
        for (var ax = 0; ax < numSqrtArea; ax++) {
            minmax[ax][ay][0] = 0xFF;
            for (var dy = 0; dy < areaHeight; dy++) {
                for (var dx = 0; dx < areaWidth; dx++) {
                    var target = image[areaWidth * ax + dx + (areaHeight * ay + dy) * base.width];
                    if (target < minmax[ax][ay][0])
                        minmax[ax][ay][0] = target;
                    if (target > minmax[ax][ay][1])
                        minmax[ax][ay][1] = target;
                }
            }
            //minmax[ax][ay][0] = (minmax[ax][ay][0] + minmax[ax][ay][1]) / 2;
        }
    }
    var middle = new Array(numSqrtArea);
    for (var i3 = 0; i3 < numSqrtArea; i3++) {
        middle[i3] = new Array(numSqrtArea);
    }
    for (var ay = 0; ay < numSqrtArea; ay++) {
        for (var ax = 0; ax < numSqrtArea; ax++) {
            middle[ax][ay] = Math.floor((minmax[ax][ay][0] + minmax[ax][ay][1]) / 2);
            //Console.out.print(middle[ax][ay] + ",");
        }
        //Console.out.println("");
    }
    //Console.out.println("");

    return middle;
}

var grayScaleToBitmap = function (image,base) {
    var middle = getMiddleBrightnessPerArea(image,base);
    var sqrtNumArea = middle.length;
    var areaWidth = Math.floor(base.width / sqrtNumArea);
    var areaHeight = Math.floor(base.height / sqrtNumArea);

    var buff = new ArrayBuffer(base.width * base.height);
    var bitmap = new Uint8Array(buff);

    //var bitmap = new Array(base.height*base.width);

    for (var ay = 0; ay < sqrtNumArea; ay++) {
        for (var ax = 0; ax < sqrtNumArea; ax++) {
            for (var dy = 0; dy < areaHeight; dy++) {
                for (var dx = 0; dx < areaWidth; dx++) {
                    bitmap[areaWidth * ax + dx + (areaHeight * ay + dy) * base.width] = (image[areaWidth * ax + dx + (areaHeight * ay + dy) * base.width] < middle[ax][ay]) ? true : false;
                }
            }
        }
    }
    return bitmap;
}

// 默认二值化阈值
var DEFAULT_THRESHOLD = 153;

// 图像预处理策略：输入 ImageData，输出 {data, width, height, threshold?}
// threshold 可以是单值或数组（多阈值依次尝试）
// 后续优化（自适应二值化、放大等）只需在此数组追加策略即可
var preprocessStrategies = [
	null, // 0. 原图原阈值
	invertImage, // 1. 颜色反转（适配深色背景的反色二维码）
	otsuStrategy, // 2. Otsu 自动阈值（适配整体偏亮/偏暗的图）
	invertedOtsuStrategy, // 3. 反转 + Otsu（适配整体偏暗的反色二维码）
	multiThresholdStrategy // 4. 多阈值扫描（兜底 Otsu 盲区：深色装饰背景等多峰分布美化二维码）
];

// 多阈值扫描集合：仅在前 4 个策略全部失败后触发
// 覆盖 Otsu 无法处理的多峰分布图（如“深色背景+灰模块+白间隔”三峰，Otsu 会选错谷），
// 以及带过渡带的低对比度图（Otsu 分割点偏低，需更高阈值才能获得清晰模块）
// 步长 15 覆盖全区间：o3.png 有效区间 125~150，o2.png 有效区间 180~240
var multiThresholdValues = [60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240];

// 多阈值策略：复用原图数据，对多阈值集合依次尝试
function multiThresholdStrategy(imageDate) {
	return {
		data: imageDate.data,
		width: imageDate.width,
		height: imageDate.height,
		threshold: multiThresholdValues
	};
}

// 颜色反转：RGB 取反，alpha 保持不变
function invertImage(imageDate) {
	var data = new Uint8ClampedArray(imageDate.data);
	for (var i = 0; i < data.length; i += 4) {
		data[i] = 255 - data[i];
		data[i + 1] = 255 - data[i + 1];
		data[i + 2] = 255 - data[i + 2];
	}
	return {
		data: data,
		width: imageDate.width,
		height: imageDate.height
	};
}

// Otsu 大津法：穷举 0-255 阈值，求类间方差最大者作为最优全局阈值
function otsuThreshold(imageDate) {
	var data = imageDate.data;

	// 1. 灰度直方图（一次扫描，跳过透明/半透明像素）
	//    透明区域在二值化时按白处理（见 Pixel），不影响检测；
	//    但统计时计入会拉偏阈值，故只统计真实可见像素
	var hist = new Array(256);
	for (var i = 0; i < 256; i++) hist[i] = 0;
	var total = 0;
	for (var i = 0; i < data.length; i += 4) {
		if (data[i + 3] < 128) continue;
		var gray = (data[i] * 33.33 + data[i + 1] * 33.33 + data[i + 2] * 33.33) / 100 | 0;
		hist[gray]++;
		total++;
	}

	// 2. 遍历阈值，找类间方差最大的
	var sum = 0; // 全局灰度加权和
	for (var i = 0; i < 256; i++) sum += i * hist[i];
	var sumB = 0; // 背景类灰度加权和
	var wB = 0; // 背景类像素数
	var maxVar = -1;
	var bestT = DEFAULT_THRESHOLD;
	for (var t = 0; t < 256; t++) {
		wB += hist[t];
		if (wB === 0) continue;
		var wF = total - wB;
		if (wF === 0) break;
		sumB += t * hist[t];
		var mB = sumB / wB; // 背景类均值
		var mF = (sum - sumB) / wF; // 前景类均值
		var between = wB * wF * (mB - mF) * (mB - mF); // 类间方差
		// 用 >= 在方差平坦时取最亮侧；阈值取两类均值中点，
		// 避免双离散值图（类间方差平台）时阈值卡在类边界导致模块误判为白
		if (between >= maxVar) {
			maxVar = between;
			bestT = Math.floor((mB + mF) / 2);
		}
	}
	return bestT;
}

// Otsu 策略：复用原图数据，仅提供自动计算的阈值（由 process 的 threshold 参数消费）
function otsuStrategy(imageDate) {
	return {
		data: imageDate.data,
		width: imageDate.width,
		height: imageDate.height,
		threshold: otsuThreshold(imageDate)
	};
}

// 反转 + Otsu 策略：先反转，再对反转后的图计算 Otsu 阈值
// 覆盖“深色不够深、浅色不够浅”的偏暗反色二维码（固定阈值反转覆盖不了）
function invertedOtsuStrategy(imageDate) {
	var inv = invertImage(imageDate);
	return {
		data: inv.data,
		width: inv.width,
		height: inv.height,
		threshold: otsuThreshold(inv)
	};
}

function decode(imageDate, debugfn) {
	var lastErr;
	for (var i = 0; i < preprocessStrategies.length; i++) {
		var img = preprocessStrategies[i] ? preprocessStrategies[i](imageDate) : imageDate;
		var base = {
			width: img.width,
			height: img.height,
			debugfn: debugfn
		};
		// 支持多阈值：threshold 为数组时依次尝试，任一成功即返回
		var thresholds = img.threshold;
		if (!(thresholds instanceof Array)) {
			thresholds = [thresholds];
		}
		for (var j = 0; j < thresholds.length; j++) {
			try {
				return process(img.data, base, thresholds[j]);
			} catch (e) {
				lastErr = e;
			}
		}
	}
	throw lastErr;
}

// ECI 声明中常见的字符集（QR 规范：ECI 编号 -> 字符集）
var ECI_CHARSET = {
	3: 'iso-8859-1',
	20: 'shift_jis',
	25: 'utf-16be',
	26: 'utf-8',
	27: 'us-ascii'
};

// UTF-8 字节数组转字符串
var decodeUTF8 = function (bytes) {
	if (typeof TextDecoder !== 'undefined') {
		return new TextDecoder('utf-8').decode(new Uint8Array(bytes));
	}
	var str = "";
	var i = 0;
	while (i < bytes.length) {
		var code = bytes[i++] & 0xFF;
		if (code < 0x80) {
			str += String.fromCharCode(code);
		}
		else if (code < 0xE0) {
			str += String.fromCharCode(((code & 0x1F) << 6) | (bytes[i++] & 0x3F));
		}
		else if (code < 0xF0) {
			str += String.fromCharCode(((code & 0x0F) << 12) | ((bytes[i++] & 0x3F) << 6) | (bytes[i++] & 0x3F));
		}
		else {
			var cp = ((code & 0x07) << 18) | ((bytes[i++] & 0x3F) << 12) | ((bytes[i++] & 0x3F) << 6) | (bytes[i++] & 0x3F);
			cp -= 0x10000;
			str += String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00);
		}
	}
	return str;
}

// UTF-16 字节数组转字符串（isLE=false 时先把 BE 转成 LE 统一处理）
var decodeUTF16 = function (bytes, isLE) {
	if (!isLE) {
		// UTF-16BE -> 交换字节序
		for (var i = 0; i + 1 < bytes.length; i += 2) {
			var t = bytes[i];
			bytes[i] = bytes[i + 1];
			bytes[i + 1] = t;
		}
	}
	if (typeof TextDecoder !== 'undefined') {
		return new TextDecoder('utf-16le').decode(new Uint8Array(bytes));
	}
	// 兜底：手写 UTF-16LE 解码（含代理对）
	var str = "";
	for (var i = 0; i + 1 < bytes.length; i += 2) {
		var code = bytes[i] | (bytes[i + 1] << 8);
		if (code >= 0xD800 && code <= 0xDBFF && i + 3 < bytes.length) {
			var low = bytes[i + 2] | (bytes[i + 3] << 8);
			if (low >= 0xDC00 && low <= 0xDFFF) {
				str += String.fromCharCode(code, low);
				i += 2;
				continue;
			}
		}
		str += String.fromCharCode(code);
	}
	return str;
}

// 8bit 字节段解码：按 ECI 声明 -> BOM -> 启发式检测 -> UTF-8 的顺序判断编码方式
var bytesToString = function (bytes, eci) {
	var len = bytes.length;
	if (len == 0) {
		return "";
	}

	// 1. 优先按 ECI 声明的字符集解码
	if (eci && ECI_CHARSET[eci]) {
		var charset = ECI_CHARSET[eci];
		if (charset == 'utf-16be') {
			return decodeUTF16(bytes, false);
		}
		if (charset == 'utf-8') {
			return decodeUTF8(bytes);
		}
		if (typeof TextDecoder !== 'undefined') {
			try {
				return new TextDecoder(charset).decode(new Uint8Array(bytes));
			}
			catch (e) {
				// 环境不支持该字符集时继续走下面的检测
			}
		}
	}

	// 2. BOM 检测
	if (len >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) {
		return decodeUTF16(bytes.slice(2), true); // UTF-16LE
	}
	if (len >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) {
		return decodeUTF16(bytes.slice(2), false); // UTF-16BE
	}
	if (len >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) {
		return decodeUTF8(bytes.slice(3)); // UTF-8 with BOM
	}

	// 3. 无 BOM 时启发式检测 UTF-16（偶数长度且奇/偶位存在成片 0x00 字节）
	if (len >= 4 && len % 2 == 0) {
		var evenZeros = 0;
		var oddZeros = 0;
		for (var i = 0; i < len; i += 2) {
			if (bytes[i] == 0) {
				evenZeros++;
			}
			if (bytes[i + 1] == 0) {
				oddZeros++;
			}
		}
		var half = len / 2;
		if (oddZeros > half * 0.7) {
			return decodeUTF16(bytes, true); // 奇数位大量 0x00 -> UTF-16LE
		}
		if (evenZeros > half * 0.7) {
			return decodeUTF16(bytes, false); // 偶数位大量 0x00 -> UTF-16BE
		}
	}

	// 4. 兜底默认 UTF-8
	return decodeUTF8(bytes);
}

var process = function (data,base,threshold) {
    // var start = new Date().getTime();
    // var image = grayScaleToBitmap(grayscale(),base);
    // var image = binarize(128);
	var image = binarize(data,base,threshold || DEFAULT_THRESHOLD); //转为位图;
	
	debug &&  base.debugfn && base.debugfn(image,base.width);

	// 位图转为QR矩阵
    var detector = new Detector(image,base);
    var qRCodeMatrix = detector.detect();

    if (debug, base.debugfn) {
		var _imgdata = [];
        for (var y = 0; y < qRCodeMatrix.bits.Height; y++) {
            for (var x = 0; x < qRCodeMatrix.bits.Width; x++) {
				_imgdata.push(qRCodeMatrix.bits.get_Renamed(x, y) ? true : 0)
            }
		}
		debugfn(_imgdata,qRCodeMatrix.bits.Width);
    }

	// 解析QR矩阵
    var reader = Decoder.decode(qRCodeMatrix.bits);

    // 先读取编码方式（mode indicator），按编码方式将数据直接转成字符串
    var MODE_NUMBER = 1;
    var MODE_ROMAN_AND_NUMBER = 2;
    var MODE_8BIT_BYTE = 4;
    var MODE_ECI = 7;
    var MODE_KANJI = 8;

    var str = "";
    var eci = 0; // ECI 声明的字符集编号，作用于后续字节段
    while (true) {
        var mode = reader.NextMode();
        if (mode == 0)
            break;
        if (mode == MODE_ECI) {
            eci = reader.parseECIValue();
            continue;
        }
        var dataLength = reader.getDataLength(mode);
        if (dataLength < 1)
            throw "Invalid data length: " + dataLength;
        switch (mode) {
            case MODE_NUMBER: // 数字模式
                str += reader.getFigureString(dataLength);
                break;
            case MODE_ROMAN_AND_NUMBER: // 字母数字模式
                str += reader.getRomanAndFigureString(dataLength);
                break;
            case MODE_8BIT_BYTE: // 8bit 字节模式，先判断编码方式（ECI/BOM/启发式）再解码
                str += bytesToString(reader.get8bitByteArray(dataLength), eci);
                break;
            case MODE_KANJI: // 汉字（日文）模式
                str += reader.getKanjiString(dataLength);
                break;
            default:
                throw "Invalid mode: " + mode;
        }
    }

    // var end = new Date().getTime();
    // var time = end - start;
    // console.log("Time:" + time + " Code: "+str);
    return str;
}

module.exports = decode;