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

import java.util.List;

import jp.agentec.abook.abv.bl.acms.client.AcmsClient;
import jp.agentec.abook.abv.bl.acms.client.json.CategoriesJSON;
import jp.agentec.abook.abv.bl.acms.client.parameters.AcmsParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.CategoriesParameters;
import jp.agentec.abook.abv.bl.common.exception.AcmsException;
import jp.agentec.abook.abv.bl.common.exception.NetworkDisconnectedException;
import jp.agentec.abook.abv.bl.data.dao.AbstractDao;
import jp.agentec.abook.abv.bl.data.dao.CategoryDao;
import jp.agentec.abook.abv.bl.data.dao.MemberInfoDao;
import jp.agentec.abook.abv.bl.dto.CategoryDto;
import jp.agentec.adf.util.StringUtil;

public class CategoryLogic extends AbstractLogic {
	private CategoryDao categoryDao = AbstractDao.getDao(CategoryDao.class);

	/**
	 * ジャンル情報をサーバから受信し、ローカルに保存します。既存のデータは上書きされます。また、サーバにないジャンルがローカルにある場合、そのジャンルは削除されます。
	 * @throws ABVException　キャッシュにユーザ情報がありません。再度ログインする必要があります。
	 * @throws Exception その他、例外です。
	 * @since 1.0.0
	 */
	public void initializeCategories() throws Exception {
		MemberInfoDao memberInfoDao = AbstractDao.getDao(MemberInfoDao.class);
		String lastUpdateDate = memberInfoDao.getLastUpdateInfor();
		CategoriesJSON json = fetchCategoriesJson(lastUpdateDate);
		applyFetchedCategories(json, lastUpdateDate);
	}

	/** カテゴリ API の取得のみ（DB は更新しない）。refresh 時の並列フェッチ用。 */
	public CategoriesJSON fetchCategoriesJson(String lastUpdateDate) throws NetworkDisconnectedException, AcmsException {
		AcmsParameters param = new CategoriesParameters(cache.getMemberInfo().sid, lastUpdateDate);
		return AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).category(param);
	}

	/** {@link #fetchCategoriesJson(String)} の結果をローカルに反映する。 */
	public void applyFetchedCategories(CategoriesJSON json, String lastUpdateDate) throws Exception {
		if (lastUpdateDate == null) {
			initializeCategories(json);
		} else {
			initializeCategories(json, lastUpdateDate);
		}
	}

	private void initializeCategories(CategoriesJSON json, String lastUpdate) throws Exception {
		//ジャンルの更新情報がない場合取得日時保存するだけ
		if ((json.categoryList == null || (json.categoryList.isEmpty() && json.dellCategoryList == null || json.dellCategoryList.isEmpty()))) {
			updateLastUpdateDate(json.updateDate);
			return;
		}
		try {
			categoryDao.beginTransaction();

			if (json.categoryList != null && !json.categoryList.isEmpty()) {
				boolean categoryRowChanged = false;
				for (CategoryDto dto: json.categoryList) {
					//	未指定ジャンルIDをキャッシュに入れておく
					if (dto.dispOrder == -1) {
						cache.refreshDefaultCategoryId(dto.categoryId);
					}

					//lastUpdateの情報がないということはデータベースにジャンルデータがないのでジャンルが存在するかどうかのチェックは不要
					if (StringUtil.isNullOrEmpty(lastUpdate)) {
						categoryDao.insertCategory(dto.categoryId, dto.parentCategoryId, dto.dispOrder, dto.categoryName);
						categoryRowChanged = true;
					} else {
						CategoryDto categoryDto = categoryDao.getCategory(dto.categoryId);
						if (categoryDto == null) {
							categoryDao.insertCategory(dto.categoryId, dto.parentCategoryId, dto.dispOrder, dto.categoryName);
							categoryRowChanged = true;
						} else if (!isSameCategoryRow(dto, categoryDto)) {
							categoryDao.updateCategory(dto.categoryId, dto.parentCategoryId, dto.dispOrder, dto.categoryName, categoryDto.parentCategoryId);
							categoryRowChanged = true;
						}
					}
				}
				//	category_pathを新たに作り直す（各ジャンルごとではなく一括で十分）
				if (categoryRowChanged) {
					categoryDao.regenerateCategoryPath();
				}
			}

			if (json.dellCategoryList != null && !json.dellCategoryList.isEmpty()) {
				int size = json.dellCategoryList.size();
				for (int i = 0; i < size; i++) {
					CategoryDto dto = categoryDao.getCategory(json.dellCategoryList.get(i));
					if (dto != null) {
						categoryDao.deleteCategory(dto, cache.getDefaultCategoryId());
					}
				}
			}
			updateLastUpdateDate(json.updateDate);
			categoryDao.commit();
		} catch (Exception e) {
			categoryDao.rollback();
			throw e;
		}
	}

    /**
     * ジャンル情報をサーバから受信し、ローカルに保存します。既存のデータは上書きされます。また、サーバにないジャンルがローカルにある場合、そのジャンルは削除されます。
     * ※1.8.1から追加されたlastUpdateの下位互換性のため、既存のメソッドを復活
     * @throws Exception その他、例外です。
     * @since 1.0.0
     */
    private void initializeCategories(CategoriesJSON json) throws Exception {
        List<CategoryDto> serverCategories = json.categoryList;
        if (serverCategories != null && serverCategories.size() > 0) {
            try {
                categoryDao.beginTransaction();

                List<CategoryDto> localCategories = categoryDao.getAllCategories();
                //List<GroupDto> localGroups = dao.getAllGroups(GroupDto.class);

                for (CategoryDto serverDto : serverCategories) {
                    boolean exist = false;

                    //	未指定ジャンルIDをキャッシュに入れておく
                    if (serverDto.dispOrder == -1) {
                        cache.refreshDefaultCategoryId(serverDto.categoryId);
                    }

                    if (localCategories != null) {
                        for (CategoryDto localDto : localCategories) {
                            exist = serverDto.equals(localDto);

                            if (exist) {
                                //	update（サーバJSONに同一ジャンルが重複しても無駄なUPDATEを避ける）
                                if (!isSameCategoryRow(serverDto, localDto)) {
                                    categoryDao.updateCategory(serverDto.categoryId, serverDto.parentCategoryId, serverDto.dispOrder, serverDto.categoryName, localDto.parentCategoryId);
                                }

                                //	更新したグループはローカルのリストから外しておく。
                                localCategories.remove(localDto);
                                break;
                            }
                        }
                    }

                    if (!exist) {
                        //	insert
                        categoryDao.insertCategory(serverDto.categoryId, serverDto.parentCategoryId, serverDto.dispOrder, serverDto.categoryName);
                    }
                }

                //	category_pathを新たに作り直す。
                categoryDao.regenerateCategoryPath();

                if (localCategories != null) {
                    for (CategoryDto localDto : localCategories) {
                        //	delete
                        categoryDao.deleteCategory(localDto, cache.getDefaultCategoryId());
                    }
                }

                categoryDao.commit();
            } catch (Exception e) {
                categoryDao.rollback();
                throw e;
            } finally {
            }
        }

        //cache.refreshCategory(dao.getCategories(CategoryDto.class));
    }

	private void updateLastUpdateDate(String lastUpdateDate) {
		MemberInfoDao memberInfoDao = AbstractDao.getDao(MemberInfoDao.class);
		memberInfoDao.updateLastUpdateInfor(lastUpdateDate);
	}

	/** 親・表示順・名称が一致すればDBのUPDATEは不要（category_pathはregenerateで揃う） */
	private static boolean isSameCategoryRow(CategoryDto a, CategoryDto b) {
		return a.parentCategoryId == b.parentCategoryId
				&& a.dispOrder == b.dispOrder
				&& sameText(a.categoryName, b.categoryName);
	}

	private static boolean sameText(String x, String y) {
		String nx = x == null ? "" : x;
		String ny = y == null ? "" : y;
		return nx.equals(ny);
	}

}
