package com.libreecm.commonAPI.model.objects;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.libreecm.commonAPI.auxiliars.exceptions.NotDefinedPropertyDefinitionException;
import com.libreecm.commonAPI.model.CmisExtensionElement;
import com.libreecm.commonAPI.model.LibreECMObject;
import com.libreecm.commonAPI.model.enums.Action;
import com.libreecm.commonAPI.model.enums.BaseTypeId;
import com.libreecm.commonAPI.model.enums.Cardinality;
import com.libreecm.commonAPI.model.enums.DateTimeResolution;
import com.libreecm.commonAPI.model.enums.PropertyIds;
import com.libreecm.commonAPI.model.enums.PropertyType;
import com.libreecm.commonAPI.model.enums.Updatability;
import com.libreecm.commonAPI.model.objects.properties.Properties;
import com.libreecm.commonAPI.model.objects.properties.PropertyBooleanDefinitionImpl;
import com.libreecm.commonAPI.model.objects.properties.PropertyBooleanImpl;
import com.libreecm.commonAPI.model.objects.properties.PropertyDateTimeDefinitionImpl;
import com.libreecm.commonAPI.model.objects.properties.PropertyDateTimeImpl;
import com.libreecm.commonAPI.model.objects.properties.PropertyDefinition;
import com.libreecm.commonAPI.model.objects.properties.PropertyIdDefinitionImpl;
import com.libreecm.commonAPI.model.objects.properties.PropertyIntegerDefinitionImpl;
import com.libreecm.commonAPI.model.objects.properties.PropertyIntegerImpl;
import com.libreecm.commonAPI.model.objects.properties.PropertyStringDefinitionImpl;
import com.libreecm.commonAPI.model.objects.properties.PropertyStringImpl;
import com.libreecm.commonAPI.model.users.Acl;


@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
public abstract class LibreECMDataObject  extends LibreECMObject{
    
	/** 
 *   
 */
private static final long serialVersionUID = 1L;
	/**
	 * Property showing the object is fiscally accessible in this moment. 
	 */
	private Boolean accessible=false;
	private Acl acl;
	/**   
	 * Property showing the object is active for the system.
	 */
	private Boolean active=false;
	private AllowableActions allowableActions;

	private BaseTypeId baseTypeId;
	private ChangeEventInfo changeEventInfo;
	private String changeToken;
	private String createdBy;
	private GregorianCalendar creationDate;
	private String description;
	//private Map<String, PropertyDefinition<?>> propertyDefinitions;
	private String displayName;
	private Boolean exactAcl;
	

	private List<CmisExtensionElement> extensions;


	/**
	 * An object could be at some folders or any, but there is one main folder (first) or none (path)
	 */
	private List<String> foldersId;

	private String id;
	private Boolean controllableAcl=true;
	private Boolean controllablePolicy=true;
	private Boolean creatable=true;
	private Boolean fileable=true;
	private Boolean fulltextIndexed=true;
	private Boolean includedInSupertypeQuery=true;
	private Boolean queryable=true;
	private GregorianCalendar lastModificationDate;
	private String lastModifiedBy;
	private String localName;

	private String localNamespace;

	private ObjectType objectType;

	private String parentId;

	private String path;
	private PolicyIdList policyIds;
	private Properties properties;
	private List<Relationship> relationships;	

	private List<RenditionData> renditions;

	private String queryName;

	private TypeMutability typeMutability;
	/**
	 * @return the queryName
	 */
	public String getQueryName() {
		return queryName;
	}

	/**
	 * @param queryName the queryName to set
	 */
	public void setQueryName(String queryName) {
		if (queryName==null) return;
		addStringProperty("cmis:queryName", queryName);
		this.queryName = queryName;
	}

	/**
	 * @return the typeMutability
	 */
	public TypeMutability getTypeMutability() {
		return typeMutability;
	}

	/**
	 * @param typeMutability the typeMutability to set
	 */
	public void setTypeMutability(TypeMutability typeMutability) {
		if (typeMutability==null)
			return;
		List<Boolean> values = new ArrayList<>();
		values.add(typeMutability.canCreate());
		values.add(typeMutability.canUpdate());
		values.add(typeMutability.canDelete());
		addListBooleanProperty("cmis:typeMutability",values);
		this.typeMutability = typeMutability;
	}
	
	public  LibreECMDataObject(){
	}
	

	public  LibreECMDataObject(Uri objectUri, ObjectType objectType){
		//ObjectType has to be the first to be setted to get access to 
		// properties definitions
		setObjectType(objectType);
		setId(objectUri.toString());
		setExtensions(objectType.getExtensions());
		setLocalName(objectUri.getObject());
		setLocalNamespace(objectType.getLocalNamespace());
		setDisplayName(objectUri.toString());
		setQueryName(this.getId());
		setDescription(objectType.getDescription());
		setBaseTypeId(objectType.getBaseTypeId());
		setTypeMutability(objectType.getTypeMutability());
		setCreationDate(new GregorianCalendar());
		setAccessible(true);
		setActive(true);
		setParentId(objectUri.getParentUri());
		List<String> folders = new ArrayList<String>();
		folders.add(this.getParentId());
		setFolders(folders);
		setLastModificationDate(new GregorianCalendar());
	}
	
	

	protected void addBooleanProperty(String propertyIds, Boolean value) {
		PropertyBooleanImpl property = new PropertyBooleanImpl();
		property.setId(propertyIds);
		property.setDisplayName(property.getId());
		property.setQueryName(property.getId());
		property.setLocalName(property.getId());
		property.setValue(value);

		if (!objectType.getPropertyDefinitions().containsKey(propertyIds)){
			throw new NotDefinedPropertyDefinitionException(propertyIds);
		}
		this.getProperties().addProperty(property);
	}

	
	protected void addDateTimeProperty(String propertyIds, GregorianCalendar value) {
		PropertyDateTimeImpl property = new PropertyDateTimeImpl();
		property.setId(propertyIds);
		property.setDisplayName(property.getId());
		property.setQueryName(property.getId());
		property.setLocalName(property.getId());
		property.setValue(value);

		if (!getObjectType().getPropertyDefinitions().containsKey(propertyIds)){
			throw new NotDefinedPropertyDefinitionException(propertyIds);
		}
		this.getProperties().addProperty(property);	
	}

	protected void addIntegerProperty(String propertyIds, BigInteger value) {
		PropertyIntegerImpl property = new PropertyIntegerImpl();
		property.setId(propertyIds);
		property.setDisplayName(property.getId());
		property.setQueryName(property.getId());
		property.setLocalName(property.getId());
		property.setValue(value);

		if (!objectType.getPropertyDefinitions().containsKey(propertyIds)){
			throw new NotDefinedPropertyDefinitionException(propertyIds);
		}
		this.getProperties().addProperty(property);
	}

	protected void addStringProperty(String propertyIds, String value) {
		PropertyStringImpl property = new PropertyStringImpl();
		property.setId(propertyIds);
		property.setDisplayName(property.getId());
		property.setQueryName(property.getId());
		property.setLocalName(property.getId());
		property.setValue(value);
		if (!objectType.getPropertyDefinitions().containsKey(propertyIds)){
			throw new NotDefinedPropertyDefinitionException(propertyIds);
		}
		this.getProperties().addProperty(property);
	}
	

	protected void addListStringProperty(String propertyIds, List<String> values) {
		PropertyStringImpl property = new PropertyStringImpl();
		property.setId(propertyIds);
		property.setDisplayName(property.getId());
		property.setQueryName(property.getId());
		property.setLocalName(property.getId());
		property.setValues(values);	

		if (!objectType.getPropertyDefinitions().containsKey(propertyIds)){
			throw new NotDefinedPropertyDefinitionException(propertyIds);
		}
		this.getProperties().addProperty(property);
	}

	protected void addListBooleanProperty(String propertyIds, List<Boolean> values) {
		PropertyBooleanImpl property = new PropertyBooleanImpl();
		property.setId(propertyIds);
		property.setDisplayName(property.getId());
		property.setQueryName(property.getId());
		property.setLocalName(property.getId());
		property.setValues(values);	

		if (!objectType.getPropertyDefinitions().containsKey(propertyIds)){
			throw new NotDefinedPropertyDefinitionException(propertyIds);
		}
		this.getProperties().addProperty(property);
	}
	public Boolean getAccessible(){
    	return accessible;
    }
	public Acl getAcl() {
		return acl;
	}
	
	
	public Boolean getActive(){ 
    	return active;
    }
	
	public AllowableActions getAllowableActions() {
		return allowableActions;
	}
	
	/**
	 * @return the baseTypeId
	 */
	public BaseTypeId getBaseTypeId() {
		return baseTypeId;
	}

	public ChangeEventInfo getChangeEventInfo() {
		return changeEventInfo;
	}

	/**
	 * @return the changeToken
	 */
	public String getChangeToken() {
		return changeToken;
	}
	
	/**
	 * @return the createdBy
	 */
	public String getCreatedBy() {
		return createdBy;
	}


	/**
	 * @return the creationDate
	 */
	public GregorianCalendar getCreationDate() {
		return creationDate;
	}

	/**
	 * @return the description
	 */
	public String getDescription() {
		return description;
	}
	
	/**
	 * @return the name
	 */
	public String getDisplayName() {
		return displayName;
	}

	/**
	 * @return the exactAcl
	 */
	public Boolean getExactAcl() {
		return exactAcl;
	}

	public List<CmisExtensionElement> getExtensions() {
		return extensions;
	}

	/**
	 * Stores the parent folders's Uri
	 * @return the folders
	 */
	public List<String> getFolders() {
		if (foldersId==null)
			foldersId=new ArrayList<String>();
		return foldersId;
	}

	/**
	 * @return the id
	 */
	public String getId() {
		return id;
	}

	/**
	 * @return the lastModificationDate
	 */
	public GregorianCalendar getLastModificationDate() {
		return lastModificationDate;
	}

	/**
	 * @return the lastModifiedBy
	 */
	public String getLastModifiedBy() {
		return lastModifiedBy;
	}

	/**
	 * @return the localName
	 */
	public String getLocalName() {
		return localName;
	}

	/**
	 * @return the localNamespace
	 */
	public String getLocalNamespace() {
		return localNamespace;
	}

	/**
	 * @return the objectTypeId
	 */
	public ObjectType getObjectType() {
		return objectType;
	}

		/**
		 * @return the parentId
		 */
		public String getParentId() {
			return parentId;
		}
  
	/**
	 * return Path where the table for a DB or root of a fs of this object
	 * Example: so File(getPath()) is correct and point to a file
	 */
	public String getPath() {
		return this.path;
	}

	public PolicyIdList getPolicyIds() {
		if (this.policyIds==null)
			policyIds=new PolicyIdList();
		return policyIds;
	}

	public Properties getProperties() {
		if (this.properties==null)
			properties=new Properties();
		return properties;
	}	


	public List<Relationship> getRelationships() {
		return relationships;
	}


	
	public List<RenditionData> getRenditions() {
		return renditions;
	}

	public Boolean isControllableAcl() {
		if (this.controllableAcl==null)
			//Defect value
			this.controllableAcl=true;
		return controllableAcl;
	}

	public Boolean isControllablePolicy() {
		if (this.controllableAcl==null)
			//Defect value
			this.controllableAcl=true;
		return controllablePolicy;
	}

	public Boolean isCreatable() {
		if (this.creatable==null)
		//Defect value
		this.creatable=true;
		return creatable;
	}

	public Boolean isExactAcl() {
		return exactAcl;
	}

	public Boolean isFileable() {
		if (this.fileable==null)
		//Defect value
		this.fileable=true;
		return fileable;
	}

	public Boolean isFulltextIndexed() {
		if (this.fulltextIndexed==null)
		//Defect value
		this.fulltextIndexed=true;
		return fulltextIndexed;
	}

	public Boolean isIncludedInSupertypeQuery() {
		if (this.includedInSupertypeQuery==null)
		//Defect value
		this.includedInSupertypeQuery=true;
		return includedInSupertypeQuery;
	}

	public Boolean isQueryable() {
		if (this.queryable==null)
			//Defect value
			this.queryable=true;
		return queryable;
	}

	/**
	 * @param accessible the accessible to set
	 */
	public void setAccessible(Boolean accessible) {
		if (accessible==null) return;
		addBooleanProperty("libreecm:accessible", accessible);
		this.accessible = accessible;
	}

	/**
	 * @param acl the acl to set
	 */
	public void setAcl(Acl acl) {
		if (acl==null) return;
		this.acl = acl;
	}

	/**
	 * @param active the active to set
	 */
	public void setActive(Boolean active) {
		if (active==null) return;
		addBooleanProperty("libreecm:active", active);
		this.active = active;
	}

	/**
	 * @param allowableActions the allowableActions to set
	 */
	public void setAllowableActions(AllowableActions allowableActions) {
		if (allowableActions==null||allowableActions.getAllowableActions().size()==0)
			return;
		List<String> actions= new ArrayList<String>();
		for (Action action: allowableActions.getAllowableActions()){
			actions.add(action.value());
		}
		addListStringProperty("cmis:allowableActions", actions);
		this.allowableActions = allowableActions;
	}

	/**
	 * @param baseTypeId the baseTypeId to set
	 */
	public void setBaseTypeId(BaseTypeId baseTypeId) {
		if (baseTypeId==null) return;
		this.baseTypeId = baseTypeId;
		addStringProperty(PropertyIds.BASE_TYPE_ID, baseTypeId.value());
		
	}

	/**
	 * @param changeEventInfo the changeEventInfo to set
	 */
	public void setChangeEventInfo(ChangeEventInfo changeEventInfo) {	
		this.changeEventInfo = changeEventInfo;
	}

	/**
	 * @param changeToken the changeToken to set
	 */
	public void setChangeToken(String changeToken) {
		if (changeToken==null) return;
		addStringProperty(PropertyIds.CHANGE_TOKEN, changeToken);
		this.changeToken = changeToken;
	}

	/**
	 * @param createdBy the createdBy to set
	 */
	public void setCreatedBy(String createdBy) {
		if (createdBy==null) return;
		addStringProperty(PropertyIds.CREATED_BY, createdBy);
		this.createdBy = createdBy;
	}

	/**
	 * @param creationDate the creationDate to set
	 */
	public void setCreationDate(GregorianCalendar creationDate) {
		if (creationDate==null) return;
		addDateTimeProperty(PropertyIds.CREATION_DATE, creationDate);
		this.creationDate = creationDate;
	}

	/**
	 * @param description the description to set
	 */
	public void setDescription(String description) {
		if (description==null) return;
		addStringProperty(PropertyIds.DESCRIPTION, description);
		this.description = description;
	}

	/**
	 * @param name the name to set
	 */
	public void setDisplayName(String name) {
		if (name==null) return;
		addStringProperty(PropertyIds.NAME, name);
		this.displayName = name;
	}


	/**
	 * @param exactAcl the exactAcl to set
	 */
	public void setExactAcl(Boolean exactAcl) {
		if (exactAcl==null) return;
		addBooleanProperty("cmis:exactACL",exactAcl);
		this.exactAcl = exactAcl;
	}

	public void setExtensions(List<CmisExtensionElement> arg0) {
		this.extensions=arg0;
	}

	
    /**
	 * @param folders the folders to set
	 *  An object could be at some folders or any, but there is one main folder (first) or none (path)
	 */
	public void setFolders(List<String> folders) {
		if (folders==null|| folders.size()==0) return;
		addListStringProperty("cmis:folders",folders);
		this.foldersId = folders;
	}
        
    /**
	 * @param id the id to set
	 */
	public void setId(String id) {
		if (id==null) return;
		this.id = id;
		addStringProperty(PropertyIds.OBJECT_ID, id);
	}
        
	/**
	 * @param lastModificationDate the lastModificationDate to set
	 */
	public void setLastModificationDate(GregorianCalendar lastModificationDate) {
		if (lastModificationDate==null) return;
		addDateTimeProperty(PropertyIds.LAST_MODIFICATION_DATE, lastModificationDate);
		this.lastModificationDate = lastModificationDate;
	}

	/**
	 * @param lastModifiedBy the lastModifiedBy to set
	 */
	public void setLastModifiedBy(String lastModifiedBy) {
		if (lastModifiedBy==null) return;
		addStringProperty(PropertyIds.LAST_MODIFIED_BY, lastModifiedBy);
		this.lastModifiedBy = lastModifiedBy;
	}

	/**
	 * @param localName the localName to set
	 */
	public void setLocalName(String localName) {
		if (localName==null) return;
		addStringProperty("cmis:localName", localName);
		this.localName = localName;
	}

	/**
	 * @param localNamespace the localNamespace to set
	 */
	public void setLocalNamespace(String localNamespace) {
		if (localNamespace==null) return;
		this.localNamespace = localNamespace;
		addStringProperty("cmis:localNamespace", localNamespace);
	}

	/**
	 * @param objectTypeId the objectType to set
	 */
	public void setObjectType(ObjectType objectType) {
		if (objectType==null) return;
		this.objectType = objectType;
		addStringProperty(PropertyIds.OBJECT_TYPE_ID, objectType.getId());
	}

	/**
	 * @param parentId the parentId to set
	 */
	public void setParentId(String parentId) {
		if (parentId==null) return;
		this.parentId = parentId;
		addStringProperty(PropertyIds.PARENT_ID, id);
	}

	
	/**
	 * @param path the path to set
	 */
	public void setPath(String path) {
		if (path==null) return;
		addStringProperty(PropertyIds.PATH, path);
		this.path = path;
	}
	
	/**
	 * @param policyIds the policyIds to set
	 */
	public void setPolicyIds(PolicyIdList policyIds) {
		if (policyIds==null||policyIds.getPoliciesIds().size()==0)
			return;
		addListStringProperty("cmis:policyIdList", policyIds.getPoliciesIds());
		this.policyIds = policyIds;
	}

	/**
	 * @param properties the properties to set
	 */
	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	/**
	 * @param relationships the relationships to set
	 */
	public void setRelationships(List<Relationship> relationships) {
		if (relationships==null||relationships.size()==0) return;
		List<String> ids=new ArrayList<>();
		for (Relationship rela: relationships){
			ids.add(rela.getId());
		}
		addListStringProperty("cmis:relationships", ids);
		this.relationships = relationships;
	}

	/**
	 * @param renditions the renditions to set
	 */
	public void setRenditions(List<RenditionData> renditions) {
		if (renditions==null||renditions.size()==0) return;
		List<String> ids=new ArrayList<>();
		for (RenditionData rela: renditions){
			ids.add(rela.getId());
		}
		addListStringProperty("cmis:renditions", ids);
		this.renditions = renditions;
	}

	public String toString(){
		return "[base id="+this.getBaseTypeId()
				+", id="+this.getId()
				+", display Name="+this.getDisplayName()
				+", description="+this.getDescription()
				+",local name="+this.getLocalName()
				+", local namespace="+this.getLocalNamespace()
				+", query name="+this.getQueryName()
				+", parent id="+this.getParentId()
				+", is controllable ACL="+this.isControllableAcl()
				+" ,	is controllable policy="+this.isControllablePolicy()
				+", is creatable="+this.isCreatable()
				//+", with "+this.objectType.getPropertyDefinitions()
				//+" ]\n Properties are: "+this.getProperties().getPropertyList()
				;
	}
}
