/*
 *    UserTermInfo.java
 *    Copyright (C) 2008 New Zealand Digital Library, http://www.nzdl.org
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.greenstone.gsdl3.util;

import java.util.Set;
import java.util.TreeSet;

public class UserTermInfo
{
	private String username;
	private String password;
        /** The user-entered value of groups listing sorted into natural ordering
	 * (alphabetic/ASCII order in this case) and with duplicates removed */
        private String compactedGroups;
        /** The user-entered value of groups listing with hierarchical groups expanded, sorted and duplicates removed
	 * e.g. if groups="nz.ac.waikato.toto,nz.ac.waikato.cs.pinky"; expandedGroups="nz,nz.ac,nz.ac.waikato,nz.ac.waikato.cs,nz.ac.waikato.cs.pinky,nz.ac.waikato.toto" */
        private String expandedGroups;
	private String accountstatus;
	private String comment;
	private String email;

    // This constructor is called to instantiate a UserTermInfo object from entries in the userDB
    // which now stores expandedGroups instead of the user-entered groups or the latter's more compactedGroups form
    public UserTermInfo(String username, String password, String expandedGroups,
			String accountStatus, String comment, String email) {
	this.username = username;
	this.password = password;

	// Ensure these 2 representations of the groups listing are always in sync
	this.expandedGroups = expandedGroups;
	this.compactedGroups = UserTermInfo.compactGroups(this.expandedGroups); // alternative: don't set here and do lazy evaluation when getCompactedGroups() is called
	
	this.accountstatus = accountStatus;
	this.comment = comment;
	this.email = email;
    }
    
	public String toString()
	{
		String result = "";
		result += "<username = " + username + ">";
		result += "<password = " + password + ">";
		result += "<groups = " + expandedGroups + ">"; // compactedGroups?
		result += "<enable = " + accountstatus + ">";
		result += "<comment = " + comment + ">";
		// no line for email?
		return result;
	}


    public String getUsername() {
	return username;
    }

    public String getPassword() {
	return password;
    }

    public String getCompactedGroups() {
	return compactedGroups;
	// could do lazy evaluation by calling UserTermInfo.compactGroups(this.expandedGroups) here and returning it
	// or 1st storing answer in compactedGroups member var before returning to avoid reevaluating multiple times.
    }    
    
    public String getExpandedGroups() {
	return expandedGroups;
    }

    public String getAccountStatus() {
	return accountstatus;
    }

    public String getComment() {
	return comment;
    }

    public String getEmail() {
	return email;
    }

    public void setEmail(String email) {
	this.email = email;
    }

    /**
     * The user may have entered groups of the form "nz.ac.waikato.toto, nz.ac.waikato.cs.pinky, admin"
     * This would have got stored in the userDB in expanded form as: "admin, nz, nz.ac, nz.ac.waikato, nz.ac.waikato.cs, nz.ac.waikato.cs.pinky, nz.ac.waikato.toto"
     * This method should return the user entered form again, or the most compact form at any rate. So duplicates removed and in natural (alphabetic) ordering.
     * @param groups: given possibly expanded_groups, this method contracts them back to what the user would have entered for groups
     * @return the user-entered form for groups     
     * Tested:
     * String groups = "administrator,all-collections-editor,nz,nz.ac,nz.ac.waikato,nz.ac.waikato.cs.pinkies, nz.ac.waikato.totos.toto, nz.ac.waikato.cs.pinkies.pinky, personal-collections-editor";
     * and shuffled:
     * String groups = "nz,nz.ac,nz.ac.waikato,administrator,nz.ac.waikato.cs.pinkies, nz.ac.waikato.totos.toto, nz.ac.waikato.cs.pinkies.pinky, personal-collections-editor,all-collections-editor";
     * Result: administrator,all-collections-editor,nz.ac.waikato.cs.pinkies.pinky,nz.ac.waikato.totos.toto,personal-collections-editor:
     */
    public static String compactGroups(String groups) {
	if(groups == null) {
	    return "";
	}
	else if(groups.indexOf('.') == -1) {
	    return groups;
	}
	else if(groups.equals("")) { // can return quickly if empty string
	    return groups;
	}
	else { // groups is a non-zero comma-separated list because we've already tested there's a period mark in it somewhere

	    // Normalize step: make sure what's in the database has no duplicates and is in natural ordering	    
	    Set<String> groupSet = new TreeSet<String>();  // uses default (alphabetic/ascii) comparator
	    String[] indivGroups = groups.split("\\s*,\\s*"); // simultaneously get rid of whitespace surrounding commas
	    for(String s : indivGroups) {
		groupSet.add(s); // will get rid of duplicate prefixes, although it "should" have been stored without duplicates
		// But normalizing here ensures that the rest of the method always works with the correct ordering
	    }

	    indivGroups = new String[groupSet.size()];
	    indivGroups = groupSet.toArray(indivGroups); // now indivGroups is in natural ordering
	    groupSet.clear();
	    groupSet = null;

	    // The actual algorithm is now simple: if next group string doesn't start with current one
	    // (so current group string is not prefix of next group string), we add it to our compactGroups list
	    Set<String> compactGroups = new TreeSet<String>(); // uses default (alphabetic/ascii) comparator
	    String currGroup, nextGroup;
	    for(int i = 0; i < indivGroups.length-1; i++) {
		currGroup = indivGroups[i];
		nextGroup = indivGroups[i+1];

		if(!nextGroup.startsWith(currGroup)) {
		    compactGroups.add(currGroup);
		}
	    }

	    // don't forget to add in the very last group string
	    compactGroups.add(indivGroups[indivGroups.length-1]);


	    // recreate comma-separate string of groups
	    StringBuilder compactGroupList = new StringBuilder();
	    for(String s : compactGroups) {
		compactGroupList.append(s);
		compactGroupList.append(',');
	    }
	    compactGroupList.deleteCharAt(compactGroupList.length()-1); // remove extraneous comma
	    
	    return compactGroupList.toString();
	}
    } 

    
    /** We might have groups of the form katoa.maori.(placename.)iwi-name.hapu-name.personal-name
     * ("katoa" means all, we could also use "mema" meaning member for this initial position).
     * We want to expand such a group into the multiple groups it encompasses:
     *   katoa, katoa.maori, katoa.maori.place, katoa.maori.place.iwi,
     *   katoa.maori.place.iwi.hapu, katoa.maori.place.iwi.hapu.person
     * This method looks through all the groups and if any contains a period,
     * it will expand such into all their explicit subgroups.
     * @param groups: String listing command-separated groups, any of which may
     * need expanding out.
     * @return a comma-separated String listing all the original groups plus any expanded groups
     * in natural order (so alphabetised).
     *
     * Tested at https://www.jdoodle.com/online-java-compiler/, where main contains:
     *    String groups = "all-collections-editor, katoa.maori.place.iwi.hapu.person, admin";
     *
     *    System.out.println(expandGroups(groups));
     * Produces:
     *    all-collections-editor,katoa,katoa.maori,katoa.maori.place,katoa.maori.place.iwi,katoa.maori.place.iwi.hapu,katoa.maori.place.iwi.hapu.person,admin
     *
     * Also tested with the expandable string at start and at end of list of groups input string.
     *
     * This expansion method doesn't preserve overall ordering,
     * e.g. if groups = "all-collections-editor, katoa.maori.place.iwi.hapu.person, admin"
     * result is:
     *    admin,all-collections-editor,katoa,katoa.maori,katoa.maori.place,katoa.maori.place.iwi,katoa.maori.place.iwi.hapu,katoa.maori.place.iwi.hapu.person
     * Where admin is now at start and all-colls-editor now comes after it.
     * However, this method will ensure the requirement of no duplicates,
     * e.g. only one katoa, one katoa.maori etc.
     * to minimising checking if a user has necessary group membership,
     *   e.g. String groups = "all-collections-editor, admin, org.waikato.cs.dl.anu , org.waikato.cs.dl.pinky";
     * Produces the result:
     *    admin,all-collections-editor,org,org.waikato,org.waikato.cs,org.waikato.cs.dl,org.waikato.cs.dl.anu,org.waikato.cs.dl.pinky
     */
    public static String expandGroups(String groups) {
	if(groups == null) {
	    return "";
	} else if(groups.indexOf('.') == -1) {
	    return groups;
	}
	else if(groups.equals("")) { // can return quickly if empty string
	    return groups;
	}
	else {
	    Set<String> allGroups = new TreeSet<String>();
	    String[] indivGroups = groups.split("\\s*,\\s*"); // simultaneously get rid of whitespace surrounding commas
	    for(String s : indivGroups) {
		int indexOfPeriod = s.indexOf('.');

		if(indexOfPeriod != -1) {
		    do {			
			String expandedGroup = s.substring(0, indexOfPeriod);
			allGroups.add(expandedGroup);
			indexOfPeriod += 1;			
			indexOfPeriod = s.indexOf('.', indexOfPeriod);
		    } while(indexOfPeriod != -1);		    
		}

		// Whether this particular group contained a period or not,
		// remember to add this group string in in its entirety too
		allGroups.add(s);
	    }
	

	    StringBuilder expandedGroupList = new StringBuilder();
	    for(String s : allGroups) {
		expandedGroupList.append(s);
		expandedGroupList.append(',');
	    }
	    expandedGroupList.deleteCharAt(expandedGroupList.length()-1); // remove extraneous comma
	    
	    return expandedGroupList.toString();
	}
    }


}
