package com.persagy.adm.server.custom.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.adm.server.custom.client.DicClient;
import com.persagy.adm.server.custom.client.RwdClient;
import com.persagy.adm.server.custom.common.AdmConst;
import com.persagy.adm.server.custom.dao.*;
import com.persagy.adm.server.custom.datatx.ObjectMapper4Tx;
import com.persagy.adm.server.custom.entity.*;
import com.persagy.adm.server.custom.entity.db.*;
import com.persagy.adm.server.custom.service.*;
import com.persagy.adm.server.custom.util.DataExtrasUtil;
import com.persagy.dmp.basic.model.QueryCriteria;
import com.persagy.dmp.common.constant.ValidEnum;
import com.persagy.dmp.define.entity.RelationDefine;
import com.persagy.dmp.digital.client.DigitalObjectClient;
import com.persagy.dmp.digital.client.DigitalRelationClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Timestamp;
import java.util.*;

@Service
public class SyncAppImpl implements ISyncApp {

	@Autowired
	private RwdClient rwdClient;

	@Autowired
	private DicClient dicClient;

	@Autowired
	private DigitalObjectClient objectClient;

	@Autowired
	private DigitalRelationClient relationClient;

	@Autowired
	private AdmClientFlagMapper clientFlagMapper;

	@Autowired
	private IConfigService configService;

	@Autowired
	private AdmPipeMapper pipeMapper;

	@Autowired
	private AdmJobSpaceMapper jobSpaceMapper;

	@Autowired
	private AdmProblemArchMapper problemArchMapper;

	@Autowired
	private AdmProblemEquipMapper problemEquipMapper;

	@Autowired
	private AdmFileMapper fileMapper;

	@Autowired
	private AdmQrCodeMapper qrCodeMapper;

	@Autowired
	private AdmServeAreaMapper serveAreaMapper;

	@Autowired
	private ISyncModel syncModel;

	@Autowired
	private ObjectMapper objectMapper;

	@Autowired
	private IAdmLogService logService;

	@Autowired
	private AdmUploadJobMapper uploadJobMapper;

	@Autowired
	private AdmMessagePMapper messageMapper;

	@Autowired
	private AdmProblemMapper problemMapper;

	private final ObjectMapper4Tx mapper4Tx;

	@Autowired
	AdmCadMapper admCadMapper;

	@Autowired
	AdmDefineProblemInfoMapper admDefineProblemInfoMapper;

	@Autowired
	public SyncAppImpl(ObjectMapper4Tx mapper4Tx) {
		this.mapper4Tx = mapper4Tx;
	}

	@Override
	public String getClientId(String userId) {
		AdmClientFlag flagEntity = new AdmClientFlag();
		flagEntity.setUserId(userId);
		clientFlagMapper.insert(flagEntity);
		return String.valueOf(flagEntity.getId());
	}

	@Override
	public Dict downloadDict(String groupCode, String projectId, String userId) {
		return queryDict(groupCode, projectId, userId, true, true);
	}

	@Override
	public Dict queryDict(String groupCode, String projectId, String userId, boolean includeRel, boolean includeInfos){
		//从知识库服务获取专业分类
		QueryCriteria majorCriteria = ServiceUtil.getQueryCriteria(objectMapper);
		majorCriteria.getCriteria().put("type", "major");
		List<Map<String, Object>> majorList = ServiceUtil.call (() -> dicClient.dataQuery(groupCode, projectId, AdmConst.APP_ID, userId, majorCriteria));

		//查询系统和设备定义
		QueryCriteria criteria = ServiceUtil.getQueryCriteria(objectMapper, AdmConst.OBJ_TYPE_SYSTEM, AdmConst.OBJ_TYPE_EQUIPMENT);
		List<TypeDef> typeList = ServiceUtil.queryAllPage(() -> rwdClient.queryObjectType(groupCode, projectId, AdmConst.APP_ID, userId, criteria), criteria, new Pagination(500));

		LinkedList<TypeDef> sysList = new LinkedList<>();
		LinkedList<TypeDef> equipList = new LinkedList<>();
		for(TypeDef def : typeList){
			if(def.getObjType().equals(AdmConst.OBJ_TYPE_SYSTEM))
				sysList.add(def);
			else if(def.getObjType().equals(AdmConst.OBJ_TYPE_EQUIPMENT))
				equipList.add(def);
		}

		Dict data = new Dict();
		data.setMajor(majorList);
		data.setSystem(sysList);
		data.setEquipment(equipList);

		//查询关系定义
		if(includeRel) {
			List<RelationDefine> relList = ServiceUtil.call(() -> rwdClient.queryRelDef(groupCode, projectId, AdmConst.APP_ID, userId, new QueryCriteria()));
			data.setRelation(relList);
		}

		//查询信息点定义
		if(includeInfos) {
			QueryCriteria qc = new QueryCriteria();
			qc.setCriteria(objectMapper.createObjectNode());
			List<InfoDef> funcList = ServiceUtil.call(() -> rwdClient.queryFunc(groupCode, projectId, AdmConst.APP_ID, userId, qc));
			data.setInfos(funcList);
		}

		return data;
	}

	@Override
	public ConfigData downloadConfig(String groupCode, String projectId, String userId) {
		//配置数据查询
		List<AdmM2dEquip> m2dTypes = configService.queryM2dEquip(projectId);
		List<AdmInfosConfig> infosConfig = configService.queryInfosConfig(projectId);
		List<AdmRelsConfig> relConfig = configService.queryRelsConfig(projectId);
		List<AdmPipeConfig> pipeConfig = configService.queryPipeConfig(projectId);
		List<AdmConfig> config = configService.queryCommonConfig(projectId);
		List<AdmComponent> component = configService.queryComponent(projectId);
		List<AdmContainerConfig> containerConfig = configService.queryContainerConfig(projectId);
		// 下载问题类型
		List<AdmDefineProblemType> admDefineProblemTypes = configService.queryDefineProblemTypes();
		// 下载问题类型对应信息点定义
		List<AdmDefineProblemInfo> admDefineProblemInfos = admDefineProblemInfoMapper.selectList(null);

		return ConfigData.builder().m2dTypes(m2dTypes)
				.infosConfig(infosConfig)
				.relConfig(relConfig)
				.pipeConfig(pipeConfig)
				.config(config)
				.component(component)
				.containerConfig(containerConfig)
				.admDefineProblemTypes(admDefineProblemTypes)
				.admDefineProblemInfos(admDefineProblemInfos).build();
	}

	@Override
	public Map<String, Object> downloadFrameData(String groupCode, String projectId, String userId) {
		QueryCriteria criteria2 = ServiceUtil.getQueryCriteria(objectMapper, AdmConst.OBJ_TYPE_BUILDING, AdmConst.OBJ_TYPE_FLOOR);
		List<ObjectNode> bdAndFl = ServiceUtil.call(() -> objectClient.query(groupCode, projectId, AdmConst.APP_ID, userId, criteria2));
		packInfos(bdAndFl);

		HashMap<String, Object> data = new HashMap<>();
		data.put("buildingsAndFloors", bdAndFl);

		return data;
	}

	private void packInfos(List<ObjectNode> dataList){
		if(dataList != null) {
			for(ObjectNode node : dataList) {
				handle4Download(node);
				DataExtrasUtil.packObjExtras(node);
			}
		}
	}

	/** 下载前处理数据结构 */
	private void handle4Download(ObjectNode node){
		JsonNode ol = node.get("outline");
		if(ol != null){
			try {
				node.set("outline", objectMapper.readValue(ol.asText(), ArrayNode.class));
			}catch (Exception e) {

			}
		}
		JsonNode bl = node.get("bimLocation");
		if(bl != null) {
			try {
				String[] arr = bl.asText().split(",");
				ObjectNode ln = objectMapper.createObjectNode();
				ln.put("x", Double.parseDouble(arr[0]));
				ln.put("y", Double.parseDouble(arr[1]));
				ln.put("z", Double.parseDouble(arr[2]));
				node.set("location", ln);
				node.remove("bimLocation");
			}catch (Exception e) {

			}
		}
	}

	@Override
	public BuildingData downloadBuildingData(String projectId, String buildingId, String uploadFlag, String lastDownloadTime) {
		throw new RuntimeException("not supported");
	}

	@Override
	public BuildingData downloadBuildingData(String groupCode, String projectId, String userId, String clientId, String buildingId, Long bdtpDownloadTs, Long admDownloadTs) {
		String operator = getOperator(clientId, userId);

		//从bdtp下载对象（空间、竖井、系统、设备、元空间)
		QueryCriteria criteria = ServiceUtil.getQueryCriteria(objectMapper, AdmConst.OBJ_TYPE_SPACE, AdmConst.OBJ_TYPE_SHAFT, AdmConst.OBJ_TYPE_SYSTEM, AdmConst.OBJ_TYPE_EQUIPMENT, "ispace");
		buildDownloadQueryCriteria(criteria, operator, bdtpDownloadTs);
		criteria.getCriteria().put("buildingId", buildingId);

		List<ObjectNode> objs = ServiceUtil.queryAllPage(() -> objectClient.query(groupCode, projectId, AdmConst.APP_ID, userId, criteria), criteria, new Pagination(500));
		if(objs == null)
			objs = new ArrayList<>(0);

		packInfos(objs);
		test4Ts(objs);

		//关系数据按项目下载，不在建筑数据中处理

		//管道数据，实验方案
		List<AdmPipe> pipes = pipeMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmPipe>(), projectId, buildingId, operator, admDownloadTs));

		//任务相关数据查询
		List<AdmJobSpace> jobSpace = jobSpaceMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmJobSpace>(), projectId, buildingId, operator, admDownloadTs));

		// 问题相关数据
		List<AdmProblem> problems = problemMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<>(),projectId,buildingId,operator,admDownloadTs));

		// 下载ADM图纸
		List<AdmCad> admCads = admCadMapper.selectList(new LambdaQueryWrapper<AdmCad>().eq(AdmCad::getProjectId,projectId).eq(AdmCad::getBuildingId,buildingId));

		List<AdmFile> file = fileMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmFile>(), projectId, buildingId, operator, admDownloadTs));
		List<AdmQrCode> qrCode = qrCodeMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmQrCode>(), projectId, buildingId, operator, admDownloadTs));
		List<AdmServeArea> serveArea = serveAreaMapper.selectList(buildDownloadQueryWrapper(new QueryWrapper<AdmServeArea>(), projectId, buildingId, operator, admDownloadTs));

		BuildingData data = new BuildingData();
		data.setBuildingId(buildingId);

		data.setObjects(objs);

		data.setPipes(pipes);

		data.setJobSpace(jobSpace);
		data.setFile(file);
		data.setQrCode(qrCode);
		data.setServeArea(serveArea);

		data.buildDownloadTs();
		// 核查问题数据
		data.setProblems(problems);
		// CAD图纸数据
		data.setAdmCads(admCads);
		return data;
	}

	private String getOperator(String clientId, String userId){
		return userId + ':' + clientId;
	}

	private <T> QueryWrapper<T> buildDownloadQueryWrapper(QueryWrapper<T> wrapper, String projectId, String buildingId, String operator, Long lastDownloadTime){
		wrapper.eq("project_id", projectId).eq("building_id", buildingId);

		if (lastDownloadTime != null && lastDownloadTime > 0) { //非初始化下载
			wrapper.gt("ts", lastDownloadTime);

			wrapper.and(
					w -> w.isNotNull("modifier").ne("modifier", operator)
							.or()
							.isNull("modifier").ne("creator", operator)
			);
		}

		return wrapper;
	}

	private void buildDownloadQueryCriteria(QueryCriteria criteria, String operator, Long lastDownloadTime){
		if (lastDownloadTime != null && lastDownloadTime > 0) {
			criteria.getCriteria().putObject("ts").put("$gt", lastDownloadTime);
			criteria.getCriteria().putObject("modifier").put("$ne", operator); //TODO 暂时只能处理modifier
		}
	}

	//TODO debug 接口未返回ts时，添加一个
	private void test4Ts(List<ObjectNode> dataList){
		long ts = System.currentTimeMillis();
		for(ObjectNode data : dataList) {
			if (data.get("ts") == null)
				data.put("ts", ts);
		}
	}

	@Override
	public ProjectData downloadProjectData(String groupCode, String projectId, String userId, String clientId, Long bdtpDownloadTs, Long admDownloadTs) {
		String operator = getOperator(clientId, userId);

		QueryCriteria criteria = ServiceUtil.getQueryCriteria(objectMapper);
		buildDownloadQueryCriteria(criteria, operator, bdtpDownloadTs);
		criteria.getCriteria().put("createApp", AdmConst.CREATOR_APP); //TODO 临时逻辑，只下载app端创建的关系

		List<ObjectNode> rels = ServiceUtil.queryAllPage(() -> rwdClient.queryRelation(groupCode, projectId, AdmConst.APP_ID, userId, criteria), criteria, new Pagination(500));
		/*
		//debug 关系数据量过大，测试下载1000条
		criteria.setPage(1L);
		criteria.setSize(1000L);
		List<ObjectNode> rels = ServiceUtil.call(() -> rwdClient.queryRelation(groupCode, projectId, AdmConst.APP_ID, userId, criteria));
		*/
		if(rels == null)
			rels = new ArrayList<>();

		//单独下载元空间和空间的对应关系
		QueryCriteria criteria2 = ServiceUtil.getQueryCriteria(objectMapper);
		buildDownloadQueryCriteria(criteria2, operator, bdtpDownloadTs);
		criteria2.getCriteria().put("graphCode", "MechInArch").put("relCode", "Sp2Si").put("relValue", "GeneralZone");
		List<ObjectNode> spRels = ServiceUtil.queryAllPage(() -> rwdClient.queryRelation(groupCode, projectId, AdmConst.APP_ID, userId, criteria2), criteria2, new Pagination(500));
		if(spRels != null)
			rels.addAll(spRels);

		test4Ts(rels);

		ProjectData data = new ProjectData();
		data.setRelations(rels);

		data.buildDownloadTs();
		return data;
	}

	@Override
	public UploadRtn uploadData(UploadData uploadData, String groupCode, String projectId, String userId, String clientId) {
		if(!uploadData.notEmpty())
			return new UploadRtn(AdmConst.UPLOAD_FINISHED);

		String operator = getOperator(clientId, userId);
		String timestamp = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);

		UploadParams uploadParams = new UploadParams(groupCode, projectId, userId, uploadData.getUploadJobId(), operator, timestamp);

		//检查上传任务状态
		String status = checkUploadStatus(uploadParams);
		if(status != null)
			return new UploadRtn(status);


		List<AdmUploadLog> logs = new ArrayList<>();

		//TODO 分布式事务管理
		try {
			//上传bdtp数据
			List<ObjectNode> relations = new ArrayList<>();

			if (CollUtil.isNotEmpty(uploadData.getObjects())) {
				D2mMessage d2mMessage = new D2mMessage("object", groupCode, projectId);
				List<ObjectNode>[] objData = prepareBdtpData(uploadData.getObjects(), uploadParams, d2mMessage, logs);
				uploadObjs(uploadParams, objData);

				sendD2mMessage(d2mMessage, uploadParams);

				autoRelations(objData[0], relations, uploadParams);
			}

			if(uploadData.getRelations() != null)
				relations.addAll(uploadData.getRelations());
			if (relations.size() > 0) {
				List<ObjectNode>[] relData = prepareBdtpData(relations, uploadParams, null, logs);
				uploadRels(uploadParams, relData);
			}

			//上传adm数据
			uploadAdmData(uploadData, uploadParams, logs);
		} catch (Exception e) {
			markStatus(uploadData.getUploadJobId(), AdmConst.UPLOAD_ERROR);

			if(e instanceof RuntimeException)
				throw (RuntimeException)e;
			throw new RuntimeException(e);
		}

		//标注上传任务状态
		markStatus(uploadData.getUploadJobId(), AdmConst.UPLOAD_FINISHED);

		//记录日志
		logService.saveUploadLog(logs);

		return new UploadRtn(AdmConst.UPLOAD_FINISHED);
	}

	private void autoRelations(List<ObjectNode> newObjs, List<ObjectNode> relations, UploadParams ps){
		for(ObjectNode obj : newObjs) {
			if (obj.get("objType") != null && AdmConst.OBJ_TYPE_EQUIPMENT.equals(obj.get("objType").asText())) {
				String eqId = obj.get("id").asText();

				if(obj.get("buildingId") != null) {
					String bdId = obj.get("buildingId").asText();
					relations.add(createRelation(eqId, bdId, "MechInArch", "Eq2Bd", ps.groupCode, ps.projectId));
				}
				if(obj.get("floorId") != null) {
					String flId = obj.get("floorId").asText();
					relations.add(createRelation(eqId, flId, "MechInArch", "Eq2Fl", ps.groupCode, ps.projectId));
				}
			}
		}
	}

	private ObjectNode createRelation(String from, String to, String graphCode, String relCode, String groupCode, String projectId){
		ObjectNode r = objectMapper.createObjectNode();
		r.put("id", IdUtil.simpleUUID());
		r.put("objFrom", from);
		r.put("objTo", to);
		r.put("graphCode", graphCode);
		r.put("relCode", relCode);
		r.put("groupCode", groupCode);
		r.put("projectId", projectId);
		r.put("valid", ValidEnum.TRUE.getType());
		r.put("state", 0); //新增标识
		return r;
	}

	private String checkUploadStatus(UploadParams uploadParams){
		AdmUploadJob job = uploadJobMapper.selectOne(new QueryWrapper<AdmUploadJob>().eq("id", uploadParams.jobId));
		if(job != null) {
			if(!AdmConst.UPLOAD_ERROR.equals(job.getStatus()))
				return job.getStatus();
		} else {
			job = new AdmUploadJob();
			job.setId(uploadParams.jobId);
			job.setProjectId(uploadParams.projectId);
			job.setOperator(uploadParams.operator);
			job.setStatus(AdmConst.UPLOAD_PROCESSING);
			job.setStartTime(new Timestamp(System.currentTimeMillis()));
			uploadJobMapper.insert(job);
		}
		return null;
	}

	private void markStatus(String uploadJobId, String status) {
		AdmUploadJob job = new AdmUploadJob();
		job.setId(uploadJobId);
		job.setStatus(status);
		job.setFinishTime(new Timestamp(System.currentTimeMillis()));
		uploadJobMapper.updateById(job);
	}

	private void uploadObjs(UploadParams ps, List<ObjectNode>[] data) {
		if(data[0].size() > 0)
			ServiceUtil.call(() -> objectClient.create(ps.groupCode, ps.projectId, AdmConst.APP_ID, ps.userId, data[0]));
		if (data[1].size() > 0)
			ServiceUtil.call(() -> objectClient.update(ps.groupCode, ps.projectId, AdmConst.APP_ID, ps.userId, data[1]));
	}

	private void uploadRels(UploadParams ps, List<ObjectNode>[] data) {
		if(data[0].size() > 0)
			ServiceUtil.call(() -> relationClient.create(ps.groupCode, ps.projectId, AdmConst.APP_ID, ps.userId, data[0]));
		if (data[1].size() > 0)
			ServiceUtil.call(() -> relationClient.update(ps.groupCode, ps.projectId, AdmConst.APP_ID, ps.userId, data[1]));
	}

	private List<ObjectNode>[] prepareBdtpData(List<ObjectNode> list, UploadParams uploadParams, D2mMessage d2mMessage, List<AdmUploadLog> logs){
		List<ObjectNode> updateList = new ArrayList<>();
		Iterator<ObjectNode> iter = list.iterator();
		while (iter.hasNext()) {
			ObjectNode entity = iter.next();
			DataExtrasUtil.unpackObjExtras(entity);
			handle4Upload(entity);

			AdmUploadLog log;
			if (entity.get("state").asInt() == 1) {
				updateList.add(entity);
				iter.remove();

				boolean remove = entity.get("valid").asInt() == ValidEnum.FALSE.getType();

				//设备数据上传时向模型服务发同步消息
				if(d2mMessage != null && needsD2m(entity)) {
					if(remove)
						d2mMessage.appendRemoveItem(entity);
					else
						d2mMessage.appendModifyItem(entity);
				}

				log = new AdmUploadLog(uploadParams.jobId, uploadParams.operator, remove ? AdmUploadLog.REMOVE : AdmUploadLog.MODIFY);
				log.setData(entity, objectMapper);

				entity.put("updateApp", AdmConst.CREATOR_APP);
				entity.put("modifier", uploadParams.operator);
				entity.put("modifiedTime", uploadParams.time);

				//TODO 可能需要进行删除信息点操作

			} else {
				if(d2mMessage != null && needsD2m(entity))
					d2mMessage.appendAddItem(entity);

				log = new AdmUploadLog(uploadParams.jobId, uploadParams.operator, AdmUploadLog.ADD);
				log.setData(entity, objectMapper);

				entity.put("createApp", AdmConst.CREATOR_APP);
				entity.put("creator", uploadParams.operator);
				entity.put("creationTime", uploadParams.time);

				entity.put("modifier", uploadParams.operator);
				entity.put("modifiedTime", uploadParams.time);
			}
			logs.add(log);
		}

		return new List[] {list, updateList};
	}

	private boolean needsD2m(ObjectNode node){
		return node != null && node.get("objType") != null && AdmConst.OBJ_TYPE_EQUIPMENT.equals(node.get("objType").asText());
	}

	@Transactional(rollbackFor = Exception.class)
	public void uploadAdmData(UploadData uploadData, UploadParams uploadParams, List<AdmUploadLog> logs) {
		if (CollUtil.isNotEmpty(uploadData.getPipes())) {
			uploadAdmEntities(uploadData.getPipes(), uploadParams, pipeMapper, new D2mMessage("pipe", uploadParams.groupCode, uploadParams.projectId), logs, true);
		}

		if (CollUtil.isNotEmpty(uploadData.getJobSpace())) {
			uploadAdmEntities(uploadData.getJobSpace(), uploadParams, jobSpaceMapper, null, logs, false);
		}
		if (CollUtil.isNotEmpty(uploadData.getFile())) {
			uploadAdmEntities(uploadData.getFile(), uploadParams, fileMapper, null, logs, false);
		}
		if (CollUtil.isNotEmpty(uploadData.getQrCode())) {
			uploadAdmEntities(uploadData.getQrCode(), uploadParams, qrCodeMapper, null, logs, false);
		}
		if (CollUtil.isNotEmpty(uploadData.getServeArea())) {
			uploadAdmEntities(uploadData.getServeArea(), uploadParams, serveAreaMapper, null, logs, false);
		}

		// 核查问题
		if (CollUtil.isNotEmpty(uploadData.getProblems())){
			uploadAdmEntities(uploadData.getProblems(),uploadParams,problemMapper,null,logs,false);
		}
	}

	private <T extends BaseAdmDataEntity> void uploadAdmEntities(List<T> list, UploadParams ps, BaseMapper<T> mapper, D2mMessage d2mMessage, List<AdmUploadLog> logs, boolean merge) {
		List<T> updateList = new ArrayList<>();
		Iterator<T> iter = list.iterator();
		while (iter.hasNext()) {
			T entity = iter.next();

			AdmUploadLog log;
			if (entity.getState() == 1) {
				updateList.add(entity);
				iter.remove();

				boolean remove = entity.getValid().intValue() == ValidEnum.FALSE.getType();

				if(d2mMessage != null) {
					if(remove)
						d2mMessage.appendRemoveItem(entity);
					else
						d2mMessage.appendModifyItem(entity);
				}

				log = new AdmUploadLog(ps.jobId, ps.operator, remove ? AdmUploadLog.REMOVE : AdmUploadLog.MODIFY);

				//任务空间不上传轮廓数据
				if(entity instanceof AdmJobSpace)
					((AdmJobSpace) entity).setOutline(null);
			} else {
				if(d2mMessage != null)
					d2mMessage.appendAddItem(entity);

				log = new AdmUploadLog(ps.jobId, ps.operator, AdmUploadLog.ADD);
			}
			log.setData(entity, objectMapper);
			logs.add(log);

			entity.setTs(null); //保存数据前，删除ts字段
		}

		//insert
		if(list.size() > 0) {
			for(T entity : list) {
				entity.setCreator(ps.operator);
				entity.setCreationTime(ps.time);
				mapper.insert(entity);
			}
		}

		//update
		if (updateList.size() > 0) {
			Map<String, T> dbObjsMap = null;
			if(merge) {
				ArrayList<String> updateIds = new ArrayList<>(updateList.size());
				updateList.forEach(entity -> updateIds.add(entity.getId()));
				List<T> dbList = mapper.selectBatchIds(updateIds);

				dbObjsMap = new HashMap<>(dbList.size());
				for(T dbObj : dbList) {
					dbObjsMap.put(dbObj.getId(), dbObj);
				}
			}

			for(T entity : updateList) {
				if(merge)
					entity = mergeEntity(entity, dbObjsMap.get(entity.getId()));

				entity.setModifier(ps.operator);
				entity.setModifiedTime(ps.time);
				mapper.updateById(entity);
			}
		}

		sendD2mMessage(d2mMessage, ps);
	}

	private <T extends BaseAdmDataEntity> T mergeEntity(T obj, T dbObj) {
		if (dbObj != null) {
			if(dbObj instanceof AdmPipe)
				((AdmPipe)obj).setInfos(mergeInfos(((AdmPipe) obj).getInfos(), ((AdmPipe) dbObj).getInfos()));
		}
		return obj;
	}

	private ObjectNode mergeInfos(ObjectNode infos, ObjectNode dbInfos){
		if (dbInfos != null) {
			if(infos != null)
				dbInfos.setAll(infos);
			return dbInfos;
		} else
			return infos;
	}

	private void sendD2mMessage(D2mMessage d2mMessage, UploadParams ps){
		if (d2mMessage != null && d2mMessage.isNotEmpty()) {
//			if(d2mMessage.getAddItems() != null){
//				for(Object o : d2mMessage.getAddItems()) {
//					markM2dState(o);
//				}
//			}
//			if(d2mMessage.getModifyItems() != null){
//				for(Object o : d2mMessage.getModifyItems()) {
//					markM2dState(o);
//				}
//			}
//			if(d2mMessage.getRemoveItems() != null) {
//				for(Object o : d2mMessage.getRemoveItems()) {
//					markM2dState(o);
//				}
//			}
			markM2dState(d2mMessage, ps);
			syncModel.sendMessageToModel(d2mMessage);
		}
	}

	/** 上传前处理数据结构 */
	private void handle4Upload(ObjectNode node){
		node.remove("ts"); //调用bdtp接口前，删除ts字段

		//轮廓不上传
		if(node.get("outline") != null)
			node.remove("outline");

		//location对象转为bimLocation
		JsonNode l = node.get("location");
		if(l != null) {
			try {
				if(l.get("x") != null)
					node.put("bimLocation", StrUtil.join(",", l.get("x").asDouble(), l.get("y").asDouble(), l.get("z").asDouble()));
				else if(l.get("X") != null)
					node.put("bimLocation", StrUtil.join(",", l.get("X").asDouble(), l.get("Y").asDouble(), l.get("Z").asDouble()));
				node.remove("location");
			}catch (Exception e) {

			}
		}
	}

	private void markM2dState(D2mMessage d2mMessage, UploadParams ps){
		AdmMessageP message = new AdmMessageP();
		message.setId(d2mMessage.getId());
		message.setProjectId(ps.projectId);
		message.setTriggerType("upload");
		message.setSender(ps.jobId);
		message.setContent(d2mMessage);
		message.setStatus(AdmConst.UPLOAD_PROCESSING);
		message.setSendTime(new Timestamp(System.currentTimeMillis()));

		messageMapper.insert(message);
	}

	private class UploadParams{

		String groupCode;

		String projectId;

		String userId;

		String jobId;

		String operator;

		String time;

		public UploadParams(String groupCode, String projectId, String userId, String jobId, String operator, String time) {
			this.groupCode = groupCode;
			this.projectId = projectId;
			this.userId = userId;
			this.jobId = jobId;
			this.operator = operator;
			this.time = time;
		}

	}

}
