/**
 *
 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/
package lucee.runtime.type.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import lucee.commons.collection.LinkedHashMapPro;
import lucee.commons.collection.MapPro;
import lucee.commons.collection.MapProWrapper;
import lucee.commons.collection.SyncMap;
import lucee.commons.collection.WeakHashMapPro;
import lucee.commons.digest.HashUtil;
import lucee.commons.lang.SizeOf;
import lucee.commons.lang.StringUtil;
import lucee.runtime.PageContext;
import lucee.runtime.dump.DumpProperties;
import lucee.runtime.dump.DumpTable;
import lucee.runtime.dump.DumpUtil;
import lucee.runtime.dump.SimpleDumpData;
import lucee.runtime.exp.PageException;
import lucee.runtime.op.Caster;
import lucee.runtime.op.Duplicator;
import lucee.runtime.type.Collection;
import lucee.runtime.type.Collection.Key;
import lucee.runtime.type.KeyImpl;
import lucee.runtime.type.Struct;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.comparator.TextComparator;

/**
 * 
 */
public final class StructUtil {

    /**
     * copy data from source struct to target struct
     * @param source 
     * @param target 
     * @param overwrite overwrite data if exist in target
     */
    public static void copy(Struct source, Struct target, boolean overwrite) {
    	Iterator<Entry<Key, Object>> it = source.entryIterator();
    	Entry<Key, Object> e;
        while(it.hasNext()) {
        	e = it.next();
            if(overwrite || !target.containsKey(e.getKey())) 
                target.setEL(e.getKey(),e.getValue());
        }
    }

    public static lucee.runtime.type.Collection.Key[] toCollectionKeys(String[] skeys) {
    	lucee.runtime.type.Collection.Key[] keys = new lucee.runtime.type.Collection.Key[skeys.length];
		for(int i=0;i<keys.length;i++) {
			keys[i]=KeyImpl.init(skeys[i]);
		}
	    return keys;
    }
    
	/**
	 * @param sct
	 * @return
	 */
	public static Struct duplicate(Struct sct,boolean deepCopy) {

		Struct rtn=new StructImpl();
		//lucee.runtime.type.Collection.Key[] keys=sct.keys();
		//lucee.runtime.type.Collection.Key key;
		Iterator<Entry<Key, Object>> it = sct.entryIterator();
    	Entry<Key, Object> e;
        while(it.hasNext()) {
        	e=it.next();
			rtn.setEL(e.getKey(),Duplicator.duplicate(e.getValue(),deepCopy));
		}
		return rtn;
	}

	public static void putAll(Struct struct, Map map) {
		Iterator<Entry> it = map.entrySet().iterator();
		Map.Entry entry;
		while(it.hasNext()) {
			entry= it.next();
			struct.setEL(KeyImpl.toKey(entry.getKey(),null), entry.getValue());
		}
	}

	public static Set<Entry<String, Object>> entrySet(Struct sct) {
		Iterator<Entry<Key, Object>> it = sct.entryIterator();
		Entry<Key, Object> e;
		HashSet<Entry<String, Object>> set=new HashSet<Entry<String, Object>>();
		while(it.hasNext()){
			e= it.next();
			set.add(new StructMapEntry(sct,e.getKey(),e.getValue()));
		}
		return set;
	}
	
	public static Set<String> keySet(Struct sct, boolean linked) {
		Iterator<Key> it = sct.keyIterator();
		Set<String> set=linked?new LinkedHashSet<String>():new HashSet<String>();
		while(it.hasNext()){
			set.add(it.next().getString());
		}
		return set;
	}

	
	public static DumpTable toDumpTable(Struct sct,String title,PageContext pageContext, int maxlevel, DumpProperties dp) {
		Key[] keys = order(sct,CollectionUtil.keys(sct));
		DumpTable table = new DumpTable("struct","#9999ff","#ccccff","#000000");// "#9999ff","#ccccff","#000000"

		int maxkeys=dp.getMaxKeys();
		if(maxkeys < sct.size()) {
			table.setComment("Entries: "+sct.size() + " (showing top " + maxkeys + ")");
		}
		else if(sct.size()>10 && dp.getMetainfo()) {
			table.setComment("Entries: "+sct.size());
		}
		
		// advanced
		/*Map<Key, FunctionLibFunction> members = MemberUtil.getMembers(pageContext, CFTypes.TYPE_STRUCT);
		if(members!=null) {
			StringBuilder sb=new StringBuilder("This Struct is supporting the following Object functions:");
			Iterator<Entry<Key, FunctionLibFunction>> it = members.entrySet().iterator();
			Entry<Key, FunctionLibFunction> e;
			while(it.hasNext()){
				e = it.next();
				sb.append("\n	.")
				.append(e.getKey())
				.append('(');
				
				
				ArrayList<FunctionLibFunctionArg> args = e.getValue().getArg();
				int optionals = 0;
				for(int i=1;i<args.size();i++) {
					FunctionLibFunctionArg arg=args.get(i);
					if(i!=0)sb.append(", ");
					if(!arg.getRequired()) {
						sb.append("[");
						optionals++;
					}
					sb.append(arg.getName());
					sb.append(":");
					sb.append(arg.getTypeAsString());
				}
				for(int i=0;i<optionals;i++)
					sb.append("]");
				sb.append("):"+e.getValue().getReturnTypeAsString());
				
				
			}
			table.setComment(sb.toString());
		}*/
		
		
		
		if(!StringUtil.isEmpty(title))table.setTitle(title);
		maxlevel--;
		int index=0;
		for(int i=0;i<keys.length;i++) {
			if(DumpUtil.keyValid(dp,maxlevel,keys[i])){
				if(maxkeys<=index++)break;
				table.appendRow(1,
						new SimpleDumpData(keys[i].toString()),
						DumpUtil.toDumpData(sct.get(keys[i],null), 
						pageContext,maxlevel,dp));
			}
		}
		return table;
	}
	
	

	private static Key[] order(Struct sct, Key[] keys) {
		if(sct instanceof StructImpl && ((StructImpl)sct).getType()==Struct.TYPE_LINKED) return keys;
		
		TextComparator comp=new TextComparator(true,true);
		Arrays.sort(keys,comp);
		return keys;
	}

	/**
	 * create a value return value out of a struct
	 * @param sct
	 * @return
	 */
	public static java.util.Collection<?> values(Struct sct) {
		ArrayList<Object> arr = new ArrayList<Object>();
		//Key[] keys = sct.keys();
		Iterator<Object> it = sct.valueIterator();
		while(it.hasNext()) {
			arr.add(it.next());
		}
		return arr;
	}

	public static Struct copyToStruct(Map map) throws PageException {
        Struct sct = new StructImpl();
        Iterator it=map.entrySet().iterator();
        Map.Entry entry;
        while(it.hasNext()) {
            entry=(Entry) it.next();
            sct.setEL(Caster.toString(entry.getKey()),entry.getValue());
        }
        return sct;
	}

	/**
	 * return the size of given struct, size of values + keys
	 * @param sct
	 * @return
	 */
	public static long sizeOf(Struct sct) {
		Iterator<Entry<Key, Object>> it = sct.entryIterator();
		Entry<Key, Object> e;
		long size = 0;
		while(it.hasNext()) {
			e = it.next();
			size+=SizeOf.size(e.getKey());
			size+=SizeOf.size(e.getValue());
		}
		return size;
	}

	public static void setELIgnoreWhenNull(Struct sct, String key, Object value) {
		setELIgnoreWhenNull(sct, KeyImpl.init(key), value);
	}
	public static void setELIgnoreWhenNull(Struct sct, Collection.Key key, Object value) {
		if(value!=null)sct.setEL(key, value);
	}

	/**
	 * remove every entry hat has this value
	 * @param map
	 * @param obj
	 */
	public static void removeValue(Map map, Object value) {
		Iterator it = map.entrySet().iterator();
		Map.Entry entry;
		while(it.hasNext()){
			entry=(Entry) it.next();
			if(entry.getValue()==value)it.remove();
		}
	}

	
    public static Struct merge(Struct[] scts) { 
		Struct sct=new StructImpl();
		
		for(int i=scts.length-1;i>=0;i--){
			Iterator<Entry<Key, Object>> it = scts[i].entryIterator();
			Entry<Key, Object> e;
			while(it.hasNext()){
				e = it.next();
				sct.setEL(e.getKey(), e.getValue());
			}
		}
		return sct;
	}
    
    public static int getType(MapPro m){
    	if(m instanceof SyncMap)
    		return ((SyncMap)m).getType();
    	
    	if(m instanceof LinkedHashMapPro) return Struct.TYPE_LINKED;
    	if(m instanceof WeakHashMapPro) return Struct.TYPE_WEAKED;
    	//if(map instanceof SyncMap) return TYPE_SYNC;
    	if(m instanceof MapProWrapper) return Struct.TYPE_SOFT;
    	return Struct.TYPE_REGULAR;
    }

    /**
     * creates a hash based on the keys of the Map/Struct
     * @param map
     * @return 
     */
	public static String keyHash(Struct sct) {
		Key[] keys;
		Arrays.sort(keys=CollectionUtil.keys(sct));
		
		StringBuilder sb=new StringBuilder();
		for(int i=0;i<keys.length;i++){
			sb.append(keys[i].getString()).append(';');
		}
		return Long.toString(HashUtil.create64BitHash(sb),Character.MAX_RADIX);
	}
}