package com.apobates.forum.utils.image;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.apobates.forum.utils.Commons;
/**
 * 文本类型的遮盖元素
 * 
 * @author xiaofanku
 * @since 20200329
 */
public class OverlayTextElement implements OverlayElement{
	private final String content;
	private final TextElementConfig config;
	private final static Logger logger = LoggerFactory.getLogger(OverlayTextElement.class);
	
	public OverlayTextElement(String content) {
		super();
		this.content = content;
		this.config = TextElementConfig.getDefault();
	}
	
	public OverlayTextElement(String content, TextElementConfig config){
		this.content = content;
		this.config = config;
	}
	
	@Override
	public BufferedImage getData() throws IOException, OverlayBoxException{
		Font font = getFont();
		if(null == font){
			throw new OverlayBoxException("文字缺少必要的字体实例");
		}
		//
		int fontWidth = 0, fontHeight = 0;
		float clientY = 0.0f, clientX = 0.0f;
		if(config.getRectWidth() > 0 && config.getRectHeight() > 0){
			fontWidth = config.getRectWidth();
			fontHeight = config.getRectHeight();
		}else{
			// 计算面积
			// https://stackoverflow.com/questions/1524855/how-to-calculate-the-fonts-width
			// https://stackoverflow.com/questions/1399536/java-fonts-and-pixels
			Rectangle rt = font.getStringBounds(content, new FontRenderContext(font.getTransform(), true, true)).getBounds();
			fontWidth = Double.valueOf(rt.getWidth()).intValue();
			fontHeight = Double.valueOf(rt.getHeight()).intValue();
			if (fontWidth < config.getSize() || fontHeight < font.getSize() / 10) {
				throw new OverlayBoxException("文字副面面积计算失败");
			}
			fontWidth += 5;
			fontHeight -= 20;
			clientY = Math.round(config.getSize() / 1.41421)+5; // 1.41421(毕达哥拉斯常数)
		}
		//
		BufferedImage resized = new BufferedImage(fontWidth, fontHeight, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = (Graphics2D) resized.getGraphics();
		g2d.setColor(parseStringColor(config.getColor()));
		g2d.setFont(font);
		//
		if(clientY == 0.0f){
			float[] coordinate = calcFixClientCoordinate(g2d, config.getRectWidth(), config.getRectHeight());
			clientX = coordinate[0]; clientY=coordinate[1];
		}
		
		g2d.drawString(content, clientX, clientY);
		// 
		g2d.dispose();
		return resized;
	}

	private Font getFont() throws IOException {
		try{
			if (null != config.getOutFontFile() && config.getOutFontFile().exists()) {
				GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
				Font tmp = Font.createFont(Font.TRUETYPE_FONT, config.getOutFontFile());
				ge.registerFont(tmp);
			}
		}catch(FontFormatException e){
			if(logger.isDebugEnabled()){
				logger.debug("[OTE]挂接系统外部字体失败", e);
			}
		}
		return new Font(config.getFamily(), (config.isBold() ? Font.BOLD : Font.PLAIN), config.getSize());
	}
	private float[] calcFixClientCoordinate(Graphics2D graphics, int imageWidth, int imageHeight){
		FontMetrics fontMetrics = graphics.getFontMetrics();
		Rectangle2D rect = fontMetrics.getStringBounds(content, graphics);
		long rh = Math.round(rect.getHeight());
		
		if(imageWidth < rect.getWidth()){
			throw new OverlayBoxException("文字副面宽度过窄");
		}
		if(imageHeight < rect.getHeight()){
			throw new OverlayBoxException("文字副面高度过窄");
		}
		double x = 0, y=imageHeight;
		if(imageWidth > rect.getWidth()){
			x = (imageWidth - rect.getWidth()) / 2;
		}
		
		if(imageHeight > rh){
			int halfH = imageHeight / 2;
			y = halfH - rh / 2 + halfH;
		}
		return new float[]{Double.valueOf(x).floatValue(), Double.valueOf(y).floatValue()};
	}
	// https://stackoverflow.com/questions/4129666/how-to-convert-hex-to-rgb-using-java
	private Color parseStringColor(String colorStr) {
		try{
			return new Color(
				Integer.valueOf(colorStr.substring(1, 3), 16), 
				Integer.valueOf(colorStr.substring(3, 5), 16),
				Integer.valueOf(colorStr.substring(5, 7), 16));
		}catch(NullPointerException | StringIndexOutOfBoundsException e){
			throw new OverlayBoxException("文字颜色解析失败,请使用十六进制的全值,不可简写");
		}
	}
	
	@Override
	public boolean isUsable() {
		return Commons.isNotBlank(content);
	}
	/**
	 * 文字配置
	 * 
	 * @author xiaofanku
	 * @since 20200331
	 */
	public static class TextElementConfig{
		private int size=50;
		private String family="SansSerif";
		private String color="#FF0000";
		private boolean bold=false;
		private int rectWidth=0;
		private int rectHeight=0;
		private File outFontFile=null;
		private TextElementConfig(){}
		/**
		 * 默认的文字配置,字体:SansSerif,字号:50,粗体:否,颜色:#FF0000
		 * 
		 * @return
		 */
		public static TextElementConfig getDefault(){
			return new TextElementConfig();
		}
		/**
		 * 文字的大小,设置过大可能在二维码时导致无法解析;默认为50
		 * 
		 * @param fontSize
		 * @return
		 */
		public TextElementConfig fontSize(int fontSize){
			if(fontSize > 0){
				this.size = fontSize;
			}
			return this;
		}
		/**
		 * 文字是否粗体,默认为false
		 * 
		 * @param boldFont true粗体
		 * @return
		 */
		public TextElementConfig isBold(boolean boldFont){
			this.bold = boldFont;
			return this;
		}
		/**
		 * 文字的字体,请使用系统平台支持的字体;默认为SansSerif
		 * 
		 * @param fontFamily
		 * @return
		 */
		public TextElementConfig fontFamily(String fontFamily){
			if(Commons.isNotBlank(fontFamily)){
				this.family = fontFamily;
			}
			return this;
		}
		/**
		 * 文字的颜色,只支持十六进制的值,例:#FFFFFF;默认为#FF0000
		 * 
		 * @param fontColor
		 * @return
		 */
		public TextElementConfig fontColor(String fontColor){
			if(Commons.isNotBlank(fontColor)){
				this.color = fontColor;
			}
			return this;
		}
		/**
		 * 可选项,挂接系统外部字体.<br/>
		 * 注意先挂接再设置fontFamily,不可反向操作<br/>
		 * 
		 * @param fontFile 字体文件
		 * @return
		 */
		public TextElementConfig outFont(File fontFile){
			this.outFontFile = fontFile;
			return this;
		}
		/**
		 * 可选项:设置文字区域的宽和高<br/>
		 * 不设程序会计算,系统内置字体都可以计算出结果.<br/>
		 * 挂接系统外部字体都需要调用此方法<br/>
		 * 
		 * @param width  文字区域的宽
		 * @param height 文字区域的高
		 * @return
		 */
		public TextElementConfig rectangle(int width, int height){
			if(width < 10 || height < 10){
				return this;
			}
			this.rectWidth = width;
			this.rectHeight = height;
			return this;
		}
		
		public int getSize() {
			return size;
		}
		public String getFamily() {
			return family;
		}
		public boolean isBold() {
			return bold;
		}
		public String getColor() {
			return color;
		}
		public int getRectWidth() {
			return rectWidth;
		}
		public int getRectHeight() {
			return rectHeight;
		}
		public File getOutFontFile() {
			return outFontFile;
		}
	}
}
