package com.cloudcross.ssp.base.web;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import javax.servlet.ServletContext;

import org.apache.commons.collections.ListUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.cloudcross.ssp.model.Account;
import com.cloudcross.ssp.base.web.result.AjaxResult;
import com.cloudcross.ssp.common.Config;
import com.cloudcross.ssp.common.consts.HttpContentType;
import com.cloudcross.ssp.common.utils.DateUtil;
import com.cloudcross.ssp.common.utils.DateUtil.DateFmts;

/**
 * 支持上传controller。
 * 
 * @author qaohao
 */
public abstract class UploadController extends SimpleController {
	/** 匿名 */
	private static final String ANONYMOUS = "anon";

	private static final Logger LOG = 
			Logger.getLogger(UploadController.class);
	
	@Autowired
	private Config config;
	
	/**
	 * 异步（ajax）处理上传文件请求。
	 * 
	 * @param file
	 *            上传文件。
	 * @return 返回上传成功（失败）信息。
	 */
	@RequestMapping(value = "upload/{resourceType}", method = {
			RequestMethod.POST, RequestMethod.GET })
	public final @ResponseBody AjaxResult upload(@PathVariable String resourceType,
			@RequestParam MultipartFile file) {
		AjaxResult result = new AjaxResult();
		if (validate(resourceType, file)) {
			result.setStatus(OK);
			result.addData("url", handleUpload(resourceType, file));
		} else {
			result.setMsg("上传图片、flash格式或者大小不对");
			result.setStatus(ERROR);
		}
		return result;
	}
	
	/**
	 * 验证上传文件是否合法，如果不合法那么会抛出异常
	 * 
	 * @param file
	 *            用户上传的文件封装类
	 */
	public final boolean validate(String resourceType, MultipartFile file) {
		Assert.notEmpty(getUploadAllowedContentTypes(resourceType),
				"The content types allowed to be uploaded must contain one at least!");
		Assert.notNull(file, "The multipart file is null!");

		if (getUploadMaxSize(resourceType) != -1
				&& file.getSize() > getUploadMaxSize(resourceType)) {
			LOG.error("The file uploaded is out of max file size!");
			return false;
		}
		if (!getUploadAllowedContentTypes(resourceType).contains(
				file.getContentType())) {
			LOG.error("The content type '" + file.getContentType()
					+ "' is not a valid content type !");
			return false;
		}
		return true;
	}
	
	/**
	 * 子类可以重写。
	 * 
	 * @return 设置可接受的文件content-type数组（默认支持）
	 */
	public List<String> getUploadAllowedContentTypes(String resourceType) {
		return ListUtils.union(HttpContentType.IMG_JPG, 
				HttpContentType.IMG_PNG);
	}
	
	/**
	 * 子类可以重写。
	 * 
	 * @return 设置文件上传大小上限（默认1M，单位字节）
	 */
	public long getUploadMaxSize(String resourceType) {
		return 1 * 1024 * 1024;
	}
	
	/**
	 * 如果有特殊处理，子类可以复写。
	 * 
	 * @param resourceType
	 * 			上传资源类型（video\image）
	 * @param file
	 * 			上传文件。
	 * @return 网络访问路径（url）
	 */
	protected String handleUpload(String resourceType, MultipartFile file) {
		Account loginUser = getLoginUser();
		
		// 计算相对路径名
		String localFilePath = new StringBuilder(config.getResourceUploadDir())
				.append(File.separator)
				.append(loginUser == null ? ANONYMOUS : loginUser.getId())
				.append(File.separator)
				.append(resourceType)
				.append(File.separator)
				.append(DateUtil.getDate(DateFmts.YYYYMMDD))
				.append(File.separator)
				.append(System.currentTimeMillis())
				.append(".")
				.append(FilenameUtils.getExtension(file.getOriginalFilename()))
				.toString();
		
		// 计算在容器中实际路径名
		String accessUrl = localFilePath;
		ServletContext servletContext = getServletContext();
		if (servletContext == null) {
			LOG.debug("单体测试情况，servletContext不存在。");
		} else {
			// 系统配置路径是在webapps下，对应外网访问路径应该是：
			// http://[domain]/[app]/upload/...
			accessUrl = servletContext.getContextPath().concat(File.separator)
					.concat(localFilePath);
			localFilePath = servletContext.getRealPath(localFilePath);
		}
		
		// 保存文件
		File localFile = new File(localFilePath);
		copyFile(file, localFile);
		LOG.info("上传保存后的文件：" + localFile.getAbsolutePath());
		
		return accessUrl;
	}
	
	/**
	 * 拷贝文件。
	 */
	protected final void copyFile(MultipartFile remoteFile, File localFile) {
		// 所属文件夹不存在，创建所有。
		File localDir = localFile.getParentFile();
		if (!localDir.exists()) {
			LOG.info("创建路径：" + localDir.getAbsolutePath());
			localDir.mkdirs();
		}

		// 将文件保存到本地
		InputStream is = null;
		FileOutputStream os = null;
		try {
			is = remoteFile.getInputStream();
			os = new FileOutputStream(localFile);
			IOUtils.copy(is, os);
		} catch (IOException e) {
			LOG.error("将资源存到本地时失败。", e);
		} finally {
			IOUtils.closeQuietly(os);
			IOUtils.closeQuietly(is);
		}
	}
	
}