package org.minimalj.util;

import java.lang.reflect.Field;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.minimalj.backend.Backend;
import org.minimalj.model.Code;
import org.minimalj.model.Keys;
import org.minimalj.model.Rendering;
import org.minimalj.repository.query.By;

public class Codes {

	private static HashMap<Class<?>, CodeCacheItem<?>> cache = new HashMap<>();
	
	public static <T extends Code> T findCode(Class<T> clazz, String codeId) {
		return findCode(clazz, (Object) codeId);
	}

	public static <T extends Code> T findCode(Class<T> clazz, Integer codeId) {
		return findCode(clazz, (Object) codeId);
	}

	public static <T extends Code> T findCode(Class<T> clazz, Object codeId) {
		List<T> codes = get(clazz);
		return findCode(codes, codeId);
	}
	
	public static <T extends Code> T findCode(List<T> codes, Object codeId) {
		for (T code : codes) {
			Object aCodeId = IdUtils.getId(code);
			if (aCodeId == null && codeId == null || aCodeId != null && aCodeId.equals(codeId)) {
				return code;
			}
		}
		return null;
	}

	public static boolean isCode(Class<?> clazz) {
		return Code.class.isAssignableFrom(clazz);
	}

	@SuppressWarnings("unchecked")
	public synchronized static <T extends Code> List<T> get(Class<T> clazz) {
		CodeCacheItem<T> cacheItem = (CodeCacheItem<T>) cache.get(clazz);
		if (cacheItem == null || !cacheItem.isValid()) {
			updateCode(clazz);
		}
		cacheItem = (CodeCacheItem<T>) cache.get(clazz);
		return cacheItem.getCodes(LocaleContext.getCurrent());
	}
	
	public static <T extends Code> void removeFromCache(Class<?> clazz) {
		cache.remove(clazz);
	}

	private static class CodeComparator implements Comparator<Code> {
		private final Collator collator;
		
		public CodeComparator(Locale locale) {
			this.collator = Collator.getInstance(LocaleContext.getCurrent());
		}
		
		@Override
		public int compare(Code o1, Code o2) {
			String string1 = Rendering.toString(o1);
			String string2 = Rendering.toString(o2);
			return collator.compare(string1, string2);
		}
	}
	
	private static <T extends Code> void updateCode(Class<T> clazz) {
		CodeCacheItem<T> codeItem = new CodeCacheItem<>();
		cache.put(clazz, codeItem);
		List<T> codes = Backend.find(clazz, By.all());
		codeItem.setCodes(codes);
	}
	
	public static class CodeCacheItem<S extends Code> {
		private final long timestamp;
		private List<S> codes;
		private Map<Locale, List<S>> codesSortedByLocale;
		
		public CodeCacheItem() {
			this.timestamp = System.currentTimeMillis();
		}

		public boolean isValid() {
			return (System.currentTimeMillis() - timestamp) / 1000 < 10;
		}
		
		public List<S> getCodes() {
			return codes;
		}

		public List<S> getCodes(Locale locale) {
			if (codes != null) {
				if (!codesSortedByLocale.containsKey(locale)) {
					List<S> sortedCodes = new ArrayList<>(codes);
					Collections.sort(sortedCodes, new CodeComparator(locale));
					codesSortedByLocale.put(locale, sortedCodes);
				}
				return codesSortedByLocale.get(locale);
			} else {
				return null;
			}
		}

		public boolean isLoading() {
			return codes == null;
		}

		public void setCodes(List<S> codes) {
			this.codes = codes;
			codesSortedByLocale = new HashMap<>();
		}
	}

	public static <T extends Code> List<T> getConstants(Class<T> clazz) {
		List<T> constants = new ArrayList<>();
		for (Field field : clazz.getDeclaredFields()) {
			if (!FieldUtils.isStatic(field)) continue;
			if (!FieldUtils.isFinal(field)) continue;
			if (field.getType() != clazz) continue;
			T constant = FieldUtils.getStaticValue(field);
			if (Keys.isKeyObject(constant)) continue;
			constants.add(constant);
		}
		return constants;
	}
	
}

@@ -301,16 +301,72 @@
 ackend;%0A
+import org.minimalj.backend.repository.ReadTransaction;%0A
 import o
@@ -498,16 +498,108 @@
 uery.By;
+%0Aimport org.minimalj.transaction.Isolation;%0Aimport org.minimalj.transaction.Isolation.Level;
 %0A%0Apublic
@@ -2518,58 +2518,413 @@
 end.
-find(clazz, By.all());%0A%09%09codeItem.setCodes(codes);
+execute(new ReadCodesTransaction%3CT%3E(clazz));%0A%09%09codeItem.setCodes(codes);%0A%09%7D%0A%0A%09@Isolation(Level.NONE)%0A%09public static class ReadCodesTransaction%3CT%3E extends ReadTransaction%3CT, List%3CT%3E%3E %7B%0A%09%09private static final long serialVersionUID = 1L;%0A%0A%09%09public ReadCodesTransaction(Class%3CT%3E clazz) %7B%0A%09%09%09super(clazz);%0A%09%09%7D%0A%0A%09%09@Override%0A%09%09public List%3CT%3E execute() %7B%0A%09%09%09return repository().find(getEntityClazz(), By.ALL);%0A%09%09%7D
 %0A%09%7D%0A

Read Codes without a nested transaction