package charactermanaj.model;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import charactermanaj.graphics.filters.ColorConvertParameter;

/**
 * パーツごとの色情報を設定・取得したり、最後に設定した色情報を記憶するなどの色情報アクセスのためのクラス.<br>
 * @author seraphy
 *
 */
public class PartsColorManager {
	
	private static final long serialVersionUID = 3246538139597289650L;
	
	private static final Logger logger = Logger.getLogger(PartsColorManager.class.getName());

	/**
	 * パーツ単位でのカラーグループを含む色情報.<br>
	 * カテゴリ全体に適用される場合はパーツ単位の色情報はリセットする.<br>
	 */
	private HashMap<PartsIdentifier, PartsColorInfo> partsColorInfoMap = new HashMap<PartsIdentifier, PartsColorInfo>();

	/**
	 * カテゴリごとに共通となる場合のカラーグループを含む色情報.<br>
	 * パーツ単位の色情報が定義されていない場合、カテゴリ単位での情報が使用される.<br>
	 */
	private HashMap<PartsCategory, PartsColorInfo> categoryColorInfoMap = new HashMap<PartsCategory, PartsColorInfo>();
	
	/**
	 * カラーグループごとの色情報.<br>
	 */
	private HashMap<ColorGroup, ColorConvertParameter> recentColorGroupMap = new HashMap<ColorGroup, ColorConvertParameter>();
	
	private PartsSpecResolver partsSpecResolver;
	
	public PartsColorManager(PartsSpecResolver partsSpecResolver) {
		if (partsSpecResolver == null) {
			throw new IllegalArgumentException();
		}
		this.partsSpecResolver = partsSpecResolver;
	}
	
	/**
	 * パーツ識別子ごとの色情報を取得します.<br>
	 * まだ一度も登録されていない場合は、現在の状態から色情報を作成して返します.<br>
	 * その場合、registered引数がtrueである場合は生成と同時に初期値として登録済みとする.<br>
	 * そうでない場合は生成された色情報は一時的なものとなる.
	 * @param partsIdentifier パーツ識別子
	 * @param registered 色情報を新規に作成した場合に登録する場合はtrue
	 * @return 色情報
	 */
	public PartsColorInfo getPartsColorInfo(PartsIdentifier partsIdentifier, boolean registered) {
		if (partsIdentifier == null) {
			throw new IllegalArgumentException();
		}
		// パーツ識別子ごとのカラー情報を取得する.
		PartsColorInfo partsColorInfo = partsColorInfoMap.get(partsIdentifier);
		if (partsColorInfo == null) {
			// パーツ識別子ごとのカラー情報が設定されていない場合は
			// カテゴリ別情報からカラー情報を生成する.
			partsColorInfo = createDefaultColorInfo(partsIdentifier);
			if (registered) {
				// 生成されたカラー情報をパーツ識別子ごとのカラー情報に適用する.(FIXME: たぶんダメな考え方、連動指定を考慮していない)
				partsColorInfoMap.put(partsIdentifier, partsColorInfo);
			}
		}
		return partsColorInfo;
	}
	
	/**
	 * パーツ識別子ごとのパーツ色情報を保存します.<br>
	 * @param partsIdentifier パーツ識別子
	 * @param partsColorInfo パーツの色情報
	 * @param applyAll パーツ識別子ではなく、カテゴリに対して保存する場合
	 */
	public void setPartsColorInfo(PartsIdentifier partsIdentifier, PartsColorInfo partsColorInfo, boolean applyAll) {
		if (partsIdentifier == null || partsColorInfo == null) {
			throw new IllegalArgumentException();
		}

		partsColorInfo = partsColorInfo.clone();
		PartsCategory partsCategory = partsIdentifier.getPartsCategory();
		
		if (applyAll) {
			// カテゴリ指定
			resetPartsColorInfo(partsCategory); // パーツ個別色をリセットすることでカテゴリを優先させる.
			
			if (logger.isLoggable(Level.FINEST)) {
				logger.log(Level.FINEST, "setPartsColorInfo(Category): " + partsIdentifier + "=" + partsColorInfo);
			}

		} else {
			// パーツ個別指定
			partsColorInfoMap.put(partsIdentifier, partsColorInfo);

			if (logger.isLoggable(Level.FINEST)) {
				logger.log(Level.FINEST, "setPartsColorInfo(Parts): " + partsIdentifier + "=" + partsColorInfo);
			}
		}

		setRecentColorGroup(partsColorInfo);
		categoryColorInfoMap.put(partsCategory, partsColorInfo);
	}

	/**
	 * パーツの色情報を指定して、パーツ識別子の各レイーヤの色グループ情報を保存します.<br>
	 * @param partsColorInfo パーツ識別子
	 */
	protected void setRecentColorGroup(PartsColorInfo partsColorInfo) {
		if (partsColorInfo == null) {
			return;
		}
		for (Map.Entry<Layer, ColorInfo> entry : partsColorInfo.entrySet()) {
			ColorInfo colorInfo = entry.getValue();
			ColorGroup colorGroup = colorInfo.getColorGroup();
			if (colorInfo.isSyncColorGroup() && colorGroup != null && colorGroup.isEnabled()) {
				ColorConvertParameter colorParam = colorInfo.getColorParameter();
				if (colorParam != null) {
					colorParam = colorParam.clone();
				}
				
				ColorConvertParameter oldColorParam = recentColorGroupMap.put(colorGroup, colorParam);

				if (logger.isLoggable(Level.FINEST)) {
					if ( !ColorConvertParameter.equals(colorParam, oldColorParam)) {
						logger.log(Level.FINEST, "setRecentColorGroup(" + colorGroup + ")=" + colorParam);
					}
				}
			}
		}
	}

	/**
	 * カラーグループごとのもっとも最近に設定した色情報を取得します.<br>
	 * カラーグループが有効でないか、まだ一度も登録されていない場合はnullが返されます.<br>
	 * @param colorGroup 色グループ、null可
	 * @return 色情報、もしくはnull
	 */
	protected ColorConvertParameter getRecentColorGroup(ColorGroup colorGroup) {
		if (colorGroup == null || !colorGroup.isEnabled()) {
			return null;
		}
		ColorConvertParameter colorParam = recentColorGroupMap.get(colorGroup);
		if (colorParam != null) {
			colorParam = colorParam.clone();
		}
		return colorParam;
	}

	/**
	 * 指定したパーツカテゴリの色情報を取得します.<br>
	 * 登録がない場合はnullが返されます.<br>
	 * @param partsCategory パーツカテゴリ
	 * @return 指定したパーツカテゴリの色情報、またはnull
	 */
	public PartsColorInfo getPartsColorInfo(PartsCategory partsCategory) {
		return categoryColorInfoMap.get(partsCategory);
	}

	/**
	 * すべてのパーツ識別子の色情報をリセットします.<br>
	 */
	public void resetPartsColorInfo() {
		resetPartsColorInfo(null);
	}
	
	/**
	 * 指定したカテゴリに属するパーツ識別子ごとの色情報をリセットします.<br>
	 * 引数partsCategoryがnullの場合は全パーツ識別子をリセットします.<br>
	 * @param partsCategory パーツカテゴリまたはnull
	 */
	public void resetPartsColorInfo(PartsCategory partsCategory) {
		if (partsCategory == null) {
			partsColorInfoMap.clear();
			return;
		}

		Iterator<Map.Entry<PartsIdentifier, PartsColorInfo>> ite = partsColorInfoMap.entrySet().iterator();
		while (ite.hasNext()) {
			Map.Entry<PartsIdentifier, PartsColorInfo> entry = ite.next();
			PartsIdentifier partsIdentifier = entry.getKey();
			if (partsIdentifier.getPartsCategory().equals(partsCategory)) {
				ite.remove();
			}
		}
	}
	
	/**
	 * パーツ識別子ごとの色情報を現在の状態から新たに構築する.
	 * @param partsIdentifier パーツ識別子
	 * @return 色情報
	 */
	protected PartsColorInfo createDefaultColorInfo(PartsIdentifier partsIdentifier) {
		PartsCategory category = partsIdentifier.getPartsCategory();
		PartsColorInfo partsColorInfo = new PartsColorInfo(category);

		// 同一カテゴリの最近設定されたカラー情報を適用する.
		PartsColorInfo categoryPartsColorInfo = categoryColorInfoMap.get(category);
		if (categoryPartsColorInfo != null) {
			for (Map.Entry<Layer, ColorInfo> entry : categoryPartsColorInfo.entrySet()) {
				Layer layer = entry.getKey();
				ColorInfo colorInfo = entry.getValue();
				if (colorInfo != null && partsColorInfo.containsKey(layer)) {
					partsColorInfo.put(layer, colorInfo.clone());
				}
			}
		}
		
		// パーツ固有の設定があり、パーツ固定のカラーグループの指定があれば
		// 全レイヤーを該当カラーグループに設定する.
		PartsSpec partsSpec = partsSpecResolver.getPartsSpec(partsIdentifier);
		if (partsSpec != null) {
			ColorGroup colorGroup = partsSpec.getColorGroup();
			if (colorGroup != null && colorGroup.isEnabled()) {
				for (Map.Entry<Layer, ColorInfo> entry : partsColorInfo.entrySet()) {
					ColorInfo colorInfo = entry.getValue();
					if (colorInfo != null) {
						colorInfo = colorInfo.clone();
					} else {
						colorInfo = new ColorInfo();
					}
					colorInfo.setColorGroup(colorGroup);
					colorInfo.setSyncColorGroup(true);
					entry.setValue(colorInfo);
				}
			}
		}

		// カラーグループが指定されている場合、もっとも最近に設定されたカラーグループの色情報に設定し直す
		for (Map.Entry<Layer, ColorInfo> entry : partsColorInfo.entrySet()) {
			ColorInfo colorInfo = entry.getValue();
			ColorGroup colorGroup = colorInfo.getColorGroup();
			if (colorGroup != null && colorGroup.isEnabled() && colorInfo.isSyncColorGroup()) {
				ColorConvertParameter param = getRecentColorGroup(colorGroup);
				if (param != null) {
					colorInfo.setColorParameter(param);
				}
			}
		}
		return partsColorInfo;
	}
	
	public Map<PartsIdentifier, PartsColorInfo> getPartsColorInfoMap() {
		return partsColorInfoMap;
	}
	
}
