/
com/planet_ink/coffee_mud/Abilities/Common/
com/planet_ink/coffee_mud/Abilities/Diseases/
com/planet_ink/coffee_mud/Abilities/Druid/
com/planet_ink/coffee_mud/Abilities/Fighter/
com/planet_ink/coffee_mud/Abilities/Languages/
com/planet_ink/coffee_mud/Abilities/Misc/
com/planet_ink/coffee_mud/Abilities/Prayers/
com/planet_ink/coffee_mud/Abilities/Properties/
com/planet_ink/coffee_mud/Abilities/Skills/
com/planet_ink/coffee_mud/Abilities/Songs/
com/planet_ink/coffee_mud/Abilities/Spells/
com/planet_ink/coffee_mud/Abilities/Thief/
com/planet_ink/coffee_mud/Abilities/Traps/
com/planet_ink/coffee_mud/Behaviors/
com/planet_ink/coffee_mud/CharClasses/interfaces/
com/planet_ink/coffee_mud/Commands/
com/planet_ink/coffee_mud/Commands/interfaces/
com/planet_ink/coffee_mud/Common/
com/planet_ink/coffee_mud/Common/interfaces/
com/planet_ink/coffee_mud/Exits/interfaces/
com/planet_ink/coffee_mud/Items/Armor/
com/planet_ink/coffee_mud/Items/Basic/
com/planet_ink/coffee_mud/Items/CompTech/
com/planet_ink/coffee_mud/Items/MiscMagic/
com/planet_ink/coffee_mud/Items/Weapons/
com/planet_ink/coffee_mud/Items/interfaces/
com/planet_ink/coffee_mud/Libraries/
com/planet_ink/coffee_mud/Libraries/interfaces/
com/planet_ink/coffee_mud/Locales/
com/planet_ink/coffee_mud/MOBS/
com/planet_ink/coffee_mud/Races/
com/planet_ink/coffee_mud/Races/interfaces/
com/planet_ink/coffee_mud/WebMacros/
com/planet_ink/coffee_mud/WebMacros/interfaces/
com/planet_ink/coffee_mud/core/
com/planet_ink/coffee_mud/core/collections/
com/planet_ink/coffee_mud/core/interfaces/
com/planet_ink/coffee_mud/core/intermud/
com/planet_ink/coffee_mud/core/intermud/i3/
com/planet_ink/coffee_web/server/
com/planet_ink/siplet/applet/
lib/
resources/factions/
resources/fakedb/
resources/progs/autoplayer/
resources/quests/holidays/
web/
web/admin.templates/
web/admin/grinder/
web/admin/images/
web/clan.templates/
web/pub.templates/
web/pub/images/mxp/
web/pub/sounds/
web/pub/textedit/
package com.planet_ink.coffee_mud.Libraries;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.interfaces.BoundedObject.BoundedCube;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag;
import com.planet_ink.coffee_mud.core.collections.*;
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.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.ChannelsLibrary.CMChannel;
import com.planet_ink.coffee_mud.Libraries.interfaces.ChannelsLibrary.ChannelFlag;
import com.planet_ink.coffee_mud.Libraries.interfaces.ChannelsLibrary.ChannelMsg;
import com.planet_ink.coffee_mud.Libraries.interfaces.ColorLibrary.Color;
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.util.*;

/*
   Copyright 2005-2016 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 CMChannels extends StdLibrary implements ChannelsLibrary
{
	@Override public String ID(){return "CMChannels";}
	public final int QUEUE_SIZE=100;

	public String[]			baseChannelNames= new String[0];
	public List<CMChannel>	channelList		= new Vector<CMChannel>();
	
	protected Language		commonLang		= null;
	
	public final static List<ChannelMsg> emptyQueue=new ReadOnlyList<ChannelMsg>(new Vector<ChannelMsg>(1));
	public final static Set<ChannelFlag> emptyFlags=new ReadOnlySet<ChannelFlag>(new HashSet<ChannelFlag>(1));

	@Override
	public int getNumChannels()
	{
		return channelList.size();
	}

	@Override
	public CMChannel getChannel(int channelNumber)
	{
		if((channelNumber>=0)&&(channelNumber<channelList.size()))
			return channelList.get(channelNumber);
		return null;
	}

	public CMChannel createNewChannel(final String name)
	{
		return createNewChannel(name, "", "", "", new HashSet<ChannelFlag>(), "", "");
	}
	
	public CMChannel createNewChannel(final String name, final String mask, final Set<ChannelFlag> flags, 
									  final String colorOverrideANSI, final String colorOverrideWords)
	{
		return createNewChannel(name, "", "", mask, flags, colorOverrideANSI, colorOverrideWords);
	}
	
	@Override
	public CMChannel createNewChannel(final String name, final String i3Name, final String imc2Name, 
									  final String mask, final Set<ChannelFlag> flags, 
									  final String colorOverrideANSI, final String colorOverrideWords)
	{
		final SLinkedList<ChannelMsg> queue = new SLinkedList<ChannelMsg>(); 
		return new CMChannel()
		{
			@Override
			public String name()
			{
				return name;
			}

			@Override
			public String i3name()
			{
				return i3Name;
			}

			@Override
			public String imc2Name()
			{
				return imc2Name;
			}

			@Override
			public String mask()
			{
				return mask;
			}

			@Override
			public String colorOverrideANSICodes()
			{
				return colorOverrideANSI;
			}
			
			@Override
			public String colorOverrideWords()
			{
				return colorOverrideWords;
			}
			
			@Override
			public Set<ChannelFlag> flags()
			{
				return flags;
			}

			@Override
			public SLinkedList<ChannelMsg> queue()
			{
				return queue;
			}
		};
	}
	
	@Override
	public List<ChannelMsg> getChannelQue(int channelNumber, int numNewToSkip, int numToReturn)
	{
		if((channelNumber>=0)&&(channelNumber<channelList.size()))
		{
			final CMChannel channel = channelList.get(channelNumber);
			LinkedList<ChannelMsg> msgs=new LinkedList<ChannelMsg>();
			if(numNewToSkip < channel.queue().size())
			{
				int skipNum=numNewToSkip;
				for(ChannelMsg msg : channel.queue())
				{
					if((--skipNum < 0)&&(msgs.size() < numToReturn))
						msgs.addFirst(msg);
				}
			}
			
			if(msgs.size()>=numToReturn)
				return msgs;
			if(channel.flags().contains(ChannelsLibrary.ChannelFlag.NOBACKLOG))
				return msgs;
			final List<Pair<String,Long>> backLog=CMLib.database().getBackLogEntries(channel.name(), numNewToSkip, numToReturn);
			if(backLog.size()<=msgs.size())
				return msgs;
			final List<ChannelMsg> allMsgs = new XVector<ChannelMsg>();
			for(int x=0;x<backLog.size()-msgs.size();x++)
			{
				final CMMsg msg=CMClass.getMsg();
				msg.parseFlatString(backLog.get(x).first);
				final long time = backLog.get(x).second.longValue(); 
				allMsgs.add(new ChannelMsg()
				{
					@Override
					public CMMsg msg()
					{
						return msg;
					}

					@Override
					public long sentTimeMillis()
					{
						return time;
					}
				});
			}
			allMsgs.addAll(msgs);
			return allMsgs;
		}
		return emptyQueue;
	}

	@Override
	public boolean mayReadThisChannel(MOB sender, boolean areaReq, MOB M, int channelNumber)
	{ 
		return mayReadThisChannel(sender,areaReq,M,channelNumber,false);
	}
	
	@Override
	public boolean mayReadThisChannel(MOB sender,
									  boolean areaReq,
									  MOB M,
									  int channelNumber,
									  boolean offlineOK)
	{
		if((sender==null)||(M==null))
			return false;
		final PlayerStats pstats=M.playerStats();
		if(pstats==null)
			return false;
		final Room R=M.location();
		if(((!offlineOK))
		&&((M.amDead())||(R==null)))
			return false;
		final CMChannel chan=getChannel(channelNumber);
		if(chan==null)
			return false;
		if(chan.flags().contains(ChannelFlag.CLANONLY)||chan.flags().contains(ChannelFlag.CLANALLYONLY))
		{
			// only way to fail an all-clan send is to have NO clan.
			if(!CMLib.clans().checkClanPrivilege(M, Clan.Function.CHANNEL))
				return false;

			if((!CMLib.clans().isAnyCommonClan(sender,M))
			&&((!chan.flags().contains(ChannelFlag.CLANALLYONLY))
				||(!CMLib.clans().findAnyClanRelations(M,sender,Clan.REL_ALLY))))
				return false;
		}

		if((!pstats.getIgnored().contains(sender.Name()))
		&&(CMLib.masking().maskCheck(chan.mask(),M,true))
		&&((!areaReq)
		   ||(sender.location()==null)
		   ||(R==null)
		   ||(R.getArea()==sender.location().getArea()))
		&&(!CMath.isSet(pstats.getChannelMask(),channelNumber)))
			return true;
		return false;
	}

	@Override
	public boolean mayReadThisChannel(MOB sender, boolean areaReq, Session ses, int channelNumber)
	{
		if(ses==null)
			return false;
		final MOB M=ses.mob();

		if((sender==null)
		||(M==null)
		||(M.amDead())
		||(M.location()==null))
			return false;
		final PlayerStats pstats=M.playerStats();
		if(pstats==null)
			return false;
		String senderName=sender.Name();
		final int x=senderName.indexOf('@');
		if(x>0)
			senderName=senderName.substring(0,x);
		final CMChannel chan=getChannel(channelNumber);
		if(chan==null)
			return false;
		if(chan.flags().contains(ChannelFlag.CLANONLY)||chan.flags().contains(ChannelFlag.CLANALLYONLY))
		{
			// only way to fail an all-clan send is to have NO clan.
			if(!CMLib.clans().checkClanPrivilege(M, Clan.Function.CHANNEL))
				return false;
			if((!CMLib.clans().isAnyCommonClan(sender,M))
			&&((!chan.flags().contains(ChannelFlag.CLANALLYONLY))
				||(!CMLib.clans().findAnyClanRelations(M,sender,Clan.REL_ALLY))))
				return false;
		}

		final Room R=M.location();
		if((!ses.isStopped())
		&&(R!=null)
		&&(!pstats.getIgnored().contains(senderName))
		&&(CMLib.masking().maskCheck(chan.mask(),M,true))
		&&((!areaReq)
		   ||(sender.location()==null)
		   ||(R.getArea()==sender.location().getArea()))
		&&(!CMath.isSet(pstats.getChannelMask(),channelNumber)))
			return true;
		return false;
	}

	@Override
	public boolean mayReadThisChannel(MOB M, int channelNumber, boolean zapCheckOnly)
	{
		if(M==null)
			return false;

		if(channelNumber>=getNumChannels())
			return false;

		final CMChannel chan=getChannel(channelNumber);
		if(chan==null)
			return false;
		if((chan.flags().contains(ChannelFlag.CLANONLY)||chan.flags().contains(ChannelFlag.CLANALLYONLY))
		&&(!CMLib.clans().checkClanPrivilege(M, Clan.Function.CHANNEL)))
			return false;

		if(((zapCheckOnly)||((!M.amDead())&&(M.location()!=null)))
		&&(CMLib.masking().maskCheck(chan.mask(),M,true))
		&&(!CMath.isSet(M.playerStats().getChannelMask(),channelNumber)))
			return true;
		return false;
	}

	@Override
	public void channelQueUp(final int channelNumber, final CMMsg msg)
	{
		CMLib.map().sendGlobalMessage(msg.source(),CMMsg.TYP_CHANNEL,msg);
		final CMChannel channel=getChannel(channelNumber);
		final SLinkedList<ChannelMsg> q=channel.queue();
		synchronized(q)
		{
			if(q.size()>=QUEUE_SIZE)
				q.removeLast();
			final long now = System.currentTimeMillis();
			q.addFirst(new ChannelMsg()
			{
				@Override
				public CMMsg msg()
				{
					return msg;
				}

				@Override
				public long sentTimeMillis()
				{
					return now;
				}
			});
		}
		if((!channel.flags().contains(ChannelsLibrary.ChannelFlag.NOBACKLOG))
		&&(!CMSecurity.isDisabled(CMSecurity.DisFlag.CHANNELBACKLOGS))
		&&(!CMProps.getVar(CMProps.Str.CHANNELBACKLOG).equals("0")))
			CMLib.database().addBackLogEntry(getChannel(channelNumber).name(), msg.toFlatString());
	}

	@Override
	public int getChannelIndex(String channelName)
	{
		channelName=channelName.toUpperCase();
		for(int c=0;c<channelList.size();c++)
		{
			if((channelList.get(c)).name().equals(channelName))
				return c;
		}
		for(int c=0;c<channelList.size();c++)
		{
			if((channelList.get(c)).name().startsWith(channelName))
				return c;
		}
		return -1;
	}

	@Override
	public int getChannelCodeNumber(String channelName)
	{
		channelName=channelName.toUpperCase();
		for(int c=0;c<channelList.size();c++)
		{
			if((channelList.get(c)).name().equals(channelName))
				return 1<<c;
		}
		for(int c=0;c<channelList.size();c++)
		{
			if((channelList.get(c)).name().startsWith(channelName))
				return 1<<c;
		}
		return -1;
	}

	@Override
	public String findChannelName(String channelName)
	{
		channelName=channelName.toUpperCase();
		for(int c=0;c<channelList.size();c++)
		{
			if((channelList.get(c)).name().equals(channelName))
				return (channelList.get(c)).name().toUpperCase();
		}
		for(int c=0;c<channelList.size();c++)
		{
			if((channelList.get(c)).name().startsWith(channelName))
				return (channelList.get(c)).name().toUpperCase();
		}
		return "";
	}

	@Override
	public List<String> getFlaggedChannelNames(ChannelFlag flag)
	{
		final List<String> channels=new Vector<String>();
		for(int c=0;c<channelList.size();c++)
		{
			if(channelList.get(c).flags().contains(flag))
				channels.add(channelList.get(c).name().toUpperCase());
		}
		return channels;
	}

	@Override
	public String getExtraChannelDesc(String channelName)
	{
		final StringBuilder str=new StringBuilder("");
		final int dex = getChannelIndex(channelName);
		if(dex >= 0)
		{
			final CMChannel chan=getChannel(dex);
			final Set<ChannelFlag> flags = chan.flags();
			final String mask = chan.mask();
			if(flags.contains(ChannelFlag.CLANALLYONLY))
				str.append(L(" This is a channel for clans and their allies."));
			if(flags.contains(ChannelFlag.CLANONLY))
				str.append(L(" Only members of the same clan can see messages on this channel."));
			if(flags.contains(ChannelFlag.PLAYERREADONLY)||flags.contains(ChannelFlag.READONLY))
				str.append(L(" This channel is read-only."));
			if(flags.contains(ChannelFlag.SAMEAREA))
				str.append(L(" Only people in the same area can see messages on this channel."));
			if((mask!=null)&&(mask.trim().length()>0))
				str.append(L(" The following may read this channel : @x1",CMLib.masking().maskDesc(mask)));
		}
		return str.toString();
	}

	private void clearChannels()
	{
		channelList=new Vector<CMChannel>();
	}

	@Override
	public List<CMChannel> getIMC2ChannelsList()
	{
		final List<CMChannel> list=new Vector<CMChannel>();
		for(int i=0;i<channelList.size();i++)
		{
			if((channelList.get(i).imc2Name()!=null)
			&&(channelList.get(i).imc2Name().length()>0))
				list.add(channelList.get(i));
		}
		return list;
	}

	@Override
	public List<CMChannel> getI3ChannelsList()
	{
		final List<CMChannel> list=new Vector<CMChannel>();
		for(int i=0;i<channelList.size();i++)
		{
			if((channelList.get(i).i3name()!=null)
			&&(channelList.get(i).i3name().length()>0))
				list.add(channelList.get(i));
		}
		return list;
	}

	@Override
	public String[] getChannelNames()
	{
		return baseChannelNames;
	}

	@Override
	public List<Session> clearInvalidSnoopers(Session mySession, int channelCode)
	{
		List<Session> invalid=null;
		if(mySession!=null)
		{
			for(final Session S : CMLib.sessions().allIterable())
			{
				if((S!=mySession)
				&&(S.mob()!=null)
				&&(mySession.isBeingSnoopedBy(S))
				&&(!mayReadThisChannel(S.mob(),channelCode,false)))
				{
					if(invalid==null)
						invalid=new Vector<Session>();
					invalid.add(S);
					mySession.setBeingSnoopedBy(S,false);
				}
			}
		}
		return invalid;
	}

	@Override
	public void restoreInvalidSnoopers(Session mySession, List<Session> invalid)
	{
		if((mySession==null)||(invalid==null))
			return;
		for(int s=0;s<invalid.size();s++)
			mySession.setBeingSnoopedBy(invalid.get(s), true);
	}

	public String parseOutFlags(String mask, Set<ChannelFlag> flags, String[] colorOverride)
	{
		final List<String> V=CMParms.parseSpaces(mask,true);
		for(int v=V.size()-1;v>=0;v--)
		{
			final String s=V.get(v).toUpperCase();
			if(CMParms.contains(CMParms.toStringArray(ChannelFlag.values()), s))
			{
				V.remove(v);
				flags.add(ChannelFlag.valueOf(s));
			}
			else
			{
				Color C=(Color)CMath.s_valueOf(Color.class, s);
				if(C!=null)
				{
					V.remove(v);
					if(s.startsWith("BG"))
						colorOverride[0]=colorOverride[0]+C.getANSICode();
					else
						colorOverride[0]=C.getANSICode()+colorOverride[0];
					colorOverride[1]+=" "+s;
				}
			}
		}
		final StringBuilder str=new StringBuilder();
		for(final String s : V)
			str.append(s).append(" ");
		return str.toString().trim();
	}

	@Override
	public int loadChannels(String list, String ilist, String imc2list)
	{
		clearChannels();
		while(list.length()>0)
		{
			int x=list.indexOf(',');

			String item=null;
			if(x<0)
			{
				item=list.trim();
				list="";
			}
			else
			{
				item=list.substring(0,x).trim();
				list=list.substring(x+1);
			}
			x=item.indexOf(' ');
			final CMChannel chan;
			if(x>0)
			{
				final String[] colorOverride=new String[]{"",""};
				final Set<ChannelFlag> flags = new HashSet<ChannelFlag>();
				String mask=parseOutFlags(item.substring(x+1).trim(),flags,colorOverride);
				item=item.substring(0,x);
				chan = this.createNewChannel(item.toUpperCase().trim(), mask, flags, colorOverride[0], colorOverride[1]);
			}
			else
				chan = this.createNewChannel(item.toUpperCase().trim());
			channelList.add(chan);
		}
		while(ilist.length()>0)
		{
			final int x=ilist.indexOf(',');

			String item=null;
			if(x<0)
			{
				item=ilist.trim();
				ilist="";
			}
			else
			{
				item=ilist.substring(0,x).trim();
				ilist=ilist.substring(x+1);
			}
			final int y1=item.indexOf(' ');
			final int y2=item.lastIndexOf(' ');
			if((y1<0)||(y2<=y1))
				continue;
			final String lvl=item.substring(y1+1,y2).trim();
			final String ichan=item.substring(y2+1).trim();
			item=item.substring(0,y1);
			final Set<ChannelFlag> flags = new HashSet<ChannelFlag>();
			String nameStr=item.toUpperCase().trim();
			final String[] colorOverride=new String[]{"",""};
			String maskStr=parseOutFlags(lvl,flags,colorOverride);
			String i3nameStr=ichan;
			final CMChannel chan = this.createNewChannel(nameStr, i3nameStr, "", maskStr, flags, colorOverride[0], colorOverride[1]);
			channelList.add(chan);
		}
		while(imc2list.length()>0)
		{
			final int x=imc2list.indexOf(',');

			String item=null;
			if(x<0)
			{
				item=imc2list.trim();
				imc2list="";
			}
			else
			{
				item=imc2list.substring(0,x).trim();
				imc2list=imc2list.substring(x+1);
			}
			final int y1=item.indexOf(' ');
			final int y2=item.lastIndexOf(' ');
			if((y1<0)||(y2<=y1))
				continue;
			final Set<ChannelFlag> flags = new HashSet<ChannelFlag>();
			final String lvl=item.substring(y1+1,y2).trim();
			final String ichan=item.substring(y2+1).trim();
			item=item.substring(0,y1);
			String nameStr=item.toUpperCase().trim();
			final String[] colorOverride=new String[]{"",""};
			String maskStr=parseOutFlags(lvl,flags,colorOverride);
			String imc2Name=ichan;
			final CMChannel chan = this.createNewChannel(nameStr, "", imc2Name, maskStr, flags, colorOverride[0], colorOverride[1]);
			channelList.add(chan);
		}
		baseChannelNames=new String[channelList.size()];
		for(int i=0;i<channelList.size();i++)
			baseChannelNames[i]=channelList.get(i).name();
		if(!CMSecurity.isDisabled(CMSecurity.DisFlag.CHANNELAUCTION))
		{
			channelList.add(this.createNewChannel("AUCTION"));
		}
		return channelList.size();
	}

	@Override
	public boolean sendChannelCMMsgTo(Session ses, boolean areareq, int channelInt, CMMsg msg, MOB sender)
	{
		final MOB M=ses.mob();
		if(M==null)
			return false;
		final Room R=M.location();
		boolean didIt=false;
		if(mayReadThisChannel(sender,areareq,ses,channelInt)
		&&(R!=null)
		&&((sender.location()==R)||(R.okMessage(ses.mob(),msg))))
		{
			if(ses.getClientTelnetMode(Session.TELNET_GMCP))
			{
				ses.sendGMCPEvent("comm.channel", "{\"chan\":\""+getChannel(channelInt).name()+"\",\"msg\":\""+
						MiniJSON.toJSONString(CMLib.coffeeFilter().fullOutFilter(null, M, msg.source(), msg.target(), msg.tool(), CMStrings.removeColors((M==msg.source())?msg.sourceMessage():msg.othersMessage()), false))
						+"\",\"player\":\""+msg.source().name()+"\"}");
			}
			M.executeMsg(M,msg);
			didIt=true;
			if(msg.trailerMsgs()!=null)
			{
				for(final CMMsg msg2 : msg.trailerMsgs())
				{
					if((msg!=msg2)&&(R.okMessage(M,msg2)))
						M.executeMsg(M,msg2);
				}
				msg.trailerMsgs().clear();
			}
		}
		return didIt;
	}

	@Override
	public void createAndSendChannelMessage(MOB mob, String channelName, String message, boolean systemMsg)
	{
		final int channelInt=getChannelIndex(channelName);
		if(channelInt<0)
			return;

		final PlayerStats pStats=mob.playerStats();

		message=CMProps.applyINIFilter(message,CMProps.Str.CHANNELFILTER);
		final CMChannel chan=getChannel(channelInt);
		final Set<ChannelFlag> flags=chan.flags();
		channelName=chan.name();
		final String channelColor="^Q";

		CMMsg msg=null;
		if(systemMsg)
		{
			String str="["+channelName+"] '"+message+"'^</CHANNEL^>^?^.";
			if((!mob.name().startsWith("^"))||(mob.name().length()>2))
				str="<S-NAME> "+str;
			msg=CMClass.getMsg(mob,null,null,
					CMMsg.MASK_CHANNEL|CMMsg.MASK_ALWAYS|CMMsg.MSG_SPEAK,channelColor+"^<CHANNEL \""+channelName+"\"^>"+str,
					CMMsg.NO_EFFECT,null,
					CMMsg.MASK_CHANNEL|(CMMsg.TYP_CHANNEL+channelInt),channelColor+"^<CHANNEL \""+channelName+"\"^>"+str);
		}
		else
		if(message.startsWith(",")
		||(message.startsWith(":")
			&&(message.length()>1)
			&&(Character.isLetter(message.charAt(1))||message.charAt(1)==' ')))
		{
			String msgstr=message.substring(1);
			final Vector<String> V=CMParms.parse(msgstr);
			Social S=CMLib.socials().fetchSocial(V,true,false);
			if(S==null)
				S=CMLib.socials().fetchSocial(V,false,false);
			if(S!=null)
				msg=S.makeChannelMsg(mob,channelInt,channelName,V,false);
			else
			{
				msgstr=CMProps.applyINIFilter(msgstr,CMProps.Str.EMOTEFILTER);
				if(msgstr.trim().startsWith("'")||msgstr.trim().startsWith("`"))
					msgstr=msgstr.trim();
				else
					msgstr=" "+msgstr.trim();
				final String srcstr="^<CHANNEL \""+channelName+"\"^>["+channelName+"] "+mob.name()+msgstr+"^</CHANNEL^>^N^.";
				final String reststr="^<CHANNEL \""+channelName+"\"^>["+channelName+"] <S-NAME>"+msgstr+"^</CHANNEL^>^N^.";
				msg=CMClass.getMsg(mob,null,null,
						CMMsg.MASK_CHANNEL|CMMsg.MASK_ALWAYS|CMMsg.MSG_SPEAK,channelColor+""+srcstr,
						CMMsg.NO_EFFECT,null,
						CMMsg.MASK_CHANNEL|(CMMsg.TYP_CHANNEL+channelInt),channelColor+reststr);
			}
		}
		else
		{
			msg=CMClass.getMsg(mob,null,null,
					CMMsg.MASK_CHANNEL|CMMsg.MASK_ALWAYS|CMMsg.MSG_SPEAK,L("@x1^<CHANNEL \"@x2\"^>You @x3 '@x4'^</CHANNEL^>^N^.",channelColor,channelName,channelName,message),
					CMMsg.NO_EFFECT,null,
					CMMsg.MASK_CHANNEL|(CMMsg.TYP_CHANNEL+channelInt),L("@x1^<CHANNEL \"@x2\"^><S-NAME> @x3S '@x4'^</CHANNEL^>^N^.",channelColor,channelName,channelName,message));
		}
		if((chan.flags().contains(ChannelsLibrary.ChannelFlag.ACCOUNTOOC)
			||(chan.flags().contains(ChannelsLibrary.ChannelFlag.ACCOUNTOOCNOADMIN) && (!CMSecurity.isStaff(mob))))
		&&(pStats!=null)
		&&(pStats.getAccount()!=null)
		&&(msg.source()==mob))
		{
			final String accountName=pStats.getAccount().getAccountName();
			if(msg.sourceMessage()!=null)
				msg.setSourceMessage(CMStrings.replaceAll(msg.sourceMessage(), "<S-NAME>", accountName));
			if(msg.targetMessage()!=null)
				msg.setTargetMessage(CMStrings.replaceAll(msg.targetMessage(), "<S-NAME>", accountName));
			if(msg.othersMessage()!=null)
				msg.setOthersMessage(CMStrings.replaceAll(msg.othersMessage(), "<S-NAME>", accountName));
		}
		if(chan.flags().contains(ChannelsLibrary.ChannelFlag.NOLANGUAGE))
			msg.setTool(getCommonLanguage());
		
		final Room R=mob.location();
		CMLib.commands().monitorGlobalMessage(R, msg);
		if((R!=null)
		&&((!R.isInhabitant(mob))||(R.okMessage(mob,msg))))
		{
			final boolean areareq=flags.contains(ChannelsLibrary.ChannelFlag.SAMEAREA);
			channelQueUp(channelInt,msg);
			for(final Session S : CMLib.sessions().localOnlineIterable())
				sendChannelCMMsgTo(S,areareq,channelInt,msg,mob);
		}
		if((CMLib.intermud().i3online()&&(CMLib.intermud().isI3channel(channelName)))
		||(CMLib.intermud().imc2online()&&(CMLib.intermud().isIMC2channel(channelName))))
			CMLib.intermud().i3channel(mob,channelName,message);
	}

	protected Language getCommonLanguage()
	{
		if(commonLang==null)
		{
			commonLang = (Language)CMClass.getAbility("Common");
		}
		return commonLang;
		
	}
	
	@Override
	public boolean activate()
	{
		if(serviceClient==null)
		{
			name="THChannels"+Thread.currentThread().getThreadGroup().getName().charAt(0);
			serviceClient=CMLib.threads().startTickDown(this, Tickable.TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK, MudHost.TIME_UTILTHREAD_SLEEP, 1);
		}
		return true;
	}

	@Override 
	public boolean tick(Tickable ticking, int tickID)
	{
		try
		{
			if(!CMSecurity.isDisabled(CMSecurity.DisFlag.UTILITHREAD))
			{
				tickStatus=Tickable.STATUS_ALIVE;
				try
				{
					final String propStr=CMProps.getVar(CMProps.Str.CHANNELBACKLOG).toUpperCase().trim();
					if((!CMSecurity.isDisabled(CMSecurity.DisFlag.CHANNELBACKLOGS))
					&&(!propStr.equalsIgnoreCase("INFINITY"))
					&&(!propStr.equalsIgnoreCase("FOREVER")))
					{
						if(CMath.isInteger(propStr))
							CMLib.database().trimBackLogEntries(getChannelNames(), CMath.s_int(propStr), 0);
						else
						{
							String[] ss=propStr.split(" ");
							if((ss.length!=2)&&(!CMath.isInteger(ss[0])))
								Log.errOut("CMChannels","Malformed CHANNELBACKLOG entry in coffeemud.ini file: "+propStr);
							else
							if(ss[1].equals("DAYS")||ss[1].equals("DAY"))
								CMLib.database().trimBackLogEntries(getChannelNames(), Integer.MAX_VALUE, System.currentTimeMillis() - (CMath.s_int(ss[0]) * TimeManager.MILI_DAY));
							else
							if(ss[1].equals("WEEKS")||ss[1].equals("WEEK"))
								CMLib.database().trimBackLogEntries(getChannelNames(), Integer.MAX_VALUE, System.currentTimeMillis() - (CMath.s_int(ss[0]) * TimeManager.MILI_WEEK));
							else
							if(ss[1].equals("MONTHS")||ss[1].equals("MONTHS"))
								CMLib.database().trimBackLogEntries(getChannelNames(), Integer.MAX_VALUE, System.currentTimeMillis() - (CMath.s_int(ss[0]) * TimeManager.MILI_MONTH));
							else
							if(ss[1].equals("YEARSS")||ss[1].equals("YEAR"))
								CMLib.database().trimBackLogEntries(getChannelNames(), Integer.MAX_VALUE, System.currentTimeMillis() - (CMath.s_int(ss[0]) * TimeManager.MILI_YEAR));
							else
								Log.errOut("CMChannels","Malformed CHANNELBACKLOG entry in coffeemud.ini file: "+propStr);
						}
					}
				}
				finally
				{
				}
			}
		}
		finally
		{
			tickStatus=Tickable.STATUS_NOT;
			setThreadStatus(serviceClient,"sleeping");
		}
		return true;
	}

	@Override
	public boolean shutdown()
	{
		clearChannels();
		if(CMLib.threads().isTicking(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK))
		{
			CMLib.threads().deleteTick(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK);
			serviceClient=null;
		}
		return true;
	}

}