package com.uas.report.service.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.uas.report.JasperserverRestAPIProperties;
import com.uas.report.SpecialProperties;
import com.uas.report.model.Resource;
import com.uas.report.service.FileService;
import com.uas.report.service.ResourceService;
import com.uas.report.util.CollectionUtils;

/**
 * 管理报表模板、图片等资源
 * 
 * @author sunyj
 * @since 2016年9月23日 下午5:23:42
 */
@Service
public class ResourceServiceImpl implements ResourceService {

	@Autowired
	private JasperserverRestAPIProperties jsRestAPIProperties;

	@Autowired
	private SpecialProperties specialProperties;

	@Autowired
	private FileService fileService;

	private Logger logger = LoggerFactory.getLogger(getClass());

	// 是否为windows系统
	private boolean isWindowsPlatform = System.getProperty("os.name").toLowerCase().startsWith("win");

	@Override
	public List<Resource> syncResources(String userName)
			throws ClientProtocolException, URISyntaxException, IOException {
		// logger.info("Synchronizing resources...");
		List<Resource> synchronizingResources = getSynchronizingResources(userName);
		if (!CollectionUtils.isEmpty(synchronizingResources)) {
			for (Resource synchronizingResource : synchronizingResources) {
				downloadFile(synchronizingResource);
			}
		}
		// logger.info("Synchronized");
		return synchronizingResources;
	}

	/**
	 * 与本地进行比较，返回需要同步的资源
	 * 
	 * @param resources
	 *            用于比较的资源
	 * @param userName
	 *            账套名
	 * @return 需要进行同步的资源
	 * @throws IOException
	 * @throws URISyntaxException
	 * @throws ClientProtocolException
	 */
	private List<Resource> getSynchronizingResources(String userName)
			throws ClientProtocolException, URISyntaxException, IOException {
		List<Resource> synchronizingResources = new ArrayList<>();
		List<Resource> remoteResources = getRemoteResources("/" + userName);
		if (CollectionUtils.isEmpty(remoteResources)) {
			return synchronizingResources;
		}

		List<Resource> localResources = getLocalResources(userName);
		for (Resource remoteResource : remoteResources) {
			if (needSynchronized(remoteResource, localResources)) {
				synchronizingResources.add(remoteResource);
			}
		}
		return synchronizingResources;
	}

	/**
	 * 比较远程资源是否需要同步
	 * 
	 * @param remoteResource
	 *            jasperserver库里的资源
	 * @param localResources
	 *            本地路径下的资源列表
	 * @return 远程资源是否需要同步
	 */
	private boolean needSynchronized(Resource remoteResource, List<Resource> localResources) {
		// 如果资源不是文件夹，并且不含"."（无后缀名），不进行比较，直接认为不需要同步
		if (!remoteResource.isFolder() && !remoteResource.getUri().contains(".")) {
			logger.error("Resource is invalid: " + remoteResource + "\n");
			return false;
		}

		for (Resource localResource : localResources) {
			// windows平台并不区分文件名的大小写，即使将该资源视为需要同步，也无法创建相应文件
			// 1.若路径完全相同
			// 2.或者为windows平台并且忽略大小写后路径相同
			if (remoteResource.getUri().equals(localResource.getUri())
					|| (isWindowsPlatform && remoteResource.getUri().equalsIgnoreCase(localResource.getUri()))) {
				// 如果远程资源比本地资源更新
				if (remoteResource.getUpdateDate().after(localResource.getUpdateDate())) {
					return true;
				} else {
					return false;
				}
			}
		}
		// 本地并没有该资源
		return true;
	}

	/**
	 * 获取本地指定账套下的所有资源
	 * 
	 * @param userName
	 * @return 资源列表
	 */
	private List<Resource> getLocalResources(String userName) {
		return getLocalResources(new File(fileService.getMasterPath(userName)));
	}

	/**
	 * 获取本地指定路径下的所有资源
	 * 
	 * @param dir
	 *            指定的路径，可能是文件夹或文件
	 * @return 资源列表
	 */
	private List<Resource> getLocalResources(File dir) {
		List<Resource> resources = new ArrayList<>();
		if (dir == null || !dir.exists()) {
			return resources;
		}
		resources.add(convertToResource(dir));
		if (dir.isDirectory()) {
			// 递归获取所有资源
			File[] files = dir.listFiles();
			for (File file : files) {
				resources.addAll(getLocalResources(file));
			}
		}
		return resources;
	}

	/**
	 * 将本地文件（夹）转为Resource对象
	 * 
	 * @param file
	 *            本地文件（夹）
	 * @return 转换的资源
	 */
	private Resource convertToResource(File file) {
		Resource resource = new Resource();
		resource.setLabel(file.getName());
		resource.setUpdateDate(new Date(file.lastModified()));
		// 替换"\"为"/"
		String absolutePath = file.getAbsolutePath().replace("\\", "/");
		// 去除文件绝对路径中前半部分，只保留自账套开始的路径
		resource.setUri(absolutePath.replace(specialProperties.getLocalBaseDir(), ""));
		return resource;
	}

	@Override
	public List<Resource> getRemoteResources(String folderPath)
			throws ClientProtocolException, URISyntaxException, IOException {
		HttpGet httpGet = new HttpGet();
		// 返回json类型数据
		httpGet.setHeader("Accept", "application/json");
		List<NameValuePair> parameters = new ArrayList<>();
		// 设置连接参数
		parameters.add(new BasicNameValuePair("folderUri", folderPath));
		HttpResponse response = sendRequest(httpGet, null, parameters);
		InputStream inputStream = response.getEntity().getContent();
		String jsonStr = IOUtils.toString(inputStream);
		// 将返回的json数据格式化为Resource列表
		List<Resource> resources = JSONObject.parseArray(JSONObject.parseObject(jsonStr).getString("resourceLookup"),
				Resource.class);
		inputStream.close();
		return resources;
	}

	/**
	 * 根据资源的信息下载相应文件
	 * 
	 * @param resource
	 *            资源信息
	 * @throws ClientProtocolException
	 * @throws URISyntaxException
	 * @throws IOException
	 */
	private void downloadFile(Resource resource) throws ClientProtocolException, URISyntaxException, IOException {
		// 如果资源是文件夹，在本地创建
		if (resource.isFolder()) {
			File folder = new File(specialProperties.getLocalBaseDir() + resource.getUri());
			if (!folder.exists()) {
				folder.mkdirs();
				logger.info("Mkdirs " + folder.getPath());
			}
			return;
		}

		String localBaseDir = specialProperties.getLocalBaseDir();
		// 如果本地资源根路径最后以资源分隔符结尾
		if (localBaseDir.endsWith(File.separator)) {
			localBaseDir = localBaseDir.substring(0, localBaseDir.length() - 1);
		}
		String uri = resource.getUri();
		// 从资源的uri中获取账套名（如"/UAS/jrxml/Purchase.jrxml"，第一个"UAS"就是账套）
		String userName = uri.split("/")[1];
		// 从uri中获取资源的名称（不能使用label，其只用于方便用户查看，并不能用于标识）
		String resourceName = uri.substring(uri.lastIndexOf("/"));

		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append(localBaseDir).append("/").append(userName);

		// 下载jrxml文件（即使该远程资源并不在jrxml根路径下，比如在嵌套文件夹下），放在localBaseDir+userName+localJrxmlDir下
		if (uri.endsWith(Resource.JRXML_RESOURCE_TYPE)) {
			stringBuilder.append(specialProperties.getLocalJrxmlDir()).append(resourceName);
			downloadFile(uri, Resource.JRXML_MIME_TYPE, stringBuilder.toString());
		}
		// 除jrxml之外的资源视为图片，放在localBaseDir+userName+localImagesDir下
		else {
			stringBuilder.append(specialProperties.getLocalImagesDir()).append(resourceName);
			downloadFile(uri, Resource.IMAGE_MIME_TYPE, stringBuilder.toString());
		}
	}

	@Override
	public void downloadFile(String filePath, String mimeType, String exportPath)
			throws ClientProtocolException, URISyntaxException, IOException {
		HttpGet httpGet = new HttpGet();
		// 设置MIME类型
		httpGet.setHeader("MIME", mimeType);
		HttpResponse response = sendRequest(httpGet, filePath, null);
		// 获取文件二进制数据
		byte[] data = IOUtils.toByteArray(response.getEntity().getContent());
		File exportFile = new File(exportPath);
		// 该文件所在的路径不存在，创建
		if (!exportFile.getParentFile().exists()) {
			exportFile.getParentFile().mkdirs();
			logger.info("Mkdirs " + exportFile.getParent());
		}
		FileOutputStream fos = new FileOutputStream(exportFile);
		fos.write(data);
		fos.flush();
		fos.close();
		logger.info("Download resource to " + exportPath + "\n");
	}

	/**
	 * 发送Http请求，获得结果
	 * 
	 * @param request
	 *            Http请求
	 * @param path
	 *            资源路径
	 * @param parameters
	 *            参数
	 * @return 服务器的响应结果
	 * @throws URISyntaxException
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	private HttpResponse sendRequest(HttpRequestBase request, String path, List<NameValuePair> parameters)
			throws URISyntaxException, ClientProtocolException, IOException {
		HttpClient httpClient = HttpClients.createDefault();
		request.setURI(createURI(path, parameters));
		// 采用HTTP Basic验证
		request.setHeader("Authorization", "Basic " + jsRestAPIProperties.getAuthorization());
		HttpResponse response = httpClient.execute(request);
		// logger.info(request.getMethod() + " " + request.getURI() + " " +
		// response.getStatusLine());
		return response;
	}

	/**
	 * 创建URI
	 * 
	 * @param path
	 *            资源路径
	 * @param parameters
	 *            参数
	 * @return URI对象
	 * @throws URISyntaxException
	 */
	private URI createURI(String path, List<NameValuePair> parameters) throws URISyntaxException {
		StringBuilder stringBuilder = new StringBuilder();
		// 拼接资源全路径
		stringBuilder.append("/").append(jsRestAPIProperties.getContextRoot()).append("/")
				.append(jsRestAPIProperties.getRest()).append("/").append(jsRestAPIProperties.getResources());
		if (path != null) {
			stringBuilder.append(path);
		}
		URIBuilder uriBuilder = new URIBuilder();
		// 协议、主机名、端口号、资源全路径
		uriBuilder.setScheme(jsRestAPIProperties.getSchema()).setHost(jsRestAPIProperties.getHost())
				.setPort(jsRestAPIProperties.getPort()).setPath(stringBuilder.toString());
		// 设置参数
		if (parameters != null) {
			uriBuilder.setParameters(parameters);
		}
		return uriBuilder.build();
	}

}
