package com.jasamedika.medifirst2000.converter;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;

import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.jasamedika.medifirst2000.base.BaseModel;
import com.jasamedika.medifirst2000.base.vo.BaseActiveVO;
import com.jasamedika.medifirst2000.converter.base.BaseVoConverter;
import com.jasamedika.medifirst2000.util.ExtendedSpringBeanUtil;
import com.jasamedika.medifirst2000.util.JsonUtil;

@Component
public class BaseConverterImpl<T extends BaseActiveVO, T1 extends BaseModel> implements BaseVoConverter<T, T1> {

	private Class<T> classOfT;
//	private Class<T1> classOfT1;
	private Boolean useGson;
	private Boolean useExclude;

	public static <TModel extends BaseModel, TVo extends BaseActiveVO> TModel transferVOToModelData(TVo vo,
			TModel model) {
		if (null == model)
			try {
				model = (TModel) model.getClass().newInstance();
			} catch (InstantiationException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			}

		try {
			// String[] fieldsToInclude = null;
			List<String> fieldsToInclude = new ArrayList<String>();
			for (Field field : model.GetFields(model.getClass())) {
				String str = field.getName();
				fieldsToInclude.add(str);
			}
			Map<String, Object> serialized = vo.ToMap();
			Gson gson = new Gson();
			String json = gson.toJson(serialized);

			model = (TModel) gson.fromJson(json, model.getClass());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return model;

	}

	public Boolean getUseExclude() {
		return useExclude;
	}

	public void setUseExclude(Boolean useExclude) {
		this.useExclude = useExclude;
	}
	
	private static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
        @Override
        @SuppressWarnings("unchecked")
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            return (HibernateProxy.class.isAssignableFrom(type.getRawType()) ? (TypeAdapter<T>) new HibernateProxyTypeAdapter(gson) : null);
        }
	};
	
	private static final class HibernateProxyTypeAdapter extends TypeAdapter<HibernateProxy> {

		private final Gson context;

	    private HibernateProxyTypeAdapter(Gson context) {
	        this.context = context;
	    }

	    @Override
	    public HibernateProxy read(JsonReader r) throws IOException {
	        throw new UnsupportedOperationException("Not supported");
	    }

	    @SuppressWarnings({"rawtypes", "unchecked"})
	    @Override
	    public void write(JsonWriter out, HibernateProxy value) throws IOException {
	        if (value == null) {
	            out.nullValue();
	            return;
	        }
	        // Retrieve the original (not proxy) class
	        Class<?> baseType = Hibernate.getClass(value);
	        // Get the TypeAdapter of the original class, to delegate the serialization
	        TypeAdapter delegate = context.getAdapter(TypeToken.get(baseType));
	        // Get a filled instance of the original class
	        Object unproxiedValue = ((HibernateProxy) value).getHibernateLazyInitializer()
	                .getImplementation();
	        // Serialize the value
	        delegate.write(out, unproxiedValue);
	}
		
	}

	public T transferModelToVOCustom(T1 model, T vo, String[] fieldsToInclude) {
		
		try {
			
// Add TypeAdapterFactory // Syamsu
// GsonBuilder builder = new GsonBuilder();
// builder.registerTypeAdapterFactory(BaseConverterImpl.FACTORY);
// Gson gson = builder.create();
// Sampai sini
			
			Map<String, Object> serialized = model.serialize(fieldsToInclude, model.getClass().getSimpleName());			
			
			Gson gson = new Gson();
			
			String json = gson.toJson(serialized);
			vo = (T) gson.fromJson(json, vo.getClass());
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return vo;
	}

	public T1 transferVOToModel(Map<String, Object> vo, T1 model) {
		if (null == model)
			try {
				model = (T1) model.getClass().newInstance();
			} catch (InstantiationException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			}

		try {
			// String[] fieldsToInclude = null;

			Gson gson = new Gson();
			String json = gson.toJson(vo);
			// model = (T1) gson.fromJson(json, model.getClass());
			model = (T1) gson.fromJson(json, model.getClass());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return model;

	}

	public T1 transferVOToModel(T vo, T1 model) {
		if (null == model)
			try {
				model = (T1) model.getClass().newInstance();
			} catch (InstantiationException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			}

		try {
			// String[] fieldsToInclude = null;
			List<String> fieldsToInclude = new ArrayList<String>();
			for (Field field : model.GetFields(model.getClass())) {
				String str = field.getName();
				fieldsToInclude.add(str);
			}
			Map<String, Object> serialized = vo.serialize(fieldsToInclude.toArray(new String[0]),
					vo.getClass().getName());
			Gson gson = new Gson();
			String json = gson.toJson(serialized);
			// model = (T1) gson.fromJson(json, model.getClass());
			if (getUseGson() == null || getUseGson() == false)
				ExtendedSpringBeanUtil.copySpecificProperties((Object) vo, (Object) model,
						fieldsToInclude.toArray(new String[0]), fieldsToInclude.toArray(new String[0]));
			else

				model = (T1) gson.fromJson(json, model.getClass());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return model;

	}

	public List<T> transferListOfModelToListOfVO(Page<T1> models, List<T> vos, Class<T> cls) {
		if (null == vos)
			vos = new ArrayList<T>();

		if (null == models)
			return vos;

		for (T1 T1 : models.getContent()) {
			T T = null;
			try {

				T = (T) cls.newInstance();
				T = transferModelToVO(T1, T);
				vos.add(T);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}

		}

		return vos;
	}

	public List<T> transferListOfModelToListOfVO(List<T1> models, List<T> vos, Class<T> cls) {
		if (null == vos)
			vos = new ArrayList<T>();

		if (null == models)
			return vos;

		for (T1 T1 : models) {
			T T = null;
			try {

				T = (T) cls.newInstance();
				T = transferModelToVO(T1, T);
				vos.add(T);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}

		}

		return vos;
	}

	public T Clone(T model, T vo, String[] expected) throws IllegalArgumentException, IllegalAccessException {

		List<String> fieldsToInclude = new ArrayList<String>();
		for (Field field : model.GetFields(model.getClass())) {
			String str = field.getName();
			Boolean valid = false;
			for (String key : expected) {
				if (str.equals(key))
					valid = true;
			}
			if (!valid) {
				field.set(vo, field.get(model));
			}
			fieldsToInclude.add(str);
		}

		return vo;
	}

	public T1 Clone(T1 model, T1 vo, String[] expected) throws IllegalArgumentException, IllegalAccessException {

		List<String> fieldsToInclude = new ArrayList<String>();
		for (Field field : model.GetFields(model.getClass())) {
			String str = field.getName();
			Boolean valid = false;
			for (String key : expected) {
				if (str.equals(key))
					valid = true;
			}
			if (!valid) {
				try {
					field.setAccessible(true);
					Object obj = field.get(model);
					field.set(vo, obj);

				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}

			}
			fieldsToInclude.add(str);
		}

		return vo;
	}

	public T transferModelToVO(T1 model, T vo) {
		if (null == vo)
			try {
				vo = (T) classOfT.newInstance();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}

		// fix thi
		List<String> fieldsToInclude = new ArrayList<String>();
		for (Field field : model.GetFields(model.getClass())) {
			String str = field.getName();
			fieldsToInclude.add(str);
		}
		Gson gson = new Gson();
		try {
			if (getUseGson() == null || getUseGson() == false) {
				if (getUseExclude() == null || getUseExclude() == false) 
				gson = new GsonBuilder()//.excludeFieldsWithoutExposeAnnotation()
						.setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").create();
				else
					gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
					.setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").create();
				//serialized = model.serialize(fieldsToInclude.toArray(new String[0]), model.getClass().getName());
				String json = gson.toJson(ToMap(model));
				gson = new GsonBuilder()//.excludeFieldsWithoutExposeAnnotation()
						.setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").create();
				vo = (T) gson.fromJson(json, vo.getClass());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (getUseGson() != null && getUseGson() == true)
			ExtendedSpringBeanUtil.copySpecificProperties((Object) model, (Object) vo,
					fieldsToInclude.toArray(new String[0]), fieldsToInclude.toArray(new String[0]));

		return vo;
	}

	public Boolean getUseGson() {
		return useGson;
	}

	public void setUseGson(Boolean useGson) {
		this.useGson = useGson;
	}

	@Override
	public List<T> transferListOfModelToListOfVO(List<T1> models, List<T> vos) {

		if (null == vos)
			vos = new ArrayList<T>();

		if (null == models)
			return vos;

		for (T1 T1 : models) {
			T T = null;
			try {
				T = transferModelToVO(T1, T);
				vos.add(T);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return vos;
	}

	public List<Map<String, Object>> ToMaps(List<T1> model) throws IllegalArgumentException, IllegalAccessException {

		List<Map<String, Object>> maps = new ArrayList<>();
		for (Iterator iterator = model.iterator(); iterator.hasNext();) {
			T1 map = (T1) iterator.next();
			Map<String, Object> convert = ToMap(map);
			maps.add(convert);
		}
		return maps;
	}

	public Map<String, Object> ToMap(T1 model) throws IllegalArgumentException, IllegalAccessException {
		Map<String, Object> maps = new HashMap<>();
		for (Field field : model.GetFields(model.getClass())) {
			String str = field.getName();
			if (str.equals("ruanganTujuan")) {
				System.out.println();
			}
			String name = field.getName();
			field.setAccessible(true);
			if (name.equals("serialVersionUID"))
				continue;
			if (name.equals("_methods_"))
				continue;
			if (name.equals("handler"))
				continue;
			if (name.equals("_filter_signature"))
				continue;
			Boolean valid = false;
			for (java.lang.annotation.Annotation annotation : field.getDeclaredAnnotations()) {
				if (annotation instanceof JoinColumn) {
					valid = true;
				} else if (annotation instanceof Column) {
					Column column = (Column) annotation;
					if (column.name().endsWith("Fk"))
						if (field.getName().endsWith("Id") == false)
							valid = true;
				} else if (annotation instanceof OneToMany) {

					valid = true;
				}

			}
			if (valid == false)
				maps.put(field.getName(), field.get(model));

		}

		for (Field field : model.GetFields(model.getClass())) {
			String str = field.getName();
			String name = field.getName();
			field.setAccessible(true);
			if (str.equals("departemen")) {
				System.out.println();
			}
			if (name.equals("serialVersionUID"))
				continue;
			if (name.equals("_methods_"))
				continue;
			if (name.equals("handler"))
				continue;
			if (name.equals("_filter_signature"))
				continue;
			Boolean valid = false;
			for (java.lang.annotation.Annotation annotation : field.getDeclaredAnnotations()) {
				if (annotation instanceof JoinColumn) {
					valid = true;
				} else if (annotation instanceof Column) {
					Column column = (Column) annotation;
					if (column.name().endsWith("Fk"))
						if (field.getName().endsWith("Id") == false)
							valid = true;
				} else if (annotation instanceof OneToMany) {

					valid = true;
				}
			}
			if (valid == true) {
				Object property = field.get(model);
				if (property instanceof HibernateProxy) {
					property = JsonUtil.initializeAndUnproxy(property);
				}
				if (property == null)
					maps.put(str, null);
				else {
					if (property instanceof BaseModel) {
						Map<String, Object> mapItems = new HashMap<>();
						for (Field fieldItem : ((BaseModel) property).GetFields(property.getClass())) {
							String nameItem = fieldItem.getName();
							fieldItem.setAccessible(true);
							if (nameItem.equals("serialVersionUID"))
								continue;
							if (nameItem.equals("_methods_"))
								continue;
							if (nameItem.equals("handler"))
								continue;
							if (nameItem.equals("_filter_signature"))
								continue;
							Boolean validItem = false;
							for (java.lang.annotation.Annotation annotationItem : fieldItem.getDeclaredAnnotations()) {
								if (annotationItem instanceof JoinColumn) {
									validItem = true;
								} else if (annotationItem instanceof Column) {
									Column columnItem = (Column) annotationItem;
									if (columnItem.name().endsWith("Fk"))
										if (fieldItem.getName().endsWith("Id") == false)
											validItem = true;
								} else if (annotationItem instanceof OneToMany) {

									validItem = true;
								}
							}
							if (validItem == false) {
								mapItems.put(fieldItem.getName(), fieldItem.get(property));
							}
						}
						for (Field fieldDetail : ((BaseModel) property).GetFields(property.getClass())) {
							String strDetail = fieldDetail.getName();
							String nameDetail = fieldDetail.getName();
							fieldDetail.setAccessible(true);
							if (nameDetail.equals("serialVersionUID"))
								continue;
							if (nameDetail.equals("_methods_"))
								continue;
							if (nameDetail.equals("handler"))
								continue;
							if (nameDetail.equals("_filter_signature"))
								continue;
							Boolean validDetail = false;
							for (java.lang.annotation.Annotation annotationDetail : fieldDetail
									.getDeclaredAnnotations()) {
								if (annotationDetail instanceof JoinColumn) {
									validDetail = true;
								} else if (annotationDetail instanceof Column) {
									Column column = (Column) annotationDetail;
									if (column.name().endsWith("Fk"))
										if (fieldDetail.getName().endsWith("Id") == false)
											validDetail = true;
								} else if (annotationDetail instanceof OneToMany) {

									validDetail = true;
								}
							}
							if (validDetail == true) {
								Object propertyDetail = fieldDetail.get(property);
								if (propertyDetail instanceof HibernateProxy) {
									propertyDetail = JsonUtil.initializeAndUnproxy(propertyDetail);
								}
								if (propertyDetail instanceof BaseModel) {
									Map<String, Object> mapDetailItems = new HashMap<>();
									for (Field fieldDetailItem : ((BaseModel) propertyDetail)
											.GetFields(propertyDetail.getClass())) {
										String nameDetailItem = fieldDetailItem.getName();
										fieldDetailItem.setAccessible(true);
										if (nameDetailItem.equals("serialVersionUID"))
											continue;
										if (nameDetailItem.equals("_methods_"))
											continue;
										if (nameDetailItem.equals("handler"))
											continue;
										if (nameDetailItem.equals("_filter_signature"))
											continue;
										Boolean validDetailItem = false;
										for (java.lang.annotation.Annotation annotationDetailItem : fieldDetailItem
												.getDeclaredAnnotations()) {
											if (annotationDetailItem instanceof JoinColumn) {
												validDetailItem = true;
											} else if (annotationDetailItem instanceof Column) {
												Column columnItem = (Column) annotationDetailItem;
												if (columnItem.name().endsWith("Fk"))
													if (fieldDetailItem.getName().endsWith("Id") == false)
														validDetailItem = true;
											} else if (annotationDetailItem instanceof OneToMany) {

												validDetailItem = true;
											}
										}
										if (validDetailItem == false) {
											mapDetailItems.put(fieldDetailItem.getName(),
													fieldDetailItem.get(propertyDetail));
										}
									}
									mapItems.put(strDetail, mapDetailItems);
								}

							}
						}
						maps.put(str, mapItems);
					}

				}

			}
		}

		return maps;
	}
	
	public T transferModelToVOCustom(T1 model, T vo) {
		if (null == vo)
			vo = (T) new Object();
		try {
			String[] fieldsToInclude = null;
			Map<String, Object> serialized = model.serialize(fieldsToInclude,model.getClass().getSimpleName());
			Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
		    String json = gson.toJson(serialized);
		    vo = (T) gson.fromJson(json, vo.getClass());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return vo;
	}

}