/*
 * Copyright (c) 2013-2024 Hutool Team and hutool.cn
 *
 * Licensed 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.
 */

package org.dromara.hutool.extra.qrcode;

import org.dromara.hutool.core.codec.Decoder;
import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.swing.img.ImgUtil;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.common.HybridBinarizer;

import java.awt.Image;
import java.util.HashMap;
import java.util.Map;

/**
 * 二维码（条形码等）解码器
 *
 * @author looly
 * @since 6.0.0
 */
public class QrDecoder implements Decoder<Image, String> {

	private final Map<DecodeHintType, Object> hints;

	/**
	 * 创建二维码（条形码等）解码器，用于将二维码（条形码等）解码为所代表的内容字符串
	 *
	 * @param isTryHarder   是否优化精度
	 * @param isPureBarcode 是否使用复杂模式，扫描带logo的二维码设为true
	 * @return QrDecoder
	 */
	public static QrDecoder of(final boolean isTryHarder, final boolean isPureBarcode) {
		return of(buildHints(isTryHarder, isPureBarcode));
	}

	/**
	 * 创建二维码（条形码等）解码器
	 *
	 * @param hints 自定义扫码配置，包括算法、编码、复杂模式等
	 * @return QrDecoder
	 */
	public static QrDecoder of(final Map<DecodeHintType, Object> hints) {
		return new QrDecoder(hints);
	}

	/**
	 * 构造
	 *
	 * @param hints 自定义扫码配置，包括算法、编码、复杂模式等
	 */
	public QrDecoder(final Map<DecodeHintType, Object> hints) {
		this.hints = hints;
	}

	@Override
	public String decode(final Image image) {
		final MultiFormatReader formatReader = new MultiFormatReader();
		formatReader.setHints(hints);

		final LuminanceSource source = new BufferedImageLuminanceSource(
			ImgUtil.castToBufferedImage(image, ImgUtil.IMAGE_TYPE_JPG));

		Result result = _decode(formatReader, new HybridBinarizer(source));
		if (null == result) {
			result = _decode(formatReader, new GlobalHistogramBinarizer(source));
		}

		return null != result ? result.getText() : null;
	}

	/**
	 * 解码多种类型的码，包括二维码和条形码
	 *
	 * @param formatReader {@link MultiFormatReader}
	 * @param binarizer    {@link Binarizer}
	 * @return {@link Result}
	 */
	private static Result _decode(final MultiFormatReader formatReader, final Binarizer binarizer) {
		try {
			return formatReader.decodeWithState(new BinaryBitmap(binarizer));
		} catch (final NotFoundException e) {
			return null;
		}
	}

	/**
	 * 创建解码选项
	 *
	 * @param isTryHarder   是否优化精度
	 * @param isPureBarcode 是否使用复杂模式，扫描带logo的二维码设为true
	 * @return 选项Map
	 */
	private static Map<DecodeHintType, Object> buildHints(final boolean isTryHarder, final boolean isPureBarcode) {
		final HashMap<DecodeHintType, Object> hints = new HashMap<>(3, 1);
		hints.put(DecodeHintType.CHARACTER_SET, CharsetUtil.NAME_UTF_8);

		// 优化精度
		if (isTryHarder) {
			hints.put(DecodeHintType.TRY_HARDER, true);
		}
		// 复杂模式，开启PURE_BARCODE模式
		if (isPureBarcode) {
			hints.put(DecodeHintType.PURE_BARCODE, true);
		}
		return hints;
	}
}
