/* Copyright 2008 we-lab-doc! (http://www.we-lab-doc.com/) 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.we_lab_doc.spacecard.model.abstracts
 
import com.we_lab_doc.spacecard.model.*
import com.we_lab_doc.spacecard.utils.*
import com.we_lab_doc.spacecard.utils.ModelUtils as MU
import com.we_lab_doc.spacecard.model.exception.ModelOperationFailedException

/**
 * @author Shigeru GOUGI
 * @since 0.1
 *        <p/>
 *        Created: February 24, 2008
 */
/* abstract */ class WorkCard extends DescribableCard implements FootprintableCard {
	boolean remove() {
		if(this.removeRelatedWorks()
		&& this.removeFootprints()
		&& this.removeTags()
		&& this.removeAssigns()
		&& this.removeAttachedFiles()
		&& this.removeUrls()
		&& this.removeComments()
		&& super.remove() ) {
			return true
		}
		return false	
	}
	boolean isClosed() {
		return closer != null
	}
	boolean close() {
		def user = SpaceCardModel.accessControlService.getLoggedInUser()		
		assert user != null		
		this.closer = user
		return ModelUtils.save(this)
	}	
	////
	WorkCard add(WorkCardExternalURL attachment){
		assert attachment != null
		addToUrls(attachment)
		return this
	}
	WorkCard add(WorkCardAttachedFile attachment) {
		assert attachment != null
		addToAttachedFiles(attachment)
		return this
	}
	WorkCard add(Comment comment) {
		assert comment != null
		addToComments(comment)
		return this
	}
	WorkCard add(RelatedWork relatedWork) {
		assert relatedWork != null
		addToRelatedWorks(relatedWork)
		return this		
	}
	////
	SpaceCard leaveFootprint(User user) {
		assert user != null
		def footprints = WorkCardFootprint.select("where w.user=:user and w.workCard=:workCard", [user:user, workCard:this])
		assert footprints.size() <= 1
		def footprint
		if(footprints.size() == 0 ) {
			footprint = new WorkCardFootprint([user:user])
			this.addToFootprints(footprint)			
		} else {
			footprint = footprints[0]
			footprint.update()
		}
		return this
	}
	SpaceCard leaveFootprint() {
		def acs = SpaceCardModel.accessControlService
		return leaveFootprint(acs.getLoggedInUser())
	}	
	////
	Collection getLeadersAsAssign(boolean includesInactive, Map paginateParams) {
		return _getAssigns(PartType.LEADER, includesInactive, paginateParams)
	}	
	Collection getWorkersAsAssign(boolean includesInactive, Map paginateParams) {
		return _getAssigns(PartType.WORKER, includesInactive, paginateParams)
	}
	int countOfLeadersAsAssign(boolean includesInactive) {
		return _countOfAssigns(PartType.LEADER, includesInactive)
	}
	int countOfWorkersAsAssign(boolean includesInactive) {
		return _countOfAssigns(PartType.WORKDER, includesInactive)
	}
	////
	Collection getLeaders(boolean includesInactive, Map paginateParams) {
		return _getUsers(PartType.LEADER, includesInactive, paginateParams)
	}	
	Collection getWorkers(boolean includesInactive, Map paginateParams) {
		return _getUsers(PartType.WORKER, includesInactive, paginateParams)
	}	
	Collection getUsers(boolean includesInactive, Map paginateParams) {
		return _getUsers(null, includesInactive, paginateParams) 
	}
	int countOfLeaders(boolean includesInactive) {
		return _countOfUsers(PartType.LEADER, includesInactive)
	}
	int countOfWorkers(boolean includesInactive) {
		return _countOfUsers(PartType.WORKER, includesInactive)
	}
	int countOfUsers(boolean includesInactive) {
		return _countOfUsers(null, includesInactive)
	}	
	////
	Assign asAssign(User user) {
		if(user == null) return null
		return Assign.findByUserAndWorkCard(user, this)
	}
	////
	////
	WorkCard updateTags(Collection updTagNames) throws ModelOperationFailedException {
		assert updTagNames != null
		def tagNames = []
		updTagNames.each {
			if(it != null && it.trim() != "") {
				tagNames << it.trim()
			}
		}
		tagNames.unique()
		def curTagNames = []
		this.tags?.each {
			curTagNames << it.name
		}
		curTagNames.unique()
		def intsecTagNames = curTagNames.intersect(tagNames)
		def addTagNames = tagNames.minus(intsecTagNames)
		for (name in addTagNames) {
			addTag(name)
		}
		def delTagNames = curTagNames.minus(intsecTagNames)
		for (name in delTagNames) {
			removeTag(name)
		}
		return this
	}
	////
	boolean isAssignedAlready(User user) {
		return asAssign(user) != null
	}
	////
	WorkCard assign(User user, String partType) throws ModelOperationFailedException {
		assert user != null && partType != null
		if(isAssignedAlready(user)) {
			if(!unassign(user)) return this
		}
		def	assign = new Assign(user:user,partType:partType)
		this.addToAssigns(assign)
		// TODO make PermissionAttributes		
		if(!MU.save(assign))
			throw new ModelOperationFailedException("assign() failed: WorkCard=${this}, user=${user}, partType=${partType}")
		return this
	}	
	WorkCard unassign(User user) throws ModelOperationFailedException {
		assert user != null	
		def assign = asAssign(user)
		if(assign) {
			this.removeFromAssigns(assign)
			// TODO make PermissionAttributes
			if(!assign.remove())
				throw new ModelOperationFailedException("unassign() failed: WorkCard=${this}, user=${user}, partType=${partType}")			
		}
		return this		
	}
	/////////////////////////////////////////////
	protected boolean removeComments(){
		for (comment in MU.cloneCollection(this.comments)) {
			if(!comment.remove()) return false
		}
		return true
	}			
	protected boolean removeUrls(){
		for (url in MU.cloneCollection(this.urls)) {
			if(!url.remove()) return false
		}
		return true
	}		
	protected boolean removeAttachedFiles(){
		for (attachedFile in MU.cloneCollection(this.attachedFiles)) {
			if(!attachedFile.remove()) return false
		}
		return true
	}	
	protected boolean removeAssigns(){
		for (assign in MU.cloneCollection(this.assigns)) {
			if(!assign.remove()) return false
		}
		return true
	}
	protected boolean removeRelatedWorks(){
		for (relatedWork in MU.cloneCollection(this.relatedWorks)) {
			if(!relatedWork.remove()) return false
		}
		return true
	}
	protected boolean removeTags() {
		for (tag in MU.cloneCollection(this.tags) ) {
			this.removeFromTags(tag)
			if (tag.entries == null || tag.entries.size() == 0) {
				if(!tag.remove()) return false
			}
		}
		return true		
	}
	protected boolean removeFootprints() {
		for (footprint in ModelUtils.cloneCollection(this.footprints) ) {
			if(! footprint.remove()) return false
		}
		return true
	}
	/////////////////////////////////////////////
	private WorkCard addTag(String tagName) {
		assert tagName != null
		WorkCardTag tag = WorkCardTag.findByName(tagName)
		if(tag){
			if(MU.containsUsingId(this.tags, tag)) return this
		} else {
			tag = new WorkCardTag([name:tagName])
		}
		this.addToTags(tag)
		return this
	}
	private WorkCard removeTag(String tagName) throws ModelOperationFailedException {
		assert tagName != null
		WorkCardTag tag = WorkCardTag.findByName(tagName)
		if(!tag) return this
		if(!MU.containsUsingId(this.tags, tag)) return this
		this.removeFromTags(tag)
		if (tag.workCards == null || tag.workCards.size() == 0) {
			if(!tag.remove()) 
				throw new ModelOperationFailedException("removeTag failed; WorkCard = ${this}, tag = ${tagName}")
		}
		return this
	}
	////
	private int _countOfUsers(String partType, boolean includesInactive) {
		def qs = _getUsersQS(partType, includesInactive)
		Integer[] counts = SpaceCardModel.executeQuery(
				"select count(u) from User as u ${qs}", [workCard:this]
			)
		return counts[0].intValue()		
	}
	private Collection _getUsers(String partType, boolean includesInactive, Map paginateParams) {
		def qs = _getUsersQS(partType, includesInactive)
		Map sParams = PaginateUtils.makeSelectParams(paginateParams, [workCard:this])
		return User.select("${qs} ${sParams.orderByClause}", sParams.args)
	}
	private String _getUsersQS(String partType, boolean includesInactive) {
		def checkActiveStatement = ""
		if(!includesInactive) {
			checkActiveStatement = "and u.active=true"
		}
		def checkPartTypeStatement = ""		
		if(partType) {
			checkPartTypeStatement = "and a.partType='${partType}'"
		}
		return """,Assign as a 
	    			where a.workCard=:workCard
	    			and a.user=u 
	    			${checkPartTypeStatement} ${checkActiveStatement}""" 
	}	
	////
	private int _countOfAssigns(String partType, boolean includesInactive) {
		def qs = _getAssignsQS(partType, includesInactive)
		Integer[] counts = SpaceCardModel.executeQuery(
				"select count(a) from Assign as a ${qs}", [workCard:this]
			)
		return counts[0].intValue()
	}
	private Collection _getAssigns(String partType, boolean includesInactive, Map paginateParams){
		def qs = _getAssignsQS(partType, includesInactive)
		Map sParams = PaginateUtils.makeSelectParams(paginateParams, [workCard:this])
		return Assign.select(
			"${qs} ${sParams.orderByClause}" , 
			sParams.args)			
	}
	private String _getAssignsQS(String partType, boolean includesInactive){
		def checkActiveStatement = ""
		if(!includesInactive) {
			checkActiveStatement = "and a.user.active=true"
		}
		def checkPartTypeStatement = ""
		if(partType) {
			checkPartTypeStatement = "and a.partType='${partType}'"
		}
		return "where a.workCard=:workCard ${checkPartTypeStatement} ${checkActiveStatement}"
	}	
	/////////////////////////////////////////////
	def addToComments(Comment obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, WorkCardComment.class, "comments", "workCard"  
				)	
	}
	def removeFromComments(Comment obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "comments", "workCard" 
				)		
	}
	def addToRelatedWorks(RelatedWork obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, RelatedWork.class, "relatedWorks", "workCard"  
				)	
	}
	def removeFromRelatedWorks(RelatedWork obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "relatedWorks", "workCard" 
				)		
	}
	def addToAssigns(Assign obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, Assign.class, "assigns", "workCard"  
				)	
	}
	def removeFromAssigns(Assign obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "assigns", "workCard" 
				)		
	}
	def addToAttachedFiles(WorkCardAttachedFile obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, WorkCardAttachedFile.class, "attachedFiles", "workCard"  
				)	
	}
	def removeFromAttachedFiles(WorkCardAttachedFile obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "attachedFiles", "workCard" 
				)		
	}
	def addToUrls(WorkCardExternalURL obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, WorkCardExternalURL.class, "urls", "workCard"  
				)	
	}
	def removeFromUrls(WorkCardExternalURL obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "urls", "workCard" 
				)		
	}
	def addToTags(WorkCardTag obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToMany(
				this, Set.class, obj, WorkCardTag.class, "tags", "workCards"  
			)
	}
	def removeFromTags(WorkCardTag obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToMany(
				this, obj, "tags", "workCards" 
			)
	}
	def addToFootprints(WorkCardFootprint obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, WorkCardFootprint.class, "footprints", "workCard"
				)	
	}
	def removeFromFootprints(WorkCardFootprint obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "footprints", "workCard" 
				)		
	}
	/////////////////////////////////////////////
	protected onBeforeUpdate(obj) {
		if( obj.createdDate == null ) {
			assert false
		} else {
			if(obj.closer != null && obj.closedDate == null) {
				obj.closedDate = new Date()
			} else {
				if( obj.closer == null && obj.closedDate != null ){
					obj.closedDate == null
				}
				def acs = SpaceCardModel.accessControlService
				obj.updater = acs.getLoggedInUser()
				obj.updatedDate = new Date()				
			}
		}
	}	
	/////////////////////////////////////////////
	static transients = ['permission','closed']
	static hasMany     = [comments:Comment,
	                      assigns:Assign,
	                      relatedWorks:RelatedWork,
	                      urls:WorkCardExternalURL,
	                      attachedFiles:WorkCardAttachedFile,
	                      tags:WorkCardTag,
	                      footprints:WorkCardFootprint,
	                      ]
	static constraints = {
		beginExpectedDate(nullable:true)
		endExpectedDate(nullable:true)
		beginActualDate(nullable:true)
		endActualDate(nullable:true)
		assigns()
		title(blank:false)		
		closer(nullable:true)
		closedDate(nullable:true)		
	}
	/////////////////////////////////////////////
	String title
	Date beginExpectedDate
	Date endExpectedDate
	Date beginActualDate
	Date endActualDate
	
	Date closedDate		
	User closer
}
