/**
 * 
 */
package com.ejie.m62fb.utils.memory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.springframework.beans.support.PropertyComparator;

import com.ejie.m62fb.utils.error.UFException;
import com.ejie.m62fb.utils.bean.BeanAccess;
import com.ejie.m62fb.utils.bean.BeanUtils;
import com.ejie.x38.dto.JQGridRequestDto;
import com.ejie.x38.dto.Pagination;

/**
XXXX
 */
public abstract class ListUtils {

	private static final int INITAL_BUFFER = 300;
	public static final String SEPARADOR_COMA = ",";

	/**
	 * <p>
	 * Funci&oacute;n <code>listaToCadenaConSeparador</code>
	 * </p>
	 * <p>
	 * Funci&oacute;n que formatea los valores de una lista de
	 * <code>Object</code> a un <code>String</code> con los valores separados
	 * por <code>separador</code>
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de objetos
	 * @param separador
	 *            Caracter por el que se van a separar los elementos de la lista
	 *            en el <code>String</code> resultante.
	 * @return La lista de elementos, separados por <code>separador</code>.
	 */
	public static <O extends Object> String listaToCadenaConSeparador(
			final List<O> lista, final String separador) {
		StringBuilder sb = new StringBuilder(ListUtils.INITAL_BUFFER);
		boolean primerElemento = true;
		for (Object o : lista) {
			if (!primerElemento) {
				sb.append(separador);
			} else {
				primerElemento = false;
			}

			sb.append(o.toString());
		}
		return sb.toString();
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>getObjectIndexFromList</code>
	 * </p>
	 * <p>
	 * Busca un <code>Object</code> dentro de una lista de objetos por sus ids y
	 * devuelve la posici&oacute;n en la que se encuentra.
	 * </p>
	 * <p>
	 * Si no existe, devuelve <code>-1</code>.
	 * </p>
	 * <p>
	 * La entidad a la que pertenece <code>objeto</code> debera implementar las
	 * funciones <code>hashCode</code> y <code>equals</code> para los campos que
	 * lo identifica.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>Object</code> donde buscar.
	 * @param objectToFind
	 *            <code>Object</code> cuyos ids tendran que coincidir con el de
	 *            algun objeto de la <code>lista</code>.
	 * @return int La posici&oacute;n en la que se encuentra en la lista.
	 */
	public static <O extends Object> int getObjectIndexFromList(
			final List<O> lista, final O objectToFind) {

		if (lista.contains(objectToFind)) { // ... para buscarlo en la
											// colección. Para eso sirve
											// el método equals(). Si
											// existe en la colección...
			return lista.indexOf(objectToFind); // ... obtenemos su índice...
		}
		return -1;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>addObjectList</code>
	 * </p>
	 * <p>
	 * A&ntilde;ade un <code>Object</code> dentro de una lista de objetos.
	 * </p>
	 * <p>
	 * Si se puede a&ntilde;adir, devuelve <code>true</code>. Si ya existe,
	 * devuelve <code>false</code>.
	 * </p>
	 * <p>
	 * La entidad a la que pertenece <code>objeto</code> debera implementar las
	 * funciones <code>hashCode</code> y <code>equals</code> para los campos que
	 * lo identifica.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>Object</code> donde buscar.
	 * @param objectToAdd
	 *            <code>Object</code> para a&ntilde;adir a la <code>lista</code>
	 *            .
	 * @return boolean Devuelve <code>true</code> si se ha podido a&ntilde;adir,
	 *         <code>false</code> en caso contrario.
	 */
	public static <O extends Object> boolean addObjectList(List<O> lista,
			final O objectToAdd) {

		if (lista.contains(objectToAdd)) {
			return false;
		}
		lista.add(objectToAdd);
		return true;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>editObjectList</code>
	 * </p>
	 * <p>
	 * Actualiza un <code>Object</code> de una lista de <code>Object</code>.
	 * </p>
	 * <p>
	 * Si se puede actualizar, devuelve <code>true</code>. Si no existe,
	 * devuelve <code>false</code>.
	 * </p>
	 * <p>
	 * La entidad a la que pertenece <code>objeto</code> debera implementar las
	 * funciones <code>hashCode</code> y <code>equals</code> para los campos que
	 * lo identifica.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>Object</code> donde buscar.
	 * @param objectToEdit
	 *            <code>Object</code> a actualizar en la <code>lista</code>.
	 * @return boolean Devuelve <code>true</code> si se ha podido actualizar,
	 *         <code>false</code> en caso contrario.
	 */
	public static <O extends Object> boolean editObjectList(List<O> lista,
			final O objectToEdit) {
		if (lista.contains(objectToEdit)) {
			int idx = lista.indexOf(objectToEdit);
			lista.set(idx, objectToEdit);
			return true;
		}
		return false;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>removeObjectList</code>
	 * </p>
	 * <p>
	 * Elimina un <code>Object</code> de una lista de objetos.
	 * </p>
	 * <p>
	 * Si se puede eliminar, devuelve <code>true</code>. Si no existe, devuelve
	 * <code>false</code>.
	 * </p>
	 * <p>
	 * La entidad a la que pertenece <code>Object</code> debera implementar las
	 * funciones <code>hashCode</code> y <code>equals</code> para los campos que
	 * lo identifica.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>Object</code> donde eliminar.
	 * @param objectToRemove
	 *            <code>Object</code> a eliminar de la <code>lista</code>.
	 * @return boolean Devuelve <code>true</code> si se ha podido eliminar,
	 *         <code>false</code> en caso contrario.
	 */
	public static <O extends Object> boolean removeObjectList(List<O> lista,
			final O objectToRemove) {
		if (lista.contains(objectToRemove)) {
			int idx = lista.indexOf(objectToRemove);
			lista.remove(idx);
			return true;
		}
		return false;
	}

	/**
	 * <p>
	 * Funci&oacute;no <code>getObjectFromList</code>
	 * </p>
	 * <p>
	 * Busca un <code>objeto</code> dentro de una lista de objetos por sus ids.
	 * </p>
	 * <p>
	 * La entidad a la que pertenece <code>objeto</code> debera implementar las
	 * funciones <code>hashCode</code> y <code>equals</code> para los campos que
	 * lo identifica.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>Object</code> donde buscar.
	 * @param objectToFind
	 *            <code>Object</code> cuyos ids tendran que coincidir con el de
	 *            algun objeto de la <code>lista</code>.
	 * @return Object Objeto encontrado.
	 */
	public static <O extends Object> O getObjectFromList(final List<O> lista,
			final O objectToFind) {

		if (lista.contains(objectToFind)) { // ... para buscarlo en la
											// colección. Para eso sirve
											// el método equals(). Si
											// existe en la colección...
			int idx = lista.indexOf(objectToFind); // ... obtenemos su índice...
			return lista.get(idx); // ... lo obtenemos de la colección...
		}
		return null;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>getListWithOutRemoved</code>
	 * </p>
	 * <p>
	 * Devuelve una lista de <code>MemoryObject</code> con los elementos
	 * insertados y actualizados. Elementos que tienen a <code>false</code> el
	 * flag <code>removed</code>.
	 * </p>
	 * 
	 * @param colecction
	 *            Lista de <code>MemoryObject</code> con los elementos a filtrar
	 * @return Lista con los elementos que tienen a <code>false</code> el flag
	 *         <code>removed</code>.
	 */
	public static List<MemoryObject> getListWithOutRemoved(
			final Collection<MemoryObject> colecction) {
		List<MemoryObject> listWithOutRemoved = new ArrayList<MemoryObject>();
		for (MemoryObject o : colecction) {
			if (!o.isRemoved()) {
				listWithOutRemoved.add(o);
			}
		}
		return listWithOutRemoved;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>getAddedObjects</code>
	 * </p>
	 * <p>
	 * Devuelve una lista de <code>MemoryObject</code> con los elementos
	 * insertados. Elementos que tienen a <code>true</code> el flag
	 * <code>added</code>.
	 * </p>
	 * 
	 * @param colecction
	 *            Lista de <code>MemoryObject</code> con los elementos a filtrar
	 * @return Lista con los elementos que tienen a <code>true</code> el flag
	 *         <code>added</code>.
	 */
	public static List<MemoryObject> getAddedObjects(
			final Collection<MemoryObject> colecction) {
		List<MemoryObject> listInserted = new ArrayList<MemoryObject>();
		for (MemoryObject o : colecction) {
			if (o.isAdded()) {
				listInserted.add(o);
			}
		}
		return listInserted;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>isListModified</code>
	 * </p>
	 * <p>
	 * Devuelve <code>true</code> si hay elementos con el flag
	 * <code>added</code>, <code>edited</code> o <code>removed</code> activado.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>MemoryObject</code> con los elementos a evaluar
	 * @return boolean <code>true</code> si hay modificaiones,
	 *         <code>false</code> en caso contrario.
	 */
	public static <O extends Object> boolean isListModified(final List<O> lista) {
		Iterator<O> i = lista.iterator();
		while (i.hasNext()) {
			Object o = i.next();
			if (o instanceof MemoryObject) {
				if (((MemoryObject) o).isAdded()
						|| ((MemoryObject) o).isEdited()
						|| ((MemoryObject) o).isRemoved()) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>getEditedObjects</code>
	 * </p>
	 * <p>
	 * Devuelve una lista de <code>MemoryObject</code> con los elementos
	 * insertados. Elementos que tienen a <code>true</code> el flag
	 * <code>edited</code>.
	 * </p>
	 * 
	 * @param colecction
	 *            Lista de <code>MemoryObject</code> con los elementos a filtrar
	 * @return Lista con los elementos que tienen a <code>true</code> el flag
	 *         <code>edited</code>.
	 */
	public static List<MemoryObject> getEditedObjects(
			final Collection<MemoryObject> colecction) {
		List<MemoryObject> listEdited = new ArrayList<MemoryObject>();
		for (MemoryObject o : colecction) {
			if (o.isEdited()) {
				listEdited.add(o);
			}
		}
		return listEdited;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>getRemovedObjects</code>
	 * </p>
	 * <p>
	 * Devuelve una lista de <code>MemoryObject</code> con los elementos
	 * eliminados. Elementos que tienen a <code>true</code> el flag
	 * <code>removed</code>.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param colecction
	 *            Lista de <code>MemoryObject</code> con los elementos a filtrar
	 * @return Lista con los elementos que tienen a <code>true</code> el flag
	 *         <code>removed</code>.
	 */
	public static List<MemoryObject> getRemovedObjects(
			final Collection<MemoryObject> colecction) {
		List<MemoryObject> listRemoved = new ArrayList<MemoryObject>();
		for (MemoryObject o : colecction) {
			if (o.isRemoved()) {
				listRemoved.add(o);
			}
		}
		return listRemoved;
	}

	// /**
	// * <p>
	// * Funci&oacute;n <code>getListObjects</code>
	// * </p>
	// * <p>
	// * Devuelve una lista que contiene los objetos de una lista del tipo
	// <code>MemoryObject<code>.
	// * </p>
	// * @param <O> Clase que extiende de <code>Object</code> manejada por la
	// instancia.
	// * @param colecction Lista de <code>MemoryObject</code> con los elementos
	// a devolver
	// * @return Lista formada &uacute;nicamente por elementos del tipo
	// <code><O></code>.
	// */
	// public static <O extends Object> List<O> getListObjects(final
	// Collection<MemoryObject<O>> colecction){
	// List<O> listObjects = new ArrayList<O>();
	// for(MemoryObject<O> o:colecction) {
	// listObjects.add(o.getObject());
	// }
	// return listObjects;
	// }
	/**
	 * <p>
	 * Funci&oacute;n <code>orderList</code>
	 * </p>
	 * <p>
	 * Ordena una lista por la propiedad y orden pasados por par&aacute;metro.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>Object</code> a ordenar.
	 * @param property
	 *            Propiedad del bean para ordenar.
	 * @param order
	 *            <code>asc</code> o <code>desc</code>del objeto Pagination.
	 */
	public static <O extends Object> void orderList(List<O> lista,
			final String property, String order) {

		boolean ascending;
		if (order.equalsIgnoreCase("asc")) {
			ascending = true;
		} else {
			ascending = false;
		}
		PropertyComparator pc = new PropertyComparator(property, true,
				ascending);
		PropertyComparator.sort(lista, pc.getSortDefinition());
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>paginateList</code>
	 * </p>
	 * <p>
	 * Devuelve una lista filtrada por la paginaci&oacute;n pasada por
	 * par&aacute;metro.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>Object</code> a paginar.
	 * @param pagination
	 *            Paginaci&oacute;n a aplicar a la lista.
	 * @return Lista filtrada por la paginaci&oacute;n.
	 */
	public static <O extends Object> List<O> paginateList(final List<O> lista,
			final Pagination pagination) {

		int fromIndex, toIndex;

		if (pagination.getPage() == null) {
			fromIndex = 0;
			toIndex = pagination.getRows().intValue();
		} else {
			fromIndex = pagination.getRows().intValue()
					* (pagination.getPage().intValue() - 1);
			toIndex = pagination.getRows().intValue()
					* (pagination.getPage().intValue());
		}
		if (toIndex > lista.size()) {
			toIndex = lista.size();
		}

		return lista.subList(fromIndex, toIndex);
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>paginateList</code>
	 * </p>
	 * <p>
	 * Devuelve una lista filtrada por la paginaci&oacute;n pasada por
	 * par&aacute;metro.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>Object</code> a paginar.
	 * @param pagination
	 *            Paginaci&oacute;n a aplicar a la lista.
	 * @return Lista filtrada por la paginaci&oacute;n.
	 */
	public static <O extends Object> List<O> paginateList(final List<O> lista,
			final JQGridRequestDto pagination) {

		int fromIndex, toIndex;

		if (pagination.getPage() == null) {
			fromIndex = 0;
			toIndex = pagination.getRows().intValue();
		} else {
			fromIndex = pagination.getRows().intValue()
					* (pagination.getPage().intValue() - 1);
			toIndex = pagination.getRows().intValue()
					* (pagination.getPage().intValue());
		}
		if (toIndex > lista.size()) {
			toIndex = lista.size();
		}

		return lista.subList(fromIndex, toIndex);
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>newIdValue</code>
	 * </p>
	 * <p>
	 * Devuelve el valor a asignar como identificador &uacute;nico de un
	 * <code>Object</code>.
	 * </p>
	 * <p>
	 * Siempre ser&aacute; un valor negativo para poder distinguir entre
	 * elementos a añadir a la tabla y los que ya existen.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista de <code>MemoryObject</code> a paginar.
	 * @param property
	 *            Propiedad del bean para ordenar.
	 * @return El valor a asignar como identificador &uacute;nico.
	 */
	public static <O extends Object> BigDecimal newIdValue(final List<O> lista,
			final String property) {
		BigDecimal newId = new BigDecimal(-1);
		// si no hay elementos, el id = -1
		if (lista.size() == 0) {
			return newId;
		}
		List<O> listaAux = lista;
		// se ordena la lista de manera ascendente para que el primer elemento
		// sea el que tenga el id más pequeño
		orderList(listaAux, property, "asc");
		// se crea el acceso a la propiedad
		BeanAccess minElement = new BeanAccess("bean." + property,
				listaAux.get(0));
		BigDecimal minId = new BigDecimal(BeanUtils.getValor(minElement)
				.toString());
		// si el minId es menor que 0, se le resta 1.
		// sino se asigna -1 por defecto, para que siempre los valores nuevos
		// sean negativos y
		// asi diferenciarlos de los que existen en BD
		if (minId.doubleValue() < 0) {
			minId = new BigDecimal(minId.doubleValue() - 1);
		} else {
			minId = newId;
		}
		return minId;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>genMemoryObjectList</code>
	 * </p>
	 * <p>
	 * Crea una lista de <code>MemoryObject</code> a partir de una lista de
	 * <code>Object</code>.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista a partir de la cual se genera la lista de objetos
	 * @param clase
	 *            Clase que es instancia de <code>MemoryObject</code>
	 * @return La lista de <code>MemoryObject</code>
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <O extends Object> List<O> genMemoryObjectList(List<O> lista,
			Class clase) {
		List<O> listaMemory = new ArrayList<O>();
		for (Object o : lista) {
			try {
				Constructor<O> constructor = clase.getConstructor(o.getClass());
				listaMemory.add(constructor.newInstance(o));
			} catch (InstantiationException e) {
				throw new UFException(e.getMessage(), e.getCause());
			} catch (SecurityException e) {
				throw new UFException(e.getMessage(), e.getCause());
			} catch (NoSuchMethodException e) {
				throw new UFException(e.getMessage(), e.getCause());
			} catch (IllegalArgumentException e) {
				throw new UFException(e.getMessage(), e.getCause());
			} catch (IllegalAccessException e) {
				throw new UFException(e.getMessage(), e.getCause());
			} catch (InvocationTargetException e) {
				throw new UFException(e.getMessage(), e.getCause());
			}
		}
		return listaMemory;
	}

	/**
	 * <p>
	 * Funci&oacute;n <code>cloneList</code>
	 * </p>
	 * <p>
	 * Crea la clonaci&oacute;n de una lista.
	 * </p>
	 * 
	 * @param <O>
	 *            Clase que extiende de <code>Object</code> manejada por la
	 *            instancia.
	 * @param lista
	 *            Lista a partir de la cual se genera la lista de objetos
	 * @return La lista clonada
	 */
	@SuppressWarnings("unchecked")
	public static <O extends Object> List<O> cloneList(List<O> lista) {
		try {
			List<O> c = lista.getClass().newInstance();
			for (O o : lista) {
				O copy = (O) o.getClass().getDeclaredConstructor(o.getClass())
						.newInstance(o);
				c.add(copy);
			}
			return c;
		} catch (Exception e) {
			throw new UFException(e.getMessage(), e.getCause());
		}
	}
	// /**
	// * <p>
	// * Funci&oacute;n <code>getSessionMemoryObjectList</code>
	// * </p>
	// * <p>
	// * Añade a la <code>lista</code> pasada por par&aacute;metro los elementos
	// de una lista obtenida de <code>session</code>.
	// * </p>
	// * @param <O> Clase que extiende de <code>Object</code> manejada por la
	// instancia.
	// * @param lista Lista donde se van a volcar la lista de memoria
	// * @param session Sesi&oacute;n del navegador del usuario
	// * @param sessionAttr Nombre del atributo que identifica la lista en
	// <code>session</code>
	// * @return
	// */
	// public static <O extends Object> void getSessionMemoryObjectList(List <O>
	// lista,final HttpSession session, final String sessionAttr ){
	// final List<? extends O> memList = (List<? extends O>)
	// session.getAttribute(sessionAttr);
	// lista.addAll( memList);
	// }
}
