package jp.agentec.abook.abv.bl.logic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import jp.agentec.abook.abv.bl.acms.client.AcmsClient;
import jp.agentec.abook.abv.bl.acms.client.parameters.AcmsParameters;
import jp.agentec.abook.abv.bl.acms.type.SecurityPolicyType;
import jp.agentec.abook.abv.bl.acms.type.ServiceOption.ServiceOptionId;
import jp.agentec.abook.abv.bl.common.ABVEnvironment;
import jp.agentec.abook.abv.bl.common.exception.AcmsException;
import jp.agentec.abook.abv.bl.common.exception.NetworkDisconnectedException;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.bl.data.dao.AbstractDao;
import jp.agentec.abook.abv.bl.data.dao.AcmsDao;
import jp.agentec.abook.abv.bl.data.dao.ServiceOptionDao;
import jp.agentec.abook.abv.bl.dto.AcmsDto;
import jp.agentec.abook.abv.bl.dto.ServiceOptionDto;
import jp.agentec.adf.util.NumericUtil;
import jp.agentec.adf.util.StringUtil;

/**
 * 契約事業者に関する機能を提供します。
 * @author Taejin Hong
 * @version 1.1.0
 */
public class ContractLogic extends AbstractLogic {
	private static final String TAG = "ContractLogic";
	private static final String True = "Y";
	private ServiceOptionDao serviceOptionDao = AbstractDao.getDao(ServiceOptionDao.class);
	private AcmsDao acmsDao = AbstractDao.getDao(AcmsDao.class);
	
	/**
	 * {@link ContractLogic} クラスのインスタンスを初期化します。
	 * context　Androidとの互換性の為のプロパティです。Androidの android.content.Context のインスタンスを指定します。<br>
	 * UIがAndroidでない場合、何かのオブジェクトを指定しても、nullと見なします。
	 * @see AbstractLogic
	 * @since 1.0.0
	 */
	/*package*/ ContractLogic() {
	}
	
	/**
	 * サービスオプションの取得のみ（DBは更新しない）。refresh 時の並列フェッチ用。
	 */
	public List<ServiceOptionDto> fetchServiceOptionsOnly() throws NetworkDisconnectedException, AcmsException {
		AcmsParameters param = new AcmsParameters(cache.getMemberInfo().sid);
		return AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).serviceOption(param);
	}

	/**
	 * {@link #fetchServiceOptionsOnly()} の結果をローカルに反映する。
	 */
	public void applyFetchedServiceOptions(List<ServiceOptionDto> list) throws NetworkDisconnectedException, AcmsException {
		if (list != null && list.size() > 0) {
			if (isSameServiceOptionListAsDb(list)) {
				Logger.d(TAG, "service options unchanged, skip delete/insert");
				return;
			}
			try {
				serviceOptionDao.beginTransaction();

				serviceOptionDao.deleteServiceOptions();
				serviceOptionDao.insertServiceOptions(list);

				serviceOptionDao.commit();
				cache.addServiceOptions(list);
			} catch (Exception e) {
				serviceOptionDao.rollback();
				throw new RuntimeException(e);
			} finally {
			}
		}
	}

	/**
	 * サービスオプション情報をローカルに保存します。この際、既存のデータがあれば削除してから新規保存します。
	 * @throws NetworkDisconnectedException
	 * @throws AcmsException
	 * @throws Exception その他、例外です。
	 * @since 1.0.0
	 */
	public void initializeContractServiceOption() throws NetworkDisconnectedException, AcmsException {
		applyFetchedServiceOptions(fetchServiceOptionsOnly());
	}

	/**
	 * サーバ取得のサービスオプションとローカルDBの内容が同一なら、削除・再挿入を省略する。
	 */
	private boolean isSameServiceOptionListAsDb(List<ServiceOptionDto> serverList) {
		List<ServiceOptionDto> localList = serviceOptionDao.getServiceOptions();
		if (localList == null || localList.size() != serverList.size()) {
			return false;
		}
		ArrayList<ServiceOptionDto> s = new ArrayList<ServiceOptionDto>(serverList);
		ArrayList<ServiceOptionDto> l = new ArrayList<ServiceOptionDto>(localList);
		Comparator<ServiceOptionDto> byId = new Comparator<ServiceOptionDto>() {
			@Override
			public int compare(ServiceOptionDto a, ServiceOptionDto b) {
				return Integer.valueOf(a.serviceOptionId).compareTo(b.serviceOptionId);
			}
		};
		Collections.sort(s, byId);
		Collections.sort(l, byId);
		for (int i = 0; i < s.size(); i++) {
			if (!serviceOptionRowEquals(s.get(i), l.get(i))) {
				return false;
			}
		}
		return true;
	}

	private static boolean serviceOptionRowEquals(ServiceOptionDto a, ServiceOptionDto b) {
		if (a.serviceOptionId != b.serviceOptionId) {
			return false;
		}
		if (!safeStrEq(a.val, b.val)) {
			return false;
		}
		if (!safeStrEq(a.serviceOptionName, b.serviceOptionName)) {
			return false;
		}
		if (!safeStrEq(a.serviceOptionDispName, b.serviceOptionDispName)) {
			return false;
		}
		return true;
	}

	private static boolean safeStrEq(String x, String y) {
		String xs = x == null ? "" : x;
		String ys = y == null ? "" : y;
		return xs.equals(ys);
	}

	/**
	 * 権限喪失,非公開,削除時のDL済コンテンツ強制削除するかどうかを示します。
	 * @return　権限喪失,非公開,削除時のDL済コンテンツ強制削除する場合、trueを返します。
	 * @since 1.0.0
	 */
	public boolean isDeleteNoAuthContent() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.NoauthDelete);
		
		if (dto != null) {
			result = getBooleanValue(dto.val);
		}
		
		return result;
	}
	
	/**
	 * 初回ログイン時パスワード強制変更のポリシーを返します。
	 * @return　初回ログイン時パスワード強制変更のポリシーです。
	 * @throws Exception
	 * @since 1.0.0
	 */
	public SecurityPolicyType getForcePwChangeOnLogin() {
		SecurityPolicyType code = SecurityPolicyType.None;
		
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.ForcePwChangeOnLogin);
		
		if (dto != null) {
			code = SecurityPolicyType.parse(NumericUtil.parseInt(dto.val));
		}
		
		return code;
	}
	
	/**
	 * 定期ログイン時パスワード強制変更のポリシーを返します。
	 * @return　定期ログイン時パスワード強制変更のポリシーです。
	 * @throws Exception
	 * @since 1.0.0
	 */
	public SecurityPolicyType getForcePwChangePeriodically() {
		SecurityPolicyType code = SecurityPolicyType.None;
		
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.ForcePwChangePeriodically);
		
		if (dto != null) {
			code = SecurityPolicyType.parse(NumericUtil.parseInt(dto.val));
		}
		
		return code;
	}
	
	/**
	 * 一定時間、ログインしっぱなしにしておくと、画面をロックし、パスワードを入れてもらうかどうかを示します。
	 * @return　一定時間、ログインしっぱなしにしておくと、画面をロックし、パスワードを入れてもらうようにになっている場合、trueを返します。
	 * @throws Exception
	 * @since 1.0.0
	 */
	public boolean getForceLoginPeriodically() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.ForceLoginPeriodically);
		
		if (dto != null) {
			result = getBooleanValue(dto.val);
		}
		
		return result;
	}

	/**
	 * 最終CMSアクセスから一定期間経過するとログアウトするかどうかを示します。
	 * @return　ログアウトする場合、trueを返します。
	 * @throws Exception
	 * @since 1.0.0
	 */
	public boolean getForceOfflineLogout() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.ForceOfflineLogout);

		if (dto != null) {
			result = getBooleanValue(dto.val);
		}

		return result;
	}

	/**
	 * 最終CMSアクセスからタイムアウトするまでの日数を示します。
	 * @return　タイムアウトするまでの日数を返します。
	 * @throws Exception
	 * @since 1.0.0
	 */
	public int getForceOfflineWait() {
		int result = 0;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.ForceOfflineWait);

		if (dto != null) {
			result = Integer.valueOf(dto.val);
		}

		return result;
	}

	/**
	 * マーキング機能を使用するかを示します。
	 * @return　マーキング機能する場合、trueを返します。
	 * @throws Exception
	 * @since 1.0.0
	 */
	public boolean getMarking() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.Marking);
		
		if (dto != null) {
			result = getBooleanValue(dto.val);
		}
		
		return result;
	}
	
	/**
	 * 連動機能を使用するかを示します。
	 * @return　マーキング機能する場合、trueを返します。
	 * @throws Exception
	 * @since 1.0.0
	 */
	public boolean getViewerSync() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.ViewerSync);
		
		if (dto != null) {
			result = getBooleanValue(dto.val);
		}
		
		return result;
	}
	
	/**
	 * コンテンツ警告を使用するかを示します。
	 * @return　コンテンツ警告を使用する場合、trueを返します。
	 * @throws Exception
	 * @since 1.0.0
	 */
	public boolean getContentAlert() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.ContentAlert);

		if (dto != null) {
			result = getBooleanValue(dto.val);
		}

		return result;
	}

	/**
	 * 閲覧ログGPS使用可を示します。
	 * @return　閲覧ログGPS使用可の場合、trueを返します。
	 * @since 1.0.0
	 */
	public boolean getUsableReadinglogGps() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.UsableReadinglogGps);

		if (dto != null) {
			result = ContractLogic.getBooleanValue(dto.val);
		}

		return result;
	}

	/**
	 * 閲覧ログ詳細ログ使用可を示します。
	 * @return　閲覧ログ詳細ログ使用可の場合、trueを返します。
	 * @since 1.0.0
	 */
	public boolean getUsableReadinglogObject() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.UsableReadinglogObject);

		if (dto != null) {
			result = ContractLogic.getBooleanValue(dto.val);
		}

		return result;
	}

	public boolean getPdfLinkDisp() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.PdfLinkDisp);
		
		if (dto != null) {
			result = getBooleanValue(dto.val);
		}
		
		return result;
	}
	
	/**
	 * PDFTextCopy 사업자옵션 여부 확인  
	 * @return 사용가능하면 true 반환
	 * @throws Exception
	 * @since 1.0.0
	 */
	public boolean getPdfTextCopy() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.PdfTextCopy);
		
		if (dto != null) {
			result = getBooleanValue(dto.val);
		}
		
		return result;
	}
	
	public boolean getUDIDOverwiteFlag() {
		boolean result = false;
		ServiceOptionDto dto = getServiceOptionFromId(ServiceOptionId.UseridUdidOverwrite);
		
		if (dto != null) {
			result = getBooleanValue(dto.val);
		}
		
		return result;
	}

	/**
	 * 契約事業者のサービスオプションを返します。
	 * @param serviceOptionId　取得するサービスオプションのサービスオプションIDです。
	 * @return　指定したサービスオプションIDにあたる情報があると　{@link ServiceOptionDto} オブジェクトを返します。存在しないとnullを返します。
	 * @throws Exception
	 * @since 1.0.0
	 */
	private ServiceOptionDto getServiceOptionFromId(int serviceOptionId) {
		ServiceOptionDto dto = cache.getServiceOption(serviceOptionId);
		
		if (dto == null) {
			dto = serviceOptionDao.getServiceOptionFromId(serviceOptionId);
			
			if (dto != null) {
				cache.addServiceOption(dto);
			}
		}
		
		return dto;
	}
	
	public String getUrlPath() {
		String urlPath = cache.getUrlPath();
		
		if (StringUtil.isNullOrEmpty(urlPath)) {
			urlPath = acmsDao.getUrlPath();
		}
		
		return urlPath;
	}

	public boolean isTodayLastAnnounceChangePasswordDate() {
		boolean result;
		try {
			result = acmsDao.isTodayLastAnnounceChangePasswordDate();
			if (!result) {
				acmsDao.beginTransaction();
				acmsDao.updateLastAnnounceChangePasswordDate();
				acmsDao.commit();
			}
		} catch (Exception e) {
			Logger.e("getUrlPath failed.", e);
			throw new RuntimeException(e);
		}
		return result;
	}
	
	public void initializeUrlPath(String urlPath) {
		if (!StringUtil.isNullOrEmpty(urlPath)) {
			if (StringUtil.isNullOrEmpty(acmsDao.getUrlPath())) {
				ABVEnvironment abvEnvironment = ABVEnvironment.getInstance();
				AcmsDto acmsDto = new AcmsDto(urlPath, abvEnvironment.acmsAddress, abvEnvironment.downloadServerAddress, abvEnvironment.websocketServerHttpUrl, abvEnvironment.websocketServerWsUrl);
				acmsDao.insert(acmsDto);
			}
			
			cache.setUrlPath(urlPath);
		}
	}
	
	public static boolean getBooleanValue(String value) {
        return True.equals(value);
	}
}
