/**
 * Copyright (c) 2008-2012 The Sakai Foundation
 *
 * Licensed under the Educational Community 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.osedu.org/licenses/ECL-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 org.sakaiproject.profile2.logic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lombok.Setter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sakaiproject.profile2.model.Person;
import org.sakaiproject.profile2.util.Messages;
import org.sakaiproject.profile2.util.ProfileConstants;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SitePage;
import org.sakaiproject.site.api.ToolConfiguration;
import org.sakaiproject.tool.api.Tool;
import org.sakaiproject.user.api.User;
import org.springframework.util.CollectionUtils;

/**
 * Implementation of ProfileWorksiteLogic API
 * 
 * @author d.b.robinson@lancaster.ac.uk
 */
public class ProfileWorksiteLogicImpl implements ProfileWorksiteLogic {

	private static final Logger log = LoggerFactory.getLogger(ProfileWorksiteLogicImpl.class);
	
	/**
	 * Profile2 creates <code>project</code> type worksites.
	 */
	private static final String SITE_TYPE_PROJECT = "project";
	
	/**
	 * Connections invited to worksites are initially given the
	 * <code>access</code> role.
	 */
	private static final String ROLE_ACCESS = "access";
	
	/**
	 * Users who create worksites are initially given the <code>maintain</code>
	 * role.
	 */
	private static final String ROLE_MAINTAIN = "maintain";
	
	/**
	 * The id of the worksite home page.
	 */
	private static final String TOOL_ID_HOME = "home";

	/**
	 * The id of the iframe tool.
	 */
	private static final String TOOL_ID_IFRAME = "sakai.iframe";
		
	/**
	 * The id of the synoptic calendar tool.
	 */
	private static final String TOOL_ID_SUMMARY_CALENDAR = "sakai.summary.calendar";
	
	/**
	 * The id of the synoptic announcements tool.
	 */
	private static final String TOOL_ID_SYNOPTIC_ANNOUNCEMENT = "sakai.synoptic.announcement";

	/**
	 * The id of the synoptic chat tool.
	 */
	private static final String TOOL_ID_SYNOPTIC_CHAT = "sakai.synoptic.chat";

	/**
	 * The id of the synoptic discussions tool.
	 */
	private static final String TOOL_ID_SYNOPTIC_DISCUSSION = "sakai.synoptic.discussion";
	
	/**
	 * The id of the synoptic message center tool.
	 */
	private static final String TOOL_ID_SYNOPTIC_MESSAGECENTER = "sakai.synoptic.messagecenter";
	
	/**
	 *  Map of synoptic tool and the related tool ids.
	 */
	private final static Map<String, List<String>> SYNOPTIC_TOOL_ID_MAP;
	static
	{
		SYNOPTIC_TOOL_ID_MAP = new HashMap<String, List<String>>();
		SYNOPTIC_TOOL_ID_MAP.put(TOOL_ID_SUMMARY_CALENDAR, new ArrayList<String>(Arrays.asList("sakai.schedule")));
		SYNOPTIC_TOOL_ID_MAP.put(TOOL_ID_SYNOPTIC_ANNOUNCEMENT, new ArrayList<String>(Arrays.asList("sakai.announcements")));
		SYNOPTIC_TOOL_ID_MAP.put(TOOL_ID_SYNOPTIC_CHAT, new ArrayList<String>(Arrays.asList("sakai.chat")));
		SYNOPTIC_TOOL_ID_MAP.put(TOOL_ID_SYNOPTIC_DISCUSSION, new ArrayList<String>(Arrays.asList("sakai.discussion")));
		SYNOPTIC_TOOL_ID_MAP.put(TOOL_ID_SYNOPTIC_MESSAGECENTER, new ArrayList<String>(Arrays.asList("sakai.messages", "sakai.forums", "sakai.messagecenter")));
	}
	
	/**
	 * Map of tools and the related synoptic tool ids.
	 */
	private final static Map<String, String> TOOLS_WITH_SYNOPTIC_ID_MAP;
	static
	{
		TOOLS_WITH_SYNOPTIC_ID_MAP = new HashMap<String, String>();
		TOOLS_WITH_SYNOPTIC_ID_MAP.put("sakai.schedule", TOOL_ID_SUMMARY_CALENDAR);
		TOOLS_WITH_SYNOPTIC_ID_MAP.put("sakai.announcements", TOOL_ID_SYNOPTIC_ANNOUNCEMENT);
		TOOLS_WITH_SYNOPTIC_ID_MAP.put("sakai.chat", TOOL_ID_SYNOPTIC_CHAT);
		TOOLS_WITH_SYNOPTIC_ID_MAP.put("sakai.discussion", TOOL_ID_SYNOPTIC_DISCUSSION);
		TOOLS_WITH_SYNOPTIC_ID_MAP.put("sakai.messages", TOOL_ID_SYNOPTIC_MESSAGECENTER);
		TOOLS_WITH_SYNOPTIC_ID_MAP.put("sakai.forums", TOOL_ID_SYNOPTIC_MESSAGECENTER);
		TOOLS_WITH_SYNOPTIC_ID_MAP.put("sakai.messagecenter", TOOL_ID_SYNOPTIC_MESSAGECENTER);
	}
	
	/**
	 * The tool to place on the home page.
	 */
	private static final String HOME_TOOL = "sakai.iframe.site";
	
	/**
	 * The tool used to modify the worksite after creation.
	 */
	private static final String SITEINFO_TOOL = "sakai.siteinfo";

	/**
	 * The tool used to unjoin worksites.
	 */
	private static final String MEMBERSHIP_TOOL = "sakai.membership";
	
	/**
	 * Worksite setup tools.
	 */
	private static final String WORKSITE_SETUP_TOOLS = "wsetup.home.toolids";
	
	/**
	 * {@inheritDoc}
	 */
	public boolean createWorksite(final String siteTitle, final String ownerId,
			final Collection<Person> members, boolean notifyByEmail) {

		// double-check permission
		if (false == sakaiProxy.isUserAllowedAddSite(ownerId)) {
			log .warn("user " + ownerId + " tried to create worksite without site.add");
			return false;
		}

		// ensure site id is unique
		String siteId = sakaiProxy.createUuid();
		while (true == sakaiProxy.checkForSite(siteId)) {
			siteId = sakaiProxy.createUuid();
		}

		final Site site = sakaiProxy.addSite(siteId, SITE_TYPE_PROJECT);
		
		if (null == site) {
			log.warn("unable to create new worksite from Profile2");
			return false;
		} else {
			// set initial site maintainer to Profile2 user creating worksite
			User owner = sakaiProxy.getUserById(ownerId);
			if (null != owner) {
				// false == provided
				site.addMember(ownerId, site.getMaintainRole(), true, false);
			} else {
				log.warn("unknown user " + ownerId + " tried to create worksite");
				return false;
			}

			addSiteMembers(members, site);
			
			addTitleAndDescription(siteTitle, ownerId, site);
			
			addHomePageAndTools(site);
			
			site.setPublished(true);
			
			if (false == sakaiProxy.saveSite(site)) {
				log.warn("unable to save new worksite from Profile2");
				return false;
			}
			
			emailSiteMembers(siteTitle, ownerId, members, notifyByEmail, site);
			
			return true;
		}
	}
	
	private void addHomePageAndTools(final Site site) {
		
		// we will always have a home page
		SitePage homePage = site.addPage();
		homePage.getPropertiesEdit().addProperty(
				SitePage.IS_HOME_PAGE, Boolean.TRUE.toString());
		
		Tool homeTool = sakaiProxy.getTool(HOME_TOOL);
		
		ToolConfiguration homeToolConfig = homePage.addTool();
		homeToolConfig.setTool(TOOL_ID_HOME, homeTool);
		homeToolConfig.setTitle(homeTool.getTitle());
					
		// normally brings in sakai.siteinfo
		List<String> toolIds = sakaiProxy.getToolsRequired(SITE_TYPE_PROJECT);
					
		int synopticToolIndex = addRequiredToolsForWorksite(site, homePage, toolIds);
		
		// for synoptic tools
		if (synopticToolIndex > 0) {
			homePage.setLayout(SitePage.LAYOUT_DOUBLE_COL);
		}
		
		addRequiredHomeTools(homePage, toolIds, synopticToolIndex);
	}
		
	private int addRequiredToolsForWorksite(final Site site,
			SitePage homePage, List<String> toolIds) {
		
		int synopticToolIndex = 0;
		
		for (String toolId : toolIds) {
			
			if (isToolToIgnore(toolId)) {
				continue;
			} else if (isToolWithSynopticTool(toolId)) {
				
				// add tool
				SitePage toolPage = site.addPage();
				toolPage.addTool(toolId);
				
				// add corresponding synoptic tool if not already added
				if (false == isToolAlreadyAdded(homePage, TOOLS_WITH_SYNOPTIC_ID_MAP.get(toolId))) {
					
					ToolConfiguration toolConfig = homePage.addTool(TOOLS_WITH_SYNOPTIC_ID_MAP.get(toolId));
					if (null != toolConfig) {
						toolConfig.setLayoutHints(synopticToolIndex + ",1");

						for (int i = 0; i < synopticToolIndex; i++) {
							toolConfig.moveUp();
						}
						
						synopticToolIndex++;
					}
				}
			} else if (null != sakaiProxy.getTool(toolId)) {
											
					SitePage toolPage = site.addPage();
					toolPage.addTool(toolId);
			}
		}
		return synopticToolIndex;
	}
	
	private void addRequiredHomeTools(SitePage homePage, List<String> toolIds,
			int synopticToolIndex) {
		
		// home tools specified in sakai.properties or default set of home tools
		List<String> homeToolIds = getHomeToolIds();
		
		for (String homeToolId : homeToolIds) {
			
			if (isToolToIgnore(homeToolId)) {
				continue;
			} else {
				
				// check for corresponding tool
				if (SYNOPTIC_TOOL_ID_MAP.get(homeToolId) != null &&
						CollectionUtils.containsAny(SYNOPTIC_TOOL_ID_MAP.get(homeToolId), toolIds)) {
											
					// check it hasn't been added already
					if (false == isToolAlreadyAdded(homePage, homeToolId)) {
													
						ToolConfiguration toolConfig = homePage.addTool(homeToolId);
						if (null != toolConfig) {
							toolConfig.setLayoutHints(synopticToolIndex + ",1");
			
							for (int i = 0; i < synopticToolIndex; i++) {
								toolConfig.moveUp();
							}
							
							synopticToolIndex++;
						}
					}
				}
			}
		}
	}
	
	private List<String> getHomeToolIds() {
		
		List<String> homeToolIds;

		if (null != sakaiProxy.getServerConfigurationParameter(WORKSITE_SETUP_TOOLS + "." + SITE_TYPE_PROJECT, null)) {
			homeToolIds = new ArrayList<String>(Arrays.asList(
					sakaiProxy.getServerConfigurationParameter(WORKSITE_SETUP_TOOLS + "." + SITE_TYPE_PROJECT, null)));
		} else if (null != sakaiProxy.getServerConfigurationParameter(WORKSITE_SETUP_TOOLS, null)) {
			homeToolIds = new ArrayList<String>(Arrays.asList(
					sakaiProxy.getServerConfigurationParameter(WORKSITE_SETUP_TOOLS, null)));
		} else {
			homeToolIds = new ArrayList<String>();
		}
		return homeToolIds;
	}
	
	private void addTitleAndDescription(final String siteTitle,
			final String ownerId, final Site site) {
		
		// finishing setting up site
		site.setTitle(siteTitle);
		// add description for editing the worksite
		site.setDescription(Messages.getString("worksite.help",
				new Object[] { sakaiProxy.getTool(SITEINFO_TOOL).getTitle(),
				sakaiProxy.getTool(MEMBERSHIP_TOOL).getTitle(),
				sakaiProxy.getUserDisplayName(ownerId),
				sakaiProxy.getUserEmail(ownerId),
				siteTitle}));
	}
	
	private void addSiteMembers(final Collection<Person> members,
			final Site site) {
		
		// user could create worksite without any connections (that's okay)
		if (null != members) {
			for (Person member : members) {
				User user = sakaiProxy.getUserById(member.getUuid());
				if (null != user) {						
					// false == provided
					site.addMember(member.getUuid(), sakaiProxy.getServerConfigurationParameter("profile2.worksite.access.role",ROLE_ACCESS), true, false);
				} else {
					log .warn("attempt to add unknown user " + member.getUuid() + " to worksite");
				}
			}
		}
	}

	private boolean isToolAlreadyAdded(SitePage homePage, String homeToolId) {
		for (ToolConfiguration tool : homePage.getTools()) {
			if (tool.getToolId().equals(homeToolId)) {
				return true;
			}
		}
		return false;
	}

	private boolean isToolWithSynopticTool(String toolId) {
		return TOOLS_WITH_SYNOPTIC_ID_MAP.containsKey(toolId);
	}
	
	private boolean isToolToIgnore(String toolId) {
		return toolId.equals(TOOL_ID_IFRAME) || toolId.equals(HOME_TOOL);
	}

	private void emailSiteMembers(final String siteTitle, final String ownerId,
			final Collection<Person> members, boolean notifyByEmail,
			final Site site) {
		
		if (true == notifyByEmail) {
			
			Thread thread = new Thread() {
				public void run() {
					emailSiteMembers(siteTitle, site.getUrl(), ownerId, members);		
				}
			};
			thread.start();
		}
	}
	
	private void emailSiteMembers(String siteTitle, String siteUrl, String ownerId,
			Collection<Person> members) {
		
		for (Person member : members) {
			if (true == member.getPreferences().isWorksiteNewEmailEnabled()) {
				emailSiteMember(siteTitle, siteUrl, ownerId, member);
			}
		}
	}

	private void emailSiteMember(String siteTitle, String siteUrl, String ownerId, Person member) {
		
		// create the map of replacement values for this email template
		Map<String, String> replacementValues = new HashMap<String, String>();
		replacementValues.put("senderDisplayName", sakaiProxy.getUserDisplayName(ownerId));
		replacementValues.put("worksiteTitle", siteTitle);
		replacementValues.put("worksiteLink", siteUrl);
		replacementValues.put("localSakaiName", sakaiProxy.getServiceName());
		replacementValues.put("localSakaiUrl", sakaiProxy.getPortalUrl());
		replacementValues.put("toolName", sakaiProxy.getCurrentToolTitle());
		
		sakaiProxy.sendEmail(member.getUuid(),
				ProfileConstants.EMAIL_TEMPLATE_KEY_WORKSITE_NEW, replacementValues);
	}

	@Setter
	private SakaiProxy sakaiProxy;
		
}
