package org.msh.tb.entities;

import org.hibernate.annotations.Index;
import org.hibernate.annotations.OrderBy;
import org.hibernate.annotations.Table;
import org.msh.etbm.commons.transactionlog.Operation;
import org.msh.etbm.commons.transactionlog.mapping.PropertyLog;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;


@Entity
@javax.persistence.Table(name="administrativeunit")
@Table(appliesTo="administrativeunit",indexes={@Index(name="idxcode", columnNames={"code"})})
public class AdministrativeUnit extends WSObject implements SyncKey{
	private static final long serialVersionUID = 7777075173601864769L;

	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private Integer id;

	@Embedded
	@PropertyLog(messageKey="form.name", operations={Operation.NEW, Operation.DELETE})
	private LocalizedNameComp name = new LocalizedNameComp();

	@ManyToOne
	@JoinColumn(name="PARENT_ID")
	@PropertyLog(operations={Operation.NEW, Operation.DELETE})
	private AdministrativeUnit parent;
	
	@OneToMany(mappedBy="parent",fetch=FetchType.LAZY)
	@OrderBy(clause="NAME1")
	private List<AdministrativeUnit> units = new ArrayList<AdministrativeUnit>();

	@Column(length=50)
	@PropertyLog(messageKey="global.legacyId")
	private String legacyId;
	
	// properties to help dealing with trees
	private int unitsCount;
	
	@Column(length=15, nullable=false)
	@PropertyLog(ignore=true)
	private String code;
	
	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="COUNTRYSTRUCTURE_ID")
	@PropertyLog(operations={Operation.ALL})
	private CountryStructure countryStructure;

	@Transient
	// Ricardo: TEMPORARY UNTIL A SOLUTION IS FOUND. Just to attend a request from the XML data model to
	// map an XML node to a property in the model
	private Integer clientId;

	/**
	 * Return the parent list including the own object
	 * @return List of {@link AdministrativeUnit} instance
	 */
	public List<AdministrativeUnit> getParents() {
		return getParentsTreeList(true);
	}


	/**
	 * Return the display name of the administrative unit concatenated with its parent units
	 * @return
	 */
	public String getFullDisplayName() {
		String s = getName().toString();

		for (AdministrativeUnit adm: getParentsTreeList(false)) {
			s += ", " + adm.getName().toString();
		}
		
		return s;
	}
	
	/**
	 * Return the parent units display name with the name of this instance only on the end.
	 * @return
	 */
	public String getFullDisplayName2() {
		String s = null;

		for (AdministrativeUnit adm: getParentsTreeList(true)) {
			if(s == null)
				s = adm.getName().toString();
			else
				s += ", " + adm.getName().toString();
		}
		
		return s;
	}
	
	/**
	 * Return a list with parents administrative unit, where the first is the upper level administrative unit and
	 * the last the lowest level
	 * @return {@link List} of {@link AdministrativeUnit} instances
	 */
	public List<AdministrativeUnit> getParentsTreeList(boolean includeThis) {
		ArrayList<AdministrativeUnit> lst = new ArrayList<AdministrativeUnit>();

		AdministrativeUnit aux;
		if (includeThis)
			 aux = this;
		else aux = getParent();

		while (aux != null) {
			lst.add(0, aux);
			aux = aux.getParent();
		}
		return lst;
	}


	/**
	 * Static method that return the parent code of a given code
	 * @param code
	 * @return
	 */
	public static String getParentCode(String code) {
		if ((code == null) || (code.length() <= 3))
			return null;

		if (code.length() <= 6)
			return code.substring(0, 3);
		if (code.length() <= 9)
			return code.substring(0, 6);
		if (code.length() <= 12)
			return code.substring(0, 9);
		return code.substring(0, 12);
	}
	
	/**
	 * Check if an administrative unit code (passed as the code parameter) is a child of the current administrative unit
	 * @param code of the unit
	 * @return true if code is of a child unit, otherwise return false
	 */
	public boolean isSameOrChildCode(String code) {
		return isSameOrChildCode(this.code, code);
/*		int len = this.code.length();
		if (len > code.length())
			return false;
		return (this.code.equals(code.substring(0, this.code.length())));
*/	}
	
	
	/**
	 * Static method to check if a code is equals of a child of the code given by the parentCode param
	 * @param parentCode
	 * @param code
	 * @return
	 */
	public static boolean isSameOrChildCode(String parentCode, String code) {
		int len = parentCode.length();
		if (len > code.length())
			return false;
		return (parentCode.equals(code.substring(0, parentCode.length())));
	}


	/**
	 * Return the parent administrative unit based on its level. If level is the same of this unit, it returns itself.
	 * If level is bigger than the level of this unit, it returns null
	 * @param level
	 * @return {@link AdministrativeUnit} instance, which is itself or a parent admin unit
	 */
	public AdministrativeUnit getAdminUnitByLevel(int level) {
		if (level == countryStructure.getLevel())
			return this;
		List<AdministrativeUnit> lst = getParents();
		for (AdministrativeUnit adm: lst) {
			if (adm.getLevel()== level)
				return adm;
		}
		return null;
	}

	
	/**
	 * Return parent administrative unit of level 1
	 * @return {@link AdministrativeUnit} instance
	 */
	public AdministrativeUnit getParentLevel1() {
		return getAdminUnitByLevel(1);
	}

	
	/**
	 * Return parent administrative unit of level 2
	 * @return {@link AdministrativeUnit} instance
	 */
	public AdministrativeUnit getParentLevel2() {
		return getAdminUnitByLevel(2);
	}

	
	/**
	 * Return parent administrative unit of level 3
	 * @return {@link AdministrativeUnit} instance
	 */
	public AdministrativeUnit getParentLevel3() {
		return getAdminUnitByLevel(3);
	}

	
	/**
	 * Return parent administrative unit of level 4
	 * @return {@link AdministrativeUnit} instance
	 */
	public AdministrativeUnit getParentLevel4() {
		return getAdminUnitByLevel(4);
	}


	/**
	 * Return parent administrative unit of level 5
	 * @return {@link AdministrativeUnit} instance
	 */
	public AdministrativeUnit getParentLevel5() {
		return getAdminUnitByLevel(5);
	}

	
	/**
	 * @return the id
	 */
	public Integer getId() {
		return id;
	}

	@Override
	public Integer getClientId() {
		return clientId;
	}

	@Override
	public void setClientId(Integer clientId) {
		this.clientId = clientId;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(Integer id) {
		this.id = id;
	}

	/**
	 * @return the name
	 */
	public LocalizedNameComp getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(LocalizedNameComp name) {
		this.name = name;
	}

	/**
	 * @return the parent
	 */
	public AdministrativeUnit getParent() {
		return parent;
	}

	/**
	 * @param parent the parent to set
	 */
	public void setParent(AdministrativeUnit parent) {
		this.parent = parent;
	}

	/**
	 * @return the units
	 */
	public List<AdministrativeUnit> getUnits() {
		return units;
	}

	/**
	 * @param units the units to set
	 */
	public void setUnits(List<AdministrativeUnit> units) {
		this.units = units;
	}

	/**
	 * @return the legacyId
	 */
	public String getLegacyId() {
		return legacyId;
	}

	/**
	 * @param legacyId the legacyId to set
	 */
	public void setLegacyId(String legacyCode) {
		this.legacyId = legacyCode;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return getFullDisplayName();
	}

	/**
	 * @return the unitsCount
	 */
	public int getUnitsCount() {
		return unitsCount;
	}

	/**
	 * @param unitsCount the unitsCount to set
	 */
	public void setUnitsCount(int unitsCount) {
		this.unitsCount = unitsCount;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == this)
			return true;
		
		if (!(obj instanceof AdministrativeUnit))
			return false;
		
		return ((AdministrativeUnit)obj).getId().equals(getId());
	}

	/**
	 * @return the countryStructure
	 */
	public CountryStructure getCountryStructure() {
		return countryStructure;
	}

	/**
	 * @param countryStructure the countryStructure to set
	 */
	public void setCountryStructure(CountryStructure countryStructure) {
		this.countryStructure = countryStructure;
	}


	/**
	 * @param code the code to set
	 */
	public void setCode(String code) {
		this.code = code;
	}

	/**
	 * @return the code
	 */
	public String getCode() {
		return code;
	}

	public int getLevel() {
		String s = getCode();
		if (s == null)
			return 0;

		return s.length()/3;
	}
}
