package io.onedev.server.web.editable;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import org.apache.wicket.Component;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.hibernate.proxy.HibernateProxyHelper;

import com.google.common.collect.Lists;

import io.onedev.commons.launcher.loader.AppLoader;
import io.onedev.server.util.ComponentContext;

@SuppressWarnings("serial")
public abstract class PropertyContext<T> implements Serializable {

	private final PropertyDescriptor descriptor;
	
	public PropertyContext(Class<?> beanClass, String propertyName) {
		descriptor = new PropertyDescriptor(beanClass, propertyName);
	}
	
	public PropertyContext(PropertyDescriptor descriptor) {
		this.descriptor = descriptor;
	}
	
	public PropertyDescriptor getDescriptor() {
		return descriptor;
	}
	
	public abstract PropertyViewer renderForView(String componentId, IModel<T> model);

	public abstract PropertyEditor<T> renderForEdit(String componentId, IModel<T> model);
	
	public List<String> getPossibleValues() {
		return Lists.newArrayList();
	}

	public static PropertyEditor<Serializable> editModel(String componentId, IModel<Serializable> beanModel, String propertyName) {
		
		PropertyContext<Serializable> editContext = of(HibernateProxyHelper.getClassWithoutInitializingProxy(beanModel.getObject()), propertyName);
		return editContext.renderForEdit(componentId, new IModel<Serializable>() {

			@Override
			public void detach() {
				beanModel.detach();
			}

			@Override
			public Serializable getObject() {
				return (Serializable) editContext.getDescriptor().getPropertyValue(beanModel.getObject());
			}

			@Override
			public void setObject(Serializable object) {
				editContext.getDescriptor().setPropertyValue(beanModel.getObject(), object);
			}
			
		});
	}

	public static PropertyEditor<Serializable> edit(String componentId, Serializable bean, String propertyName) {
		
		IModel<Serializable> beanModel = new IModel<Serializable>() {

			@Override
			public void detach() {
			}

			@Override
			public Serializable getObject() {
				return bean;
			}

			@Override
			public void setObject(Serializable object) {
				throw new IllegalStateException();
			}
			
		};
		return editModel(componentId, beanModel, propertyName);
	}

	public static Component viewModel(String componentId, IModel<Serializable> beanModel, String propertyName) {
		PropertyContext<Serializable> editContext = of(HibernateProxyHelper.getClassWithoutInitializingProxy(beanModel.getObject()), propertyName);
		return editContext.renderForView(componentId, new LoadableDetachableModel<Serializable>() {

			@Override
			protected Serializable load() {
				return (Serializable) editContext.getDescriptor().getPropertyValue(beanModel.getObject());
			}
			
		});
	}

	public static Component view(String componentId, Serializable bean, String propertyName) {
		return viewModel(componentId, Model.of(bean), propertyName);
	}

	public static PropertyContext<Serializable> of(Class<?> beanClass, String propertyName) {
		return of(new PropertyDescriptor(beanClass, propertyName));
	}
	
	public static PropertyContext<Serializable> of(PropertyDescriptor propertyDescriptor) {
		EditSupportRegistry registry = AppLoader.getInstance(EditSupportRegistry.class);
		return registry.getPropertyEditContext(propertyDescriptor);
	}
	
	public String getDisplayName() {
		return descriptor.getDisplayName();
	}
	
	public String getPropertyName() {
		return descriptor.getPropertyName();
	}
	
	public Method getPropertyGetter() {
		return descriptor.getPropertyGetter();
	}
	
	public Class<?> getPropertyClass() {
		return descriptor.getPropertyClass();
	}
	
	public Object getPropertyValue(Object bean) {
		return descriptor.getPropertyValue(bean);
	}
	
	public void setPropertyValue(Object bean, Object value) {
		descriptor.setPropertyValue(bean, value);
	}
	
	public boolean isPropertyExcluded()	{
		return descriptor.isPropertyExcluded();
	}
	
	public void setPropertyExcluded(boolean propertyExcluded) {
		descriptor.setPropertyExcluded(propertyExcluded);
	}
	
	public boolean isPropertyRequired() {
		return descriptor.isPropertyRequired();
	}

	public boolean isPropertyVisible(Map<String, ComponentContext> componentContexts, BeanDescriptor beanDescriptor) {
		return descriptor.isPropertyVisible(componentContexts, beanDescriptor);
	}
	
}