package gov.va.vinci.dart.dms.biz;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NoResultException;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import gov.va.vinci.dart.common.ValidationHelper;
import gov.va.vinci.dart.common.exception.ValidationException;
import gov.va.vinci.dart.biz.BusinessObject;
import gov.va.vinci.dart.biz.DartRequest;
import gov.va.vinci.dart.biz.DocumentTemplate;
import gov.va.vinci.dart.biz.Request;
import gov.va.vinci.dart.service.DartObjectFactory;

@SuppressWarnings("rawtypes")
@Entity
@Table(name="Document",schema="hib")
public class Document  extends BusinessObject implements Comparable {

	private static Log log = LogFactory.getLog(Document.class);
	
	public static final String CURRENT_LABEL = "CURRENT";
	
	public static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy/MM/dd");
	
	// TODO- version should perhaps be a string of the form '1.2.3.4'
	@Column(name="versionnumber")
	private int versionNumber;

	// id of the version 1 of this document.
	@Column(name="head")
	private int head;

	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="documenttemplateid")
	DocumentTemplate template;
	
	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="contentid")
	private Content content;
	
	@Column(name="typesuffix")
	private String typeSuffix;

	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="requestid")
	Request request;

 	@Column(name="sortorder")
	protected int sortOrder;

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "document")
	protected Set<Label> labels;

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "document")
	protected Set<Attribute> attrs;

	protected transient HashMap<String,String> attributes = null;
	
	public Request getRequest() {
		return request;
	}

	public DartRequest getDartRequest() {
		return (DartRequest)request;
	}
	
	Document() {}

	/** Create a new document.  Initializes the document content from the userId and typeSuffix.
	 * 
	 * @param name
	 * @param typeSuffix
	 * @param userId
	 * @param createdBy
	 * @return
	 * @throws ValidationException
	 */
	public static Document create(final Request request, final String name, final String typeSuffix, int userId, final String createdBy) throws ValidationException {
		ValidationHelper.required("Request", request);
		ValidationHelper.required("Document Name", name);
		ValidationHelper.validateSize("Document Name", name, 1, 64);
		ValidationHelper.required("Document Type Suffix", typeSuffix);
		ValidationHelper.validateSize("Document Type Suffix", typeSuffix, 1, 4);
		ValidationHelper.required("Document Creator Name", createdBy);
		ValidationHelper.validateSize("Document Creator Name", createdBy, 1, 32);
		
		Document doc = new Document();
		doc.version = 0; // the Hibernate row locking version, not the document version number
		doc.name = name;
		doc.createdBy = createdBy;
		doc.createdOn = new Date();
		doc.versionNumber = 1;
		doc.typeSuffix = typeSuffix;
		doc.request = request;

		// no content until something is uploaded
//		doc.repositoryPath = generateFilePathName(userId, typeSuffix);
		 
		DartObjectFactory.getInstance().getDocumentDAO().save(doc);
		
		doc.addLabel(CURRENT_LABEL);
		doc.head = doc.id;

		DartObjectFactory.getInstance().getDocumentDAO().save(doc);	//save the new head value (the ID of this newly created document)		
		
		return doc;
	}

	/** Create a document from a document template.  Does not initialize the document content
	 * 
	 * @param template
	 * @param createdBy
	 * @return
	 * @throws ValidationException 
	 */
	public static Document create(final Request request, final DocumentTemplate template, final String createdBy) throws ValidationException {
		ValidationHelper.required("Request", request);
		ValidationHelper.required("Document Template", template);
		ValidationHelper.required("Created By", createdBy);
		
		Document doc = new Document();
		doc.version = 0; // the Hibernate row locking version, not the document version number
		doc.createdBy = createdBy;
		doc.createdOn = new Date();
		doc.versionNumber = 1;
		doc.request = request;
		
		// copy some properties from the template to the document
		doc.template = template;
		doc.name = template.getName();
		doc.sortOrder = template.getSortOrder();
		
		doc.description = template.getName();	//copy the name into the document description (template description originally null)
		
		DartObjectFactory.getInstance().getDocumentDAO().save(doc);
		
		doc.addLabel(CURRENT_LABEL);
		doc.head = doc.id;

		DartObjectFactory.getInstance().getDocumentDAO().save(doc);	//save the new head value (the ID of this newly created document)				
		
		return doc;
	}

	/** Create a copy of a document.
	 Only called when creating a request amendment.  A copy is not another version of a document, so it is not connected to the previous version chain.
	 The copy is the head of a new version chain and has version #1.  Also, a copy has the current label whether or not the document it copied did.  
	 The document copy points to the same content as the original document.
	 
	 @param request which request these documents live under.
	 @param createdBy username of the user to attribute the new documents to.
	 @param incrementVersion if true, increment the version number of the new document.
	 @param setCreatedOn if true, set the createdOn date/time to now, else leave it with the original value.
	 * @return
	 * @throws ValidationException
	 */
	public Document copy(final Request request, final String createdBy, final boolean incrementVersion, boolean setCreatedOn) throws ValidationException {

		ValidationHelper.required("Request", request);
		ValidationHelper.required("Created By", createdBy);
		
		Document doc = new Document();		
		
		doc.version = 0; // the Hibernate row locking version, not the document version number
		doc.createdBy = createdBy;
		
		if (setCreatedOn) {
			doc.createdOn = new Date();
		}
		else {
			doc.createdOn = this.createdOn;
		}
		
		if( incrementVersion && this.updatedBy != null ) {	//don't increment the version number when creating an amendment (or if nothing yet uploaded)	
			doc.versionNumber = this.versionNumber + 1;
		} else {
			doc.versionNumber = this.versionNumber;	// TODO- I think the new document should have version number 1
		}//end else
		
		
		doc.request = request;
		
		doc.content = this.content;
		doc.typeSuffix = this.typeSuffix;
		doc.template = this.template;
		doc.name = this.name;
		doc.description = this.description;
		doc.head = this.head;
		doc.sortOrder = this.sortOrder;

		if( this.updatedBy != null ) {
			doc.updatedBy = this.updatedBy;
			
			if (setCreatedOn) {
				doc.updatedBy = doc.createdBy;
				doc.updatedOn = new Date();
			}
			else {
				doc.updatedOn = this.updatedOn;
			}
		}//end if

		DartObjectFactory.getInstance().getDocumentDAO().save(doc);

		doc.head = doc.id;	//start at the current document version
		
		doc.addLabel(CURRENT_LABEL);

		DartObjectFactory.getInstance().getDocumentDAO().save(doc);	//save the new head value (the ID of this newly created document)		
		
		return doc;
	}

	/** Create a new version of an existing document. Does not initialize the document content.
	 * 
	 * @param createdBy
	 * @return
	 */
	public Document createNewVersion(final String createdBy) {
		//TODO:  we might want to copy over the remaining attributes (who did the copy, and specifically, what location is this document tied to)...
		Document doc = new Document();
		doc.version = 0; // the Hibernate row locking version, not the document version number
		doc.head = this.head;
		doc.createdBy = createdBy;
		doc.createdOn = new Date();
		doc.versionNumber = this.versionNumber + 1;
	
		// copy some properties from previous to the new document
		doc.template = this.template;
		doc.name = this.name;
		doc.description = this.description;
		doc.request = this.request;
		doc.sortOrder = this.sortOrder;
		doc.content = this.content;
		
		// note: no content is initially allocated to this object.
		DartObjectFactory.getInstance().getDocumentDAO().save(doc);

		//this.removeLabel(CURRENT_LABEL);
		DartObjectFactory.getInstance().getLabelDAO().deleteAllCurrentLabels(this.getId());

		doc.addLabel(CURRENT_LABEL);

		DartObjectFactory.getInstance().getDocumentDAO().save(doc);	//save the new head value (the ID of this newly created document)		
		
		return doc;
	}

	public Content createContent(final String typeSuffix) {
		this.content  = Content.create(typeSuffix, Repository.defaultRepository);
		
		return this.content;
	}

	public static Document getCurrentOrMostRecentDocument(final int documentId) {
		
		Document doc = null;
		try {
			doc = Document.findById(documentId);
			if( doc != null ) {
				
				if (doc.hasLabel(Document.CURRENT_LABEL) == false) {
					// find the current document
					Document currentDoc = Document.findCurrentVersion(doc.getId());
					
					if (currentDoc == null) {
						currentDoc = Document.findMostRecentVersion(doc.getId());
					}
					
					if (currentDoc != null) {
						doc = currentDoc;
					}
				}
	
			}
		} catch( NoResultException e) {
			log.error("Error loading the document: " + documentId);
		}
		
		return doc;
	}
	
	public static Document findById(int documentId) {
		return DartObjectFactory.getInstance().getDocumentDAO().findById(documentId);
	}
	
	public static List<Document> listVersionsById(int documentId) {
		return DartObjectFactory.getInstance().getDocumentDAO().listVersionsById(documentId);
	}
	
	public static List<Document> listVersionsByReqIdandTemplIdandContId(int requestId, int templateId, int contentId) {
		//TODO: contentid can be NULL
		return DartObjectFactory.getInstance().getDocumentDAO().listVersionsByReqIdandTemplIdandContId(requestId, templateId, contentId);
	}	
	
	public static List<Document> listVersionsByReqIdandTemplId(int requestId, int templateId) {
		return DartObjectFactory.getInstance().getDocumentDAO().listVersionsByReqIdandTemplId(requestId, templateId);
	}	

	public static List<Document> listByContentId(int contentId) {
		return DartObjectFactory.getInstance().getDocumentDAO().listByContentId(contentId);
	}

	public static List<Document> listByRepositoryId(int repositoryId) {
		return DartObjectFactory.getInstance().getDocumentDAO().listByRepositoryId(repositoryId);
	}
	
	public static List<Document> listByRequestAndNonNullContentId(final int requestId, final int contentId) {
		return DartObjectFactory.getInstance().getDocumentDAO().listByRequestAndNonNullContentId(requestId, contentId);
	}
	
	public static int countRequestAndNonNullContentId(final int requestId, final int contentId) {
		return DartObjectFactory.getInstance().getDocumentDAO().countRequestAndNonNullContentId(requestId, contentId);
	}
	
	public static Document findCurrentVersion(final int documentId) {
		log.debug("findCurrentVersion for document " + documentId);
		return DartObjectFactory.getInstance().getDocumentDAO().findCurrentVersion(documentId);
	}

	public static Document findMostRecentVersion(final int documentId) {
		log.debug("findMostRecentVersion for document " + documentId);
		return DartObjectFactory.getInstance().getDocumentDAO().findMostRecentVersion(documentId);
	}	
	
	public static Document findMostRecentSortorder(final int requestId, final int sortorderId, final int contentId) {
		log.debug("findMostRecentSortorder for sortorder " + sortorderId);
		//TODO: contentid can be NULL
		return DartObjectFactory.getInstance().getDocumentDAO().findMostRecentSortorder(requestId, sortorderId, contentId);
	}
	
	public static String findLocationByDocId(final int documentId) {
		log.debug("findLocationByDocId for document " + documentId);
		return (String)DartObjectFactory.getInstance().getDocumentDAO().findLocationByDocId(documentId);
	}
	
	public static String findParticipantNameByDocId(final int documentId) {
		log.debug("findParticipantNameByDocId for document " + documentId);
		return (String)DartObjectFactory.getInstance().getDocumentDAO().findParticipantNameByDocId(documentId);
	}

	public void delete() {
		log.debug("Deleting document " + id);
		DartObjectFactory.getInstance().getDocumentDAO().delete(this);
	}
	
	public Content getContent() {
		return content;
	}

	public void setContent(final Content content) {
		this.content = content;
	}
	
	public int getVersionNumber() {
		return versionNumber;
	}

	public int getHead() {
		return head;
	}
	
	public int getSortOrder() {
		return sortOrder;
	}

	public DocumentTemplate getDocumentTemplate() {
		return template;
	}

	public Set<Label> getLabels() {
		Set<Label> result = labels;
			
		if (result == null) {
			labels = new HashSet<Label>();
			result = labels;
		}

		return result;
	}
	
	public void addLabel(final String value) {
		if (hasLabel(value) == false) {
			Label lbl = Label.create(this, value);
			getLabels().add(lbl);
		}
	}

	public void removeLabel(final String value) {
		log.debug("removing label " + value + " for document " + this.id);

		Label lbl = findLabel(value);
		if (lbl != null) {
			labels.remove(lbl);
			lbl.delete();
		}
	}

	private Label findLabel(final String value) {
		log.debug("find label " + value + " for document " + this.id);
		
		Set<Label> labelSet = getLabels();
		if (labelSet != null) {
			for (Label lbl : labelSet) {
				if (lbl != null) {  // a null in the set? really?
					String lblValue = lbl.getValue();
					if (value != null && lblValue != null && value.equals(lblValue) == true) {
						return lbl;
					}
				}
			}
		}
		
		return null;
	}
	
	public boolean isNotCurrent() {
		return hasLabel(Document.CURRENT_LABEL) == false;
	}
	
	public boolean hasLabel(final String value) {
		return findLabel(value) != null;
	}
	
	public Map<String,String> getAttributes() {
		if (attributes == null) {
			attributes = new HashMap<String,String>();
			for (Attribute attr : attrs) {
				attributes.put(attr.getKey(), attr.getValue());
			}
		}
		
		return attributes;
	}
	
	public String getAttribute(final String key) {
		return getAttributes().get(key);
	}
	
	public boolean hasAttribute(final String key) {
		return getAttribute(key) != null;
	}
	
	public void setAttribute(final String key, final String value) {
		
		for (Attribute attr : attrs) {
			if (attr.getKey().equals(key) == true) {
				attr.modify(value);
				getAttributes().put(key,  value);
				return;
			}
		}
		
		Attribute newAttr = Attribute.create(this, key, value);
		attrs.add(newAttr);
		getAttributes().put(key,  value);
		
	}
	
	public String getTypeSuffix() {
		return typeSuffix;
	}

	public void setTypeSuffix(final String typeSuffix) {
		this.typeSuffix = typeSuffix;
	}


	public boolean isUploaded() {
		
		if (getContent() == null || getContent().getRepositoryPath() == null) {
			return false;
		}

		return true;
	}

	@Override
	public int hashCode() {
		return id;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		
		if (Document.class.equals(obj.getClass()) == false) {
			return false;
		}
		
		return this.getId() == ((Document)obj).getId();
	}
	
	@Override
	public int compareTo(Object o) {
		if (o == null) {
			return -1;
		}
		if (Document.class.isAssignableFrom(o.getClass()) == false) {
			return -1;
		}
		
		Document doc2 = (Document)o;
		if (this.getName() == null) {
			return doc2.getName() == null ? 0 : -1;
		}

		// sort by sort order
		if (this.getSortOrder() == doc2.getSortOrder()) {
			return 0;
		}
		
		return this.getSortOrder() < doc2.getSortOrder() ? -1 : 1;
	}

}
