package com.apobates.forum.utils.image;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Random;
import javax.imageio.ImageIO;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.image.OverlayTextElement.TextElementConfig;
/**
 * 遮盖盒<br/>
 * 现用于为图片增加水印和为二维码图片增加logo
 * 
 * @author xiaofanku
 * @since 20200328
 */
public class OverlayBox {
	//遮盖元素
	private final OverlayElement element;
	//遮盖配置
	private final OverlayConfig config;
	
	private OverlayBox(OverlayElement element, OverlayConfig config) {
		super();
		this.element = element;
		this.config = config;
	}
	
	/**
	 * 保存图片到outputFile文件中
	 * 
	 * @param originalImage 原始图片
	 * @param outputFile    遮盖后的图片文件
	 * @throws IOException 
	 * @throws OverlayBoxException 
	 */
	public void toFile(Path originalImage, Path outputFile) throws IOException, OverlayBoxException{
		BufferedImage image = ImageIO.read(originalImage.toFile());
		saveFile(image, outputFile.toFile()); 
	}
	
	/**
	 * 保存图片到outputFile文件中
	 * 
	 * @param input        原始图片的输入流
	 * @param outputFile   遮盖后的图片文件
	 * @throws IOException 
	 * @throws OverlayBoxException
	 */
	public void toFile(InputStream originalImageInputStream, Path outputFile) throws IOException, OverlayBoxException{
		BufferedImage image = ImageIO.read(originalImageInputStream);
		saveFile(image, outputFile.toFile());
	}
	
	/**
	 * 保存图片到outputFile文件中
	 * 
	 * @param image      原始图片的缓存
	 * @param outputFile 遮盖后的图片文件
	 * @throws IOException 
	 * @throws OverlayBoxException
	 */
	public void toFile(BufferedImage image, File outputFile) throws IOException, OverlayBoxException{
		saveFile(image, outputFile);
	}
	
	/**
	 * 保存图片到outputStream输出流中,调用前需自行判断遮盖元素可用
	 * 
	 * @param originalImage 原始图片
	 * @param outputStream  遮盖后的图片输出流
	 * @throws IOException  
	 */
	public void toStream(File originalImage, OutputStream outputStream) throws IOException{
		BufferedImage image = ImageIO.read(originalImage);
		BufferedImage logoImgBuffer = element.getData(); 
		toStream(image, logoImgBuffer, outputStream, Commons.getFileExtension(originalImage.getName()));
	}
	
	/**
	 * 为原始图片增加水印后保存到outputFile文件
	 * 
	 * @param image         原始图片的缓存
	 * @param overlayBuffer 遮盖盒里的数据,需要调用前自行判断,保证其可用
	 * @param outputFile    遮盖后的图片文件
	 * @throws IOException 
	 * @throws OverlayBoxException
	 */
	public void watermark(BufferedImage image, BufferedImage overlayBuffer, File outputFile) throws IOException, OverlayBoxException{
		// 保存后的图片类型
		String type=Commons.getFileExtension(outputFile.getName());
		try(OutputStream out = new FileOutputStream(outputFile)){ //20200329@AutoCloseable
			toStream(image, overlayBuffer, out, type);
		}
	}
	
	private void toStream(BufferedImage image, BufferedImage overlayElementData, OutputStream outputStream, String outImageType) throws IOException, OverlayBoxException{
		int oeWidth = overlayElementData.getWidth(), oeHeight = overlayElementData.getHeight();
		// 水印与图片的面积是否允许水印
		if(!isAllowOverlay(image.getWidth(), image.getHeight(), oeWidth, oeHeight)){
			isContinue("图片不适合增加遮盖元素", image, outImageType, outputStream);
			return;
		}
		//获得一个遮盖层
		BufferedImage overlay = getOverley(overlayElementData, config.getBackgroundColor(), config.getBorderColor());
		// determine image type and handle correct transparency
		int imageType = "png".equalsIgnoreCase(outImageType) ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB;
		BufferedImage imagePaint = new BufferedImage(image.getWidth(), image.getHeight(), imageType);
		// initializes necessary graphic properties
		Graphics2D w = (Graphics2D) imagePaint.getGraphics();
		w.drawImage(image, 0, 0, null);
		// 设置透明度
		AlphaComposite alphaChannel = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, config.getOpacity());
		w.setComposite(alphaChannel);
		// 计算遮盖层在图片的位置(左上角的坐标)
		int[] xyArray = getPosition(overlayElementData, config.getPosition(), image.getWidth(), image.getHeight());
		// xyArray[0] x轴坐标,xyArray[1] y轴坐标
		w.drawImage(overlay, xyArray[0], xyArray[1], null);
		// 保存
		ImageIO.write(imagePaint, outImageType, outputStream);
		w.dispose();
	}
	
	private void saveFile(BufferedImage image, File outputFile) throws IOException, OverlayBoxException{
		// 保存后的图片类型
		String type=Commons.getFileExtension(outputFile.getName());
		// 是否继续
		if(null == element){ //没有实例
			isContinueSaveFile("遮盖图片没有实例或不可用", image, type, outputFile);
			return;
		}
		if(!element.isUsable()){ //文件不存在
			isContinueSaveFile("遮盖图片在物理路径无法访问", image, type, outputFile);
			return;
		}
		BufferedImage overlayElementData = element.getData(); 
		int oeWidth = overlayElementData.getWidth(), oeHeight = overlayElementData.getHeight();
		// 水印与图片的面积是否允许水印
		if(!isAllowOverlay(image.getWidth(), image.getHeight(), oeWidth, oeHeight)){
			isContinueSaveFile("图片不适合增加遮盖图片", image, type, outputFile);
			return;
		}
		//
		BufferedImage overlay = getOverley(overlayElementData, config.getBackgroundColor(), config.getBorderColor());
		// determine image type and handle correct transparency
		int imageType = "png".equalsIgnoreCase(type) ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB;
		BufferedImage imagePaint = new BufferedImage(image.getWidth(), image.getHeight(), imageType);
		// initializes necessary graphic properties
		Graphics2D w = (Graphics2D) imagePaint.getGraphics();
		w.drawImage(image, 0, 0, null);
		// 设置透明度
		AlphaComposite alphaChannel = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, config.getOpacity());
		w.setComposite(alphaChannel);
		// 计算遮盖层在图片的位置(左上角的坐标)
		int[] xyArray = getPosition(overlayElementData, config.getPosition(), image.getWidth(), image.getHeight());
		// xyArray[0] x轴坐标,xyArray[1] y轴坐标
		w.drawImage(overlay, xyArray[0], xyArray[1], null);
		// 保存
		ImageIO.write(imagePaint, type, outputFile);
		w.dispose();
	}
	/**
	 * 返回遮盖配置
	 * 
	 * @return
	 */
	public OverlayConfig getConfig(){
		return this.config;
	}
	/**
	 * 返回遮盖元素,遮盖盒中的元素
	 * 
	 * @return
	 */
	public OverlayElement getOverlayElement(){
		return this.element;
	}
	/**
	 * 若原始图片不适合遮盖是否继续保存
	 * 
	 * @param exceptionMessage 不继续保存时异常的消息内容
	 * @param image            原始图片
	 * @param type             遮盖后的图片类型
	 * @param outputFile       遮盖后的图片文件
	 * @throws IOException
	 * @throws OverlayBoxException
	 */
	private void isContinueSaveFile(String exceptionMessage, BufferedImage image, String type, File outputFile)throws IOException, OverlayBoxException{
		if(!config.isContinueSave()){
			throw new OverlayBoxException(exceptionMessage);
		}
		ImageIO.write(image, type, outputFile);
	}
	private void isContinue(String exceptionMessage, BufferedImage image, String type, OutputStream outputStream)throws IOException, OverlayBoxException{
		if(!config.isContinueSave()){
			throw new OverlayBoxException(exceptionMessage);
		}
		ImageIO.write(image, type, outputStream);
	}
	/**
	 * 是否可以为图片增加遮盖.通过面积判断,主要适用于矩形
	 * 
	 * @param width           图片宽度
	 * @param height          图片高度
	 * @param waterMarkWidth  遮盖图片的宽度
	 * @param waterMarkHeight 遮盖图片的高度
	 * @return 可以返回true,反之返回false
	 */
	private boolean isAllowOverlay(int width, int height, int overlayWidth, int overlayHeight){
		//计算图片的面积
		int imgSV = width * height;
		//计算水印的面积
		int wmSV = overlayWidth * overlayHeight;
		if(wmSV >= imgSV){
			return false; //水印的大小比图片大
		}
		return imgSV / wmSV > Math.PI;
	}
	//获取遮盖层
	private BufferedImage getOverley(BufferedImage logoImgBuffer, BoxColor backgroundColor, BoxColor borderColor) {
		int logoWidth=logoImgBuffer.getWidth(), logoHeight=logoImgBuffer.getHeight();
		
		Image tmp = logoImgBuffer.getScaledInstance(logoWidth, logoHeight, Image.SCALE_SMOOTH);
		BufferedImage resized = new BufferedImage(logoWidth, logoHeight, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = resized.createGraphics();
		//设置背景色
		if(BoxColor.NONE != backgroundColor){
			Color bgc = getOverlayColor(backgroundColor);
			if(null != bgc){
				g2d.setColor(bgc);
				g2d.fillRect(0, 0, logoWidth, logoHeight);
			}
		}
		g2d.drawImage(tmp, 0, 0, null);
		if(BoxColor.NONE != borderColor){
			Color bc = getOverlayColor(borderColor);
			if(null != bc){
				g2d.setColor(bc);
				g2d.drawRect(0, 0, logoWidth - 1, logoHeight - 1);
				g2d.drawRect(1, 1, logoWidth - 1, logoHeight - 1);
				g2d.drawRect(0, 0, logoWidth - 2, logoHeight - 2);
			}
		}
		g2d.dispose();
		return resized;
	}
	//计算遮盖颜色(背景,边框)
	private Color getOverlayColor(BoxColor color){
		if(BoxColor.NONE == color){
			return null;
		}
		if(BoxColor.TRANSPARENT == color){
			return new Color(0f,0f,0f,.1f);
		}
		if(BoxColor.WHITE == color){
			return Color.WHITE;
		}
		if(BoxColor.BLACK == color){
			return Color.BLACK;
		}
		return null;
	}
	//计算遮盖的起码坐标
	private int[] getPosition(BufferedImage logoImgBuffer, BoxPosition position, int imageWidth, int imageHeight) throws IOException{
		//logo的大小
		int logoWidth = logoImgBuffer.getWidth(), logoHeight = logoImgBuffer.getHeight();
		//
		int x = 0, y = 0;
		if(BoxPosition.MIDDLE == position){
			x = Math.round(imageWidth / 2) - Math.round(logoWidth / 2);
			y = Math.round(imageHeight / 2) - Math.round(logoHeight / 2);
		}
		if(BoxPosition.BOTTOM_RIGHT == position){
			x = imageWidth - logoWidth;
			y = imageHeight - logoHeight;
		}
		if(BoxPosition.RANDOM == position){
			Random ra =new Random();
			x = ra.nextInt(imageWidth)+1;
			y = ra.nextInt(imageHeight)+1;
			
			//是否越界了
			if(x+logoWidth > imageWidth){
				x -= imageWidth - logoWidth;
			}
			if(y+logoHeight > imageHeight){
				y-= imageHeight - logoHeight;
			}
		}
		return new int[]{x,y};
	}

	/**
	 * 遮盖配置
	 * 
	 * @author xiaofanku
	 * @since 20200327
	 */
	public static class OverlayConfig{
		private BoxColor backgroundColor = BoxColor.NONE;
		private BoxColor borderColor = BoxColor.NONE;
		private BoxPosition position = BoxPosition.MIDDLE;
		private float opacity = 1.0f;
		private boolean continueSave =false;
		
		/**
		 * 返回默认的遮盖配置<br/>
		 * 背景色                                          (BoxColor.NONE/无),<br/>
		 * 边框色                                          (BoxColor.NONE/无),<br/>
		 * 位置                                             (BoxPosition.MIDDLE/水平垂直居中),<br/>
		 * 透明度                                          (1.0f/不透明),<br/>
		 * 遮盖图片增加失败时是否继续保存 (false/不继续保存,抛出OverlayBoxException异常)
		 * @return
		 */
		public static OverlayConfig getDefault(){
			return new OverlayConfig();
		}
		
		private OverlayConfig() {
			super();
		}
		
		/**
		 * 遮盖的背景色,默认为:BoxColor.NONE/无
		 * 
		 * @param backgroundColor
		 * @return
		 */
		public OverlayConfig backgroundColor(BoxColor backgroundColor){
			this.backgroundColor = backgroundColor;
			return this;
		}
		
		/**
		 * 遮盖的边框色,默认为:BoxColor.NONE/无
		 * 
		 * @param borderColor
		 * @return
		 */
		public OverlayConfig borderColor(BoxColor borderColor){
			this.borderColor = borderColor;
			return this;
		}
		
		/**
		 * 遮盖的位置,默认为:BoxPosition.MIDDLE/水平垂直居中
		 * 
		 * @param position
		 * @return
		 */
		public OverlayConfig position(BoxPosition position){
			this.position = position;
			return this;
		}
		
		/**
		 * 遮盖的透明度,默认为:1.0f/不透明
		 * 
		 * @param opacity 有效值为0.xf至1.0f区间
		 * @return
		 */
		public OverlayConfig opacity(float opacity){
			this.opacity = opacity;
			return this;
		}
		
		/**
		 * 若图片不适合增加遮盖是否继续保存,默认为false
		 * 
		 * @param continueSave true继续保存,false抛出异常(OverlayBoxException)中止操作
		 * @return
		 */
		public OverlayConfig isContinueSave(boolean continueSave){
			this.continueSave = continueSave;
			return this;
		}
		
		/**
		 * 返回一个包含图片的遮盖盒<br/>
		 * 设置二维码的logo时若图片面积大于100X100可能导致无法解析<br/>
		 * 在调用此方法前须完成配置选项:背景色,边框色,位置,透明度,遮盖失败时是否继续保存<br/>
		 * 
		 * @param overlayImageFile 遮盖图片
		 * @return
		 */
		public OverlayBox image(File overlayImageFile){
			return new OverlayBox(new OverlayImageElement(overlayImageFile), this);
		}
		
		/**
		 * 返回一个包含文字的遮盖盒<br/>
		 * 采用默认的文字配置: 字体:SansSerif,字号:50,粗体:否,颜色:#FF0000<br/>
		 * 在调用此方法前须完成配置选项:背景色,边框色,位置,透明度,遮盖失败时是否继续保存<br/>
		 * 
		 * @param content 文字内容
		 * @return
		 */
		public OverlayBox text(String content){
			return new OverlayBox(new OverlayTextElement(content), this);
		}
		
		/**
		 * 返回一个包含文字的遮盖盒<br/>
		 * 在调用此方法前须完成配置选项:背景色,边框色,位置,透明度,遮盖失败时是否继续保存<br/>
		 * 
		 * @param content    文字内容
		 * @param textConfig 文字配置
		 * @return
		 */
		public OverlayBox text(String content, TextElementConfig textConfig){
			return new OverlayBox(new OverlayTextElement(content, textConfig), this);
		}
		
		public BoxColor getBackgroundColor() {
			return backgroundColor;
		}
		
		public BoxColor getBorderColor() {
			return borderColor;
		}
		
		public BoxPosition getPosition() {
			return position;
		}

		public float getOpacity() {
			return opacity;
		}

		public boolean isContinueSave() {
			return continueSave;
		}
	}
	
	/**
	 * 遮盖的颜色<br/>
	 * 适用于背景色和边框;支持:白色(WHITE),黑色(BLACK),透明色(TRANSPARENT),无(NONE)
	 * 
	 * @author xiaofanku
	 *
	 */
	public enum BoxColor{
		WHITE(1), BLACK(2), TRANSPARENT(3), NONE(4);
		private final int type;

		private BoxColor(int type) {
			this.type = type;
		}

		public int getType() {
			return type;
		}
	}
	
	/**
	 * 遮盖盒的位置<br/>
	 * 支持:垂直居中(MIDDLE),右下角(RIGHT_BOTTOM),随机(RANDOM)
	 * 
	 * @author xiaofanku
	 * @since 20200327
	 */
	public enum BoxPosition{
		MIDDLE(1), BOTTOM_RIGHT(2), RANDOM(3);
		private final int type;
		
		private BoxPosition(int type) {
			this.type = type;
		}
		
		public int getType() {
			return type;
		}
		public static BoxPosition getInstance(int type){
			BoxPosition ins = null;
			for(BoxPosition bp : BoxPosition.values()){
				if(type == bp.getType()){
					ins = bp;
					break;
				}
			}
			return ins;
		}
	}
}
