package nlib.cmm.qrcode;

import java.awt.image.BufferedImage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;

import egovframework.com.utl.sim.service.EgovFileScrty;
import nlib.cmm.crypto.NlibPasswordEncoder;
import nlib.user.service.NlibLoginVO;
import nlib.util.StringUtil;

/**
 * <pre>
 * @Class Name : QRCodeUtil.java
 * 
 * @Description : QR 코드 생성 처리 
 * 
 * 
 * @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
 *
 * </pre>
 * 
 * --------------------------------------------------------
 * ※ 참고 : 바코드 정보
 * --------------------------------------------------------
 * [Universal Product Code : UPC] : 1D 바코드 
 * - UPC-A : 12자리 (6자리 제조사ID번호 + 5자리품목번호 + 1자리 체크 숫자) 
 * - UPC-E : 8자리 (주로 작은 패키지용으로 사용) 
 * 
 * [European Article Number : EAN Codes]
 * - EAN-13(Most commonly), EAN-8, JAN-13, ISBN 
 * - EAN-13 : UPC 코드와 유사, "o" + UPC-A코드
 * 
 * [Code 128]
 * - 함축 고밀도 선형 코드로, 물류/배송에서 주문/유통 목적으로 주로 사용
 * - 아스키 128자 인코딩 가능 
 * 
 * [PDF417]
 * - 다중 1D 바코드가 다른 1D 바코드위에 쌓여서 구성되는 선형 바코드 
 * - 일반적인 선형 바코드기기 사용 가능 
 * - 여행 승차권,ID카드,물품목록 관리용으로 사용
 * - 체크숫자 대신 Reed-Solomon 오류 교정 방식 사용
 * 
 * [QR Codes : Quick Response Code]
 * - 바코드 매트릭스 즉,2D 바코드로 가장 많이 쓰여지고 있는 바코드 
 * - 제한된 공간에 많은 양의 정보를 담을 수 있음 
 * 
 * 라이브러리 
 * - Barbecue : 오픈소스 자바 라이브러리, 1D바코드, PNG/GIF/JPEG/SVG 생성 기능 
 * - Barcode4j : 오픈소스 라이브러리, 2D바코드 포멧 지원, DataMarix, PDF417
 * - ZXing(Zebra Crossing) : 오픈소스, 자바로 구현된 멀티포멧 1D/2D바코드 이미지 처리 라이브러리, QR코드 지원하는 주 자바 라이브러리 (본시스템에서 사용하는 LIB) 
 * - QRGen : ZXing을 사용한 간단한 자바 QR코드 생성 API  
 *   
 *
 * @ ------------    --------    ---------------------------
 * @ 수정일                     수정자             수정내용
 * @ ------------    --------    ---------------------------
 * @ 2021. 6. 25.    KNKIM       최초 생성
 *
 * 
 * @author 이씨플라자 * DIGITALSHIP KNKIM
 * @since 2021. 6. 25.
 * @version 1.0
 *
 */
@Service("qrCodeService")
public class QRCodeUtil {
	
	private static final Logger log = LoggerFactory.getLogger(QRCodeUtil.class);

	/**
	 * QR코드로 생성할 수 있는 최대문자열 바이트 길이  
	 * 
	 * QR코드로 생성할 수 있는 코드값은 다음과 같으나, 본 시스템 용도에 충분한 크기인 "2000"자로 크기를 제한한다.  
	 * 
	 * - Numeric only    Max. 7,089 characters (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
     * - Alphanumeric    Max. 4,296 characters (0–9, A–Z [upper-case only], space, $, %, *, +, -, ., /, :)
     * - Binary/byte     Max. 2,953 characters (8-bit bytes) (23624 bits)
	 */
	public static final int MAX_QRTEXT_BYTES = 2000;
	
	/**
	 * 입력된 문자열에 대한 QR 2D 바코드를 BufferedImage 로 생성하여 리턴한다.  
	 * 
     *
	 * @param barcodeText
	 * @return
	 * @throws Exception
	 */
	public static BufferedImage generateQRCodeImageByZxing(String barcodeText) throws Exception {
		
		if(StringUtil.isEmpty(barcodeText)) {
			throw new Exception("QR 2D바코드로 생성할 문자 정보가 없습니다.");
		}
		
		if(barcodeText.getBytes().length > MAX_QRTEXT_BYTES) {
			throw new Exception(String.format("QR 2D바코드로 생성할 문자의 길이는 %d 바이트를 초과할 수 없습니다.", MAX_QRTEXT_BYTES));
		}
		
	    QRCodeWriter barcodeWriter = new QRCodeWriter();
	    BitMatrix bitMatrix = barcodeWriter.encode(barcodeText, BarcodeFormat.QR_CODE, 200, 200);

	    return MatrixToImageWriter.toBufferedImage(bitMatrix);
	}
	
	/**
	 * 사용자정보를 가지고, QRCode 바코드를 BufferedImage 로 생성하여 리턴한다. 
	 * 
	 * (1) "사용자고유번호;이메일;이름;" 형식으로 문자열을 구성
	 * (2) 앞의 (1)의 문자열을 암호화 처리 
	 * (3) 앞의 (2) 암호화 문자열로 QRCode 생성하여  BufferedImage형식으로 리턴 
	 *  
	 * @param loginVO
	 * @return
	 * @throws Exception
	 */
	public static BufferedImage generateMemberQrcodeImage(NlibLoginVO loginVO) throws Exception {
		
		if(loginVO == null) {
			throw new Exception("QR 2D바코드로 생성할 사용자 정보가 없습니다.");
		}
		
		if(StringUtil.isEmpty(loginVO.getMbInfoId())) {
			throw new Exception("QR 2D바코드로 생성에 필요한 사용자고유번호 정보가 없습니다.");
		}

		if(StringUtil.isEmpty(loginVO.getEmail())) {
			throw new Exception("QR 2D바코드로 생성에 필요한 이메일 정보가 없습니다.");
		}

		if(StringUtil.isEmpty(loginVO.getName())) {
			throw new Exception("QR 2D바코드로 생성에 필요한 이름 정보가 없습니다.");
		}

		// (1) "사용자고유번호;이메일;이름;" 형식으로 문자열을 구성
		String barcodeText = String.format("%s;%s;%s;"
								, loginVO.getMbInfoId()
								, loginVO.getEmail()
								, loginVO.getName());
		log.debug("generateMemberQRCodeImage : barcodeText=" + barcodeText);
		
		// (2) 앞의 (1)의 문자열을 암호화 처리 
		String cryptedBarcodeText = EgovFileScrty.encodeBinary(barcodeText.getBytes());
		log.debug("generateMemberQRCodeImage : cryptedBarcodeText=" + cryptedBarcodeText);
		
		// (3) 앞의 (2) 암호화 문자열로 QRCode 생성하여  BufferedImage형식으로 리턴 
		return generateQRCodeImageByZxing(cryptedBarcodeText);
	}
	
	/**
	 * 사용자 QR코드 추출 정보를 통해 사용자정보VO를 생성하여 리턴한다. 
	 * 
	 * @param encodedBarcodeText
	 * @return
	 * @throws Exception
	 */
	public static NlibLoginVO getMemberInfoFromQRCode(String encodedBarcodeText) throws Exception {

		if(StringUtil.isEmpty(encodedBarcodeText)) {
			throw new Exception("사용자 QR코드 추출 정보가 정보가 없습니다.");
		}
		
		log.debug("getMemberInfoFromQRCode : encodedBarcodeText=" + encodedBarcodeText);
		
		String barcodeText = new String(EgovFileScrty.decodeBinary(encodedBarcodeText));
		log.debug("getMemberInfoFromQRCode : decodedBarcodeText=" + barcodeText);
		
		String[] split = barcodeText.split(";");
		
		if(split.length < 3) {
			throw new Exception("사용자 QR코드 추출 정보가 적합하지 않습니다.");
		}
		
		NlibLoginVO info = new NlibLoginVO();
		
		info.setMbInfoId(split[0]);
		info.setEmail(split[1]);
		info.setName(split[2]);
		
		return info;
	}

}
