/* 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

import com.we_lab_doc.spacecard.model.abstracts.*
import com.we_lab_doc.spacecard.utils.*
import com.we_lab_doc.spacecard.model.exception.ModelOperationFailedException

/**
 * @author Shigeru GOUGI
 * @since 0.1
 *        <p/>
 *        Created: February 13, 2008
 */ 
final class Organization implements SpaceCardDomainModel {
	boolean remove() {
		if( ModelUtils.isEmpty(workspaces) ) {
			if( ModelUtils.isEmpty(children) ) {
				if( ModelUtils.isEmpty(cardHolders) ) {
					for(role in ModelUtils.cloneCollection(roles) ) {
						if(! role.remove()) return false
						return ModelUtils.delete(this)
					}					
				}
			}
		}
		return false
	}
	/////////////////////////////////////////////
	boolean isRoot() {
		return parent == null
	}
	boolean isSupervisorCommunity() {
		def root = this.getRootOrganization()
		return root.isSupervisorCommunity
	}
	boolean isCommunity() {
		def root = this.getRootOrganization()
		return root.isCommunity		
	}
	boolean isCompany() {
		def root = this.getRootOrganization()
		return (!root.isCommunity) && (!root.isSupervisorCommunity)
	}
	boolean isSubOrganization() {
		return this.parent != null
	}
	boolean isRootOrganization() {
		return this.parent == null
	}
	////
	Role getAdminRole(){
		return getRole(AuthorityType.ADMIN)
	}
	Role getUserRole(){
		return getRole(AuthorityType.USER)
	}
	////
	Organization getRootOrganization() {
		def org = this
		while(org.parent != null)
			org = org.parent
		return org
	}
	////
	Collection getAdmins(boolean includesInactive, Map paginateParams) {
		return _getUsers(includesInactive, null, paginateParams)
	}	
	Collection getUsers(boolean includesInactive, boolean includesAsAdmin, Map paginateParams) {
		return _getUsers(includesInactive, (includesAsAdmin)? Boolean.TRUE: Boolean.FALSE, paginateParams)
	}	
	int countOfAdmins(boolean includesInactive) {
		return  _countOfUsers(includesInactive, null)
	}	
	int countOfUsers(boolean includesInactive, boolean includesAsAdmin) {
		return  _countOfUsers(includesInactive, includesAsAdmin)		
	}
	////
	Organization addUser(User user) throws ModelOperationFailedException {
		assert user != null		
		for (role in this.roles) {
			if(role.isUserAuthority()) {
				role.addToUsers(user)
				return this
			}
		}
		throw new ModelOperationFailedException("bug?; addUser")
	}
	Organization removeUser(User user) throws ModelOperationFailedException {
		assert user != null
		def roles = user.getRoles(user, this)
		for (role in roles ) {
			if(role.isUserAuthority()) {
				role.removeFromUsers(user)
				return this
			}
		}
		throw new ModelOperationFailedException("bug?; removeUser")
	}
	Organization addAdmin(User user) throws ModelOperationFailedException {
		assert user != null		
		for (role in this.roles) {
			if(role.isAdminAuthority()) {
				role.addToUsers(user)
				return this
			}
		}
		throw new ModelOperationFailedException("bug?; addAdmin")		
	}
	Organization removeAdmin(User user) throws ModelOperationFailedException {
		assert user != null
		def roles = Role.getRoles(user, this)
		if (roles.size() == 0) return this
		for (role in roles ) {
			if(role.isAdminAuthority()) {
				role.removeFromUsers(user)
				return this
			}
		}
		throw new ModelOperationFailedException("bug?; removeAdmin")		
	}	
	////
	boolean belongsTo(User user) {
		return belongsTo(user, true) 
	}
	boolean belongsTo(User user, boolean includesInactive) {
		if(user == null) return false
		if( !includesInactive && !user.active) return false
		if( includesInactive || this.active ){
			return user.isAdmin(this) || user.isUser(this)
		}
		return false
	}
	boolean deepBelongsTo(User user) {
		return deepBelongsTo(user, true)
	}
	boolean deepBelongsTo(User user, boolean includesInactive) {
		if(belongsTo(user, includesInactive)) return true
		if(this.children == null) return false
		for(subOrg in this.children) {
			if(subOrg.deepBelongsTo(user, includesInactive)) return true
		}
		return false
	}	
	////
	Organization moveUserOnSameOrganizationStructure(User user, Organization destOrg) throws ModelOperationFailedException {
		assert user != null && destOrg != null
		def destRoles = user.getRoles(destOrg)
		if(Role.hasUserAuthority(destRoles)) {
			throw new ModelOperationFailedException("bug?; moveUserOnSameOrganizationStructure: if(Role.hasUserAuthority(${destRoles}))")
		}
		def srcRoles = user.getRoles(this)
		if(Role.hasUserAuthority(srcRoles)) {
			if(! removeUser(user) ) {
				throw new ModelOperationFailedException("bug?; moveUserOnSameOrganizationStructure: if(! removeUser(${user}) )")
			}
		}
		return addUser(user)
	}
	Organization activate()  {
		this.active = true
		return this
	}	
	Organization deactivate() {
		// TODO inactive に出来る条件を明確にする必要がある。;RuntimeExceptionを投げるようになる可能性がある。
		this.active = false
		this.children.each { org ->
			org.deactivate()
		}
		// TODO role をremoveするかどうか。
		return this
	}
	/////
	CardHolder getCardHolder(String name) {
		assert name != null
		def cardHolders = OrganizationCardHolder.select("where name=:name and organization=:org", [name:name, org:this])
		if(cardHolders.size() == 0) return null
		return cardHolders[0]
	}
	CardHolder createCardHolder(String name) {
		assert name != null
		return OrganizationCardHolder.create([name:name, organization:this])
	}
	/////
	static Organization getSupervisorCommunity(){
		def os = Organization.select("where o.isSupervisorCommunity=true")
		if( os.size() == 0 ) return null
		if( os.size() == 2 ) throw new IllegalStateException("bug: getSupervisorCommunity() ; os.size() == 2")
		return os[0]		
	}	
	/////
	static boolean isSameRoot(Organization org1, Organization org2) {
		assert org1 != null && org2 != null
		def org1root = org1.getRootOrganization()
		assert org1root != null
		def org2root = org2.getRootOrganization()
		assert org2root != null
		return org1root.id == org2root.id 
	}
	static Organization createSupervisorCommunity() {
		def scom = getSupervisorCommunity()
		if(scom!=null) throw new IllegalStateException("SupervisorCommunity is single!")
		def props = SpaceCardModel.props
		def org = new Organization(
				name:props.supervisorCommunityName,
				isSupervisorCommunity:true
			)
		def role = Role.createAdminRole(org)
		assert role != null 
		return org
	}
	static Organization createCommunity(String name, String description) {
		assert name != null && description != null
		return createCommunity(name:name, description:description)
	}
	static Organization createCommunity(params) {
		// TODO 名前の重複チェックをするか
		def org = new Organization(params)
		org.isCommunity = true
		def role = Role.createAdminRole(org)
		assert role != null
		role = Role.createUserRole(org)
		assert role != null
		return org
	}
	static Organization createCompany(String name, String description) {
		assert name != null && description != null		
		return createCompany(name:name, description:description) 
	}
	static Organization createCompany(params) {
		// TODO 名前の重複チェックをするか		
		def org = new Organization(params)
		def role = Role.createAdminRole(org)
		assert role != null
		role = Role.createUserRole(org)
		assert role != null
		return org
	}
	static Organization createSubOrganization(String name, String description, Organization parentOrganization) {
		return createSubOrganization([name:name, description:description], parentOrganization) 
	}
	static Organization createSubOrganization(params, Organization parentOrganization) {
		assert params != null && parentOrganization != null
		def org = new Organization(params)
		parentOrganization.addToChildren(org)
		def role = Role.createUserRole(org)
		assert role != null
		return org
	}
	/////////////////////////////////////////////
	private int _countOfUsers(boolean includesInactive, Boolean includesAsAdmin) {
		def qs = _getUsersQS(includesInactive, includesAsAdmin)
		def Integer[] counts = SpaceCardModel.executeQuery(
				"select distinct count(u) from User as u ${qs}",
				[org:this]) 
		return  counts[0].intValue()		
	}	
	private Collection _getUsers(boolean includesInactive, Boolean includesAsAdmin, Map paginateParams) {
		def qs = _getUsersQS(includesInactive, includesAsAdmin)
		Map sParams = PaginateUtils.makeSelectParams(paginateParams, [org:this])
		return User.selectDistinct(
				"${qs} ${sParams.orderByClause}",
				sParams.args)
	}
	private String _getUsersQS(boolean includesInactive, Boolean includesAsAdmin) {
		def checkIncludesAsAdminStatement = ""
		if(includesAsAdmin != null) {
			if(!includesAsAdmin.booleanValue())
				checkIncludesAsAdminStatement = "and r.authority!='${AuthorityType.ADMIN}'"
			else
				checkIncludesAsAdminStatement = "and r.authority!=null"				
		} else {
			checkIncludesAsAdminStatement = "and r.authority='${AuthorityType.ADMIN}'"
		}
		def checkIncludesInactiveStatement = ""
		if(!includesInactive) {
			checkIncludesInactiveStatement = "and u.active=true"
		}
		return "join u.roles as r where r.organization=:org ${checkIncludesAsAdminStatement} ${checkIncludesInactiveStatement}" 
	}
	////
	private Role getRole(String authority){
		assert authority != null
		def roles = Role.select("where r.authority=:authority and r.organization=:organization", [authority:authority, organization:this])
		if(roles.size() == 0) return null
		return roles[0]
	}		
	/////////////////////////////////////////////
	def addToChildren(Organization obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, Organization.class, "children", "parent"  
				)	
	}
	def removeFromChildren(Organization obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "children", "parent" 
				)		
	}
	def addToRoles(Role obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, Role.class, "roles", "organization"  
				)	
	}
	def removeFromRoles(Role obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "role", "organization" 
				)		
	}	
	def addToCardHolders(OrganizationCardHolder obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, OrganizationCardHolder.class, "cardHolders", "organization"  
				)	
	}
	def removeFromCardHolders(OrganizationCardHolder obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "cardHolders", "organization"
				)		
	}
	def addToWorkspaces(OrganizationWorkspace obj) {
		return CollectionBugEvasion.addToForBidirectionalManyToOne(
					this, Set.class, obj, OrganizationWorkspace.class, "workspaces", "organization"  
				)	
	}
	def removeFromWorkspaces(OrganizationWorkspace obj) {
		return CollectionBugEvasion.removeFromForBidirectionalManyToOne(
					this, obj, "workspaces", "organization"
				)		
	}	
	/////////////////////////////////////////////
	def beforeInsert = {
		ModelUtils.insertLog(this)
		createdDate = new Date()
	}
	def beforeUpdate = {
		ModelUtils.updateLog(this)
		updatedDate = new Date()
	}
	def beforeDelete = {
		ModelUtils.deleteLog(this)
	}	
	/////////////////////////////////////////////
	static transients  = ['adminRole',
	                      'userRole',
	                      'rootOrganization',
	                      'root',
	                      'supervisorCommunity',
	                      'community',
	                      'company',
	                      'subOrganization',
	                      'rootOrganization',
	                     ]
	static belongsTo   = [parent:Organization]	
	static hasMany     = [
	                      children:Organization, 
	                      roles:Role, 
	                      cardHolders:OrganizationCardHolder,
	                      workspaces:OrganizationWorkspace
	                     ]
	static constraints = {
		parent(
			nullable:true, // don't delete
			validator:{val, obj ->
				if(val && val.isCommunity || val.isSupervisorCommunity) {
					return ['invalid.validator.message']
				}
				return 
			}
		)
		name(blank:false, unique:false)
		active()
		description(nullable:true)
		createdDate(nullable:true)
		updatedDate(nullable:true)
		children()
	}
	/////////////////////////////////////////////
	String name
	boolean active = true
	boolean isCommunity = false
	boolean isSupervisorCommunity = false
	String description
	Date   createdDate
	Date   updatedDate

}