package com.planet_ink.coffee_mud.Common;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.threads.ServiceEngine;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.core.database.DBConnections;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.AccountStats.Agent;
import com.planet_ink.coffee_mud.Common.interfaces.Clan.Function;
import com.planet_ink.coffee_mud.Common.interfaces.Clan.Authority;
import com.planet_ink.coffee_mud.Common.interfaces.Clan.ClanVote;
import com.planet_ink.coffee_mud.Common.interfaces.Clan.MemberRecord;
import com.planet_ink.coffee_mud.Common.interfaces.PlayerAccount.AccountFlag;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.AbilityMapper.AbilityMapping;
import com.planet_ink.coffee_mud.Libraries.interfaces.AchievementLibrary.Achievement;
import com.planet_ink.coffee_mud.Libraries.interfaces.AchievementLibrary.AchievementLoadFlag;
import com.planet_ink.coffee_mud.Libraries.interfaces.AchievementLibrary.Award;
import com.planet_ink.coffee_mud.Libraries.interfaces.AchievementLibrary.Tracker;
import com.planet_ink.coffee_mud.Libraries.interfaces.DatabaseEngine.PlayerData;
import com.planet_ink.coffee_mud.Libraries.interfaces.JournalsLibrary.ForumJournal;
import com.planet_ink.coffee_mud.Libraries.interfaces.PlayerLibrary.ThinPlayer;
import com.planet_ink.coffee_mud.Libraries.interfaces.XMLLibrary.XMLTag;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.*;

/**
 * Portions Copyright (c) 2003 Jeremy Vyska
 * Portions Copyright (c) 2004-2022 Bo Zimmerman
 * 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.
 */
public class DefaultClan implements Clan
{
	@Override
	public String ID()
	{
		return "DefaultClan";
	}

	private final int	tickStatus	= Tickable.STATUS_NOT;

	@Override
	public int getTickStatus()
	{
		return tickStatus;
	}

	protected String				clanName				= "";
	protected String				clanCategory			= null;
	protected String				clanPremise				= "";
	protected String				clanRecall				= "";
	protected String				clanMorgue				= "";
	protected String				clanClass				= "";
	protected int					clanLevel				= 0;
	protected String				clanDonationRoom		= "";
	protected int					clanTrophies			= 0;
	protected Boolean				isRivalrous				= null;
	protected int					autoPosition			= -1;
	protected String				acceptanceSettings		= "";
	protected int					clanStatus				= 0;
	protected long					lastStatusChange		= 0;
	protected String				lastClanKillLog			= null;
	protected double				taxRate					= 0.0;
	protected volatile long			exp						= 0;
	protected volatile long			lastClanTickMs			= System.currentTimeMillis();
	protected Object				expSync					= new Object();
	protected List<ClanVote>		voteList				= null;
	protected List<Long>			clanKills				= new Vector<Long>();
	protected Integer				overrideMinClanMembers	= null;
	protected long					lastPropsReload			= System.currentTimeMillis();
	protected ItemCollection		extItems				= (ItemCollection) CMClass.getCommon("WeakItemCollection");
	protected Map<String, long[]>	relations				= new Hashtable<String, long[]>();
	protected int					government				= 0;
	protected long					lastGovernmentLoadTime	= -1;
	protected ClanGovernment		govt					= null;
	protected long					totalOnlineMins			= 0;
	protected long					totalLevelsGained		= 0;
	protected int					monthOnlineMins			= 0;
	protected int					monthPlayerXP			= 0;
	protected int					monthClanXP				= 0;
	protected int					monthConquered			= 0;
	protected int					monthClanLevels			= 0;
	protected int					monthControlPoints		= 0;
	protected int					monthNewMembers			= 0;
	protected volatile int			tickUp					= 0;
	protected volatile int			transientSize			= -1;
	protected Set<ClanFlag>			clanFlags				= new SHashSet<ClanFlag>();
	protected MOB					factoryMob				= null;

	protected final static List<Ability> empty=new XVector<Ability>(1,true);

	protected final List<Pair<Clan, Integer>>	channelSet		= new XVector<Pair<Clan, Integer>>(1, true);
	protected CMUniqNameSortSVec<Tattoo>		tattoos			= new CMUniqNameSortSVec<Tattoo>(1);
	protected Map<String, Tracker>				achievementers	= new STreeMap<String, Tracker>();

	/** return a new instance of the object*/
	@Override
	public CMObject newInstance()
	{
		try
		{
			return getClass().newInstance();
		}
		catch (final Exception e)
		{
			return new DefaultClan();
		}
	}

	@Override
	public void initializeClass()
	{
	}

	@Override
	public int compareTo(final CMObject o)
	{
		return CMClass.classID(this).compareToIgnoreCase(CMClass.classID(o));
	}

	@Override
	public CMObject copyOf()
	{
		try
		{
			final DefaultClan C=(DefaultClan)this.clone();
			C.extItems=(ItemCollection)extItems.copyOf();
			C.clanFlags = new SHashSet<ClanFlag>(clanFlags);
			return C;
		}
		catch(final CloneNotSupportedException e)
		{
			return new DefaultClan();
		}
	}

	@Override
	public ClanGovernment getGovernment()
	{
		return govt();
	}

	protected ClanGovernment govt()
	{
		if((govt != null) && ((government < 0) || (lastGovernmentLoadTime == CMLib.clans().getLastGovernmentLoad())))
			return govt;
		else
		{

			ClanGovernment govt = CMLib.clans().getStockGovernment(government);
			if(govt == null)
			{
				govt = CMLib.clans().getDefaultGovernment();
				government = govt.getID();
			}
			lastGovernmentLoadTime = CMLib.clans().getLastGovernmentLoad();
			return govt;
		}
	}

	private synchronized void clanKills()
	{
		if(lastClanKillLog==null)
		{
			final List<PlayerData> V=CMLib.database().DBReadPlayerData(clanID(),"CLANKILLS",clanID()+"/CLANKILLS");
			clanKills.clear();
			if(V.size()==0)
				lastClanKillLog="";
			else
			{
				lastClanKillLog=V.get(0).xml();
				final List<String> V2=CMParms.parseSemicolons(lastClanKillLog,true);
				for(int v=0;v<V2.size();v++)
					clanKills.add(Long.valueOf(CMath.s_long(V2.get(v))));
			}
		}
	}

	private void updateClanKills()
	{
		Long date=null;
		final StringBuffer str=new StringBuffer("");
		final long now=System.currentTimeMillis();
		for(int i=clanKills.size()-1;i>=0;i--)
		{
			date=clanKills.get(i);
			if(date.longValue()<now)
				clanKills.remove(i);
			else
				str.append(date.longValue()).append(";");
		}
		if((lastClanKillLog==null)||(!lastClanKillLog.equals(str.toString())))
		{
			lastClanKillLog=str.toString();
			CMLib.database().DBReCreatePlayerData(clanID(),"CLANKILLS",clanID()+"/CLANKILLS",str.toString());
		}
	}

	@Override
	public void resetMonthlyTrophyData()
	{
		monthOnlineMins = 0;
		monthPlayerXP = 0;
		monthClanXP = 0;
		monthConquered = 0;
		monthClanLevels = 0;
		monthControlPoints = 0;
		monthNewMembers = 0;
	}

	@Override
	public long getTrophyData(final Trophy trophy)
	{
		switch(trophy)
		{
		case MonthlyPlayerMinutes:
			return monthOnlineMins;
		case MonthlyPlayerXP:
			return monthPlayerXP;
		case MonthlyClanXP:
			return monthClanXP;
		case MonthlyConquests:
			return monthConquered;
		case MonthlyClanLevels:
			return monthClanLevels;
		case MonthlyControlPoints:
			return monthControlPoints;
		case MonthlyNewMembers:
			return monthNewMembers;
		case Points:
			return calculateMapPoints();
		case Experience:
			return exp;
		case  Areas:
			return getControlledAreas().size();
		case ClanKills:
			return getCurrentClanKills(null);
		case Members:
			return getSize();
		case MemberLevel:
			return filterMedianLevel(getFullMemberList());
		case PlayerMinutes:
			return totalOnlineMins;
		case PlayerLevelsGained:
			return totalLevelsGained;
		default:
			return 0;
		}
	}

	@Override
	public void bumpTrophyData(final Trophy trophy, final int amt)
	{
		switch(trophy)
		{
		case MonthlyPlayerMinutes:
			monthOnlineMins += amt;
			break;
		case MonthlyPlayerXP:
			monthPlayerXP += amt;
			break;
		case MonthlyClanXP:
			monthClanXP += amt;
			break;
		case MonthlyConquests:
			monthConquered += amt;
			break;
		case MonthlyClanLevels:
			monthClanLevels += amt;
			break;
		case MonthlyControlPoints:
			monthControlPoints += amt;
			break;
		case MonthlyNewMembers:
			monthNewMembers += amt;
			break;
		case Points:
			// derived
			break;
		case Experience:
			this.exp += amt;
			break;
		case  Areas:
			// derived
			break;
		case ClanKills:
			// derived from member records
			break;
		case Members:
			// derived from member records
			break;
		case  MemberLevel:
			// derived from mob records of member records
			break;
		case PlayerMinutes:
			totalOnlineMins += amt;
			break;
		case PlayerLevelsGained:
			totalLevelsGained += amt;
			break;
		}
	}

	@Override
	public void updateVotes()
	{
		final XMLLibrary xml=CMLib.xml();
		final StringBuilder str=new StringBuilder("");
		for(final Enumeration<ClanVote> e=votes();e.hasMoreElements();)
		{
			final ClanVote CV=e.nextElement();
			str.append(xml.convertXMLtoTag("BY",CV.voteStarter));
			str.append(xml.convertXMLtoTag("FUNC",CV.function));
			str.append(xml.convertXMLtoTag("ON",""+CV.voteStarted));
			str.append(xml.convertXMLtoTag("STATUS",""+CV.voteStatus));
			str.append(xml.convertXMLtoTag("CMD",CV.matter));
			if((CV.votes!=null)&&(CV.votes.size()>0))
			{
				str.append("<VOTES>");
				for(int v=0;v<CV.votes.size();v++)
				{
					str.append("<VOTE>");
					str.append(xml.convertXMLtoTag("BY",CV.votes.getFirst(v)));
					str.append(xml.convertXMLtoTag("YN",CV.votes.getSecond(v).toString()));
					str.append("</VOTE>");
				}
				str.append("</VOTES>");
			}
		}
		if(str.length()>0)
			CMLib.database().DBReCreatePlayerData(clanID(),"CLANVOTES",clanID()+"/CLANVOTES","<BALLOTS>"+str.toString()+"</BALLOTS>");
		else
			CMLib.database().DBDeletePlayerData(clanID(),"CLANVOTES",clanID()+"/CLANVOTES");
	}

	@Override
	public void addVote(final ClanVote CV)
	{
		if(CV==null)
			return;
		votes();
		voteList.add(CV);
	}

	@Override
	public void delVote(final ClanVote CV)
	{
		votes();
		voteList.remove(CV);
	}

	@Override
	public void recordClanKill(final MOB killer, final MOB killed)
	{
		clanKills();
		final Area A=CMLib.map().areaLocation(killer);
		if(A!=null)
			clanKills.add(Long.valueOf(System.currentTimeMillis() + (365L * 24L * 60L * 60L * 1000)));
		updateClanKills();
		if((killer != null)
		&&(killed != null))
		{
			if(killed.isMonster())
				CMLib.database().DBUpdateClanKills(this.clanID(), killer.Name(), 1, 0);
			else
				CMLib.database().DBUpdateClanKills(this.clanID(), killer.Name(), 0, 1);
		}
	}

	@Override
	public int getCurrentClanKills(final MOB killer)
	{
		if(killer==null)
		{
			clanKills();
			return clanKills.size();
		}
		else
		{
			final MemberRecord M = CMLib.database().DBGetClanMember(this.clanID(), killer.Name());
			return M.playerpvps;
		}
	}


	@Override
	public double getCurrentClanGoldDonations(final MOB killer)
	{
		if(killer==null)
		{
			return 0;
		}
		else
		{
			final MemberRecord M = CMLib.database().DBGetClanMember(this.clanID(), killer.Name());
			return M.donatedGold;
		}
	}

	@Override
	public long getCurrentClanXPDonations(final MOB killer)
	{
		if(killer==null)
		{
			return this.exp;
		}
		else
		{
			final MemberRecord M = CMLib.database().DBGetClanMember(this.clanID(), killer.Name());
			return M.donatedXP;
		}
	}

	@Override
	public boolean isSet(final ClanFlag flag)
	{
		return clanFlags.contains(flag);
	}

	@Override
	public void setFlag(final ClanFlag flag, final boolean setOrUnset)
	{
		if(setOrUnset)
			clanFlags.add(flag);
		else
			clanFlags.remove(flag);
	}


	@Override
	public boolean isOnlyFamilyApplicants()
	{
		return govt().isFamilyOnly();
	}

	@Override
	public boolean isLoyaltyThroughItems()
	{
		return govt().isConquestItemLoyalty();
	}

	@Override
	public boolean isWorshipConquest()
	{
		return govt().isConquestByWorship();
	}

	@Override
	public long calculateMapPoints()
	{
		return calculateMapPoints(getControlledAreas());
	}

	@Override
	public long calculateMapPoints(final List<Area> controlledAreas)
	{
		long points=0;
		if(controlledAreas!=null)
		{
			for(final Area A : controlledAreas)
			{
				final LegalBehavior B=CMLib.law().getLegalBehavior(A);
				if(B!=null)
					points+=B.controlPoints();
			}
		}
		return points;
	}

	@Override
	public List<Area> getControlledAreas()
	{
		final Vector<Area> done=new Vector<Area>();
		for(final Enumeration<Area> e=CMLib.map().areas();e.hasMoreElements();)
		{
			final Area A=e.nextElement();
			final LegalBehavior B=CMLib.law().getLegalBehavior(A);
			if(B!=null)
			{
				final String controller=B.rulingOrganization();
				final Area A2=CMLib.law().getLegalObject(A);
				if(controller.equals(clanID())&&(!done.contains(A2)))
					done.addElement(A2);
			}
		}
		return done;
	}

	@Override
	public Enumeration<ClanVote> votes()
	{
		if(voteList==null)
		{
			final List<PlayerData> V=CMLib.database().DBReadPlayerData(clanID(),"CLANVOTES",clanID()+"/CLANVOTES");
			final XMLLibrary xmlLib=CMLib.xml();
			voteList=new Vector<ClanVote>();
			for(int v=0;v<V.size();v++)
			{
				final ClanVote CV=new ClanVote();
				final String rawxml=V.get(v).xml();
				if(rawxml.trim().length()==0)
					return new IteratorEnumeration<Clan.ClanVote>(voteList.iterator());
				final List<XMLLibrary.XMLTag> xml=xmlLib.parseAllXML(rawxml);
				if(xml==null)
				{
					Log.errOut("Clans","Unable to parse: "+rawxml);
					return new IteratorEnumeration<Clan.ClanVote>(voteList.iterator());
				}
				final List<XMLLibrary.XMLTag> voteData=xmlLib.getContentsFromPieces(xml,"BALLOTS");
				if(voteData==null)
				{
					Log.errOut("Clans","Unable to get BALLOTS data.");
					return new IteratorEnumeration<Clan.ClanVote>(voteList.iterator());
				}
				CV.voteStarter=xmlLib.getValFromPieces(voteData,"BY");
				CV.voteStarted=xmlLib.getLongFromPieces(voteData,"ON");
				CV.function=xmlLib.getIntFromPieces(voteData,"FUNC");
				CV.voteStatus=xmlLib.getIntFromPieces(voteData,"STATUS");
				CV.matter=xmlLib.getValFromPieces(voteData,"CMD");
				CV.votes=new PairVector<String,Boolean>();
				final List<XMLLibrary.XMLTag> xV=xmlLib.getContentsFromPieces(voteData,"VOTES");
				if((xV!=null)&&(xV.size()>0))
				{
					for(int x=0;x<xV.size();x++)
					{
						final XMLTag iblk=xV.get(x);
						if((!iblk.tag().equalsIgnoreCase("VOTE"))||(iblk.contents()==null))
							continue;
						final String userID=iblk.getValFromPieces("BY");
						final boolean yn=iblk.getBoolFromPieces("YN");
						CV.votes.addElement(userID,Boolean.valueOf(yn));
					}
				}
				voteList.add(CV);
			}
		}
		return new IteratorEnumeration<Clan.ClanVote>(voteList.iterator());
	}

	@Override
	public int getAutoPosition()
	{
		return autoPosition<0?govt().getAutoRole():autoPosition;
	}

	@Override
	public void setAutoPosition(final int pos)
	{
		if(pos == govt().getAutoRole())
			autoPosition=-1;
		else
			autoPosition=pos;
	}

	@Override
	public void setExp(final long newexp)
	{
		synchronized(expSync)
		{
			final long oldxp=exp;
			exp=newexp;
			if(exp<0)
				exp=0;
			final CMath.CompiledFormula form = govt().getXPCalculationFormula();
			if(oldxp < exp) // we gained
			{
				double nextLevelXP = CMath.parseMathExpression(form, new double[]{getClanLevel()}, 0.0);
				while(exp > nextLevelXP)
				{
					setClanLevel(getClanLevel()+1);
					bumpTrophyData(Trophy.MonthlyClanLevels, 1);
					clanAnnounce(""+getGovernmentName()+" "+name()+" has attained clan level "+getClanLevel()+"!");
					CMLib.achievements().possiblyBumpAchievement(getResponsibleMember(), AchievementLibrary.Event.CLANLEVELSGAINED, 1, this);
					update();
					nextLevelXP = CMath.parseMathExpression(form, new double[]{getClanLevel()}, 0.0);
				}
			}
			else
			if((oldxp > exp) && (getClanLevel()>1))
			{
				double prevLevelXP = CMath.parseMathExpression(form, new double[]{getClanLevel()-1}, 0.0);
				while(exp < prevLevelXP)
				{
					setClanLevel(getClanLevel()-1);
					CMLib.achievements().possiblyBumpAchievement(getResponsibleMember(), AchievementLibrary.Event.CLANLEVELSGAINED, -1, this);
					clanAnnounce(""+getGovernmentName()+" "+name()+" has reverted to clan level "+getClanLevel()+"!");
					update();
					prevLevelXP = CMath.parseMathExpression(form, new double[]{getClanLevel()-1}, 0.0);
				}
			}
		}
	}

	@Override
	public void adjExp(final MOB memberM, final int howMuch)
	{
		if (howMuch != 0)
		{
			setExp(getExp() + howMuch);
			if(howMuch > 0)
				bumpTrophyData(Trophy.MonthlyClanXP, howMuch);
			if(memberM != null)
				CMLib.database().DBUpdateClanDonates(this.clanID(), memberM.Name(), 0, howMuch);
		}
	}

	@Override
	public void adjDeposit(final MOB memberM, final double howMuch)
	{
		if(memberM != null)
			CMLib.database().DBUpdateClanDonates(this.clanID(), memberM.Name(), howMuch, 0);
	}


	@Override
	public long getExp()
	{
		return exp;
	}

	@Override
	public int getTrophies()
	{
		return clanTrophies;
	}

	@Override
	public void setTrophies(final int trophyFlag)
	{
		clanTrophies = trophyFlag;
	}

	@Override
	public void setTaxes(final double rate)
	{
		taxRate=rate;
	}

	@Override
	public double getTaxes()
	{
		return taxRate;
	}

	@Override
	public int getClanRelations(final String id)
	{
		final long i[]=relations.get(id.toUpperCase());
		if(i!=null)
			return (int)i[0];
		return  REL_NEUTRAL;
	}

	@Override
	public long getLastRelationChange(final String id)
	{
		final long i[]=relations.get(id.toUpperCase());
		if(i!=null)
			return i[1];
		return 0;
	}

	@Override
	public void setClanRelations(final String id, final int rel, final long time)
	{
		relations.remove(id.toUpperCase());
		final long[] i=new long[2];
		i[0]=rel;
		i[1]=time;
		relations.put(id.toUpperCase(),i);
	}

	@Override
	public int getGovernmentID()
	{
		return government;
	}

	@Override
	public void setGovernmentID(final int type)
	{
		government = type;
		lastGovernmentLoadTime = -1;
	}

	@Override
	public String getCategory()
	{
		if(clanCategory!=null)
			return clanCategory;
		return govt().getCategory();
	}

	@Override
	public int getMinClanMembers()
	{
		if(overrideMinClanMembers!=null)
			return overrideMinClanMembers.intValue();
		if(govt().getOverrideMinMembers()!=null)
			return govt().getOverrideMinMembers().intValue();
		return CMProps.getIntVar(CMProps.Int.MINCLANMEMBERS);
	}

	@Override
	public void setMinClanMembers(final int amt)
	{
		overrideMinClanMembers=null;
		if(govt().getOverrideMinMembers()!=null)
		{
			if(govt().getOverrideMinMembers().intValue()==amt)
				return;
			overrideMinClanMembers=Integer.valueOf(amt);
		}
		else
		{
			if(CMProps.getIntVar(CMProps.Int.MINCLANMEMBERS)==amt)
				return;
			overrideMinClanMembers=Integer.valueOf(amt);
		}
	}

	@Override
	public void setCategory(final String newCategory)
	{
		if(govt().getCategory().equalsIgnoreCase(newCategory))
			clanCategory=null;
		else
			clanCategory=newCategory;
	}

	@Override
	public boolean isRivalrous()
	{
		if(isRivalrous==null)
			return govt().isRivalrous();
		return isRivalrous.booleanValue();
	}

	@Override
	public void setRivalrous(final boolean isRivalrous)
	{
		if(govt().isRivalrous()==isRivalrous)
			this.isRivalrous=null;
		else
			this.isRivalrous=Boolean.valueOf(isRivalrous);
	}

	@Override
	public void create()
	{
		CMLib.database().DBCreateClan(this);
		CMLib.clans().addClan(this);
	}

	@Override
	public void update()
	{
		CMLib.database().DBUpdateClan(this);
	}

	@Override
	public void addMember(final MOB M, final int role)
	{
		transientSize=-1;
		M.setClan(clanID(),role);
		CMLib.database().DBUpdateClanMembership(M.Name(), clanID(), role);
		updateClanPrivileges(M);
		bumpTrophyData(Trophy.MonthlyNewMembers, 1);
	}

	@Override
	public void delMember(final MOB M)
	{
		transientSize=-1;
		CMLib.database().DBUpdateClanMembership(M.Name(), clanID(), -1);
		M.setClan(clanID(),-1);
		updateClanPrivileges(M);
	}

	@Override
	public boolean updateClanPrivileges(final MOB M)
	{
		boolean didUpdatePlayer=false;
		if(M==null)
			return false;
		final Pair<Clan,Integer> p=M.getClanRole(clanID());

		if((p!=null)
		&& (getAuthority(p.second.intValue(),Function.CLAN_BENEFITS)!=Clan.Authority.CAN_NOT_DO))
		{
			final CharClass CC=getClanClassC();
			if((CC!=null)
			&&(CC.availabilityCode()!=0)
			&&(M.baseCharStats().getCurrentClass()!=CC))
			{
				M.baseCharStats().setCurrentClass(CC);
				didUpdatePlayer=true;
				M.recoverCharStats();
			}
			CMLib.achievements().loadClanAchievements(M,AchievementLoadFlag.NORMAL);
		}
		else
		{
			final String removeMsg =CMLib.achievements().removeClanAchievementAwards(M, this);
			if((removeMsg != null)&&(removeMsg.length()>0))
				M.tell(removeMsg);
		}

		// Get a list of all possible spell grants for this clan, check against class qualifications.
		// Form a list of forbidden spells, and then remove THOSE.
		final Map<String, AbilityMapping> allAbles =  CMLib.ableMapper().getAbleMapping(getGovernment().getName());
		final Set<String> qualAbleIDS = new TreeSet<String>();
		for(final Ability A :  getGovernment().getClanLevelAbilities(M, this, Integer.valueOf(getClanLevel())))
			qualAbleIDS.add(A.ID());
		for(final String ableID : allAbles.keySet())
		{
			final Ability A=M.fetchAbility(ableID);
			if((A != null)
			&&(!qualAbleIDS.contains(ableID))
			&&(!CMLib.ableMapper().qualifiesByLevel(M, ableID)) // this will check ALL clans, as well as other sources.
			&&(allAbles.get(ableID).autoGain()))
				M.delAbility(A);
		}

		final PlayerStats pStats = M.playerStats();
		if(pStats!=null)
		{
			final Set<String> myAllowedTitles = new TreeSet<String>();
			if(p != null)
			{
				final ClanPosition myPos = govt().findPositionRole(p.second);
				final String myNicePosName = CMStrings.capitalizeAllFirstLettersAndLower(myPos.getName());
				for(final String baseTitle : govt().getTitleAwards())
					myAllowedTitles.add(L(baseTitle,name(),myNicePosName));
				for(final String posTitle : myPos.getTitleAwards())
					myAllowedTitles.add(L(posTitle,name(),myNicePosName));
				if(getAuthority(p.second.intValue(),Function.CLAN_TITLES)!=Clan.Authority.CAN_NOT_DO)
				{
					for(final String title : myAllowedTitles)
					{
						if(!pStats.getTitles().contains(title))
							pStats.getTitles().add(title);
					}
				}
			}
			for(final ClanPosition pos : govt().getPositions())
			{
				if((p==null)||(p.second.intValue()!=pos.getRoleID()))
				{
					final String nicePosName = CMStrings.capitalizeAllFirstLettersAndLower(pos.getName());
					for(final String baseTitle : govt().getTitleAwards())
					{
						final String badTitle = L(baseTitle,name(),nicePosName);
						if(!myAllowedTitles.contains(badTitle))
						{
							for(final String titleCheck : pStats.getTitles())
							{
								if(titleCheck.equalsIgnoreCase(badTitle))
									pStats.getTitles().remove(titleCheck);

							}
						}
					}
					for(final String posTitle : pos.getTitleAwards())
					{
						final String badTitle = L(posTitle,name(),nicePosName);
						if(!myAllowedTitles.contains(badTitle))
						{
							for(final String titleCheck : pStats.getTitles())
							{
								if(titleCheck.equalsIgnoreCase(badTitle))
									pStats.getTitles().remove(titleCheck);

							}
						}
					}
				}
			}
		}
		if(p==null)
		{
			Item I=null;
			final List<Item> itemsToMove=new ArrayList<Item>();
			for(int i=0;i<M.numItems();i++)
			{
				I=M.getItem(i);
				if(I instanceof ClanItem)
					itemsToMove.add(I);
			}
			for(int i=0;i<itemsToMove.size();i++)
			{
				I=itemsToMove.get(i);
				if(I!=null)
				{
					Room R=null;
					if((getDonation()!=null)
					&&(getDonation().length()>0))
						R=CMLib.map().getRoom(getDonation());
					if((R==null)
					&&(getRecall()!=null)
					&&(getRecall().length()>0))
						R=CMLib.map().getRoom(getRecall());
					if(I instanceof Container)
					{
						final List<Item> V=((Container)I).getDeepContents();
						for(int v=0;v<V.size();v++)
							V.get(v).setContainer(null);
					}
					I.setContainer(null);
					I.wearAt(Wearable.IN_INVENTORY);
					if(R!=null)
						R.moveItemTo(I);
					else
					if(M.isMine(I))
						I.destroy();
					didUpdatePlayer=true;
				}
			}
		}
		if((didUpdatePlayer)&&(!CMSecurity.isSaveFlag(CMSecurity.SaveFlag.NOPLAYERS)))
			CMLib.database().DBUpdatePlayer(M);
		return didUpdatePlayer;
	}

	@Override
	public void destroyClan()
	{
		final List<MemberRecord> members=getMemberList();
		for(final MemberRecord member : members)
		{
			final MOB M=CMLib.players().getLoadPlayer(member.name);
			if(M!=null)
			{
				M.setClan(clanID(),-1);
				updateClanPrivileges(M);
				CMLib.database().DBUpdateClanMembership(M.Name(), clanID(), -1);
			}
		}
		CMLib.database().DBDeleteJournal("a Journal of "+getGovernmentName()+" "+getName(), null);
		CMLib.database().DBDeleteJournal("CLAN_MOTD"+clanID(), null);
		CMLib.database().DBDeleteClan(this);
		CMLib.clans().removeClan(this);
	}

	protected CharClass getClanClassC()
	{
		if(clanClass.length()==0)
			return null;
		CharClass C=CMClass.getCharClass(clanClass);
		if(C==null)
			C=CMClass.findCharClass(clanClass);
		return C;
	}

	@Override
	public String getDetail(final MOB mob)
	{
		final int COLBL_WIDTH=CMLib.lister().fixColWidth(16.0,mob);
		final StringBuilder msg=new StringBuilder("");
		final Pair<Clan,Integer> mobClanRole=(mob!=null)?(mob.getClanRole(clanID())):null;
		final boolean member=(mob!=null)
							&&(mobClanRole!=null)
							&&(getAuthority(mobClanRole.second.intValue(),Function.LIST_MEMBERS)!=Authority.CAN_NOT_DO);
		final boolean sysmsgs=(mob!=null)&&mob.isAttributeSet(MOB.Attrib.SYSOPMSGS);
		final CMath.CompiledFormula form = govt().getXPCalculationFormula();
		final double nextLevelXP = CMath.parseMathExpression(form, new double[]{getClanLevel()}, 0.0);
		msg.append("^x"+CMStrings.padRight(L(getGovernmentName()+" Profile"),COLBL_WIDTH)+":^.^N "+clanID()+"\n\r");
		msg.append("-----------------------------------------------------------------\n\r");
		msg.append(getPremise()+"\n\r");
		msg.append("-----------------------------------------------------------------\n\r");
		msg.append("^x"+CMStrings.padRight(L("Level"),COLBL_WIDTH)+":^.^N "+getClanLevel());
		if(member||sysmsgs)
			msg.append(L("                      (Next at ^w@x1^Nxp)",""+nextLevelXP));
		msg.append("\n\r");
		msg.append("^x"+CMStrings.padRight(L("Type"),COLBL_WIDTH)+":^.^N "+CMStrings.capitalizeAndLower(govt().getName())+"\n\r");

		if(getAcceptanceSettings().length()>0)
		{
			msg.append("^x"+CMStrings.padRight(L("Qualifications"),COLBL_WIDTH)+":^.^N "+CMLib.masking().maskDesc(getAcceptanceSettings())+"\n\r");
			if(getBasicRequirementMask().length()>0)
				msg.append("^x"+CMStrings.padLeft(L("Plus "),COLBL_WIDTH)+":^.^N "+CMLib.masking().maskDesc(getBasicRequirementMask())+"\n\r");
		}
		else
		if(getBasicRequirementMask().length()>0)
			msg.append("^x"+CMStrings.padRight(L("Qualifications"),COLBL_WIDTH)+":^.^N "+CMLib.masking().maskDesc(getBasicRequirementMask())+"\n\r");
		else
			msg.append("^x"+CMStrings.padRight(L("Qualifications"),COLBL_WIDTH)+":^.^N "+L("Anyone may apply")+"\n\r");
		final CharClass clanC=getClanClassC();
		if(clanC!=null)
			msg.append("^x"+CMStrings.padRight(L("Class"),COLBL_WIDTH)+":^.^N "+clanC.name()+"\n\r");
		msg.append("^x"+CMStrings.padRight(L("Exp. Tax Rate"),COLBL_WIDTH)+":^.^N "+((int)Math.round(getTaxes()*100))+"%\n\r");
		if(member||sysmsgs)
		{
			msg.append("^x"+CMStrings.padRight(L("Experience Pts."),COLBL_WIDTH)+":^.^N "+getExp()+"\n\r");
			if(getMorgue().length()>0)
			{
				final Room R=CMLib.map().getRoom(getMorgue());
				if(R!=null)
					msg.append("^x"+CMStrings.padRight(L("Morgue"),COLBL_WIDTH)+":^.^N "+R.displayText(mob)+"\n\r");
			}
			if(getDonation().length()>0)
			{
				final Room R=CMLib.map().getRoom(getDonation());
				if(R!=null)
					msg.append("^x"+CMStrings.padRight(L("Donations"),COLBL_WIDTH)+":^.^N "+R.displayText(mob)+"\n\r");
			}
			if(getRecall().length()>0)
			{
				final Room R=CMLib.map().getRoom(getRecall());
				if(R!=null)
					msg.append("^x"+CMStrings.padRight(L("Recall"),COLBL_WIDTH)+":^.^N "+R.displayText(mob)+"\n\r");
			}
		}
		final List<MemberRecord> members=getMemberList();
		final Set<ClanPosition> sortedPositions=new HashSet<ClanPosition>();
		for(int i=0;i<govt().getPositions().length;i++)
		{
			ClanPosition topRankedPos=null;
			for(final ClanPosition pos : govt().getPositions())
			{
				if((pos.isPublic())
				&&(!sortedPositions.contains(pos))
				&&((topRankedPos==null)||(pos.getRank() < topRankedPos.getRank())))
					topRankedPos = pos;
			}
			if(topRankedPos != null)
			{
				msg.append("^x"+CMStrings.padRight(CMStrings.capitalizeAllFirstLettersAndLower(topRankedPos.getPluralName()),COLBL_WIDTH)+":^.^N "+crewList(members, topRankedPos.getRoleID())+"\n\r");
				sortedPositions.add(topRankedPos);
			}
		}
		msg.append("^x"+CMStrings.padRight(L("Total Members"),COLBL_WIDTH)+":^.^N "+members.size()+"\n\r");
		if(CMLib.clans().numClans()>1)
		{
			msg.append("-----------------------------------------------------------------\n\r");
			msg.append("^x"+CMStrings.padRight(L("Clan Relations"),COLBL_WIDTH)+":^.^N \n\r");
			boolean others = false;
			final int COLCL_WIDTH=CMLib.lister().fixColWidth(26.0,mob);
			for(final Enumeration<Clan> e=CMLib.clans().clans();e.hasMoreElements();)
			{
				final Clan C=e.nextElement();
				if((C!=this)&&(C.isRivalrous()))
				{
					final int rel=getClanRelations(C.clanID());
					final int orel=C.getClanRelations(clanID());
					if((rel!=REL_NEUTRAL) || (orel != REL_NEUTRAL))
					{
						msg.append("^H"+CMStrings.padRight(C.name(),COLCL_WIDTH)+":^.^N ");
						msg.append(REL_COLORS[rel]).append(CMStrings.capitalizeAndLower(REL_DESCS[rel]));
						if((rel!=REL_NEUTRAL) || (orel != REL_NEUTRAL))
							msg.append("^N (^W<-").append(REL_COLORS[orel]).append(CMStrings.capitalizeAndLower(REL_DESCS[orel])).append("^N)");
						else
							msg.append("^N");
						msg.append("\n\r");
					}
					else
						others=true;
				}
			}
			if(others)
			{
				msg.append("^H"+CMStrings.padRight("All Others",COLCL_WIDTH)+":^.^N ");
				msg.append(REL_COLORS[REL_NEUTRAL]).append(CMStrings.capitalizeAndLower(REL_DESCS[REL_NEUTRAL]));
				msg.append("^N").append("\n\r");
			}
		}
		if(member||sysmsgs)
		{
			updateClanPrivileges(mob);
			for(final ClanPosition pos : govt().getPositions())
			{
				if((!pos.isPublic())
				&&(member)
				&&((pos.getRoleID()!=govt().getAutoRole())||(pos.getRoleID()==govt().getAcceptPos())))
				{
					msg.append("-----------------------------------------------------------------\n\r");
					msg.append("^x"+CMStrings.padRight(CMStrings.capitalizeAndLower(pos.getPluralName()),COLBL_WIDTH)
							  +":^.^N "+crewList(members, pos.getRoleID())+"\n\r");
				}
			}
			if((mobClanRole!=null)
			&&(govt().getAutoRole()!=govt().getAcceptPos())
			&&((getAuthority(mobClanRole.second.intValue(),Function.ACCEPT)!=Clan.Authority.CAN_NOT_DO)||sysmsgs))
			{
				final ClanPosition pos=govt().getPositions()[getAutoPosition()];
				msg.append("-----------------------------------------------------------------\n\r");
				msg.append("^x"+CMStrings.padRight(CMStrings.capitalizeAndLower(pos.getPluralName()),COLBL_WIDTH)
						  +":^.^N "+crewList(members, pos.getRoleID())+"\n\r");
			}
		}

		final List<String> control=new ArrayList<String>();
		final List<Area> controlledAreas=getControlledAreas();
		for(final Area A : controlledAreas)
			control.add(A.name());
		if(control.size()>0)
		{
			msg.append("-----------------------------------------------------------------\n\r");
			msg.append(L("^xClan Controlled Areas (% revolt):^.^N\n\r"));
			Collections.sort(control);
			int col=0;
			final int COL_LEN=CMLib.lister().fixColWidth(25.0,mob);
			for(int i=0;i<control.size();i++)
			{
				if((++col)>3)
				{
					msg.append("\n\r");
					col=1;
				}
				final Area A=CMLib.map().getArea(control.get(i));
				if(A!=null)
				{
					final LegalBehavior B=CMLib.law().getLegalBehavior(A);
					final Area legalA=CMLib.law().getLegalObject(A);
					int pctRevolt=0;
					if((B!=null)&&(legalA!=null))
						pctRevolt=B.revoltChance();
					msg.append("^c"+CMStrings.padRight(A.name()+"^N ("+pctRevolt+"%)",COL_LEN)+"^N");
				}
			}
			msg.append("\n\r");
		}
		if((CMLib.clans().trophySystemActive())
		&&(getTrophies()!=0)
		&&(!isSet(ClanFlag.NOTROPHY)))
		{
			msg.append("-----------------------------------------------------------------\n\r");
			msg.append(L("^xTrophies awarded:^.^N\n\r"));
			for(final Trophy t : Trophy.values())
			{
				if(CMath.bset(getTrophies(),t.flagNum()))
				{
					msg.append(t.codeString+" ");
					if(t.name().toUpperCase().indexOf("MONTHLY")<0)
						msg.append("(").append(this.getTrophyData(t)).append(") ");
					else
						msg.append(":");
					msg.append(L(" Prize: @x1\n\r",CMLib.clans().translatePrize(t)));
				}
			}
		}
		final List<Achievement> achievements = new ArrayList<Achievement>(); // current users achievements rechecked above
		for(final Tattoo tatt : this.tattoos)
		{
			final Achievement A=CMLib.achievements().getAchievement(tatt.getTattooName());
			if(A!=null) // let's just trust this one.
				achievements.add(A);
		}
		if(achievements.size()>0)
		{
			msg.append("-----------------------------------------------------------------\n\r");
			msg.append(L("^xClan Achievements:^.^N\n\r"));
			for(final Achievement A : achievements)
				msg.append("^N"+A.getDisplayStr()+"\n\r");
		}
		if(((mobClanRole!=null)&&(getAuthority(mobClanRole.second.intValue(),Function.CLAN_BENEFITS)!=Clan.Authority.CAN_NOT_DO))||sysmsgs)
		{
			msg.append("-----------------------------------------------------------------\n\r");
			msg.append(L("^xClan Benefits:^.^N\n\r"));
			final List<AbilityMapper.AbilityMapping> abilities=CMLib.ableMapper().getUpToLevelListings(govt().getName(),getClanLevel(),true,false);
			if(abilities.size()>0)
			{
				final List<String> names = new Vector<String>();
				for(final AbilityMapper.AbilityMapping aMap : abilities)
				{
					final Ability A=CMClass.getAbility(aMap.abilityID());
					if(A!=null)
					{
						if((aMap.extFields().size()==0)
						||(mobClanRole==null)
						||(sysmsgs)
						||(aMap.extFields().containsKey(mobClanRole.second.toString())))
							names.add(A.name()+(aMap.autoGain()?"":"(q)")+((aMap.extFields().size()>0)?"*":""));
					}
				}
				for(final Achievement A : achievements)
				{
					final Award[] awards = A.getRewards();
					for(final Award award : awards)
						names.add(CMLib.achievements().fixAwardDescription(A, award, mob, mob));
				}
				msg.append(CMLib.lister().makeColumns(mob,names,null,3));
				msg.append("\n\r");
			}
			final int numReff=CMath.s_int(govt().getStat("NUMREFF"));
			for(int i=0;i<numReff;i++)
			{
				final String ableName=govt().getStat("GETREFF"+i);
				final String ableText=govt().getStat("GETREFFPARM"+i);
				final int ableLvl=CMath.s_int(govt().getStat("GETREFFLVL"+i));
				final List<String> ableRoles=CMParms.parseCommas(govt().getStat("GETREFFROLE"+i),true);
				final Ability A=CMClass.getAbility(ableName);
				if((A!=null)
				&&(ableLvl<=this.clanLevel)
				&&((ableRoles.size()==0)
					||(mobClanRole==null)
					||(sysmsgs)
					||(ableRoles.contains(mobClanRole.second.toString()))))
				{
					A.setMiscText(ableText);
					msg.append(A.accountForYourself()).append("\n\r");
				}
			}
		}
		return msg.toString();
	}

	public String L(final String str, final String ... xs)
	{
		return CMLib.lang().fullSessionTranslation(str, xs);
	}

	@Override
	public String getGovernmentName()
	{
		return CMStrings.capitalizeAndLower(govt().getName());
	}

	@Override
	public boolean canBeAssigned(final MOB mob, final int role)
	{
		if(mob==null)
			return false;
		if((role<0)||(role>govt().getPositions().length))
			return false;
		final ClanPosition pos = govt().getPositions()[role];
		return CMLib.masking().maskCheck(fixRequirementMask(pos.getInnerMaskStr()), mob, true);
	}

	private boolean canBeAssigned(final ThinPlayer mob, final int role)
	{
		if(mob==null)
			return false;
		if((role<0)||(role>govt().getPositions().length))
			return false;
		final ClanPosition pos = govt().getPositions()[role];
		if((pos.getInnerMaskStr() == null)
		||(pos.getInnerMaskStr().trim().length()==0))
			return true;
		final String mask = this.fixRequirementMask(pos.getInnerMaskStr());
		return CMLib.masking().maskCheck(CMLib.masking().getPreCompiledMask(mask), mob);
	}

	@Override
	public Authority getAuthority(final int roleID, final Function function)
	{
		if((roleID<0)||(roleID>=govt().getPositions().length))
			return Authority.CAN_NOT_DO;
		return govt().getPositions()[roleID].getFunctionChart()[function.ordinal()];
	}

	public String fixRequirementMask(final String oldMask)
	{
		if((oldMask==null)||(oldMask.trim().length()==0))
			return "";
		final StringBuilder mask=new StringBuilder(oldMask.trim());
		if(mask.length()==0)
			return "";
		final MOB M=getResponsibleMember();
		int x=mask.indexOf("%[");
		while(x>=0)
		{
			final int y=mask.indexOf("]%",x+1);
			if(y>x)
			{
				final String tag=mask.substring(x+2,y);
				String value="Unknown";
				if(isStat(tag))
					value=getStat(tag);
				else
				if(M!=null)
				{
					if(tag.equalsIgnoreCase("WORSHIPCHARID"))
					{
						value=M.charStats().getWorshipCharID();
						if(value.length()==0)
							value="ANY";
					}
					else
					if(CMLib.coffeeMaker().isAnyGenStat(M, tag))
						value=CMLib.coffeeMaker().getAnyGenStat(M, tag);
				}
				else
				if(tag.equalsIgnoreCase("WORSHIPCHARID"))
					value="ANY";
				mask.replace(x, y+2, value);
			}
			if(x>=mask.length()-1)
				break;
			x=mask.indexOf("%[",x+1);
		}
		return mask.toString();
	}

	@Override
	public String getBasicRequirementMask()
	{
		return fixRequirementMask(govt().getRequiredMaskStr());
	}

	protected List<MemberRecord> getRealMemberList(final int PosFilter)
	{
		final List<MemberRecord> members=getMemberList(PosFilter);
		if(members==null)
			return null;
		final List<MemberRecord> realMembers=new Vector<MemberRecord>();
		for(final MemberRecord member : members)
		{
			if(CMLib.players().playerExists(member.name))
				realMembers.add(member);
		}
		return members;
	}

	@Override
	public int getSize()
	{
		if(transientSize < 0)
			transientSize = CMLib.database().DBReadClanMembers(clanID()).size();
		return transientSize;
	}

	@Override
	public String name()
	{
		return clanName;
	}

	@Override
	public String getName()
	{
		return clanName;
	}

	@Override
	public String clanID()
	{
		return clanName;
	}

	@Override
	public void setName(final String newName)
	{
		clanName = newName;
	}

	@Override
	public String getPremise()
	{
		return clanPremise;
	}

	@Override
	public void setPremise(final String newPremise)
	{
		clanPremise = newPremise;
	}

	@Override
	public MOB getFactoryMOB()
	{
		if(this.factoryMob == null)
		{
			this.factoryMob = CMClass.getMOB("StdMOB");
			this.factoryMob.setName("^</B^>");
			this.factoryMob.basePhyStats().setDisposition(PhyStats.IS_GOLEM);
			this.factoryMob.phyStats().setDisposition(PhyStats.IS_GOLEM);
			this.factoryMob.setClan(clanID(),this.autoPosition);
		}
		return this.factoryMob;
	}

	@Override
	public int getClanLevel()
	{
		return clanLevel;
	}

	@Override
	public void setClanLevel(final int newClanLevel)
	{
		if(newClanLevel<=0)
			clanLevel=1;
		else
			clanLevel = newClanLevel;
	}

	@Override
	public String getAcceptanceSettings()
	{
		return acceptanceSettings;
	}

	@Override
	public void setAcceptanceSettings(final String newSettings)
	{
		acceptanceSettings = newSettings;
	}

	@Override
	public String getClanClass()
	{
		return clanClass;
	}

	@Override
	public void setClanClass(final String newClass)
	{
		clanClass = newClass;
	}

	@Override
	public String getDataXML()
	{
		final StringBuilder str=new StringBuilder("");
		final XMLLibrary xmlLib=CMLib.xml();
		str.append("<POLITICS>");
		str.append(xmlLib.convertXMLtoTag("GOVERNMENT",""+getGovernmentID()));
		str.append(xmlLib.convertXMLtoTag("TAXRATE",""+getTaxes()));
		str.append(xmlLib.convertXMLtoTag("EXP",""+getExp()));
		str.append(xmlLib.convertXMLtoTag("ONLINEMINS",""+totalOnlineMins));
		str.append(xmlLib.convertXMLtoTag("LVLSGAINED",""+totalLevelsGained));
		str.append(xmlLib.convertXMLtoTag("LEVEL",""+getClanLevel()));
		str.append(xmlLib.convertXMLtoTag("CCLASS",""+getClanClass()));
		str.append(xmlLib.convertXMLtoTag("AUTOPOS",""+getAutoPosition()));
		str.append(xmlLib.convertXMLtoTag("FLAGS",""+getStat("FLAGS")));
		str.append(xmlLib.convertXMLtoTag("LASTSTATUSCHANGE",""+this.lastStatusChange));
		if(clanCategory!=null)
			str.append(xmlLib.convertXMLtoTag("CATE",clanCategory));
		if(overrideMinClanMembers!=null)
			str.append(xmlLib.convertXMLtoTag("MINM",overrideMinClanMembers.toString()));
		if(isRivalrous!=null)
			str.append(xmlLib.convertXMLtoTag("RIVAL",isRivalrous.toString()));
		str.append("<ACHIEVEMENTS");
		for(final Iterator<Tracker> i=achievementers.values().iterator();i.hasNext();)
		{
			final Tracker T = i.next();
			if(T.getAchievement().isSavableTracker() && (T.getCount(null) != 0))
				str.append(" ").append(T.getAchievement().getTattoo()).append("=").append(T.getCount(null));
			// getCount(null) should be ok, because it's only the un-savable trackers that need the mob obj
		}
		str.append(" />");
		str.append("<TATTOOS>").append(CMParms.toListString(tattoos)).append("</TATTOOS>");
		if(relations.size()==0)
			str.append("<RELATIONS/>");
		else
		{
			str.append("<RELATIONS>");
			for(final Iterator<String> e=relations.keySet().iterator();e.hasNext();)
			{
				final String key=e.next();
				str.append("<RELATION>");
				str.append(xmlLib.convertXMLtoTag("CLAN",key));
				final long[] i=relations.get(key);
				str.append(xmlLib.convertXMLtoTag("STATUS",""+i[0]));
				str.append("</RELATION>");
			}
			str.append("</RELATIONS>");
		}
		str.append("</POLITICS>");
		str.append(xmlLib.convertXMLtoTag("ONLINEMINS",""+totalOnlineMins));
		str.append(xmlLib.convertXMLtoTag("LVLSGAINED",""+totalLevelsGained));
		final StringBuilder monthlies = new StringBuilder();
		monthlies.append(monthOnlineMins).append(",");
		monthlies.append(monthPlayerXP).append(",");
		monthlies.append(monthClanXP).append(",");
		monthlies.append(monthConquered).append(",");
		monthlies.append(monthClanLevels).append(",");
		monthlies.append(monthControlPoints).append(",");
		monthlies.append(monthNewMembers);
		str.append(xmlLib.convertXMLtoTag("MONTHLYSTATS",monthlies.toString()));

		return str.toString();
	}

	@Override
	public void setDataXML(final String politics)
	{
		final XMLLibrary xmlLib=CMLib.xml();
		XMLTag piece;
		relations.clear();
		government=0;
		if(politics.trim().length()==0)
			return;
		final List<XMLLibrary.XMLTag> xml=xmlLib.parseAllXML(politics);
		if(xml==null)
		{
			Log.errOut("Clans","Unable to parse: "+politics);
			return;
		}

		final List<XMLLibrary.XMLTag> poliData=xmlLib.getContentsFromPieces(xml,"POLITICS");
		if(poliData==null)
		{
			Log.errOut("Clans","Unable to get POLITICS data.");
			return;
		}
		government=xmlLib.getIntFromPieces(poliData,"GOVERNMENT");
		exp=xmlLib.getLongFromPieces(poliData,"EXP");
		setClanLevel(xmlLib.getIntFromPieces(poliData,"LEVEL"));
		setExp(exp); // may change the level
		taxRate=xmlLib.getDoubleFromPieces(poliData,"TAXRATE");
		clanClass=xmlLib.getValFromPieces(poliData,"CCLASS");
		lastStatusChange=xmlLib.getLongFromPieces(poliData,"LASTSTATUSCHANGE");

		autoPosition=xmlLib.getIntFromPieces(poliData,"AUTOPOS");
		clanCategory=null;
		piece=xmlLib.getPieceFromPieces(poliData, "CATE");
		if(piece!=null)
			setCategory(piece.value());
		overrideMinClanMembers=null;
		piece=xmlLib.getPieceFromPieces(poliData, "MINM");
		if(piece!=null)
			this.setMinClanMembers(CMath.s_int(piece.value()));
		isRivalrous=null;
		piece=xmlLib.getPieceFromPieces(poliData, "RIVAL");
		if(piece!=null)
			setRivalrous(CMath.s_bool(piece.value()));

		final String monthlyData = xmlLib.getValFromPieces(poliData, "MONTHLYSTATS");
		final int[] data=CMParms.parseIntList(monthlyData, ',');
		if((data != null)&&(data.length>6))
		{
			monthOnlineMins = data[0];
			monthPlayerXP = data[1];
			monthClanXP = data[2];
			monthConquered = data[3];
			monthClanLevels = data[4];
			monthControlPoints = data[5];
			monthNewMembers = data[6];
		}
		totalOnlineMins=xmlLib.getIntFromPieces(poliData,"ONLINEMINS");
		totalLevelsGained=xmlLib.getIntFromPieces(poliData,"LVLSGAINED");
		setStat("FLAGS",xmlLib.getValFromPieces(poliData,"FLAGS"));

		final XMLTag achievePiece = xmlLib.getPieceFromPieces(poliData, "ACHIEVEMENTS");
		achievementers.clear();
		for(final Enumeration<Achievement> a=CMLib.achievements().achievements(Agent.CLAN);a.hasMoreElements();)
		{
			final Achievement A=a.nextElement();
			if((achievePiece != null) && achievePiece.parms().containsKey(A.getTattoo()))
				achievementers.put(A.getTattoo(), A.getTracker(CMath.s_int(achievePiece.parms().get(A.getTattoo()).trim())));
			else
				achievementers.put(A.getTattoo(), A.getTracker(0));
		}
		final String[] allTattoos=xmlLib.getValFromPieces(poliData, "TATTOOS").split(",");
		this.tattoos.clear();
		for(final String tattoo : allTattoos)
			this.addTattoo(tattoo);

		// now RESOURCES!
		final List<XMLLibrary.XMLTag> xV=xmlLib.getContentsFromPieces(poliData,"RELATIONS");
		if((xV!=null)&&(xV.size()>0))
		{
			for(int x=0;x<xV.size();x++)
			{
				final XMLTag iblk=xV.get(x);
				if((!iblk.tag().equalsIgnoreCase("RELATION"))||(iblk.contents()==null))
					continue;
				final String relClanID=iblk.getValFromPieces("CLAN");
				final int rel=iblk.getIntFromPieces("STATUS");
				setClanRelations(relClanID,rel,0);
			}
		}
	}

	@Override
	public int getStatus()
	{
		return clanStatus;
	}

	@Override
	public void setStatus(final int newStatus)
	{
		if(newStatus != clanStatus)
			this.lastStatusChange=System.currentTimeMillis();
		clanStatus = newStatus;
	}

	@Override
	public String getRecall()
	{
		return clanRecall;
	}

	@Override
	public void setRecall(final String newRecall)
	{
		clanRecall = newRecall;
	}

	@Override
	public String getMorgue()
	{
		return clanMorgue;
	}

	@Override
	public void setMorgue(final String newMorgue)
	{
		clanMorgue = newMorgue;
	}

	@Override
	public String getDonation()
	{
		return clanDonationRoom;
	}

	@Override
	public void setDonation(final String newDonation)
	{
		clanDonationRoom = newDonation;
	}

	@Override
	public List<MemberRecord> getMemberList()
	{
		return getMemberList(-1);
	}

	public int filterMedianLevel(final List<FullMemberRecord> members)
	{
		final List<Integer> lvls=new SortedListWrap<Integer>(new XVector<Integer>());
		for(final FullMemberRecord r : members)
			lvls.add(Integer.valueOf(r.level));
		if(lvls.size()>0)
			return lvls.get(lvls.size()/2).intValue();
		return 0;
	}

	public List<MemberRecord> filterMemberList(final List<? extends MemberRecord> members, final int posFilter)
	{
		final Vector<MemberRecord> filteredMembers=new Vector<MemberRecord>();
		for(final MemberRecord member : members)
		{
			if(((member.role==posFilter)||(posFilter<0))
			&&(member.name.length()>0))
				filteredMembers.add(member);
		}
		return filteredMembers;
	}

	@Override
	public List<MemberRecord> getMemberList(final int posFilter)
	{
		final List<MemberRecord> members=CMLib.database().DBReadClanMembers(clanID());
		transientSize = members.size();
		return filterMemberList(members, posFilter);
	}

	@Override
	public MemberRecord findMemberRecord(final String name)
	{
		final List<MemberRecord> members=CMLib.database().DBReadClanMembers(clanID());
		transientSize = members.size();
		for(final MemberRecord member : members)
		{
			if(member.name.equalsIgnoreCase(name))
				return member;
		}
		for(final MemberRecord member : members)
		{
			if(member.name.startsWith(name))
				return member;
		}
		return null;
	}

	@Override
	public MOB findMember(final String name)
	{
		final MemberRecord M=findMemberRecord(name);
		if(M != null)
			return CMLib.players().getPlayerAllHosts(M.name);
		return null;
	}

	@Override
	public MemberRecord getMember(final String name)
	{
		return CMLib.database().DBGetClanMember(clanID(),name);
	}

	@Override
	public List<FullMemberRecord> getFullMemberList()
	{
		final List<FullMemberRecord> members=new Vector<FullMemberRecord>();
		final List<MemberRecord> fullMembers = CMLib.database().DBReadClanMembers(clanID());
		final List<MemberRecord> subMembers=filterMemberList(fullMembers, -1);
		transientSize = fullMembers.size();
		for(final MemberRecord member : subMembers)
		{
			if(member!=null)
			{
				final MOB M=CMLib.players().getPlayer(member.name);
				if((M!=null)
				&&(M.playerStats()!=null))
				{
					final boolean isAdmin=CMSecurity.isASysOp(M) || M.phyStats().level() > CMProps.get(M.session()).getInt(CMProps.Int.LASTPLAYERLEVEL);
					if(M.lastTickedDateTime()>0)
						members.add(new FullMemberRecord(member,M.basePhyStats().level(),M.lastTickedDateTime(),isAdmin));
					else
						members.add(new FullMemberRecord(member,M.basePhyStats().level(),M.playerStats().getLastDateTime(),isAdmin));
				}
				else
				{
					final PlayerLibrary.ThinPlayer tP = CMLib.database().getThinUser(member.name);
					if(tP != null)
					{
						final boolean isAdmin=CMSecurity.isASysOp(tP) || tP.level() > CMProps.getIntVar(CMProps.Int.LASTPLAYERLEVEL);
						members.add(new FullMemberRecord(member,tP.level(),tP.last(),isAdmin));
					}
					else
					{
						Log.warnOut("Clan "+clanID()+" removed member '"+member.name+"' due to being nonexistant!");
						CMLib.database().DBUpdateClanMembership(member.name, clanID(), -1);
					}
				}
			}
		}
		return members;
	}

	private String crewList(List<? extends MemberRecord> members, final int posFilter)
	{
		final StringBuffer list=new StringBuffer("");
		members = filterMemberList(members, posFilter);
		if(members.size()>1)
		{
			for(int j=0;j<(members.size() - 1);j++)
				list.append(members.get(j).name+", ");
			list.append("and "+members.get(members.size()-1).name);
		}
		else
		if(members.size()>0)
			list.append(members.get(0).name);
		return list.toString();
	}

	@Override
	public int getNumVoters(final Function function)
	{
		int voters=0;
		final List<MemberRecord> members=getMemberList();
		final Function voteFunc = (function == Function.ASSIGN) ? Function.VOTE_ASSIGN : Function.VOTE_OTHER;
		for(final MemberRecord member : members)
		{
			if(getAuthority(member.role, voteFunc)==Authority.CAN_DO)
				voters++;
		}
		return voters;
	}

	@Override
	public List<Integer> getTopRankedRoles(final Function func)
	{
		final List<ClanPosition> allRoles=new LinkedList<ClanPosition>();
		for(final ClanPosition pos : govt().getPositions())
		{
			if((func==null)||(pos.getFunctionChart()[func.ordinal()]!=Authority.CAN_NOT_DO))
				allRoles.add(pos);
		}
		final List<Integer> roleIDs=new LinkedList<Integer>();
		int topRank=Integer.MAX_VALUE;
		for(final ClanPosition pos : allRoles)
		{
			if(pos.getRank() < topRank)
				topRank=pos.getRank();
		}
		for(final ClanPosition pos : allRoles)
		{
			if(pos.getRank() == topRank)
				roleIDs.add(Integer.valueOf(pos.getRoleID()));
		}
		return roleIDs;
	}

	@Override
	public int getNumberRoles()
	{
		return govt().getPositions().length;
	}

	@Override
	public int getTopQualifiedRoleID(final Function func, final MOB mob)
	{
		if(mob==null)
			return govt().getAutoRole();
		ClanPosition topPos = null;
		for(final ClanPosition pos : govt().getPositions())
		{
			if(canBeAssigned(mob,pos.getRoleID())
			&&((topPos==null)||(pos.getRank() < topPos.getRank()))
			&&((func==null)||(getAuthority(pos.getRoleID(),func)!=Authority.CAN_NOT_DO)))
				topPos = pos;
		}
		if(topPos == null)
			return govt().getAutoRole();
		return topPos.getRoleID();
	}

	@Override
	public int getRoleFromName(String position)
	{
		position=position.toUpperCase().trim();
		for(final ClanPosition pos : govt().getPositions())
		{
			if(pos.getID().equalsIgnoreCase(position)
			||pos.getName().equalsIgnoreCase(position)
			||pos.getPluralName().equalsIgnoreCase(position))
				return pos.getRoleID();
		}
		for(final ClanPosition pos : govt().getPositions())
		{
			if(pos.getID().toUpperCase().startsWith(position)
			||pos.getName().toUpperCase().equalsIgnoreCase(position))
				return pos.getRoleID();
		}
		for(final ClanPosition pos : govt().getPositions())
		{
			if((pos.getID().toUpperCase().indexOf(position)>0)
			||(pos.getName().toUpperCase().indexOf(position)>0))
				return pos.getRoleID();
		}
		return -1;
	}

	@Override
	public boolean isPubliclyListedFor(final MOB mob)
	{
		if((!govt().isPublic())&&(mob.getClanRole(clanID())==null))
			return false;
		return true;
	}

	@Override
	public String[] getRolesList()
	{
		final List<String> roleNames=new LinkedList<String>();
		for(final ClanPosition pos : govt().getPositions())
			roleNames.add(pos.getName());
		return roleNames.toArray(new String[0]);
	}

	@Override
	public int getMostInRole(final int roleID)
	{
		if((roleID<0)||(roleID>=govt().getPositions().length))
			return 0;
		final double most = govt().getPositions()[roleID].getMax();
		if(most >= 1.0)
			return (int)Math.round(most);
		if(most <= 0)
			return 1;
		final double rawMost = CMath.mul(getSize(), most);
		if(rawMost < 1.0)
			return 1;
		return (int)Math.round(rawMost);
	}

	@Override
	public String getRoleName(final int roleID, final boolean titleCase, final boolean plural)
	{
		if((roleID<0)||(roleID>=govt().getPositions().length))
			return "";
		final ClanPosition pos=govt().getPositions()[roleID];
		if(plural)
		{
			if(!titleCase)
				return pos.getPluralName().toLowerCase();
			else
				return CMStrings.capitalizeAndLower(pos.getPluralName());
		}
		if(!titleCase)
			return pos.getName().toLowerCase();
		else
			return CMStrings.capitalizeAllFirstLettersAndLower(pos.getName());
	}

	protected boolean isSafeFromPurge()
	{
		if(getMinClanMembers()<=0)
			return true;
		final List<String> protectedOnes=Resources.getFileLineVector(Resources.getFileResource("protectedplayers.ini",false));
		if((protectedOnes!=null)&&(protectedOnes.size()>0))
		{
			for(int b=0;b<protectedOnes.size();b++)
			{
				final String B=protectedOnes.get(b);
				if(B.equalsIgnoreCase(clanID()))
					return true;
			}
		}
		return false;
	}

	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		if(tickID!=Tickable.TICKID_CLAN)
			return true;
		if(CMSecurity.isDisabled(CMSecurity.DisFlag.CLANTICKS))
			return true;

		synchronized(this)
		{
			tickUp++;
		}

		if(lastPropsReload < CMProps.getLastResetTime())
		{
			lastPropsReload=CMProps.getLastResetTime();
			this.overrideMinClanMembers=null;
		}

		for (final Tattoo tattoo : tattoos)
		{
			if ((tattoo != null) && (tattoo.getTickDown() > 0))
			{
				if (tattoo.tickDown() <= 0)
					delTattoo(tattoo);
			}
		}

		try
		{
			if((tickUp % CMProps.getTicksPerMudHour())==0)
			{
				int onlineMembers=0;
				for(final Iterator<Session> s=CMLib.sessions().sessions();s.hasNext();)
				{
					final Session S=s.next();
					if(S!=null)
					{
						final MOB M=S.mob();
						if((M!=null)
						&&(M.getClanRole(clanID())!=null))
							onlineMembers++;
					}
				}
				final long ellapsedMs = System.currentTimeMillis() - this.lastClanTickMs;
				final int playerMinutes = (int)((onlineMembers * ellapsedMs) / (1000 * 60));
				bumpTrophyData(Trophy.MonthlyPlayerMinutes, playerMinutes);
				bumpTrophyData(Trophy.PlayerMinutes, playerMinutes);
				this.lastClanTickMs = System.currentTimeMillis();
			}

			// only do the following once per rl day
			if(tickUp < CMProps.getTicksPerDay())
			{
				return true;
			}
			tickUp = 0;

			final List<FullMemberRecord> members=getFullMemberList();
			int activeMembers=0;
			final long deathMilis=CMProps.getIntVar(CMProps.Int.DAYSCLANDEATH)*CMProps.getIntVar(CMProps.Int.TICKSPERMUDDAY)*CMProps.getTickMillis();
			final long overthrowMilis=CMProps.getIntVar(CMProps.Int.DAYSCLANOVERTHROW)*CMProps.getIntVar(CMProps.Int.TICKSPERMUDDAY)*CMProps.getTickMillis();

			for(final FullMemberRecord member : members)
			{
				final long lastLogin=member.lastActiveTimeMs;
				if(((System.currentTimeMillis()-lastLogin)<deathMilis)||(deathMilis==0))
					activeMembers++;
			}
			final int minimumMembers = getMinClanMembers();
			if(CMSecurity.isDebugging(CMSecurity.DbgFlag.CLANS))
				Log.debugOut("DefaultClan","("+clanID()+"): "+activeMembers+"/"+minimumMembers+" active members.");
			if(activeMembers<minimumMembers)
			{
				if(!isSafeFromPurge())
				{
					final long duration=(3L * 24L * 60L * 60L * 1000L);
					if((lastStatusChange > 0)
					&& ((System.currentTimeMillis() - this.lastStatusChange)>duration))
					{
						if(getStatus()==CLANSTATUS_FADING)
						{
							Log.sysOut("Clans","Clan '"+getName()+" deleted with only "+activeMembers+" having logged on lately.");
							destroyClan();
							final StringBuffer buf=new StringBuffer("");
							for(final FullMemberRecord member : members)
								buf.append(member.name+" on "+CMLib.time().date2String(member.lastActiveTimeMs)+"  ");
							Log.sysOut("Clans","Clan '"+getName()+" had the following membership: "+buf.toString());
							return true;
						}
						setStatus(CLANSTATUS_FADING);
						final List<Integer> topRoles=getTopRankedRoles(Function.ASSIGN);
						for(final MemberRecord member : members)
						{
							final String name = member.name;
							final int role=member.role;
							//long lastLogin=((Long)members.elementAt(j,3)).longValue();
							if(topRoles.contains(Integer.valueOf(role)))
							{
								if(CMLib.players().playerExists(name))
								{
									CMLib.smtp().emailIfPossible("AutoPurge",CMStrings.capitalizeAndLower(name),"AutoPurge: "+name(),
											""+getGovernmentName()+" "+name()+" is in danger of being deleted if at least "+(minimumMembers-activeMembers)
											+" members do not log on within 24 hours.");
								}
							}
						}

						Log.sysOut("Clans","Clan '"+getName()+"' fading with only "+activeMembers+" having logged on lately.  Will purge on "+CMLib.time().date2String(this.lastStatusChange)+duration);
						clanAnnounce(""+getGovernmentName()+" "+name()+" is in danger of being deleted if more members do not log on within 24 hours.");
						update();
					}
				}
				else
				if(getStatus()!=CLANSTATUS_ACTIVE)
				{
					setStatus(CLANSTATUS_ACTIVE);
					update();
				}
			}
			else
			switch(getStatus())
			{
			case CLANSTATUS_FADING:
				setStatus(CLANSTATUS_ACTIVE);
				clanAnnounce(""+getGovernmentName()+" "+name()+" is no longer in danger of being deleted.  Be aware that there is required activity level.");
				update();
				break;
			case CLANSTATUS_PENDING:
				setStatus(CLANSTATUS_ACTIVE);
				Log.sysOut("Clans",""+getGovernmentName()+" '"+getName()+" now active with "+activeMembers+".");
				clanAnnounce(""+getGovernmentName()+" "+name()+" now has sufficient members.  The "+getGovernmentName()+" is now fully approved.");
				update();
				break;
			default:
				break;
			}

			// handle any necessary promotions
			if(govt().getAutoPromoteBy() != AutoPromoteFlag.NONE)
			{
				// first step is to figure out which positions need filling
				// 1. if at least one position can assign others, then the highest of those is the answer.
				// 2. if none can assign others, than each over the applicants is the answer

				// Algorithm:
				// Get all the members who qualify by their last login time
				// Sort them by the qualifying criteria
				// for each available position, either fill holes (no overwrite), or move them (overwrite)
				final List<FullMemberRecord> highestQualifiedMembers = new LinkedList<FullMemberRecord>();
				for(final FullMemberRecord member : members)
				{
					if((member.role<0)||(member.role>=govt().getPositions().length))
						continue;
					// checking if they are only an applicant
					if((member.role == govt().getAutoRole())
					&&(govt().getAcceptPos() != govt().getAutoRole())
					&&(govt().getAutoRole() >= 0)
					&&(govt().getAcceptPos() >= 0)
					&&(getTopRankedRoles(Function.ACCEPT).size()>0))
						continue;
					final ThinPlayer M = CMLib.database().getThinUser(member.name);
					if(M==null)
						continue;
					if((((System.currentTimeMillis()-member.lastActiveTimeMs)<overthrowMilis)||(overthrowMilis==0))
					&&(!highestQualifiedMembers.contains(member)))
						highestQualifiedMembers.add(member);
				}
				if(highestQualifiedMembers.size()==0)
				{
					for(final FullMemberRecord member : members)
					{
						if((member.role<0)||(member.role>=govt().getPositions().length))
							member.role=0;
						final ThinPlayer M = CMLib.database().getThinUser(member.name);
						if(M==null)
							continue;
						if((((System.currentTimeMillis()-member.lastActiveTimeMs)<overthrowMilis)||(overthrowMilis==0))
						&&(!highestQualifiedMembers.contains(member)))
							highestQualifiedMembers.add(member);
					}
				}
				if(highestQualifiedMembers.size()==0)
				{
					for(final FullMemberRecord member : members)
					{
						if((member.role<0)||(member.role>=govt().getPositions().length))
							member.role=0;
						final ThinPlayer M = CMLib.database().getThinUser(member.name);
						if(M==null)
							continue;
						highestQualifiedMembers.add(member);
					}
				}

				// now sort them.
				AutoPromoteFlag basePromoteBy = govt().getAutoPromoteBy();
				boolean overWrite = false;
				// Now, sort the members by the qualifying criteria
				switch(basePromoteBy)
				{
				case LEVEL_OVERWRITE:
					overWrite=true;
					basePromoteBy=AutoPromoteFlag.LEVEL;
					//$FALL-THROUGH$
				case LEVEL:
					Collections.sort(highestQualifiedMembers,new Comparator<FullMemberRecord>()
					{
						@Override
						public int compare(final FullMemberRecord o1, final FullMemberRecord o2)
						{
							if(o2.level==o1.level)
								return 0;
							if(o2.level<o1.level)
								return -1;
							return 1;
						}
					});
					break;
				case RANK_OVERWRITE:
					overWrite=true;
					basePromoteBy=AutoPromoteFlag.RANK;
					//$FALL-THROUGH$
				case RANK:
					Collections.sort(highestQualifiedMembers,new Comparator<MemberRecord>()
					{
						@Override
						public int compare(final MemberRecord o1, final MemberRecord o2)
						{
							final ClanPosition cp1 = govt().getPositions()[o1.role];
							final ClanPosition cp2 = govt().getPositions()[o2.role];
							if(cp1.getRank()==cp2.getRank())
								return 0;
							if(cp1.getRank()<cp2.getRank())
								return -1;
							return 1;
						}
					});
					break;
				case GOLD_OVERWRITE:
					overWrite=true;
					basePromoteBy=AutoPromoteFlag.GOLD;
					//$FALL-THROUGH$
				case GOLD:
					Collections.sort(highestQualifiedMembers,new Comparator<MemberRecord>()
					{
						@Override
						public int compare(final MemberRecord o1, final MemberRecord o2)
						{
							if(o2.donatedGold==o1.donatedGold)
								return 0;
							if(o2.donatedGold<o1.donatedGold)
								return -1;
							return 1;
						}
					});
					break;
				case XP_OVERWRITE:
					overWrite=true;
					basePromoteBy=AutoPromoteFlag.XP;
					//$FALL-THROUGH$
				case XP:
					Collections.sort(highestQualifiedMembers,new Comparator<MemberRecord>()
					{
						@Override
						public int compare(final MemberRecord o1, final MemberRecord o2)
						{
							if(o2.donatedXP==o1.donatedXP)
								return 0;
							if(o2.donatedXP<o1.donatedXP)
								return -1;
							return 1;
						}
					});
					break;
				case JOINDATE_OVERWRITE:
					overWrite=true;
					basePromoteBy=AutoPromoteFlag.JOINDATE;
					//$FALL-THROUGH$
				case JOINDATE:
					Collections.sort(highestQualifiedMembers,new Comparator<MemberRecord>()
					{
						@Override
						public int compare(final MemberRecord o1, final MemberRecord o2)
						{
							if(o1.joinDate==o2.joinDate)
								return 0;
							if(o1.joinDate<o2.joinDate)
								return -1;
							return 1;
						}
					});
					break;
				case NONE:
					break;
				default:
					break;
				}

				// now get all the positions we need to fill
				// if a position can assign, then that's the winner(s)
				final List<Integer> highPositionList = getTopRankedRoles(Function.ASSIGN);
				if(highPositionList.size()==0)
				{
					// otherwise, every position that's not an applicant is the winner
					for(final ClanPosition pos : govt().getPositions())
					{
						if(((pos.getRoleID() == govt().getAutoRole())||(pos.getRoleID() == govt().getAcceptPos()))
						&&(govt().getAcceptPos() != govt().getAutoRole())
						&&(govt().getAutoRole() >= 0)
						&&(govt().getAcceptPos() >= 0)
						&&(getTopRankedRoles(Function.ACCEPT).size()>0))
							continue;
						highPositionList.add(Integer.valueOf(pos.getRoleID()));
					}
				}
				Collections.sort(highPositionList, new Comparator<Integer>()
				{
					@Override
					public int compare(final Integer o1, final Integer o2)
					{
						final ClanPosition pos1=govt().getPositions()[o1.intValue()];
						final ClanPosition pos2=govt().getPositions()[o2.intValue()];
						if(pos1.getRank()==pos2.getRank())
							return 0;
						if(pos1.getRank()<pos2.getRank())
							return -1;
						return 1;
					}
				});

				final HashMap<String,Reference<ThinPlayer>> thinCache = new HashMap<String,Reference<ThinPlayer>>();
				final boolean fillAll = getTopRankedRoles(Function.ASSIGN).size()==0;
				// finally we fill the positions
				// if we are overwriting, then we always pick the best people who fit and
				// kick out the rest.  If we are NOT overwriting, then we are only filling
				// holes for when inactivity drops one out.
				final Map<FullMemberRecord,Integer> finalHighRollers = new HashMap<FullMemberRecord,Integer>();
				for(final Integer highPosI : highPositionList)
				{
					final int highRoleID = highPosI.intValue();
					int most=getMostInRole(highRoleID);
					if(most>getSize())
						most=getSize();
					int numToAdd = 0;
					if(!overWrite)
					{
						int current = 0;
						for(final Iterator<FullMemberRecord> i=highestQualifiedMembers.iterator();i.hasNext();)
						{
							final FullMemberRecord M=i.next();
							if(M.role == highRoleID)
							{
								i.remove();
								finalHighRollers.put(M, highPosI);
								current++;
							}
						}
						numToAdd = most-current;
						if(!fillAll)
						{
							if(current>0)
								numToAdd=0;
							else
							if(numToAdd>0)
								numToAdd=1;
						}
					}
					else
					{
						numToAdd = most;
						if(!fillAll)
						{
							for(final Iterator<FullMemberRecord> i=highestQualifiedMembers.iterator();i.hasNext();)
							{
								final FullMemberRecord M=i.next();
								if(M.role == highRoleID)
								{
									i.remove();
									finalHighRollers.put(M, highPosI);
									numToAdd=0;
								}
							}
							if(numToAdd>0)
								numToAdd=1;
						}
					}
					for(int i=0;i<numToAdd;i++)
					{
						for(int s=0;s<highestQualifiedMembers.size();s++)
						{
							final FullMemberRecord M=highestQualifiedMembers.get(s);
							if(!thinCache.containsKey(M.name))
								thinCache.put(M.name, new WeakReference<ThinPlayer>(CMLib.database().getThinUser(M.name)));
							final ThinPlayer tP=thinCache.get(M.name).get();
							if((tP!=null)
							&&(canBeAssigned(tP, highRoleID)))
							{
								highestQualifiedMembers.remove(s);
								finalHighRollers.put(M, highPosI);
								break;
							}
						}
					}
				}
				final ClanPosition acceptRole=govt().getPositions()[govt().getAcceptPos()];
				final Map<FullMemberRecord,Integer> finalRollers = finalHighRollers;
				for(final FullMemberRecord member : members)
				{
					if(highPositionList.contains(Integer.valueOf(member.role))
					&&(!finalRollers.containsKey(member)))
					{
						boolean someoneIsTakingMyPlace = false;
						for(final FullMemberRecord R : finalRollers.keySet())
						{
							if((finalRollers.get(R).intValue() == member.role)
							&&(R.role != member.role))
								someoneIsTakingMyPlace=true;
						}
						if(someoneIsTakingMyPlace)
							finalRollers.put(member, Integer.valueOf(acceptRole.getRoleID()));
					}
				}

				for(final FullMemberRecord member : members)
				{
					if(finalRollers.containsKey(member))
					{
						final Integer newRoleI = finalRollers.get(member);
						if(newRoleI.intValue() != member.role)
						{
							final ClanPosition oldPos = govt().getPositions()[member.role];
							final ClanPosition newPos = govt().getPositions()[newRoleI.intValue()];

							final MOB mob=CMLib.players().getPlayerAllHosts(member.name);
							clanAnnounce(member.name+" is now a "+newPos.getName()+" of the "+getGovernmentName()+" "+name()+".");
							if(oldPos.getRank() < newPos.getRank())
								Log.sysOut("Clans",member.name+" of "+getGovernmentName()+" "+name()+" was auto-demoted to "+newPos.getName()+".");
							else
							if(oldPos.getRank() > newPos.getRank())
								Log.sysOut("Clans",member.name+" of "+getGovernmentName()+" "+name()+" was auto-promoted to "+newPos.getName()+".");
							else
								Log.sysOut("Clans",member.name+" of "+getGovernmentName()+" "+name()+" was auto-assigned to "+newPos.getName()+".");
							if((mob!=null)
							&&(mob.getClanRole(clanID())!=null))
								mob.setClan(clanID(),newPos.getRoleID());
							member.role=newPos.getRoleID();
							CMLib.database().DBUpdateClanMembership(member.name, name(), newPos.getRoleID());
						}
					}
				}

				final List<MemberRecord> highMembers=new LinkedList<MemberRecord>();
				for(final FullMemberRecord member : members)
				{
					if((((System.currentTimeMillis()-member.lastActiveTimeMs)<deathMilis)||(deathMilis==0))
					&&(highPositionList.contains(Integer.valueOf(member.role))))
						highMembers.add(member);
				}

				if(highMembers.size()==0)
				{
					if(!isSafeFromPurge())
					{
						Log.sysOut("Clans","Clan '"+getName()+" deleted for lack of leadership.");
						destroyClan();
						final StringBuffer buf=new StringBuffer("");
						for(final FullMemberRecord member : members)
							buf.append(member.name+" on "+CMLib.time().date2String(member.lastActiveTimeMs)+"  ");
						Log.sysOut("Clans","Clan '"+getName()+" had the following membership: "+buf.toString());
						return true;
					}
				}
			}

			boolean anyVoters = false;
			for(final ClanPosition pos : govt().getPositions())
			{
				if((pos.getFunctionChart()[Function.VOTE_ASSIGN.ordinal()]==Clan.Authority.CAN_DO)
				||(pos.getFunctionChart()[Function.VOTE_OTHER.ordinal()]==Clan.Authority.CAN_DO))
					anyVoters=true;
			}

			// now do votes
			if(anyVoters&&(votes()!=null))
			{
				boolean updateVotes=false;
				final Vector<ClanVote> votesToRemove=new Vector<ClanVote>();
				long duration=govt().getMaxVoteDays();
				if(duration<=0)
					duration=54;
				duration=duration*CMProps.getIntVar(CMProps.Int.TICKSPERMUDDAY)*CMProps.getTickMillis();
				for(final Enumeration<ClanVote> e=votes();e.hasMoreElements();)
				{
					final ClanVote CV=e.nextElement();
					final int numVotes=getNumVoters(Function.values()[CV.function]);
					int quorum=govt().getVoteQuorumPct();
					quorum=(int)Math.round(CMath.mul(CMath.div(quorum,100.0),numVotes));
					if(quorum<2)
						quorum=2;
					if(numVotes==1)
						quorum=1;
					final long endsOn=CV.voteStarted+duration;
					if(CV.voteStatus==VSTAT_STARTED)
					{
						if(CV.votes==null)
							CV.votes=new PairVector<String,Boolean>();
						boolean voteIsOver=false;
						if(System.currentTimeMillis()>endsOn)
							voteIsOver=true;
						else
						if(CV.votes.size()==numVotes)
							voteIsOver=true;
						if(voteIsOver)
						{
							CV.voteStarted=System.currentTimeMillis();
							updateVotes=true;
							if(CV.votes.size()<quorum)
								CV.voteStatus=VSTAT_FAILED;
							else
							{
								int yeas=0;
								int nays=0;
								for(int i=0;i<CV.votes.size();i++)
								{
									if(CV.votes.getSecond(i).booleanValue())
										yeas++;
									else
										nays++;
								}
								if(yeas<=nays)
									CV.voteStatus=VSTAT_FAILED;
								else
								{
									CV.voteStatus=VSTAT_PASSED;
									final MOB mob=CMClass.getFactoryMOB();
									mob.setName(clanID());
									mob.setClan(clanID(),getTopRankedRoles(Function.values()[CV.function]).get(0).intValue());
									mob.basePhyStats().setLevel(1000);
									if(mob.location()==null)
									{
										mob.setLocation(mob.getStartRoom());
										if(mob.location()==null)
											mob.setLocation(CMLib.map().getRandomRoom());
									}
									final Vector<String> V=CMParms.parse(CV.matter);
									mob.doCommand(V,MUDCmdProcessor.METAFLAG_FORCED);
									mob.destroy();
								}
							}
						}
					}
					else
					if(System.currentTimeMillis()>endsOn)
					{
						updateVotes=true;
						votesToRemove.addElement(CV);
					}
				}
				for(int v=0;v<votesToRemove.size();v++)
					delVote(votesToRemove.elementAt(v));
				if(updateVotes)
					updateVotes();
			}

			update(); // also saves exp, and trophies
			CMLib.database().DBUpdateClanItems(this);
		}
		catch(final Exception x2)
		{
			Log.errOut("Clans",x2);
		}
		return true;
	}

	@Override
	public boolean doesOutRank(final int highRoleID, final int lowRoleID)
	{
		final ClanGovernment govt=govt();
		if((highRoleID == lowRoleID)
		||(highRoleID < 0)
		||(highRoleID >= govt.getPositions().length))
			return false;
		if((lowRoleID<0)
		||(lowRoleID >= govt.getPositions().length))
			return true;
		return govt.getPositions()[highRoleID].getRank() < govt.getPositions()[lowRoleID].getRank();
	}

	@Override
	public void clanAnnounce(final String msg)
	{
		if(channelSet.size()==0)
		{
			synchronized(channelSet)
			{
				if(channelSet.size()==0)
					channelSet.add(new Pair<Clan,Integer>(this,Integer.valueOf(getGovernment().getAcceptPos())));
			}
		}
		final List<String> channels=CMLib.channels().getFlaggedChannelNames(ChannelsLibrary.ChannelFlag.CLANINFO, null);
		for(int i=0;i<channels.size();i++)
			CMLib.commands().postChannel(channels.get(i),channelSet,msg,true);
	}

	private static final SearchIDList<Ability> emptyAbles =new CMUniqSortSVec<Ability>(1);

	@Override
	public SearchIDList<Ability> clanAbilities(final MOB mob)
	{
		final Pair<Clan,Integer> p=(mob!=null)?mob.getClanRole(clanID()):null;
		if((mob==null)||((p!=null)&&(getAuthority(p.second.intValue(),Function.CLAN_BENEFITS)!=Clan.Authority.CAN_NOT_DO)))
			return govt().getClanLevelAbilities(mob,this,Integer.valueOf(getClanLevel()));
		return emptyAbles;
	}

	@Override
	public int numClanEffects(final MOB mob)
	{
		return govt().getClanLevelEffects(mob, this, Integer.valueOf(getClanLevel())).size();
	}

	@Override
	public ChameleonList<Ability> clanEffects(final MOB mob)
	{
		return govt().getClanLevelEffects(mob,this, Integer.valueOf(getClanLevel()));
	}

	@Override
	public int applyExpMods(final MOB memberM, int exp)
	{
		boolean changed=false;
		if((getTaxes()>0.0)&&(exp>1))
		{
			final int clanshare=(int)Math.round(CMath.mul(exp,getTaxes()));
			if(clanshare>0)
			{
				exp-=clanshare;
				adjExp(memberM, clanshare);
				changed=true;
			}
		}

		// player xp punished for taxes, but not awarded for trophies
		bumpTrophyData(Trophy.MonthlyPlayerXP, exp);

		if(getTrophies() != 0)
		{
			for(final Trophy t : Trophy.values())
			{
				if(CMath.bset(getTrophies(),t.flagNum()))
					exp = CMLib.clans().adjustXPAward(t, exp);
			}
		}


		if(changed)
			update();
		return exp;
	}

	@Override
	public MOB getResponsibleMember()
	{
		final List<MemberRecord> members=getMemberList();
		final List<Integer> topRoles=getTopRankedRoles(null);
		MOB respMember = null;
		final int level = -1;
		for(final MemberRecord member : members)
		{
			if(topRoles.contains(Integer.valueOf(member.role)))
			{
				final MOB M=CMLib.players().getLoadPlayer(member.name);
				if((M!=null)&&(M.basePhyStats().level() > level))
					respMember = M;
			}
		}
		if(respMember != null)
			return respMember;
		String memberName = null;
		ClanPosition newPos=null;
		for(final MemberRecord member : members)
		{
			if((member.role<govt().getPositions().length)
			&&(member.role>=0)
			&&((newPos==null)||(govt().getPositions()[member.role].getRank()<newPos.getRank())))
			{
				newPos = govt().getPositions()[member.role];
				memberName = member.name;
			}
		}
		if(memberName != null)
			return CMLib.players().getLoadPlayer(memberName);
		return null;
	}

	@Override
	public ItemCollection getExtItems()
	{
		return extItems;
	}

	/** Manipulation of the tatoo list */
	@Override
	public void addTattoo(final String of)
	{
		final Tattoo T=(Tattoo)CMClass.getCommon("DefaultTattoo");
		addTattoo(T.set(of));
	}

	@Override
	public void addTattoo(final String of, final int tickDown)
	{
		final Tattoo T=(Tattoo)CMClass.getCommon("DefaultTattoo");
		addTattoo(T.set(of,tickDown));
	}

	@Override
	public void delTattoo(final String of)
	{
		final Tattoo T=findTattoo(of);
		if(T!=null)
			tattoos.remove(T);
	}

	@Override
	public void addTattoo(final Tattoo of)
	{
		if ((of == null) || (of.getTattooName() == null) || (of.getTattooName().length() == 0) || findTattoo(of.getTattooName()) != null)
			return;
		tattoos.addElement(of);
	}

	@Override
	public void delTattoo(final Tattoo of)
	{
		if ((of == null) || (of.getTattooName() == null) || (of.getTattooName().length() == 0))
			return;
		final Tattoo tat = findTattoo(of.getTattooName());
		if (tat == null)
			return;
		tattoos.remove(tat);
	}

	@Override
	public Enumeration<Tattoo> tattoos()
	{
		return tattoos.elements();
	}

	@Override
	public Tattoo findTattoo(final String of)
	{
		if ((of == null) || (of.length() == 0))
			return null;
		return tattoos.find(of.trim());
	}

	@Override
	public Tattoo findTattooStartsWith(final String of)
	{
		if ((of == null) || (of.length() == 0))
			return null;
		return tattoos.findStartsWith(of.trim());
	}

	@Override
	public void killAchievementTracker(final Achievement A, final Tattooable C, final MOB mob)
	{
		if(achievementers.containsKey(A.getTattoo()))
		{
			achievementers.remove(A.getTattoo());
		}
	}

	@Override
	public Tracker getAchievementTracker(final Achievement A, final Tattooable C, final MOB mob)
	{
		final Tracker T;
		if(achievementers.containsKey(A.getTattoo()))
		{
			T=achievementers.get(A.getTattoo());
		}
		else
		{
			T=A.getTracker(0);
			achievementers.put(A.getTattoo(), T);
		}
		return T;
	}

	@Override
	public void rebuildAchievementTracker(final Tattooable C, final MOB mob, final String achievementTattoo)
	{
		final Achievement A=CMLib.achievements().getAchievement(achievementTattoo);
		if(A!=null)
		{
			if(achievementers.containsKey(A.getTattoo()))
				achievementers.put(A.getTattoo(), A.getTracker(achievementers.get(A.getTattoo()).getCount(C)));
			else
				achievementers.put(A.getTattoo(), A.getTracker(0));
		}
		else
			achievementers.remove(achievementTattoo);
	}

	/** Stat variables associated with clan objects. */
	private final static String[] CLAN_STATS={
		"ACCEPTANCE", // 0
		"DETAIL", // 1
		"DONATEROOM", // 2
		"EXP", // 3
		"GOVT", // 4
		"MORGUE", // 5
		"POLITICS", // 6
		"PREMISE", // 7
		"RECALL", // 8
		"SIZE", // 9
		"STATUS", // 10
		"TAXES", // 11
		"TROPHIES", // 12
		"TYPE", // 13
		"AREAS", // 14
		"MEMBERLIST", // 15
		"TOPMEMBER", // 16
		"CLANLEVEL", // 17
		"CATEGORY", // 18
		"RIVALROUS",//19
		"MINMEMBERS", //20
		"CLANCHARCLASS", // 21
		"NAME", // 22
		"FLAGS" // 23
	};

	@Override
	public String[] getStatCodes()
	{
		return CLAN_STATS;
	}

	@Override
	public int getSaveStatIndex()
	{
		return CLAN_STATS.length;
	}

	@Override
	public boolean isStat(final String code)
	{
		return CMParms.indexOf(getStatCodes(), code.toUpperCase().trim()) >= 0;
	}

	@Override
	public String getStat(final String code)
	{
		final int dex=CMParms.indexOf(getStatCodes(),code.toUpperCase().trim());
		if(dex<0)
			return "";
		switch(dex)
		{
		case 0:
			return getAcceptanceSettings();
		case 1:
			return getDetail(null);
		case 2:
			return getDonation();
		case 3:
			return "" + getExp();
		case 4:
			return "" + getGovernmentName();
		case 5:
			return getMorgue();
		case 6:
			return getDataXML();
		case 7:
			return getPremise();
		case 8:
			return getRecall();
		case 9:
			return "" + getSize();
		case 10:
			return Clan.CLANSTATUS_DESC[getStatus()];
		case 11:
			return "" + getTaxes();
		case 12:
			return "" + getTrophies();
		case 13:
			return "0";
		case 14:
		{
			final List<Area> areas = getControlledAreas();
			final StringBuffer list = new StringBuffer("");
			for (int i = 0; i < areas.size(); i++)
				list.append("\"" + areas.get(i).name() + "\" ");
			return list.toString().trim();
		}
		case 15:
		{
			final List<MemberRecord> members = getMemberList();
			final StringBuffer list = new StringBuffer("");
			for (final MemberRecord member : members)
				list.append("\"" + member.name + "\" ");
			return list.toString().trim();
		}
		case 16:
		{
			final MOB M = getResponsibleMember();
			if (M != null)
				return M.Name();
			return "";
		}
		case 17:
			return Integer.toString(getClanLevel());
		case 18:
			return "" + getCategory();
		case 19:
			return "" + isRivalrous();
		case 20:
			return "" + getMinClanMembers();
		case 21:
			return "" + getClanClass();
		case 22:
			return "" + getName();
		case 23:
			return CMParms.toListString(clanFlags);
		}
		return "";
	}

	@Override
	public void setStat(final String code, final String val)
	{
		final int dex=CMParms.indexOf(getStatCodes(),code.toUpperCase().trim());
		if(dex<0)
			return;
		switch(dex)
		{
		case 0:
			setAcceptanceSettings(val);
			break;
		case 1:
			break; // detail
		case 2:
			setDonation(val);
			break;
		case 3:
			setExp(CMath.s_long(val.trim()));
			break;
		case 4:
			setGovernmentID(CMath.s_int(val.trim()));
			break;
		case 5:
			setMorgue(val);
			break;
		case 6:
			setDataXML(val);
			break;
		case 7:
			setPremise(val);
			break;
		case 8:
			setRecall(val);
			break;
		case 9:
			break; // size
		case 10:
			if(CMath.s_int(val.trim())!=getStatus())
				setStatus(CMath.s_int(val.trim()));
			break;
		case 11:
			setTaxes(CMath.s_double(val.trim()));
			break;
		case 12:
			setTrophies(CMath.s_int(val.trim()));
			break;
		case 13:
			break; // type
		case 14:
			break; // areas
		case 15:
			break; // memberlist
		case 16:
			break; // topmember
		case 17:
			setClanLevel(CMath.s_int(val.trim()));
			break; // clanlevel
		case 18:
			setCategory(val.trim());
			break; // clancategory
		case 19:
			setRivalrous(CMath.s_bool(val.trim()));
			break; // isrivalrous
		case 20:
			setMinClanMembers(CMath.s_int(val.trim()));
			break; // minmembers
		case 21:
			setClanClass(val.trim());
			break; // clancharclass
		case 22:
			this.setName(val.trim());
			break; // name
		case 23:
		{
			clanFlags = new SHashSet<ClanFlag>();
			for(final String s : CMParms.parseCommas(val.toUpperCase(),true))
			{
				final ClanFlag flag = (ClanFlag)CMath.s_valueOf(ClanFlag.class, s);
				if(flag != null)
					clanFlags.add(flag);
			}
			break;
		}
		}
	}
}
