package kr.co.kihyun.sync;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Set;

import javax.jdo.Extent;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;

import kr.co.kihyun.beans.entity.HandySync;
import kr.co.kihyun.beans.entity.MDept;
import kr.co.kihyun.beans.entity.MUser;
import kr.co.kihyun.beans.entity.Role;
import kr.co.kihyun.beans.entity.UserDept;
import kr.co.kihyun.beans.entity.UserPart;
import kr.co.kihyun.beans.entity.UserUser;
import kr.co.kihyun.beans.entity.util.*;
import kr.co.kihyun.lang.MInteger;
import kr.co.kihyun.moumi.MoumiConfig;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.handysoft.beans.entity.HDept;
import com.handysoft.beans.entity.HDocUnit;
import com.handysoft.beans.entity.HUser;
import com.handysoft.beans.entity.HUserDept;
import com.handysoft.beans.entity.HUserPart;
import com.handysoft.beans.entity.HUserUser;
import com.handysoft.beans.entity.HandyEntity;

// TODO: extract method
public class OrgSynchronizer extends Thread {
	private static final Logger LOG = LoggerFactory.getLogger(OrgSynchronizer.class);

	private boolean isDone = false;
	private int deptCapacity;
	private int userCapacity;
	private int userPartCapacity;
	private int userDeptCapacity;
	private int userUserCapacity;
	private Extent<HDept> hDeptExtent;
	private Extent<HUser> hUserExtent;
	private Extent<HDocUnit> hReceiverExtent;
	private Extent<MDept> mDeptExtent;
	private Extent<HUserPart> hUserPartExtent;
	private Extent<HUserDept> hUserDeptExtent;
	private Extent<HUserUser> hUserUserExtent;
	private Extent<UserPart> mUserPartExtent;
	private Extent<UserDept> mUserDeptExtent;
	private Extent<UserUser> mUserUserExtent;
	private PersistenceManager moumiPM;
	private PersistenceManager handyPM;

        
        private static final String SYNC = "SYNC";   //20.검사시점과 사용시점(멀티쓰레드)_CWE-367 : Add by KWON,HAN +++


	public void done() {
		this.isDone = true;
	}

	@Override
	public void run() {
            synchronized(SYNC){   //20.검사시점과 사용시점(멀티쓰레드)_CWE-367 : Add by KWON,HAN +++
		try {
			userPartCapacity = MInteger.parseInt(MoumiConfig.getInitParameter("moumi.initialUserPartCapacity"), 1500);
		} catch (MissingResourceException mre) {
			userPartCapacity = 1500;
		}
		try {
			userDeptCapacity = MInteger.parseInt(MoumiConfig.getInitParameter("moumi.initialUserDeptCapacity"), 200);
		} catch (MissingResourceException mre) {
			userDeptCapacity = 200;
		}
		try {
			userUserCapacity = MInteger.parseInt(MoumiConfig.getInitParameter("moumi.initialUserUserCapacity"), 30000);
		} catch (MissingResourceException mre) {
			userUserCapacity = 30000;
		}
		try {
			deptCapacity = MInteger.parseInt(MoumiConfig.getInitParameter("moumi.initialDeptCapacity"), 600);
		} catch (MissingResourceException mre) {
			deptCapacity = 600;
		}
		try {
			userCapacity = MInteger.parseInt(MoumiConfig.getInitParameter("moumi.initialUserCapacity"), 2000);
		} catch (MissingResourceException mre) {
			userCapacity = 2000;
		}

		while (!this.isDone) {
			moumiPM = new MPersistenceManager(PMF.get().getPersistenceManager());
			handyPM = PMF.get("HANDY").getPersistenceManager();
			Transaction moumiTx = moumiPM.currentTransaction();
			Transaction handyTx = handyPM.currentTransaction();
			try {
				LOG.info("getting extents has been started.");
				getExtents();
				LOG.info("getting extents ended.");

				handyTx.begin();
				LOG.info("handy scanning has been started.");
				LOG.debug("dept scanning has been started.");
				List<MDept> mDepts = getDepts();
				LOG.debug("dept scanning ended.");
				LOG.debug("user scanning has been started.");
				List<MUser> mUsers = getUsers();
				LOG.debug("user scanning ended.");
				LOG.debug("user part scanning has been started.");
				List<UserPart> mUserParts = getUserParts();
				LOG.debug("user part scanning ended.");
				LOG.debug("user dept scanning has been started.");
				List<UserDept> mUserDepts = getUserDepts();
				LOG.debug("user dept scanning ended.");
				LOG.debug("user user scanning has been started.");
				List<UserUser> mUserUsers = getUserUsers();
				LOG.debug("user user scanning ended.");
				List<UserUser> deletedUserUserList = getDeletedList(hUserUserExtent, mUserUserExtent);
				List<UserDept> deletedUserDeptList = getDeletedList(hUserDeptExtent, mUserDeptExtent);
				List<UserPart> deletedUserPartList = getDeletedList(hUserPartExtent, mUserPartExtent);
				LOG.info("handy scanning ended.");
				handyTx.commit();

				StringBuilder deletedUserSB = new StringBuilder();
				for (UserUser deletedUser : deletedUserUserList)
					deletedUserSB.append(",").append(deletedUser);
				LOG.debug("{} will be deleted.", deletedUserSB);

				moumiTx.begin();
				LOG.info("moumi persisting has been started.");
				LOG.debug("dept persisting has been started.");
				moumiPM.makePersistentAll(mDepts);
				LOG.debug("dept persisting ended.");
				LOG.debug("user persisting has been started.");
				moumiPM.makePersistentAll(mUsers);
				LOG.debug("user persisting ended.");
				moumiTx.commit();
				moumiTx.begin();
				LOG.debug("user part persisting has been started.");
				moumiPM.makePersistentAll(mUserParts);
				LOG.debug("user part persisting ended.");
				LOG.debug("user dept persisting has been started.");
				moumiPM.makePersistentAll(mUserDepts);
				LOG.debug("user dept persisting ended.");
				LOG.debug("user user persisting has been started.");
				moumiPM.makePersistentAll(mUserUsers);
				LOG.debug("user user persisting ended.");
				moumiPM.deletePersistentAll(deletedUserUserList);
				for (UserUser userUser : deletedUserUserList)
					LOG.debug("{} deleted.", userUser);
				moumiPM.deletePersistentAll(deletedUserDeptList);
				for (UserDept userDept : deletedUserDeptList)
					LOG.debug("{} deleted.", userDept);
				moumiPM.deletePersistentAll(deletedUserPartList);
				for (UserPart userPart : deletedUserPartList)
					LOG.debug("{} deleted.", userPart);
				LOG.debug("empty group deleting has been started.");
				StringBuilder deletedUserParts = new StringBuilder(1024);
				LOG.debug("these empty user_parts will be removed: {}", deletedUserParts);
				LOG.debug("empty group deleting ended.");
				LOG.info("moumi persisting ended.");
				moumiTx.commit();

				OrgSynchronizer.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (handyTx.isActive())
					handyTx.rollback();
				if (moumiTx.isActive())
					moumiTx.rollback();
				moumiPM.close();
				handyPM.close();
			}
		}
            }   //20.검사시점과 사용시점(멀티쓰레드)_CWE-367 : Add by KWON,HAN +++
	}

	private void getExtents() {
		LOG.info("getting hDeptExtent has been started.");
		hDeptExtent = handyPM.getExtent(HDept.class, false);
		LOG.info("getting hDeptExtent ended.");
		LOG.info("getting hUserExtent has been started.");
		hUserExtent = handyPM.getExtent(HUser.class, false);
		LOG.info("getting hUserExtent ended.");
		LOG.info("getting hReceiverExtent has been started.");
		hReceiverExtent = handyPM.getExtent(HDocUnit.class, false);
		LOG.info("getting hReceiverExtent ended.");
		LOG.info("getting mDeptExtent has been started.");
		mDeptExtent = moumiPM.getExtent(MDept.class, false);
		LOG.info("getting mDeptExtent ended.");
		LOG.info("getting hUserPartExtent has been started.");
		hUserPartExtent = handyPM.getExtent(HUserPart.class, false);
		LOG.info("getting hUserPartExtent ended.");
		LOG.info("getting hUserDeptExtent has been started.");
		hUserDeptExtent = handyPM.getExtent(HUserDept.class, false);
		LOG.info("getting hUserDeptExtent ended.");
		LOG.info("getting hUserUserExtent has been started.");
		hUserUserExtent = handyPM.getExtent(HUserUser.class, false);
		LOG.info("getting hUserUserExtent ended.");
		LOG.info("getting mUserPartExtent has been started.");
		mUserPartExtent = moumiPM.getExtent(UserPart.class, false);
		LOG.info("getting mUserPartExtent ended.");
		LOG.info("getting mUserDeptExtent has been started.");
		mUserDeptExtent = moumiPM.getExtent(UserDept.class, false);
		LOG.info("getting mUserDeptExtent ended.");
		LOG.info("getting mUserUserExtent has been started.");
		mUserUserExtent = moumiPM.getExtent(UserUser.class, false);
		LOG.info("getting mUserUserExtent ended.");
	}

	private List<UserPart> getUserParts() {
		List<UserPart> mUserParts = new ArrayList<UserPart>(userPartCapacity);

		for (Iterator<HUserPart> userPartIter = hUserPartExtent.iterator(); userPartIter.hasNext();) {
			HUserPart hUserPart = userPartIter.next();

			Query q = moumiPM.newQuery(mUserPartExtent, "handyId == handy_id");
			q.declareParameters("String handy_id");
			q.setUnique(true);
			UserPart mUserPart = (UserPart) q.execute(hUserPart.getId());

			MUser owner = null;
			try {
				owner = moumiPM.getObjectById(MUser.class, "000000001".equals(hUserPart.getUserId()) ? "admin"
						: hUserPart.getUserId());
			} catch (javax.jdo.JDOObjectNotFoundException jnfe) {
				// LOG.warn("{}'s owner MUser@{} is not exists. so this user part will be ignored.",
				// hUserPart, hUserPart.getUserId());
				continue;
			}
			if (mUserPart == null) {
				mUserPart = new UserPart(hUserPart.getName(), owner, null);
			}

			mUserPart.setDescription(hUserPart.getDes());
			mUserPart.setHandyId(hUserPart.getId());
			mUserPart.setName(hUserPart.getName());
			mUserPart.setUser(owner);

			mUserParts.add(mUserPart);
		}

		return mUserParts;
	}

	private <H extends HandyEntity, M extends HandySync> List<M> getDeletedList(Extent<H> handyExtent,
			Extent<M> moumiExtent) {
		List<M> deletedList = new ArrayList<M>(5000);
		List<String> handyIds = new ArrayList<String>();

		for (Iterator<H> handyIter = handyExtent.iterator(); handyIter.hasNext();) {
			H handy = handyIter.next();
			handyIds.add(handy.getId());
		}

		for (Iterator<M> moumiIter = moumiExtent.iterator(); moumiIter.hasNext();) {
			M moumi = moumiIter.next();
			if (!handyIds.contains(moumi.getHandyId()))
				deletedList.add(moumi);
		}

		return deletedList;
	}

	private List<UserDept> getUserDepts() {
		List<UserDept> mUserDepts = new ArrayList<UserDept>(userDeptCapacity);

		for (Iterator<HUserDept> userDeptIter = hUserDeptExtent.iterator(); userDeptIter.hasNext();) {
			HUserDept hUserDept = userDeptIter.next();

			Query userPartQuery = moumiPM.newQuery(mUserPartExtent, "handyId == handy_id");
			userPartQuery.declareParameters("String handy_id");
			userPartQuery.setUnique(true);
			Query userDeptQuery = moumiPM.newQuery(mUserDeptExtent, "userPart == m_part && id == m_dept");
			userDeptQuery.declareParameters("kr.co.kihyun.beans.entity.UserPart m_part, String m_dept");
			userDeptQuery.setUnique(true);
			Query deptQuery = moumiPM.newQuery(mDeptExtent, "handyId == handy_id");
			deptQuery.declareParameters("String handy_id");
			deptQuery.setUnique(true);

			MDept mDept = (MDept) deptQuery.execute(hUserDept.getDeptId());
			if (mDept == null) {
				// LOG.warn("MDept@{} is not exists. so this user dept will be ignored.",
				// hUserDept.getDeptId());
				continue;
			}
			UserPart mUserPart = (UserPart) userPartQuery.execute(hUserDept.getPartId());
			if (mUserPart == null) {
				// LOG.warn("UserPart@{} is not exists. so this user dept will be ignored.",
				// hUserDept.getPartId());
				continue;
			}
			UserDept mUserDept = (UserDept) userDeptQuery.execute(mUserPart, mDept.getId());
			if (mUserDept == null) {
				mUserDept = new UserDept(mDept, hUserDept.getName(), mUserPart);
			}

			mUserDept.setHandyId(hUserDept.getId());
			mUserDept.setDept(mDept);
			mUserDept.setName(mDept.getName());
			mUserDept.setUserPart(mUserPart);

			mUserDepts.add(mUserDept);
		}

		return mUserDepts;
	}

	private List<UserUser> getUserUsers() {
		List<UserUser> mUserUsers = new ArrayList<UserUser>(userUserCapacity);

		for (Iterator<HUserUser> userUserIter = hUserUserExtent.iterator(); userUserIter.hasNext();) {
			HUserUser hUserUser = userUserIter.next();

			Query userPartQuery = moumiPM.newQuery(mUserPartExtent, "handyId == handy_id");
			userPartQuery.declareParameters("String handy_id");
			userPartQuery.setUnique(true);
			Query userUserQuery = moumiPM.newQuery(mUserUserExtent, "userPart == m_part && id == m_user");
			userUserQuery.declareParameters("kr.co.kihyun.beans.entity.UserPart m_part, String m_user");
			userUserQuery.setUnique(true);

			MUser mUser = null;
			try {
				mUser = moumiPM.getObjectById(MUser.class, hUserUser.getUserId());
			} catch (javax.jdo.JDOObjectNotFoundException jnfe) {
				// LOG.warn("MUser@{} is not exists. so this user user will be ignored.",
				// hUserUser.getUserId());
				continue;
			}
			UserPart mUserPart = (UserPart) userPartQuery.execute(hUserUser.getPartId());
			if (mUserPart == null) {
				// LOG.warn("UserPart@{} is not exists. so this user user will be ignored.",
				// hUserUser.getPartId());
				continue;
			}
			UserUser mUserUser = (UserUser) userUserQuery.execute(mUserPart, mUser.getId());
			if (mUserUser == null) {
				mUserUser = new UserUser(mUser, hUserUser.getName(), mUser.getUpperDept(moumiPM), mUserPart);
			}

			mUserUser.setHandyId(hUserUser.getId());
			mUserUser.setUser(mUser);
			mUserUser.setName(mUser.getName(moumiPM));
			mUserUser.setDept(mUser.getUpperDept(moumiPM));
			mUserUser.setPart(mUserPart);
			mUserUser.setValid(true);

			mUserUsers.add(mUserUser);
		}

		return mUserUsers;
	}

	private List<MDept> getDepts() {
		List<MDept> mDepts = new ArrayList<MDept>(deptCapacity);

		for (Iterator<HDept> deptIter = hDeptExtent.iterator(); deptIter.hasNext();) {
			HDept hDept = deptIter.next();

			Query q = moumiPM.newQuery(mDeptExtent, "handyId == handy_id");
			q.declareParameters("String handy_id");
			q.setUnique(true);
			MDept mDept = (MDept) q.execute(hDept.getId());

			if (mDept == null) {
				mDept = new MDept(hDept.getPasscode(), hDept.getName());
			}

//			mDept.setName(hDept.getName());
//			mDept.setPasscode(hDept.getPasscode());
//			mDept.setHandyId(hDept.getId());
//			mDept.setAddress(hDept.getAddress1() + hDept.getAddress2());
//			mDept.setPriority(hDept.getPriority());
//			mDept.setDocUnit(hDept.isDocUnit());
			q = moumiPM.newQuery(mDeptExtent, "handyId == handy_id");
			q.declareParameters("String handy_id");
			q.setUnique(true);
//			mDept.setUpperDept((MDept) q.execute(hDept.getUpperDept()));

			mDepts.add(mDept);
		}

		return mDepts;
	}

	private List<MUser> getUsers() {
		List<MUser> mUsers = new ArrayList<MUser>(userCapacity);

		for (Iterator<HUser> deptIter = hUserExtent.iterator(); deptIter.hasNext();) {
			HUser hUser = deptIter.next();
			MUser mUser = null;

			Query docUnitQuery = handyPM.newQuery(hReceiverExtent, "userId == currentUserId");
			docUnitQuery.declareParameters("String currentUserId");
			Query deptQuery = moumiPM.newQuery(mDeptExtent, "handyId == handy_id");
			deptQuery.declareParameters("String handy_id");
			deptQuery.setUnique(true);

			try {
				mUser = moumiPM.getObjectById(MUser.class, hUser.getId());
			} catch (javax.jdo.JDOObjectNotFoundException jnfe) {
				mUser = new MUser();
//				mUser.setId(hUser.getId());
			}

			List<HDocUnit> receivers = (List<HDocUnit>) docUnitQuery.execute(hUser.getId());
			Set<MDept> depts = new HashSet<MDept>();

			MDept upperDept = null;
			if (receivers.size() == 0) { // if this user is not document manager
				upperDept = (MDept) deptQuery.execute(hUser.getmDept());
				if (upperDept != null)
					depts.add(upperDept);
			} else {
				for (HDocUnit receiver : receivers) { // user can only have one dept.
					MDept tmpDept = (MDept) deptQuery.execute(receiver.getRelId());
					if (tmpDept != null) {
						upperDept = tmpDept;
						depts.add(upperDept);
					}
				}
//				mUser.setRoles(Role.DOC_MANAGER);
			}

			if (upperDept == null) {
				// LOG.warn("upper dept for {} is not exists. so changing upper dept will be ignored.",
				// mUser);
				continue;
			}

			// mUser.setPassword(hUser.getPassword());
//			mUser.setName(hUser.getName());
//			mUser.setEmail(hUser.getEmail());
//			mUser.setPhone(hUser.getPhone());
//			mUser.setDeleted(hUser.isDeleted());
//			mUser.setPriority(hUser.getPriority());
//			mUser.setDutyName(hUser.getDutyName());
//			mUser.setEmpCode(hUser.getEmpCode());
//			mUser.setGwId(hUser.getEmpCode());
//			mUser.setRoles(Role.DOC_MANAGER);
			// mUser.setDepts(depts);
//			mUser.setUpperDept(upperDept);

			mUsers.add(mUser);
		}

		return mUsers;
	}

}
