package org.gcube.portlet.user.userstatisticsportlet.server;

import java.text.DecimalFormat;
import java.util.Date;
import java.util.List;

import org.gcube.application.framework.core.session.ASLSession;
import org.gcube.application.framework.core.session.SessionManager;
import org.gcube.common.homelibrary.home.HomeLibrary;
import org.gcube.common.homelibrary.home.workspace.Workspace;
import org.gcube.common.scope.impl.ScopeBean;
import org.gcube.common.scope.impl.ScopeBean.Type;
import org.gcube.portal.custom.communitymanager.OrganizationsUtil;
import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper;
import org.gcube.portal.databook.server.DBCassandraAstyanaxImpl;
import org.gcube.portal.databook.server.DatabookStore;
import org.gcube.portal.databook.shared.Feed;
import org.gcube.portlet.user.userstatisticsportlet.client.UserStatisticsService;
import org.gcube.portlet.user.userstatisticsportlet.shared.PostsStatsBean;
import org.gcube.portlet.user.userstatisticsportlet.shared.UserInformation;
import org.slf4j.LoggerFactory;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.liferay.portal.kernel.util.WebKeys;
import com.liferay.portal.model.Contact;
import com.liferay.portal.model.User;
import com.liferay.portal.model.Website;
import com.liferay.portal.service.UserLocalServiceUtil;
import com.liferay.portal.service.WebsiteLocalServiceUtil;
import com.liferay.portal.theme.ThemeDisplay;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class UserStatisticsServiceImpl extends RemoteServiceServlet implements UserStatisticsService {

	// Logger
	private static final org.slf4j.Logger _log = LoggerFactory.getLogger(UserStatisticsServiceImpl.class);

	//dev user
	public static final String userid = "test.user";

	//dev vre
	private static final String vreID = "/gcube/devsec/devVRE";

	// Cassandra connection
	private DatabookStore store;

	@Override
	public void init() {
		// get connection to Cassandra
		_log.info("Getting connection to Cassandra..");
		store = new DBCassandraAstyanaxImpl();

	}

	@Override
	public void destroy(){
		// shutting down connection to Cassandra
		_log.info("Closing connection to Cassandra");
		store.closeConnection();
	}

	/**
	 * the current ASLSession
	 * @return the session
	 */
	private ASLSession getASLSession() {

		String sessionID = this.getThreadLocalRequest().getSession().getId();
		String user = (String) this.getThreadLocalRequest().getSession().getAttribute(ScopeHelper.USERNAME_ATTRIBUTE);

		if (user == null) {

			_log.warn("USER IS NULL setting " + userid + " and Running OUTSIDE PORTAL");
			user = getDevelopmentUser();
			SessionManager.getInstance().getASLSession(sessionID, user).setScope(vreID);

		}		

		return SessionManager.getInstance().getASLSession(sessionID, user);
	}

	/**
	 * Indicates whether the scope is the whole infrastructure.
	 * @return <code>true</code> if it is, <code>false</code> otherwise.
	 */
	private boolean isInfrastructureScope() {
		boolean toReturn = false;
		try {
			ScopeBean scope = new ScopeBean(getASLSession().getScope());
			toReturn = scope.is(Type.INFRASTRUCTURE);
			return toReturn;
		}
		catch (NullPointerException e) {
			_log.error("NullPointerException in isInfrastructureScope returning false");
			return false;
		}		
	}

	/**
	 * Online or in development mode?
	 * @return true if you're running into the portal, false if in development
	 */
	private boolean isWithinPortal() {
		try {
			UserLocalServiceUtil.getService();
			return true;
		} 
		catch (com.liferay.portal.kernel.bean.BeanLocatorException ex) {			
			_log.trace("Development Mode ON");
			return false;
		}			
	}

	/**
	 * when packaging test will fail if the user is not set to test.user
	 * @return .
	 */
	public String getDevelopmentUser() {
		String user = userid;
//		user = "massimiliano.assante";
		return user;
	}


	@Override
	public String getTotalSpaceInUse() {

		String storageInUse = null;

		// get the session
		ASLSession session = getASLSession();

		//username
		String userName = session.getUsername();

		//in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info)
		//this check just return nothing if that happens
		if (userName.compareTo(userid) == 0) {

			_log.debug("Found " + userName + " returning nothing");
			return null;
			
		}else{

			_log.info("Getting " + userName + " amount of workspace in use.");

			try{
				
				long init = System.currentTimeMillis();
				
				Workspace workspace = HomeLibrary.getUserWorkspace(userName);
				long storage = workspace.getDiskUsage();
				storageInUse = formatFileSize(storage);
				
				long end = System.currentTimeMillis();
				_log.info("[USER-STATISTICS] time taken to retrieve user space is " + (end - init) + "ms");

			}catch(Exception e){

				_log.error("Unable to retrieve workspace information!");

			}
		}
		return storageInUse;

	}

	@Override
	public int getProfileStrength() {

		int profileStrenght = -1;

		// get the session
		ASLSession session = getASLSession();

		//username
		String userName = session.getUsername();

		//in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info)
		//this check just return nothing if that happens
		if (userName.compareTo(userid) == 0) {
			_log.debug("Found " + userName + " returning nothing");
			return profileStrenght;
		}else{

			// valuate profile strength
			if(isWithinPortal()){

				try{

					// check if the avatar is present
					boolean avatarPresent = session.getUserAvatarId() != null;

					long init = System.currentTimeMillis();
					
					User user = OrganizationsUtil.validateUser(userName);
					profileStrenght = evaluateProfileStrenght(user, avatarPresent);
					
					long end = System.currentTimeMillis();
					_log.info("[USER-STATISTICS] time taken to evaluate user profile strenght is " + (end - init) + "ms");

				}catch(Exception e){

					_log.error("Profile strenght evaluation failed!!");

				}
			}
		}
		return profileStrenght;

	}

	@Override
	public UserInformation getUserSettings() {

		// get the session
		ASLSession session = getASLSession();

		//username
		String userName = session.getUsername();

		//in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info)
		//this check just return nothing if that happens
		if (userName.compareTo(userid) == 0) {
			_log.debug("Found " + userName + " returning nothing");
			return null;
		}

		if(isWithinPortal()){
			// If the user is in the root panel, we have to send him the overall number of posts made, comments/likes(received) and the space in use.
			// Otherwise we have to filter on the vre.
			boolean isInfrastructure = isInfrastructureScope();
			_log.info("User scope is " + (isInfrastructure ? " the whole infrastucture " : " a VRE"));

			// get path of the avatar
			String thumbnailURL = session.getUserAvatarId();
			_log.info(userName + " avatar has url " + thumbnailURL);

			// get the vre (if not in the infrastructure)
			String actualVre = null;

			if(!isInfrastructure){

				String[] temp = session.getScope().split("/");
				actualVre = temp[temp.length - 1];

			}

			// url account to change the avatar
			String accountURL = null;
			ThemeDisplay themeDisplay = (ThemeDisplay) this.getThreadLocalRequest().getSession().getAttribute(WebKeys.THEME_DISPLAY);
			accountURL = themeDisplay.getURLMyAccount().toString();
			_log.info("Account url for " + userName + " is "  + accountURL);

			return new UserInformation(isInfrastructure, thumbnailURL, userName, actualVre, accountURL);
		}
		else return new UserInformation(true, null, userName, null, null);
	}

	@SuppressWarnings("deprecation")
	@Override
	public PostsStatsBean getPostsStats(){
		
		// get the session
		ASLSession session = getASLSession();

		//username
		String userName = session.getUsername();

		//in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info)
		//this check just return nothing if that happens
		if (userName.compareTo(userid) == 0) {
			_log.debug("Found " + userName + " returning nothing");
			return null;
		}

		long totalFeeds = 0, totalLikes = 0, totalComments = 0;

		// check if the user is or not in a VRE
		boolean isInfrastructure = isInfrastructureScope();

		// date corresponding to one year ago
		Date oneYearAgo = new Date();
		oneYearAgo.setYear(oneYearAgo.getYear() - 1);

		_log.info("Reference time is " + oneYearAgo.toString());

		try {

			long init = System.currentTimeMillis();
			_log.info("Getting " + userName + " feeds in the last year.");

			List<Feed> userFeeds = store.getRecentFeedsByUserAndDate(userName, oneYearAgo.getTime());

			_log.info("Evaluating number of comments and likes of " + userName + "'s feeds.");

			for (Feed feed : userFeeds) {

				try{

					// check if the user is in the root, if not check if the VRE of the feed is the current one
					if(!isInfrastructure && !feed.getVreid().equals(session.getScope()))
						continue;

					// increment feeds number
					totalFeeds ++;

					//increment number of post replies and likes
					totalComments += Integer.parseInt(feed.getCommentsNo());
					totalLikes += Integer.parseInt(feed.getLikesNo());

				}catch(NumberFormatException e){
					_log.error(e.toString());
				}
			}

			long end = System.currentTimeMillis();
			
			_log.info("[USER-STATISTICS] time taken to retrieve and filter user feeds, get likes and replies got is " + (end - init) + "ms");
			_log.info("Total number of feeds (after time filtering) of  " + userName + " is " + totalFeeds);
			_log.info("Total number of likes (after time filtering) for " + userName + " is " + totalLikes);
			_log.info("Total number of comments (after time filtering) for " + userName + " is " + totalComments);

		}catch(Exception e){
			_log.error(e.toString());
			return null;
		}

		// return the object
		return new PostsStatsBean(totalFeeds, totalLikes, totalComments);
	}

	/**
	 * returns dynamically the formated size.
	 *
	 * @param size the size
	 * @return the string
	 */
	private static String formatFileSize(long size) {
		String formattedSize = null;

		double b = size;
		double k = size/1024.0;
		double m = ((size/1024.0)/1024.0);
		double g = (((size/1024.0)/1024.0)/1024.0);
		double t = ((((size/1024.0)/1024.0)/1024.0)/1024.0);

		DecimalFormat dec = new DecimalFormat("0.00");

		if ( t >= 1.0 ) {
			formattedSize = dec.format(t).concat(" TB");
		} else if ( g >= 1.0 ) {
			formattedSize = dec.format(g).concat(" GB");
		} else if ( m >= 1.0 ) {
			formattedSize = dec.format(m).concat(" MB");
		} else if ( k >= 1.0 ) {
			formattedSize = dec.format(k).concat(" KB");
		} else {
			formattedSize = dec.format(b).concat(" Bytes");
		}
		return formattedSize;
	}

	/**
	 * Evaluates the profile strenght of the user
	 * @param user 
	 * @return a int in [0, 100]
	 */
	private static int evaluateProfileStrenght(User user, boolean imageIsPresent) {

		int score = evaluateContactScore(user);
		score += evaluateInformationScore(user, imageIsPresent);

		return score;
	}


	/**
	 * Evaluates a score according to the information of the user such as job, organization, comments
	 * @param user
	 * @return a score in [0, 65]
	 */
	private static int evaluateInformationScore(User user, boolean imageIsPresent) {
		int score = 0;

		score += user.getJobTitle().compareTo("") != 0 ? 20 : 0;
		score += user.getOpenId().compareTo("") != 0 ? 20 : 0;
		String summary = getSummary(user);
		int lenght = summary.replace(" ", "").length(); 
		float partialScore = ((float)lenght / 10.0f);
		score +=  partialScore  > 20f ? 20 : (int)partialScore;

		if(imageIsPresent)
			score += 5;

		return score;
	}

	/**
	 * get the user's comment
	 * @param user
	 * @return
	 */
	private static String getSummary(User user) {
		String toReturn = escapeHtml(user.getComments());
		// replace all the line breaks by <br/>
		toReturn = toReturn.replaceAll("(\r\n|\n)"," <br/> ");
		// then replace all the double spaces by the html version &nbsp;
		toReturn = toReturn.replaceAll("\\s\\s","&nbsp;&nbsp;");
		return toReturn;
	}

	/**
	 * Escape an html string. Escaping data received from the client helps to
	 * prevent cross-site script vulnerabilities.
	 * 
	 * @param html the html string to escape
	 * @return the escaped string
	 */
	private static String escapeHtml(String html) {
		if (html == null) {
			return null;
		}
		return html.replaceAll("&", "&amp;").replaceAll("<", "&lt;")
				.replaceAll(">", "&gt;");
	}

	/**
	 * Evaluates user's contact information
	 * @param user
	 * @return a value in [0, 35]
	 */
	private static int evaluateContactScore(User user){

		int score = 0;

		try{
			Contact contact = user.getContact();

			score += contact.getMySpaceSn().compareTo("") != 0 ? 5 : 0;
			score += contact.getTwitterSn().compareTo("") != 0 ? 5 : 0;
			score += contact.getFacebookSn().compareTo("") != 0 ? 5 : 0;
			score += contact.getSkypeSn().compareTo("") != 0 ? 5 : 0;
			score += contact.getJabberSn().compareTo("") != 0 ? 5 : 0;
			score += contact.getAimSn().compareTo("") != 0 ? 5 : 0;

			List<Website> websites = WebsiteLocalServiceUtil.getWebsites(user.getCompanyId(), "com.liferay.portal.model.Contact", contact.getContactId());
			score += websites.size() > 0 ? 5 : 0; 
		}catch(Exception e ){

			_log.error("Contact profile score evaluation failed!!");
			score = 0;
		}

		return score;
	}
}
