package com.palmnest.application.core.util;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifIFD0Directory;
import net.coobird.thumbnailator.Thumbnails;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

/**
 * 文件上传帮助类
 *
 * @author yubing
 */
public class FileUploadUtils {
	private static Logger LOG = LoggerFactory.getLogger(FileUploadUtils.class);

	private static File           tempPathFile        = null;
	private static int            sizeThreshold       = 1024;
	private static String         ATTACHMENT_ROOT; // 文件存储父路径
	private static String         VISIT_PREFIX        = PropertiesKit.readPropertiesValue("image_server", "wechat.properties"); // 前端访问路径
	private        String         ATTACHMENT_ROOT_KEY = "attachment.root";
	private static List<String> allow_upload_suffix = new ArrayList<>(Arrays.asList(".png", ".jpg", ".jpeg"));
	private static PathGeneration pathGeneration;

	private static final String CHARSET     = "utf-8";
	private static final String FORMAT_NAME = "JPG";
	// 二维码尺寸
	private static final int    QRCODE_SIZE = 200;
	// LOGO宽度
	private static final int    WIDTH       = 60;
	// LOGO高度
	private static final int    HEIGHT      = 60;

	public static final String getVisitPrefix() {
		return VISIT_PREFIX;
	}

	public String fullVisitPath(String path) {
		StringBuilder pathBuilder = new StringBuilder(VISIT_PREFIX.length()
				+ path.length());
		pathBuilder.append(VISIT_PREFIX).append(path);
		return pathBuilder.toString();
	}

	static {
		ATTACHMENT_ROOT = PropertiesKit.readPropertiesValue("attachment.root", "wechat.properties");
		if (pathGeneration == null) {
			pathGeneration = new DefaultPathGeneration();
		}
	}

	@PostConstruct
	public void init() {
		ATTACHMENT_ROOT = PropertiesKit.readPropertiesValue(ATTACHMENT_ROOT_KEY, "wechat.properties");
		if (pathGeneration == null)
			pathGeneration = new DefaultPathGeneration();
	}

	public static PathGeneration getPathGeneration() {
		return pathGeneration;
	}

	public static String getATTACHMENT_ROOT() {
		return ATTACHMENT_ROOT;
	}

	/**
	 * 上传文件
	 * uploadPath 文件上传的路径
	 * boolean compress 是否压缩
	 * int width  压缩的高
	 * int height 压缩的宽
	 *
	 * @param request
	 * @return true 上传成功
	 * false上传失败
	 */
	@SuppressWarnings("unchecked")
	public static String uploadFile(HttpServletRequest request, String uploadPath, boolean compress, int width, int height) {
		int sizeMax = Integer.parseInt(PropertiesKit.readPropertiesValue("sizeMax", "wechat.properties")); ////最大缓存大小

		String fileName = null;
		// 检查输入请求是否为multipart表单数据
		boolean isMultipart = ServletFileUpload.isMultipartContent(request);
		// 若果是的话
		if (isMultipart) {
			/*
			 * 为该请求创建一个DiskFileItemFactory对象，通过它来解析请求。 执行解析后，所有的表单项目都保存在一个List中
			 */
			try {
				DiskFileItemFactory factory = new DiskFileItemFactory();
				factory.setSizeThreshold(sizeThreshold); // 设置缓冲区大小，这里是4kb
				factory.setRepository(tempPathFile);// 设置缓冲区目录
				ServletFileUpload upload = new ServletFileUpload(factory);
				upload.setHeaderEncoding("UTF-8");// 解决文件乱码问题
				upload.setSizeMax(sizeMax);// 设置最大文件尺寸 4M
				List<FileItem> items = upload.parseRequest(request);
				Iterator<FileItem> itr = items.iterator();// 所有的表单项
				// 保存文件
				while (itr.hasNext()) {
					FileItem item = (FileItem) itr.next();// 循环获得每个表单项
					if (!item.isFormField()) { // 如果是文件类型
						long fileSize = item.getSize();
						LOG.info("fileSize:" + fileSize);
						if (fileSize > (sizeMax)) {
							fileName = "false";
							break;
						} else {
							String name = item.getName();// 获得文件名 包括路径啊
							if (name != null) {
								String ext = name.substring(name.lastIndexOf("."));
								if (".JPG".equals(ext.toUpperCase())
										|| ".PNG".equals(ext.toUpperCase())
										|| ".GIF".equals(ext.toUpperCase())
										|| ".JPEG".equals(ext.toUpperCase())
										|| ".BMP".equals(ext.toUpperCase())) {
									LOG.info("<<<<<filePath:" + uploadPath + ext);
									String tempfilePath = "";
									if (compress) {
										tempfilePath = uploadPath + "temp" + ext; //临时文件完整路径
									} else {
										tempfilePath = uploadPath + ext;
									}
									File savedFile = new File(tempfilePath);
									//item.write(savedFile);

									OutputStream out = new FileOutputStream(savedFile);
									InputStream in = item.getInputStream();
									int length = 0;
									byte[] buf = new byte[1024];
									while ((length = in.read(buf)) != -1) {
										out.write(buf, 0, length);
									}
									in.close();
									out.close();

									if (compress) {
										//文件压缩
										fileName = uploadPath + ext;
										compressPic(tempfilePath, fileName, width, height, true);
									} else {
										fileName = tempfilePath;
									}
								}

							}
						}
					}
				}
			} catch (FileUploadException e) {
				LOG.info("<<<<<<FileUploadException:" + e.getMessage());
			} catch (Exception e) {
				LOG.info("<<<<<<FileUploadException:" + e.getMessage());
			}
		} else {
			LOG.info("the enctype must be multipart/form-data");
		}
		return fileName;
	}

	public static String getPicture(MultipartHttpServletRequest request) throws Exception {
		String picUrl = "";
		Iterator<String> fileNames = request.getFileNames();
		while (fileNames.hasNext()) {
			MultipartFile multipartFile = request.getFile(fileNames.next());

			//获得文件原始名称
			InputStream stream = null;
			String fileExt = null;
			String fileName = "";
			if (multipartFile != null) {
				stream = multipartFile.getInputStream();
				fileName = multipartFile.getOriginalFilename();
				fileName = new String(fileName.getBytes(), "utf-8");
				fileExt = StringUtils.isNotEmpty(fileName) ? fileName
						.substring(fileName.lastIndexOf("."))
						: StringUtils.EMPTY;
			}

			FileUploadUtils fileUploadUtils = new FileUploadUtils();
			fileUploadUtils.init();
			String url = fileUploadUtils.uploadFile(stream, fileExt);
			picUrl += ";" + fileUploadUtils.fullVisitPath(url);
		}
		if (!";".equals(picUrl)) {
			if (picUrl.length() > 0) {
				picUrl = picUrl.substring(1);
			}
		}

		return picUrl;
	}

	public static String uploadFile(InputStream data, String ext) {
		if (data == null) {
			return "";
		}

		ext = ext.toLowerCase();
		if (!allow_upload_suffix.contains(ext)) {
			LOG.info("不允许上传扩展名文件:" + ext);
			return "";
		}
		String _ext = "";
		if (StringUtils.isNotEmpty(ext)) {
			_ext = ext.startsWith(".") ? ext : "." + ext;
		}
		String path = pathGeneration.generic(ATTACHMENT_ROOT);
		File parent = getParent(path);

		String uuid = UUID.randomUUID().toString();
		String filename = uuid + _ext;

		File file = new File(parent, filename);
		OutputStream out = null;
		try {
			int picSize = data.available();
			LOG.info("文件大小 =====>:" + picSize);
			LOG.info("文件后缀 =====>:" + ext);
			if (data.available() == 0) {//如果是0KB的破图就不进行上传
				return "";
			}

			out = new BufferedOutputStream(new FileOutputStream(file));
			int len = -1;
			byte[] b = new byte[1024];
			while ((len = data.read(b)) != -1) {
				out.write(b, 0, len);
			}
			out.close();
			data.close();

			//图片压缩
			File compressPic = new File(path + File.separator + filename);
			if (isPic(ext) && picSize > (500 * 1024) && compressPic != null) {//图片大于500KB
				compressPic(compressPic);
			}

		} catch (IOException e) {

			LOG.error("上传文件发生错误", e);
		}
		path = path.replaceAll(ATTACHMENT_ROOT, StringUtils.EMPTY).replaceAll("\\\\", "/");// FIXME windows分割符替换为'/'

		String pathStr = "";
		if (StringUtils.isNotEmpty(path)) {
			pathStr = path.substring(0, 1);
		}
		if (!"/".equals(pathStr)) {
			return "/" + path + "/" + filename;
		}
		return path + "/" + filename;
	}

	/**
	 * 图片压缩
	 *
	 * @param compressPic
	 */
	public static void compressPic(File compressPic) {

		LOG.info("===== 上传图片进行压缩 =====");
		int angle = getPicRotateAngle(compressPic);
		LOG.info("===== 上传图片需要旋转角度为:" + angle + " =====");

		try {
			ImageIO.setUseCache(false);
			BufferedImage sourceImg = ImageIO.read(compressPic);
			if (sourceImg == null) {//不是图片
				return;
			}
			Thumbnails.of(sourceImg)
					.size(1080, 1920)
					.outputQuality(0.8f)//质量压缩到原来的0.5
					.rotate(angle)
					.toFile(compressPic);
		} catch (Exception e) {
			LOG.info("图片压缩异常 FileUploadUtils compressPic Error ==========>" + e.getMessage());
		}

	}

	/**
	 * 获取照片应该旋转的角度
	 *
	 * @param compressPic
	 * @return
	 */
	public static int getPicRotateAngle(File compressPic) {
		Metadata metadata = null;
		int angle = 0;
		try {
			metadata = ImageMetadataReader.readMetadata(compressPic);
			ExifIFD0Directory directoryOfType = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
			if (directoryOfType != null) {
				int anInt = directoryOfType.getInt(ExifIFD0Directory.TAG_ORIENTATION);

				switch (anInt) {
					case 1:
						angle = 0;
						break;
					case 3:
						angle = 180;
						break;
					case 6:
						angle = 90;
						break;
					case 8:
						angle = -90;
						break;
					default:
						angle = 0;
				}
			}
		} catch (ImageProcessingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return angle;
	}

	/**
	 * 判断该文件是否是图片
	 *
	 * @param ext
	 * @return
	 */
	public static boolean isPic(String ext) {
		if (".JPG".equals(ext.toUpperCase())
				|| ".PNG".equals(ext.toUpperCase())
				|| ".GIF".equals(ext.toUpperCase())
				|| ".JPEG".equals(ext.toUpperCase())
				|| ".BMP".equals(ext.toUpperCase())) {

			return true;
		}
		return false;
	}

	private static File getParent(String path) {
		File parentFolder = new File(path);
		if (!parentFolder.exists()) {
			try {
				parentFolder.mkdirs();
			} catch (Exception e) {
				LOG.error("生成目录出错", e);
			}
		}
		return parentFolder;
	}

	/**
	 * @param originalFilePath 原始图片
	 * @param targetFilePath   目标图片
	 * @param width            压缩宽 默认640px
	 * @param height           压缩高
	 * @param proportion       是否等比缩放，ture-缩放
	 */
	public static void compressPic(String originalFilePath,
								   String targetFilePath, int width, int height, boolean proportion) {
		if (width == 0) {
			width = 640; //默认
		}
		if (height == 0) {
			height = 640;
		}

		File originalFile = new File(originalFilePath);
		File targetFile = new File(targetFilePath);
		int newWidth = 0;
		int newHeight = 0;
		if (!originalFile.exists()) {
			LOG.info("originalFilePath is not found.");
			return;
		}
		Image img;
		try {
			img = ImageIO.read(originalFile);
			if (img.getWidth(null) == -1) {
				LOG.info("can not read");
				return;
			}
			//大于50Kb 进行压缩
			if (originalFile.length() > 1024 * 50) {
				if (proportion) {
					//以宽度为主
					LOG.info("width*height " + img.getWidth(null) + "*" + img.getHeight(null));

					double rate = ((double) img.getWidth(null)) / (double) width;
					if (rate > 0) {
						newWidth = (int) (((double) img.getWidth(null)) / rate);
						newHeight = (int) (((double) img.getHeight(null)) / rate);
						BufferedImage tag = new BufferedImage((int) newWidth, (int) newHeight, BufferedImage.TYPE_INT_RGB);

						// Image.SCALE_SMOOTH 的缩略算法 生成缩略图片的平滑度的,优先级比速度高 生成的图片质量比较好 但速度慢
						tag.getGraphics().drawImage(img.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH), 0, 0, null);
						FileOutputStream out = new FileOutputStream(targetFile);

						// JPEGImageEncoder可适用于其他图片类型的转换
						/*JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
						encoder.encode(tag);*/
						out.close();

						deleteFileByPath(originalFilePath);
					} else {
						FileUtils.copyFile(originalFile, targetFile);
						deleteFileByPath(originalFilePath);
						//更改文件的名称
						//originalFile.renameTo(targetFile);
					}
				} else {
					FileUtils.copyFile(originalFile, targetFile);
					deleteFileByPath(originalFilePath);
				}
			} else {
				FileUtils.copyFile(originalFile, targetFile);
				deleteFileByPath(originalFilePath);
			}

		} catch (IOException e1) {
			try {
				FileUtils.copyFile(originalFile, targetFile);
				deleteFileByPath(originalFilePath);
			} catch (IOException e) {
				LOG.error("copy file occur error.{} " + e, e.getMessage());
			}
			LOG.error("zip jpg occur error..." + e1, e1.getMessage());
		}
	}

	/**
	 * 删除附件
	 *
	 * @param filePath
	 */
	public static void deleteFileByPath(String filePath) {
		File file = new File(filePath);
		if (file.exists() && file.isFile()) {
			//file.getAbsoluteFile().delete();
			file.delete();
		}
	}


	public static void main(String[] args) throws Exception {

	}


	//**************路径生成规则******************

	/**
	 * 生成文件存放的路径。
	 *
	 * @author tianjie
	 */
	public interface PathGeneration {
		/**
		 * 文件存放路径生成规则。在指定的prefix目录下生成子目录。返回完整路径
		 *
		 * @param prefix
		 * @return
		 */
		String generic(String prefix);
	}

	@Component
	public static class DefaultPathGeneration implements PathGeneration {
		@Override
		public String generic(String prefix) {
			boolean hasSeparator = prefix.endsWith(File.separator);

			StringBuffer fullpath = new StringBuffer(
					hasSeparator ? (prefix.length() + 10)
							: (prefix.length() + 11));

			fullpath.append(prefix);
			if (!hasSeparator) fullpath.append(File.separator);

			Calendar c = Calendar.getInstance();

			fullpath.append(c.get(Calendar.YEAR) + File.separator);
			if (c.get(Calendar.MONDAY) < 9) {
				fullpath.append("0");
			}

			fullpath.append(c.get(Calendar.MONDAY) + 1);
			fullpath.append(File.separator);
			fullpath.append(c.get(Calendar.DAY_OF_MONTH));

			return fullpath.toString();
		}
	}

	public static String getBase64ImageUrl(String imageUrl) {
		String rootPath = FileUploadUtils.getATTACHMENT_ROOT();
		Calendar cal = Calendar.getInstance();
		String imagePath = "/" + cal.get(Calendar.YEAR) + "/" + (cal.get(Calendar.MONTH) + 1) + "/" + cal.get(Calendar.DAY_OF_MONTH);
		File file = new File(rootPath + imagePath);
		if (!file.exists()) {
			file.mkdirs();
		}
		String imageName = UUID.randomUUID().toString() + ".jpeg";
		if (string2Image(imageUrl, rootPath + imagePath + "/" + imageName)) {
			imageUrl = imagePath + "/" + imageName;
		} else {
			imageUrl = "";
		}
		return imageUrl;
	}

	public static String getBase64ImageUrl(String base64ImageStr, String imageName, String imagePath) {
		String imageUrl = "";
		String rootPath = FileUploadUtils.getATTACHMENT_ROOT();
		File file = new File(rootPath + imagePath);
		if (!file.exists()) {
			file.mkdirs();
		}
		imageName = imageName + ".jpeg";
		if (string2Image(base64ImageStr, rootPath + imagePath + "/" + imageName)) {
			imageUrl = imagePath + "/" + imageName;
		} else {
			imageUrl = "";
		}
		return imageUrl;
	}

	/**
	 * 通过BASE64Decoder解码，并生成图片
	 *
	 * @param imgStr 解码后的string
	 */
	public static boolean string2Image(String imgStr, String imgFilePath) {
		// 对字节数组字符串进行Base64解码并生成图片
		if (imgStr == null)
			return false;
		try {
			// Base64解码
			if (imgStr.contains(";base64,")) {
				imgStr = imgStr.split("base64,")[1];
			}
			imgStr = imgStr.replaceAll(" ", "+");
			byte[] b = new Base64().decode(imgStr);
			for (int i = 0; i < b.length; ++i) {
				if (b[i] < 0) {
					// 调整异常数据
					b[i] += 256;
				}
			}
			// 生成Jpeg图片
			OutputStream out = new FileOutputStream(imgFilePath);
			out.write(b);
			out.flush();
			out.close();
			return true;
		} catch (Exception e) {
			LOG.error("string2Image base64转换异常=======>", e.getMessage());
			return false;
		}
	}
}
