WebGsdbTools.java

package eu.javaexperience.gsdbrpc;

import eu.javaexperience.query.F;

import hu.ddsi.java.database.FieldData;
import hu.ddsi.java.database.GenericStorable;
import hu.ddsi.java.database.GenericStorage;
import hu.ddsi.java.database.GenericStorageMappingData;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.teavm.jso.JSObject;
import org.teavm.jso.dom.events.Event;
import org.teavm.jso.dom.events.EventListener;
import org.teavm.jso.dom.html.HTMLElement;
import org.teavm.jso.dom.html.HTMLInputElement;
import org.teavm.jso.json.JSON;

import eu.javaexperience.arrays.ArrayTools;
import eu.javaexperience.classes.ClassDescriptor;
import eu.javaexperience.classes.ClassDescriptorTools;
import eu.javaexperience.classes.ClassFieldDescriptor;
import eu.javaexperience.collection.CollectionTools;
import eu.javaexperience.collection.enumerations.EnumLike;
import eu.javaexperience.collection.enumerations.EnumManager;
import eu.javaexperience.collection.map.ConcurrentMapTools;
import eu.javaexperience.datareprez.DataArray;
import eu.javaexperience.datareprez.DataReprezTools;
import eu.javaexperience.gsdbrpc.api.ModelManager;
import eu.javaexperience.interfaces.simple.getBy.GetBy1;
import eu.javaexperience.interfaces.simple.getBy.GetBy2;
import eu.javaexperience.interfaces.simple.publish.SimplePublish1;
import eu.javaexperience.log.JavaExperienceLoggingFacility;
import eu.javaexperience.log.LogLevel;
import eu.javaexperience.log.Loggable;
import eu.javaexperience.log.Logger;
import eu.javaexperience.log.LoggingTools;
import eu.javaexperience.reflect.CastTo;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.semantic.references.MayNull;
import eu.javaexperience.teavm.datareprez.DataArrayTeaVMImpl;
import eu.javaexperience.text.Format;
import eu.javaexperience.text.StringTools;
import eu.javaexperience.webgsdb.commons.FieldExtraAttributes;
import eu.javaexperience.webgsdb.commons.FrontendFieldEditContext;
import eu.javaexperience.webgsdb.commons.FrontendFieldManager;
import eu.javaexperience.webgsdb.commons.ModelData;
import eu.javaexperience.webgsdb.frontend.modellayer.WebModel;
import eu.jvx.js.lib.ImpTools;
import eu.jvx.js.lib.bindings.H;
import eu.jvx.js.lib.bindings.VanillaTools;
import eu.jvx.js.lib.ui.component.func.HtmlDataContainer;
import eu.jvx.js.lib.ui.component.func.HtmlDataContainerTools;
import eu.jvx.js.lib.ui.component.func.HtmlListContainerUserEditable;
import eu.jvx.js.lib.ui.component.input.SelectList;
import eu.jvx.js.lib.ui.component.input.SelectOption;
import eu.jvx.js.tbs.ui.TbsLayoutTools.SimpleFormRow;

public class WebGsdbTools
{
	public static final Logger LOG = JavaExperienceLoggingFacility.getLogger(new Loggable("WebGsdbTools"));
	
	public static <E extends EnumLike<E>> HTMLElement generateEnumChoose(EnumManager<E> es, @MayNull String noneLabel, String name, String value)
	{
		LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateEnumChoose(enumManager: %s, noneLabel: %s, name: %s, value: %s)", es, noneLabel, name, value);
		
		SelectList l = new SelectList();
		if(null != noneLabel)
		{
			l.addOption(null, noneLabel);
		}
		
		for(Object s:es.getValues())
		{
			EnumLike e = (EnumLike) s;
			String n = e.getName();
			SelectOption so = l.addOption(n, n);
			LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateEnumChoose: add option (%s)", n);			
			
			if(null != value && value.equals(n))
			{
				LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateEnumChoose: selecting (%s)", n);
				so.selectThis();
			}
		}
		
		if(null != name)
		{
			l.getHtml().setAttribute("name", name);
		}
		
		VanillaTools.stringAttrs((HTMLElement) l.getHtml(), "class", "form-control");
		
		return (HTMLElement) l.getHtml();
	}
	
	public static <T extends WebDbModel> SimpleFormRow createModelFieldInputRow
	(
		String fieldLabel, 
		FrontendFieldEditContext<T> ctx,
		@MayNull FrontendFieldManager<T> mngr	
	)
	{
		if(null == mngr)
		{
			mngr = DEFAULT_FRONTEND_FIELD_MANAGER;
		}
		
		return new SimpleFormRow(ctx.idLabelFor, fieldLabel+": ", mngr.toEditable(ctx));
	}
	
	public static SimpleFormRow createModelFieldInputRow
	(
		ModelManager<WebDbModel> sma,
		String id,
		String trname,
		ClassFieldDescriptor fd,
		final Object value
	)
	{
		FrontendFieldEditContext<WebDbModel> ctx = new FrontendFieldEditContext<WebDbModel>();
		return createModelFieldInputRow(trname, ctx, DEFAULT_FRONTEND_FIELD_MANAGER);
	}
	
	public static final FrontendFieldManager DEFAULT_FRONTEND_FIELD_MANAGER = new FrontendFieldManager<WebDbModel>()
	{
		@Override
		public String toString(WebDbModel model, String field)
		{
			Object ret = model.get(field);
			if(!(ret instanceof Object))
			{
				return "";
			}
			
			if(ret instanceof Date)
			{
				return Format.SQL_TIMESTAMP.format((Date) ret);
			}
			
			if(ret.getClass().isArray())
			{
				return ArrayTools.toString((Object[])ret);
			}
			
			return String.valueOf(ret);
		}

		@Override
		public HTMLElement toEditable(FrontendFieldEditContext<WebDbModel> ctx)
		{
			return generateEditableOfType(ctx, ctx.field.getName(), ctx.field.getType(), ctx.value);
		}
	};
	
	protected static Set<String> collect(String... args)
	{
		return Collections.unmodifiableSet(CollectionTools.inlineAdd(new HashSet<>(), args));
	}
	
	public static final Set<String> CLS_BOOLEAN = collect("java.lang.Boolean", "boolean");
	
	public static final Set<String> CLS_NUMBER = collect
	(
		"java.lang.Byte", "byte",
		"java.lang.Short", "short",
		"java.lang.Integer", "int",
		"java.lang.Long", "long",
		"java.lang.Float", "float",
		"java.lang.Double", "double",
		"java.lang.Number",
		"java.math.BigDecimal", "java.math.BigInteger"
	);
	
	public static final Set<String> CLS_DATE = collect("java.util.Date", "java.sql.Date");
	
	protected static HTMLElement generateModelEditable
	(
		FrontendFieldEditContext<WebDbModel> ctx,
		String name,
		ClassDescriptor type,
		Object value
	)
	{
		LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateModelEditable type is GenericStorable");
		
		//TODO teavm: anonymus class method has been not overridden.
		final SelectList sl = new SelectList();
		
		List<WebDbModel> a = ctx.acc.select
		(
			type,
			F.gt.is("do", -1)
		);
		
		Long selected = (Long) CastTo.Long.cast(value);
		
		for(WebDbModel d:a)
		{
			String id = String.valueOf(CastTo.Long.cast(d.getModelId()));
			SelectOption so = sl.addOption(id, d.toString());
			LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateModelEditable - GenericStorable: add option: %s -> %s", id, d.toString());
			
			
			Long check = (Long) CastTo.Long.cast(id);
			if(LOG.mayLog(LogLevel.TRACE))
			{
				LoggingTools.tryLogFormat(LOG, LogLevel.TRACE, "generateModelEditable - isSelected? search: %s actual: %s", selected, check);
			}
			
			if(null != selected && selected.equals(check))
			{
				LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateModelEditable - GenericStorable: set selected: %s -> %s", selected, check);
				so.selectThis();
			}
		}
		return new H((HTMLElement)sl.getHtml()).attrs("name", name, "class", "form-control").getHtml();
	}
	
	protected static HTMLElement generateEditableOfPrimitiveType
	(
		FrontendFieldEditContext<WebDbModel> ctx,
		String name,
		ClassDescriptor type,
		Object value
	)
	{
		String fType = "text";
		if(CLS_BOOLEAN.contains(type.getClassName()))
		{
			fType = "checkbox";
		}
		else if(CLS_NUMBER.contains(type.getClassName()))
		{
			fType = "number";
		}
		
		H elem = new H("input").attrs("type", fType, "name", name, "class", "form-control");
		final HTMLInputElement e = (HTMLInputElement) elem.getHtml();
		HtmlDataContainer<String> dc = null;
		
		if(CLS_DATE.contains(type.getClassName()))
		{
			elem.attrs("type", "datetime-local");
			dc = HtmlDataContainerTools.browserDatetimeLocal((HTMLInputElement) e);
		}
		else
		{
			dc = HtmlDataContainerTools.wrapInput(e);
		}
		
		ImpTools.appendImp(e, dc);
		if(null != value && value instanceof Object)
		{
			dc.setData(value.toString());
		}
		
		return e;
	}
	
	protected static HTMLElement generateEditableOfType
	(
		FrontendFieldEditContext<WebDbModel> ctx,
		String name,
		ClassDescriptor type,
		Object value
	)
	{
		if(type.isArray())
		{
			return generateMultiOf(name, type.getComponentType(), ctx, value);
		}
		
		LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateEditableOfType(name: %s, type: %s, value: %s)", name, type.getClassName(), value);
		FieldExtraAttributes fa = FieldExtraAttributes.parse(ctx.field);
		if(null != fa)
		{
			if(null != fa.frontendFieldManager)
			{
				try
				{
					LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateEditableOfType(fa.frontendFieldManager: %s)", fa.frontendFieldManager);
					return ((FrontendFieldManager)Class.forName(fa.frontendFieldManager).newInstance()).toEditable(ctx);
				}
				catch(Exception e)
				{
					LoggingTools.tryLogFormatException(LOG, LogLevel.ERROR, e, "generateEditableOfType: ");
					Mirror.propagateAnyway(e);
				}
			}
		}
		
		boolean gs = false;
		
		List<ClassDescriptor> sups = new ArrayList<>();
		ClassDescriptorTools.collectAllSuperClass(sups, type);
		
		for(ClassDescriptor sup: sups)
		{
			if("hu.ddsi.java.database.GenericStorable".equals(sup.getClassName()))
			{
				gs = true;
				break;
			}
		}
		
		if(gs)
		{
			return generateModelEditable(ctx, name, type, value);
		}
		else if(type.isEnum())
		{
			return new H(generateEnumChoose(type.getEnumManager(), null, name, String.valueOf(value))).getHtml();
		}
		else
		{
			return generateEditableOfPrimitiveType(ctx, name, type, value);
		}		
	}
	
	public static HTMLElement generateMultiOf
	(
		String masterName,
		ClassDescriptor type,
		FrontendFieldEditContext ctx,
		Object value,
		GetBy2<HtmlDataContainer<String>, FrontendFieldEditContext, ClassDescriptor> unitCreator
	)
	{
		final HtmlListContainerUserEditable ret = new HtmlListContainerUserEditable()
		{
			@Override
			protected HtmlDataContainer<String> create()
			{
				return unitCreator.getBy(ctx, type);
			}
		};
		
		LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateMultiOf(name: %s, type: %s, value: %s)", masterName, type.getClassName(), value);
		
		final H input = new H("input").attrs("type", "hidden", "name", masterName);
		input.on("serialize", new EventListener<Event>()
		{
			@Override
			public void handleEvent(Event arg0)
			{
				((HTMLInputElement)input.getHtml()).setValue(ret.getData());
			}
		});
		
		new H((HTMLElement)ret.getHtml()).addChilds(input);
		
		if(null != value)
		{
			if(value.getClass().isArray())
			{
				DataArray arr = new DataArrayTeaVMImpl();
				Object[] os = (Object[]) value;
				for(int i=0;i<os.length;++i)
				{
					Object o = os[i];
					if(o instanceof GenericStorable)
					{
						o = GenericStorage.getID((GenericStorable) o);
					}
					
					DataReprezTools.put(arr, i, o);
				}
				
				value = JSON.stringify((JSObject)arr.getImpl());
			}
			else if(value instanceof DataArray)
			{
				value = JSON.stringify((JSObject)((DataArray)value).getImpl());
			}
			
			try
			{
				LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateMultiOf -  setting container value: %s", value);
				ret.setData(String.valueOf(value));
			}
			catch(Exception e)
			{
				LoggingTools.tryLogFormatException(LOG, LogLevel.ERROR, e, "value: %s", value);
			}
		}
		
		return (HTMLElement) ret.getHtml();
	}
	
	
	public static HTMLElement generateMultiOf
	(
		String masterName,
		ClassDescriptor type,
		FrontendFieldEditContext ctx,
		Object value
	)
	{
		return generateMultiOf(masterName, type, ctx, value, new GetBy2<HtmlDataContainer<String>, FrontendFieldEditContext, ClassDescriptor>()
		{
			@Override
			public HtmlDataContainer<String> getBy(FrontendFieldEditContext ctx, ClassDescriptor type)
			{
				boolean embedded = ctx.isEmbedded;
				ctx.isEmbedded = true;
				HTMLElement cre = generateEditableOfType(ctx, null, type, null);
				ctx.isEmbedded = embedded;
				return ImpTools.getImp(cre);
			}
		});
	}
	
	public static final String GSDB_FIELD_EXTRA_ATTRIBUTES_KEY = "GSDB_FIELD_EXTRA_ATTRIBUTES";
	
	public static FieldExtraAttributes tryGetModelFieldExtraAttributes(FieldData fd)
	{
		return (FieldExtraAttributes)fd.getExtraDataMap().get(GSDB_FIELD_EXTRA_ATTRIBUTES_KEY);
	}
	
	protected static final ConcurrentMap<Class, ModelData> MODEL_EXTRA_DATA = new ConcurrentHashMap<>();
	
	protected static final GetBy1<ModelData, Class> CREATE_MODEL_DATA_STORAGE = (cls) -> new ModelData(cls);
	
	public static ModelData getModelData(Class<? extends WebModel> cls)
	{
		return ConcurrentMapTools.getOrCreate(MODEL_EXTRA_DATA, cls, CREATE_MODEL_DATA_STORAGE);
	}

	public static void putFieldAttributes(Class<? extends WebDbModel> cls, Object... name_FieldExtraAttributes)
	{
		Map<String, FieldExtraAttributes> fieldDatas = new HashMap<>();
		for(int i=0;i<name_FieldExtraAttributes.length;i+=2)
		{
			fieldDatas.put
			(
				(String) name_FieldExtraAttributes[i],
				(FieldExtraAttributes) name_FieldExtraAttributes[i+1]
			);
		}
		
		ModelData md = new ModelData(cls);
		
		FieldData[] fds = GenericStorageMappingData.getOrCollectAllClassData(cls).allFd;
		for(FieldData fd:fds)
		{
			if(!RemoteGsdbTools.isWebgsdbUseField(fd.f))
			{
				continue;
			}
			
			FieldExtraAttributes dat = fieldDatas.get(fd.f.getName());
			if(null == dat)
			{
				System.out.println("No field metadata attached: "+cls.getName()+"."+fd.f.getName());
				continue;
			}
			dat.owner = md;
			
			if(null != dat)
			{
				fd.getExtraDataMap().put(GSDB_FIELD_EXTRA_ATTRIBUTES_KEY, dat);
			}
		}
	}
	
	public static <D extends WebDbModel> FrontendFieldManager<D> getFrontendFieldManager(D model, ClassFieldDescriptor fd, FieldExtraAttributes fa)
	{
		FrontendFieldManager<D> ffm = null;

		LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "getFrontendFieldManager(model: %s, field: %s, extra: %s)", model, fd, fa);
		if(null != fa && null != fa.frontendFieldManager)
		{
			try
			{
				LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "getFrontendFieldManager - creating new instance of custom frontendFieldManager: %s", fa.frontendFieldManager);
				ffm = (FrontendFieldManager<D>) Class.forName(fa.frontendFieldManager).newInstance();
			}
			catch(Exception e)
			{
				LoggingTools.tryLogFormatException(LOG, LogLevel.ERROR, e, "getFrontendFieldManager - Can't initialize FrontendFieldManager for: %s : %s | %s ", model.getClass(), fd.getName(), fa.frontendFieldManager);
				e.printStackTrace();
			}
		}
		
		if(null == ffm)
		{
			LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "getFrontendFieldManager - using default frontendFieldManager");
			ffm = WebGsdbTools.DEFAULT_FRONTEND_FIELD_MANAGER;
		}
		return ffm;
	}
	
	public static String translate(String trname)
	{
		if(null != trname && trname.contains("."))
		{
			trname = StringTools.getSubstringAfterLastString(trname, ".", trname);
		}
		return trname;
	}
	
	public static void generateModelEditor(SimplePublish1<SimpleFormRow> builder, ClassDescriptor cls, WebDbModel model, FrontendFieldEditContext baseCtx)
	{
		generateModelEditor
		(
			builder,
			(List<ClassFieldDescriptor> ) cls.getAllField(),
			fd->translate(fd.getTranslationSymbol()),
			model,
			baseCtx
		);
	}
	
	public static void generateModelEditor
	(
		SimplePublish1<SimpleFormRow> builder,
		List<ClassFieldDescriptor> fields,
		GetBy1<String, ClassFieldDescriptor> getLabel,
		WebDbModel model,
		FrontendFieldEditContext baseCtx
	)
	{
		String ns = StringTools.randomString(10);
		
		LoggingTools.tryLogFormat(LOG, LogLevel.DEBUG, "generateModelEditor(cls: %s)", model.getModelClass());
		
		for(ClassFieldDescriptor fd:fields)
		{
			if(!fd.isUserAccessible())
			{
				continue;
			}
			
			String fieldName = fd.getName();
			FieldExtraAttributes fa = FieldExtraAttributes.parse(fd);
			
			if(null == fa || (fa.userCanModify && fa.userMaySee))
			{
				String trname = getLabel.getBy(fd);
				Object val = null;
				
				if(null != model)
				{
					Object o = model.get(fd.getName());
					if(o instanceof WebDbModel)
					{
						val = String.valueOf(CastTo.Long.cast(((WebDbModel)o).get("do")));
					}
					else
					{
						val = model.get(fieldName);
					}
				}

				FrontendFieldEditContext<WebDbModel> ctx = baseCtx.clone();
				ctx.field = fd;
				ctx.idLabelFor = ns+fieldName;
				ctx.model = model;
				ctx.value = val;
				
				HTMLElement edit = generateEditableOfType(ctx, fieldName, fd.getType(), val);
				
				builder.publish(new SimpleFormRow(ctx.idLabelFor, trname+": ", edit));

				
/*				SimpleFormRow row = null;
				FrontendFieldManager ffm = WebGsdbTools.getFrontendFieldManager(model, fd, fa);
				if(null != ffm)
				{
					FrontendFieldEditContext ctx = baseCtx.clone();
					ctx.field = fd;
					ctx.gdb = GenericStorage.getOwnerDatabase(model);
					ctx.idLabelFor = ns+fieldName;
					ctx.model = model;
					ctx.value = val;
					row = WebGsdbTools.createModelFieldInputRow(fieldName, ctx, ffm);
				}
				else
				{
					row = WebGsdbTools.createModelFieldInputRow(null, ns+fieldName, trname, fd, val);
				}
				
				builder.publish(row);*/
			}
		}
	}
}