package com.uas.search.console.b2b.service.impl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.uas.search.util.CollectionUtils;

import com.alibaba.fastjson.JSONObject;
import com.uas.search.b2b.exception.SearchException;
import com.uas.search.b2b.model.SPage;
import com.uas.search.b2b.service.SearchService.Table_name;
import com.uas.search.console.b2b.util.ContextUtils;
import com.uas.search.console.b2b.schedule.model.DailyTaskInformation;
import com.uas.search.console.b2b.schedule.service.Executable;
import com.uas.search.console.b2b.schedule.service.TaskService;
import com.uas.search.console.b2b.service.IndexService;
import com.uas.search.console.b2b.service.InnerSearchService;
import com.uas.search.console.b2b.service.UpdateVirtualColumnService;
import com.uas.search.console.b2b.util.ClassAndTableNameUtils;
import com.uas.search.console.b2b.util.FileUtils;
import com.uas.search.console.b2b.util.ObjectUtil;
import com.uas.search.console.b2b.util.SearchUtils;

/**
 * 每天定时更新虚拟列索引
 * 
 * @author sunyj
 * @since 2016年12月12日 下午5:40:42
 */
@Service
public class UpdateVirtualColumnServiceImpl implements UpdateVirtualColumnService {

	@Autowired
	private TaskService taskService;

	@Autowired
	private IndexService indexService;

	private InnerSearchService innerSearchService = ContextUtils.getApplicationContext().getBean("searchServiceImpl",
			InnerSearchService.class);

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

	/**
	 * 更新指定实体类的虚拟列的索引
	 * 
	 * @param <T>
	 * 
	 * @param clazz
	 *            实体类
	 * @param fields
	 *            需更新的虚拟列，键值对（虚拟列名称：类型，实体中必须存在对应的get/set方法）
	 */
	private <T> void updateVirtualColumn(Class<T> clazz, Map<String, Class<?>> fields) {
		int page = 1;
		int size = 1000;
		// 不能边更新索引边分页获取索引中的数据，因为索引更新后，分页顺序可能也会变化，
		// 所以要先把数据保存到本地，等待全部获取之后重建索引
		Long startTime = new Date().getTime();
		Table_name tableName = ClassAndTableNameUtils.toTableName(clazz);
		// 先删除旧的文件
		FileUtils.deleteSubFiles(new File(SearchUtils.getDataPath(tableName)));

		SPage<T> sPage = innerSearchService.getAllObjects(clazz, page, size);

		// 索引中数据的总数目
		long totalElements = sPage.getTotalElement();
		logger.info("发现数据：" + totalElements + "条");
		int fileIndex = 1;
		PrintWriter printWriter = null;
		int count = 0;
		try {
			File file = new File(SearchUtils.getDataPath(tableName));
			if (!file.exists()) {
				file.mkdirs();
			}
			printWriter = new PrintWriter(SearchUtils.getDataPath(tableName) + "/" + fileIndex + ".txt");
			while (true) {
				// 一个文件存放100000条数据，一旦超过，写入新的文件
				if (count > IndexServiceImpl.SINGLE_FILE_MAX_SIZE) {
					count = 1;
					printWriter.flush();
					printWriter.close();
					fileIndex++;
					printWriter = new PrintWriter(SearchUtils.getDataPath(tableName) + "/" + fileIndex + ".txt");
				}
				List<T> content = sPage.getContent();
				for (T element : content) {
					Set<Entry<String, Class<?>>> entrySet = fields.entrySet();
					for (Entry<String, Class<?>> entry : entrySet) {
						String key = entry.getKey();
						Class<?> type = entry.getValue();
						// 首字母大写
						key = key.substring(0, 1).toUpperCase() + key.substring(1);
						Method getMethod = clazz.getMethod("get" + key);
						Method setMethod = clazz.getMethod("set" + key, type);
						setMethod.invoke(element, getMethod.invoke(element));
					}
					printWriter.println(JSONObject.toJSONString(element));
					count++;
				}
				logger.info(String.format(tableName.value() + "...................%.2f%%",
						((page - 1) * size + content.size()) * 100.0 / totalElements));

				if (++page > sPage.getTotalPage()) {
					break;
				}
				sPage = innerSearchService.getAllObjects(clazz, page, size);
			}
			printWriter.flush();
			printWriter.close();
		} catch (FileNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException
				| IllegalArgumentException | InvocationTargetException e) {
			throw new SearchException(e).setDetailedMessage(e);
		}
		Long endTime = new Date().getTime();
		logger.info(String.format("修改数据%s条，耗时%.2fs\n ", totalElements, (endTime - startTime) / 1000.0));
		indexService.createIndexes(Arrays.asList(tableName), true, true);
	}

	@Override
	public <T> DailyTaskInformation newDailyTask(final Class<T> clazz, Map<String, Class<?>> fields, Integer hour,
			Integer minute, Integer second) throws NotSerializableException, ClassNotFoundException, IOException {
		if (hour == null || minute == null || second == null) {
			throw new NullPointerException();
		}
		if (CollectionUtils.isEmpty(fields)) {
			throw new IllegalArgumentException("fields");
		}

		String title = "更新虚拟列索引" + fields + "：" + ClassAndTableNameUtils.toTableName(clazz).value().toLowerCase();
		// 必须对fields进行克隆，否则，当execute真正运行时，fields可能已被外部方法更改了
		final Map<String, Class<?>> cloneFields = ObjectUtil.clone(fields);
		Executable command = new Executable() {
			@Override
			public String execute() {
				updateVirtualColumn(clazz, cloneFields);
				return "success";
			}
		};
		DailyTaskInformation dailyTaskInformation = new DailyTaskInformation(title, command, hour, minute, second);
		taskService.newTask(dailyTaskInformation);
		return dailyTaskInformation;
	}

}
