package com.railway.common.utils;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import javax.imageio.ImageIO;
import lombok.extern.slf4j.Slf4j;
import sun.font.FontDesignMetrics;
import sun.misc.BASE64Encoder;

/**
 * @author ZhaoMn
 */
@Slf4j
public class QrCodeUtil {

  private static final String CHARSET = "utf-8";
  private static final String FORMAT_NAME = "JPG";
  /** 二维码尺寸 */
  private static final int QRCODE_SIZE = 300;
  /** LOGO宽度 */
  private static final int WIDTH = 60;
  /** LOGO高度 */
  private static final int HEIGHT = 60;
  /** 字体大小 */
  private static final int FONT_SIZE = 18;
  /** 字体高度 */
  private static final int FONT_HEIGHT = 30;

  private static BufferedImage createImage(String content, List<String> bottomDes, String imgPath,
      boolean needCompress) throws Exception {
    Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
    hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
    hints.put(EncodeHintType.MARGIN, 1);
    BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE,
        QRCODE_SIZE, QRCODE_SIZE, hints);
    int width = bitMatrix.getWidth();
    int height = bitMatrix.getHeight();
    int tempHeight = height;
    boolean needDescription = (null != bottomDes && bottomDes.size() > 0);
    if (needDescription) {
      tempHeight += (FONT_HEIGHT * bottomDes.size());
    }
    BufferedImage image = new BufferedImage(width, tempHeight, BufferedImage.TYPE_INT_RGB);
    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
      }
    }
    // 插入图片
    if (imgPath != null && !"".equals(imgPath)) {
      insertImage(image, imgPath, needCompress);
    }
    //添加底部文字
    if (needDescription) {
      addFontImage(image, bottomDes);
    }
    return image;
  }

  private static BufferedImage createImage(String content, String bottomDes, String imgPath,
      boolean needCompress) throws Exception {
    return createImage(content, Collections.singletonList(bottomDes), imgPath, needCompress);
  }

  /**
   * 添加 底部图片文字
   *
   * @param source 图片源
   * @param declareText 文字本文
   */
  private static void addFontImage(BufferedImage source, List<String> declareText) {

    Graphics2D graph = source.createGraphics();
    //开启文字抗锯齿
    graph.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

    for(int i = 0; i < declareText.size(); i ++){
      String declare = declareText.get(i);
      BufferedImage textImage = strToImage(declare);
      int width = textImage.getWidth(null);
      int height = textImage.getHeight(null);
      int y = QRCODE_SIZE + (declareText.size() * 20) - ((declareText.size() - i - 1) * FONT_HEIGHT);
      graph.drawImage(textImage, 0, y, width, height, null);
    }

    graph.dispose();
  }

  public static void main(String[] args) {
    try {
      String qcode = "LKDSLSFD2412341234";
      String toolName = "名称";
      List<String> bottomDes = Arrays.asList("长春西高铁综合车间", "存放处",
          toolName + " - " + "2314321412");
      encode(qcode, bottomDes, "D:/" + DateUtils.dateTimeNow() + ".jpg");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static BufferedImage strToImage(String str) {
    BufferedImage textImage = new BufferedImage(QRCODE_SIZE, FONT_HEIGHT, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = (Graphics2D) textImage.getGraphics();
    //开启文字抗锯齿
    g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    g2.setBackground(Color.WHITE);
    g2.clearRect(0, 0, QRCODE_SIZE, FONT_HEIGHT);
    g2.setPaint(Color.BLACK);
    FontRenderContext context = g2.getFontRenderContext();
    Font font = new Font("宋体", Font.BOLD, FONT_SIZE);
    g2.setFont(font);
    LineMetrics lineMetrics = font.getLineMetrics(str, context);
    FontMetrics fontMetrics = FontDesignMetrics.getMetrics(font);
    float offset = ((float) QRCODE_SIZE - fontMetrics.stringWidth(str)) / 2;
    float y =
        (FONT_HEIGHT + lineMetrics.getAscent() - lineMetrics.getDescent() - lineMetrics.getLeading())
            / 2;
    g2.drawString(str, (int) offset, (int) y);
    return textImage;
  }

  private static void insertImage(BufferedImage source, String imgPath, boolean needCompress)
      throws Exception {
    File file = new File(imgPath);
    if (!file.exists()) {
      System.err.println("" + imgPath + "   该文件不存在！");
      return;
    }
    Image src = ImageIO.read(new File(imgPath));
    int width = src.getWidth(null);
    int height = src.getHeight(null);
    // 压缩LOGO
    if (needCompress) {
      if (width > WIDTH) {
        width = WIDTH;
      }
      if (height > HEIGHT) {
        height = HEIGHT;
      }
      Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
      BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
      Graphics g = tag.getGraphics();
      // 绘制缩小后的图
      g.drawImage(image, 0, 0, null);
      g.dispose();
      src = image;
    }
    // 插入LOGO
    Graphics2D graph = source.createGraphics();
    int x = (QRCODE_SIZE - width) / 2;
    int y = (QRCODE_SIZE - height) / 2;
    graph.drawImage(src, x, y, width, height, null);
    Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
    graph.setStroke(new BasicStroke(3f));
    graph.draw(shape);
    graph.dispose();
  }

  public static void encode(String content, String bottomDes, String imgPath, String destPath,
      boolean needCompress) throws Exception {
    BufferedImage image = createImage(content, bottomDes, imgPath, needCompress);
    mkdirs(destPath);
    ImageIO.write(image, FORMAT_NAME, new File(destPath));
  }

  /**
   * 获取二维码base64数据，转回图片时前面加上  data:image/png;base64,
   */
  public static String encodeStr(String content, List<String> bottomDes) throws Exception {
    BufferedImage image = createImage(content, bottomDes, null, false);
    //io流
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    //写入流中
    ImageIO.write(image, FORMAT_NAME, baos);
    //转换成字节
    byte[] bytes = baos.toByteArray();
    BASE64Encoder encoder = new BASE64Encoder();
    //转换成base64串
    String jpgBase64 = encoder.encodeBuffer(bytes).trim();
    //删除 \r\n
    jpgBase64 = jpgBase64.replaceAll("\n", "").replaceAll("\r", "");
    return jpgBase64;
  }

  public static BufferedImage encode(String content, String bottomDes, String imgPath,
      boolean needCompress) throws Exception {
    return createImage(content, bottomDes, imgPath, needCompress);
  }

  public static void mkdirs(String destPath) throws Exception {
    File file = new File(destPath);
    // 当文件夹不存在时，mkdirs会自动创建多层目录，区别于mkdir．(mkdir如果父目录不存在则会抛出异常)
    if (!file.exists() && !file.isDirectory()) {
      if(!file.mkdirs()){
        throw new Exception(destPath + ", 创建失败");
      }
    }
  }

  public static void encode(String content, List<String> bottomDes, String destPath)
      throws Exception {
    BufferedImage image = createImage(content, bottomDes, destPath, false);
    mkdirs(destPath);
    ImageIO.write(image, FORMAT_NAME, new File(destPath));
  }

  public static void encode(String content, String bottomDes, String imgPath, String destPath)
      throws Exception {
    encode(content, bottomDes, imgPath, destPath, false);
  }

  public static void encode(String content, String bottomDes, String destPath) throws Exception {
    encode(content, bottomDes, null, destPath, false);
  }

  public static void encode(String content, String bottomDes, String imgPath, OutputStream output,
      boolean needCompress) throws Exception {
    BufferedImage image = createImage(content, bottomDes, imgPath, needCompress);
    ImageIO.write(image, FORMAT_NAME, output);
  }

  public static void encode(String content, String bottomDes, OutputStream output)
      throws Exception {
    encode(content, bottomDes, null, output, false);
  }

  public static String decode(File file) throws Exception {
    BufferedImage image;
    image = ImageIO.read(file);
    if (image == null) {
      return null;
    }
    BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
    BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
    Result result;
    Hashtable<DecodeHintType, Object> hints = new Hashtable<>();
    hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
    result = new MultiFormatReader().decode(bitmap, hints);
    return result.getText();
  }

  public static String decode(String path) throws Exception {
    return decode(new File(path));
  }

}
