/**
 * Licensed under the MICE s.r.l.
 * End User License Agreement
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.micegroup.it/voila/license.html
 */
package it.mice.voila.runtime.entity;

import java.util.List;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.pojomatic.Pojomatic;
import org.pojomatic.diff.Difference;
import org.pojomatic.diff.Differences;
import org.pojomatic.diff.NoDifferences;
import org.pojomatic.diff.PropertyDifferences;
import org.pojomatic.diff.ValueDifference;

import it.mice.voila.runtime.service.EntityService;
import it.mice.voila.runtime.util.GlobalConstants;

/**
 * Classe DAOGeneric. La presente abstract class rappresenta il punto d'accesso unico per
 * tutti i dataaccess. Essa, oltre a fornire la connessione al DBMS, offre anche la
 * possibilità, in futuro, di aggiungere funzioni centralizzate utilizzabili da tutti i
 * dataaccess.
 */
public abstract class EntityGeneric extends CoreEntityGeneric implements Entity {
	public static final String[] EMPTY_STRING_ARRAY = {};
	public static final byte ENTITY_STATE_NEW = 1;
	public static final byte ENTITY_STATE_LAZY = 11;
	public static final byte ENTITY_STATE_LOADED = 2;
	public static final byte ENTITY_STATE_CHANGED = 3;
	public static final byte ENTITY_STATE_DELETED = 4;
	public static final int HASH_CODE_NUMBER = 29;
	/**
	 * Stato interno dell'entity che assume i seguenti valori: <b>new</b> - impostato dal
	 * costruttore dell'Entity a segnalare che l'Entity non risulta presente in base dati.<br>
	 * <b>loaded</b> - impostato dai finder dei dataaccess a segnalare che l'Entity
	 * risulta presente in base dati.<br>
	 * <b>changed</b> - impostato dai setter dell'Entity a segnalare che l'Entity risulta
	 * essere stato alterato.<br>
	 *
	 * ATTENZIONE !!! A tutti coloro che gestiscono gli accessi particolari dei
	 * dataaccess: Ricordarsi di impostare questo flag a "loaded" per ogni Entity che
	 * viene manualmente caricato, richiamando il metodo setInternalEntityStateToLoaded().
	 */
	@JsonProperty
	private byte internalEntityState = ENTITY_STATE_NEW;

	/**
	 * Useful fields that store checked information of the element in a list.
	 */
	private boolean checked = false;

	/**
	 * Old entity state to track changes
	 */
	private String entityStateDump = null;
	
	/**
	 * Costruttore EntityGeneric.
	 */
	public EntityGeneric() {
	}

	public boolean isNewEntity() {
		return internalEntityState == ENTITY_STATE_NEW;
	}

	public boolean isNewOrLazyEntity() {
		return internalEntityState == ENTITY_STATE_NEW
				|| internalEntityState == ENTITY_STATE_LAZY;
	}

	public boolean isLazyEntity() {
		return internalEntityState == ENTITY_STATE_LAZY;
	}

	public boolean isLoadedEntity() {
		return internalEntityState == ENTITY_STATE_LOADED;
	}

	public boolean isChangedEntity() {
		return internalEntityState == ENTITY_STATE_CHANGED;
	}

	public boolean isDeletedEntity() {
		return internalEntityState == ENTITY_STATE_DELETED;
	}

	public byte getInternalEntityState() {
		return internalEntityState;
	}

	public void setInternalEntityStateToNew() {
		internalEntityState = ENTITY_STATE_NEW;
	}

	public void setInternalEntityStateToLoaded() {
		internalEntityState = ENTITY_STATE_LOADED;
	}

	public void setInternalEntityStateToChanged() {
		internalEntityState = ENTITY_STATE_CHANGED;
	}

	public void setInternalEntityStateToChangedIfLoaded() {
		if (isLoadedEntity())
			setInternalEntityStateToChanged();
	}

	public void setInternalEntityStateToDeleted() {
		internalEntityState = ENTITY_STATE_DELETED;
	}

	public void setInternalEntityStateToLazy() {
		internalEntityState = ENTITY_STATE_LAZY;
	}

	public void setInternalEntityState(byte internalState) {
		internalEntityState = internalState;
	}

	/**
	 * Check that the given entity is different from null, its internal state is NEW and
	 * its PK attributes are not null.
	 * 
	 * @param entity
	 *            entity to be checked.
	 * @return true if the entity is as listed above, else return false.
	 */
	public boolean isNewPkFilledEntity(Entity entity) {
		return (entity != null && entity.isNewEntity() && !entity.isPkNull()); 
	}

	/**
	 * Check that this is different from null, its internal state is NEW and its PK
	 * attributes are not null.
	 * 
	 * @return true if the entity is as listed above, else return false.
	 */
	public boolean isNewPkFilledEntity() {
		return isNewPkFilledEntity(this);
	}

	/**
	 * Check that the given entity is in lazy status and have its pk fully filled.
	 * 
	 * @param entity
	 *            entity to be checked.
	 * @return true if the entity is as listed above, else return false.
	 */
	public boolean isLazyPkFilledEntity(Entity entity) {
		return (entity != null && entity.isLazyEntity() && entity.isPkNotNull());
	}

	/**
	 * Check that this is in lazy status and have its pk fully filled.
	 * 
	 * @return true if the entity is as listed above, else return false.
	 */
	public boolean isLazyPkFilledEntity() {
		return isLazyPkFilledEntity(this);
	}

	public void setChecked(boolean checked) {
		this.checked = checked;
	}
	
	public boolean isChecked() {
		return checked;
	}

	/**
	 * Initialize the given parent entity.
	 * @param <T> the entity type to process
	 * @param theEntity the entity to process
	 * @param key the key trying to set into the parent entity
	 * @param service the service to use to instantiate a new entity
	 * @return the new initialized entity or null if both the given entity and the given key are null.
	 */
	protected <T extends Entity> T initEntityIfNull(T theEntity, Object key, EntityService<T> service) {
		if (theEntity == null) {
			if (key == null) {
				// If parent entity is null and given attribute is null,
				// return without perform any operation.
				return null;
			} else {
				// If parent entity is null and given attribute is not null,
				// create a new parent entity instance.
				theEntity = service.newEntity();
				theEntity.setInternalEntityStateToLazy();
			}
		}
		return theEntity;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	public String getObjectTitle() {
		return getObjectKey();
	}
	
	public String getIconImageName() {
		return getImageName("icon");
	}
	
	public String getBannerImageName() {
		return getImageName("banner");
	}
	
	public String getImageName(String fieldName) {
		if (isNewEntity()) {
			return "#";
		}
		String result = getClass().getSimpleName() + "_" + replaceInvalidFileChars(getObjectKey()) + "_" + fieldName + ".image";
		return result;
	}

	/**
	 * replaceInvalidFileChars.
	 * @param objectKey the objectKey.
	 * @return the result.
	 */
	protected String replaceInvalidFileChars(String objectKey) {
		String result = objectKey.replaceAll("[\\<\\>\\:\\\"\\\\\\/\\|\\?\\*]", "_");
		return result;
	}

	public String getEntityStateDump() {
		return entityStateDump;
	}

	public void setEntityStateDump(String entityStateDump) {
		this.entityStateDump = entityStateDump;
	}
	
	public void saveEntityState() {
		setEntityStateDump(getObjectDump());
	}

	/**
	 * Return a string representation of the entity.
	 * 
	 * @return string representation of the entity.
	 */
	@Override
	public String toString() {
		return Pojomatic.toString(this);
	}

	/**
	 * 
	 * @param <T> the T.
	 * @param currentValue the current value to be replaced
	 * @param newValue the new value to replace to
	 * @return the value to be set
	 */
	protected <T extends Object> T setIfValueChanged(T currentValue, T newValue) {
		return setIfValueChanged(currentValue, newValue, null);
	}

	/**
	 * 
	 * @param <T> the T.
	 * @param currentValue the current value to be replaced
	 * @param newValue the new value to replace to
	 * @param upperCase upper case flag:
	 * <ul>
	 * <li>true - the value must be in upper case</li>
	 * <li>false - the value must be in lower case</li>
	 * <li>null - the value must be leave as is</li>
	 * </ul>
	 * @return the value to be set
	 */
	@SuppressWarnings("unchecked")
	protected <T extends Object> T setIfValueChanged(T currentValue, T newValue, Boolean upperCase) {
		if (isValueChanged(currentValue, newValue, upperCase != null)) {
			setInternalEntityStateToChangedIfLoaded();
			if (upperCase == null) {
				return newValue;
			}
			if (upperCase == true) {
				return (T)StringUtils.upperCase((String)newValue);
			}
			return (T)StringUtils.lowerCase((String)newValue);
		} else {
			return currentValue;
		}
	}

	protected void checkLazyLoading() {
	}
	
	/**
	 * Force entity loading if in lazy state
	 */
	public void loadEntity() {
		checkLazyLoading();
	}
	
	public Differences diff(Entity theEntity) {
		if (theEntity == null) {
			throw new IllegalArgumentException("Given parameter cannot be null!");
		}
		if (!this.getClass().equals(theEntity.getClass())) {
			throw new IllegalArgumentException("Given parameter is not of the same type of " + this);
		}
		return NoDifferences.getInstance();
	}

    /**

     * @param diffs the diffs.
     * @param otherEntity the otherEntity.
     * @param propertyName the propertyName.
     */
    protected void computeDiff(List<Difference> diffs, Entity otherEntity, String propertyName) {
        try {
            Object leftValue = PropertyUtils.getSimpleProperty(this, propertyName);
            Object rightValue = PropertyUtils.getSimpleProperty(otherEntity, propertyName);
            if (leftValue == null && rightValue == null) {
                return;
            }
            if ((leftValue == null && rightValue != null) || (leftValue != null && rightValue == null) || (!leftValue.equals(rightValue))) {
            	if (leftValue instanceof Entity || rightValue instanceof Entity) {
            		Entity leftEntity = (Entity)leftValue;
            		Entity rightEntity = (Entity)rightValue;
                    diffs.add(new ValueDifference(propertyName, leftEntity != null ? leftEntity.getObjectTitle() : null, rightEntity != null ? rightEntity.getObjectTitle() : null));
            	} else {
                    diffs.add(new ValueDifference(propertyName, leftValue, rightValue));
            	}
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected Differences finalizeDiffs(List<Difference> diffs) {
        if (diffs.size() == 0) {
            return NoDifferences.getInstance();
        }
        return new PropertyDifferences(diffs);
    }

    /**
     * getRowIdFieldDelimiter.
     * @return the result.
     */
    protected String getRowIdFieldDelimiter() {
        return GlobalConstants.ROW_ID_FIELD_NEW_DELIMITER;
    }

}
