package junkutil.set;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;

import junkutil.common.StringUtil;

/**
 * Mapを集合論的に扱う。
 * @author Hiroyuki Shiota
 */
public class LzMap {
	private java.util.Map base;

	/**
	 * コンストラクタ
	 */
	public LzMap() {
		base = new HashMap();
	}
	/**
	 * コンストラクタ
	 * @param list
	 */
	public LzMap(Map map) {
		base = map;
	}
	/**
	 * コンストラクタ
	 * @param list
	 */
	public LzMap(Object[] keyValues) {
		if (keyValues.length % 2 != 0) throw new IllegalArgumentException();
		Map map = new HashMap();
		for (int i = 0; i < keyValues.length; i+=2) {
			map.put(keyValues[i], keyValues[i+1]);
		}
		base = map;
	}
	/**
	 * コンストラクタ
	 * @param list
	 */
	public LzMap(List keyValues) {
		if (keyValues.size() % 2 != 0) throw new IllegalArgumentException();
		Map map = new HashMap();
		for (int i = 0; i < keyValues.size(); i+=2) {
			map.put(keyValues.get(i), keyValues.get(i+1));
		}
		base = map;
	}
	/**
	 * コンストラクタ
	 * @param list
	 */
	public LzMap(Object[] keys, Object[] values) {
		if (keys.length != values.length) throw new IllegalArgumentException();
		Map map = new HashMap();
		for (int i = 0; i < keys.length; i++) {
			map.put(keys[i], values[i]);
		}
		base = map;
	}
	/**
	 * コンストラクタ
	 * @param list
	 */
	public LzMap(List keys, List values) {
		if (keys.size() != values.size()) throw new IllegalArgumentException();
		Map map = new HashMap();
		for (int i = 0; i < keys.size(); i++) {
			map.put(keys.get(i), values.get(i));
		}
		base = map;
	}
	/**
	 * コンストラクタ
	 * @param list
	 */
	public LzMap(Properties prop) {
		Map map = new HashMap();
		for (Iterator i = prop.keySet().iterator(); i.hasNext();) {
			String key = (String)i.next();
			map.put(key, prop.getProperty(key));
		}
		base = map;
	}
	
	/**
	 * キーのリストを返す。
	 * @return
	 */
	public List keys() {
		return map2keys(base);
	}
	
	/**
	 * 順序付けられたキーのリストを返す。
	 * @return
	 */
	public List sortedKeys() {
		return new LzList(map2keys(base)).sortAsc().list();
	}
	
	/**
	 * 順序付けられたキーのリストを返す。
	 * @param comp
	 * @return
	 */
	public List sortedKeys(Comparator comp) {
		return new LzList(map2keys(base)).sort(comp).list();
	}
	
	/**
	 * マップのキーのリストを返す
	 * @param arg
	 * @return
	 */
	public static List map2keys(Map arg) {
		return Arrays.asList(arg.keySet().toArray());
	}

	/**
	 * 値のリストを返す。
	 * @return
	 */
	public List values() {
		return map2values(base);
	}

	/**
	 * マップの値のリストを返す。
	 * @param arg
	 * @return
	 */
	public static List map2values(Map arg) {
		return Arrays.asList(arg.values().toArray());
	}

	/**
	 * マップを取得する。
	 * @return
	 */
	public Map map() {
		return base;
	}
	
	/**
	 * orを取得する。上書きしない。
	 * {@link LzMap#orLeft}
	 * @param arg
	 * @return
	 */
	public LzMap or(Map arg) {
		return orLeft(arg);
	}
	
	/**
	 * orを取得する。上書きしない。
	 * @param arg
	 * @return
	 */
	public LzMap orLeft(Map arg) {
		Map map = new HashMap(base);
		for (Iterator i = arg.keySet().iterator(); i.hasNext();) {
			Object key = i.next();
			if (!map.containsKey(key)) map.put(key, arg.get(key)); 
		}
		return new LzMap(map);
	}
	
	/**
	 * マージする。
	 * {@link LzMap#orRight}
	 * @param arg
	 * @return
	 */
	public LzMap marge(Map arg) {
		return orRight(arg);
	}
	
	/**
	 * orを取得する。引数の情報で上書きする。
	 * @param arg
	 * @return
	 */
	public LzMap orRight(Map arg) {
		Map map = new HashMap(base);
		for (Iterator i = arg.keySet().iterator(); i.hasNext();) {
			Object key = i.next();
			map.put(key, arg.get(key)); 
		}
		return new LzMap(map);
	}
		
	/**
	 * andを取得する。nullでない限り、左を優先する。
	 * @param arg
	 * @return
	 */
	public LzMap andLeft(Map arg) {
		Map map = new HashMap();
		List mapKeys = map2keys(base);
		List argKeys = map2keys(arg);
		List andKeys = new LzList(mapKeys).and(argKeys).list(); //共通のキー
		for (Iterator i = andKeys.iterator(); i.hasNext();) {
			Object key = i.next();
			Object obj1 = base.get(key);
			Object obj2 = arg.get(key);
			if (obj1 != null) {
				map.put(key, obj1);
			} else {
				map.put(key, obj2);
			}
		}
		return new LzMap(map);
	}
		
	/**
	 * andを取得する。nullでない限り、右を優先する。
	 * @param arg
	 * @return
	 */
	public LzMap andRight(Map arg) {
		Map map = new HashMap();
		List mapKeys = map2keys(base);
		List argKeys = map2keys(arg);
		List andKeys = new LzList(mapKeys).and(argKeys).list(); //共通のキー
		for (Iterator i = andKeys.iterator(); i.hasNext();) {
			Object key = i.next();
			Object obj1 = base.get(key);
			Object obj2 = arg.get(key);
			if (obj2 != null) {
				map.put(key, obj2);
			} else {
				map.put(key, obj1);
			}
		}
		return new LzMap(map);
	}
		
	/**
	 * セットする。元のキーに合致するものだけ上書きする。
	 * @param arg
	 * @return
	 */
	public LzMap set(Map arg) {
		Map map = new HashMap(base);
		for (Iterator i = map.keySet().iterator(); i.hasNext();) {
			Object key = i.next();
			if (arg.containsKey(key)) {
				map.put(key, arg.get(key));
			}
		}
		return new LzMap(map);
	}
	
	/**
	 * null以外を取得する。
	 * @return
	 */
	public LzMap notNull() {
		Map map = new HashMap();
		for (Iterator i = base.keySet().iterator(); i.hasNext();) {
			Object key = i.next();
			Object obj = base.get(key);
			if (obj != null) map.put(key, obj);
		}
		return new LzMap(map);
	}
	
	/**
	 * キーと値が等しいことを確認する。
	 * @param arg
	 * @return
	 */
	public boolean equals(Map arg) {
		if (base == arg) return true;
		if (base.keySet().size() != arg.keySet().size()) return false;
		if (!new LzList(map2keys(base)).equalsOnSort(map2keys(arg))) return false; //キーが等しい
		for (Iterator i = base.keySet().iterator(); i.hasNext();) {
			Object key = i.next();
			Object obj1 = base.get(key);
			Object obj2 = arg.get(key);
			if (obj1 == null && obj2 == null) {
			} else {
				if (obj1 == null || obj2 == null) return false;
				if (!obj1.equals(obj2)) return false;
			}
		}
		return true;
	}
	
	/**
	 * サブセットを取得する。
	 * @param keys
	 * @return
	 */
	public LzMap subset(Object [] keys) {
		return subset(Arrays.asList(keys));
	}
	
	/**
	 * サブセットを取得する。
	 * @param keys
	 * @return
	 */
	public LzMap subset(List keys) {
		HashMap map = new HashMap();
		for (int i = 0; i < keys.size(); i++) {
			Object key = keys.get(i);
			if (key == null) continue;
			map.put(key, base.get(key));
		}
		return new LzMap(map);
	}
	
	/**
	 * キーをgerpする。
	 * @param arg
	 * @retune
	 */
	public LzMap keyGrep(String re) {
		HashMap map = new HashMap();
		Pattern pattern = Pattern.compile(re);
		List keys = keys();
		for (Iterator i = keys.iterator(); i.hasNext();) {
			Object key = i.next();
			if (StringUtil.match(key.toString(), pattern)) {
				map.put(key, base.get(key));
			}
		}
		return new LzMap(map);
	}
	
	/**
	 * キーをungerpする。
	 * @param arg
	 * @retune
	 */
	public LzMap keyUngrep(String re) {
		HashMap map = new HashMap();
		Pattern pattern = Pattern.compile(re);
		List keys = keys();
		for (Iterator i = keys.iterator(); i.hasNext();) {
			Object key = i.next();
			if (!StringUtil.match(key.toString(), pattern)) {
				map.put(key, base.get(key));
			}
		}
		return new LzMap(map);
	}
	
	/**
	 * クロージャを順に呼び出す。
	 * @param keys
	 * @param closure
	 * @return
	 */
	public LzMap forEach(LzMapClosure closure) {
		return forEach(keys(), closure);
	}

	/**
	 * クロージャを順に呼び出す。
	 * @param keys
	 * @param closure
	 * @return
	 */
	public LzMap forEach(List keys, LzMapClosure closure) {
		Map map = new HashMap();
		for (Iterator i = keys.iterator(); i.hasNext(); ) {
			Object key = i.next();
			Object value = base.get(key);
			if (closure.process(key, value)) {
				map.put(key, value);
			}
		}
		return new LzMap(map);
	}

	/**
	 * キーと値が等しいことを確認する。
	 * @param arg
	 * @return
	 */
	public boolean equals(String arg) {
		return toString().equals(arg);
	}
	
	/**
	 * 文字列化する。この際、ソートされる。
	 * @Override
	 */
	public String toString() {
		List keys = sortedKeys();
		StringBuffer sb = new StringBuffer();
		sb.append("{");
		for (int i = 0; i < keys.size(); i++) {
			Object key = keys.get(i);
			Object value = base.get(key);
			if (i != 0) sb.append(" ");
			sb.append(key.toString());
			sb.append("=");
			if (value != null) {
				sb.append(value.toString());
			} else {
				sb.append("null");
			}
			if (i != keys.size() - 1) {
				sb.append(",");
			}
		}
		sb.append("}");
		return new String(sb);
	}
}
