/
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.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.ColorLibrary.ColorState;
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.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.util.*;

/*
   Copyright 2004-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 CoffeeFilter extends StdLibrary implements TelnetFilter
{
	@Override
	public String ID()
	{
		return "CoffeeFilter";
	}

	private Hashtable<String, Pronoun>	tagTable	= null;
	private ColorState					normalColor	= null;

	@Override
	public void initializeClass()
	{
		normalColor = CMLib.color().getNormalColor();
	}

	@Override
	public Map<String, Pronoun> getTagTable()
	{
		if(tagTable==null)
		{
				tagTable=new Hashtable<String,Pronoun>();
				for(final Pronoun P : Pronoun.values())
					tagTable.put(P.suffix, P);
		}
		return tagTable;
	}


	@Override
	public String simpleOutFilter(String msg)
	{
		if(msg==null)
			return null;
		final StringBuffer buf=new StringBuffer(msg);
		for(int i=0;i<buf.length();i++)
		{
			switch(buf.charAt(i))
			{
			case '`':
				buf.setCharAt(i,'\'');
				break;
			case '\\':
				if(i<buf.length()-1)
				{
					switch(buf.charAt(i+1))
					{
					case 'n':
					case 'r':
						{
						buf.setCharAt(i,(char)13);
						if((i>=buf.length()-2)||((i<buf.length()-2)&&((buf.charAt(i+2))!=10)))
							buf.setCharAt(i+1,(char)10);
						else
						if(i<buf.length()-2)
							buf.deleteCharAt(i+1);
						}
						break;
					case '\'':
					case '`':
						{
						buf.setCharAt(i,'\'');
						buf.deleteCharAt(i+1);
						}
						break;
					}
				}
				break;
			}
		}
		if(CMSecurity.isDebugging(CMSecurity.DbgFlag.OUTPUT))
			Log.debugOut("CoffeeFilter","OUTPUT: ?: "+buf.toString());
		return buf.toString();
	}

	@Override
	public String[] wrapOnlyFilter(String msg, int wrap)
	{
		int loop=0;
		final StringBuilder buf=new StringBuilder(msg);
		int len=(wrap>0)?wrap:(Integer.MAX_VALUE/3);
		int lastSpace=0;
		int firstAlpha=-1;
		int amperStop = -1;

		while(buf.length()>loop)
		{
			int lastSp=-1;
			while((loop<len)&&(buf.length()>loop))
			{
				switch(buf.charAt(loop))
				{
				case ' ':
					{
						if(lastSp>lastSpace)
							lastSpace=lastSp;
						lastSp=loop;
					}
					break;
				case (char)13:
					{
						if(((loop<buf.length()-1)&&((buf.charAt(loop+1))!=10))
						&&((loop>0)&&((buf.charAt(loop-1))!=10)))
							buf.insert(loop+1,(char)10);
						if(wrap>0)
							len=loop+wrap;
						lastSpace=loop;
					}
					break;
				case (char)10:
					{
						if(wrap>0)
							len=loop+wrap;
						lastSpace=loop;
					}
					break;
				case '`': break;
				case '!':
					if((loop<buf.length()-10)
					&&(buf.charAt(loop+1)=='!')
					&&((buf.substring(loop+2,loop+7).equalsIgnoreCase("sound"))
					   ||(buf.substring(loop+2,loop+7).equalsIgnoreCase("music"))))
					{
						final int x=buf.indexOf("(",loop+7);
						final int y=buf.indexOf(")",loop+7);
						if((x>=0)&&(y>=x))
						{
							buf.delete(loop,y+1);
							loop--;
						}
					}
					break;
				case '>': break;
				case '"': break;
				case '&':
					if(loop < amperStop)
						break;
					else
					if(loop<buf.length()-3)
					{
						if(buf.substring(loop,loop+3).equalsIgnoreCase("lt;"))
							buf.replace(loop,loop+3,"<");
						else
						if(buf.substring(loop,loop+3).equalsIgnoreCase("gt;"))
							buf.replace(loop,loop+3,">");
					}
					break;
				case '%':
					if(loop<buf.length()-2)
					{
						final int dig1=hexStr.indexOf(buf.charAt(loop+1));
						final int dig2=hexStr.indexOf(buf.charAt(loop+2));
						if((dig1>=0)&&(dig2>=0))
						{
							buf.setCharAt(loop,(char)((dig1*16)+dig2));
							buf.deleteCharAt(loop+1);
							if((buf.charAt(loop))==13)
								buf.setCharAt(loop+1,(char)10);
							else
								buf.deleteCharAt(loop+1);
							loop--; // force a retry of this char
						}
					}
					break;
				case '(': break;
				case '\\':
					if(loop<buf.length()-1)
					{
						switch(buf.charAt(loop+1))
						{
						case 'n':
						case 'r':
							{
							buf.setCharAt(loop,(char)13);
							if((loop>=buf.length()-2)||((loop<buf.length()-2)&&((buf.charAt(loop+2))!=10)))
								buf.setCharAt(loop+1,(char)10);
							else
							if(loop<buf.length()-2)
								buf.deleteCharAt(loop+1);
							}
							break;
						case '\'':
						case '`':
							{
							buf.setCharAt(loop,'\'');
							buf.deleteCharAt(loop+1);
							}
							break;
						}
					}
					break;
				case '<': break;
				case '\033': // skip escapes
					if((loop < buf.length()-1) && (buf.charAt(loop+1)=='['))
					{
						char loopc=buf.charAt(loop);
						while( (loop < buf.length()-1) && (loopc!='m') && (loopc!='z') )
						{
							len++;
							loop++;
							loopc=buf.charAt(loop);
						}
						len++; // and one more for the 'm'.
					}
					break;
				case '^':
				{
					len++;
					loop++;
					if(loop<buf.length())
					{
						final char c=buf.charAt(loop);
						switch(c)
						{
						case ColorLibrary.COLORCODE_BACKGROUND:
							if(loop+1<buf.length())
							{
								len++;
								loop++;
							}
							break;
						case ColorLibrary.COLORCODE_FANSI256:
						case ColorLibrary.COLORCODE_BANSI256:
						  if(loop+3<buf.length())
						  {
							  len+=3;
							  loop+=3;
						  }
						  break;
						case '<': case '&':
						{
							len++;
							loop++;
							while(loop<(buf.length()-1))
							{
								if(((c=='<')&&((buf.charAt(loop)!='^')||(buf.charAt(loop+1)!='>')))
								||((c=='&')&&(buf.charAt(loop)!=';')))
								{
									len++;
									loop++;
								}
							}
							len++;
							break;
						}
						default: break;
						}
					}
					break;
				}
				default:
					if((firstAlpha < 0)&&(Character.isLetter(buf.charAt(loop))))
						firstAlpha = loop;
					break;
				}
				loop++;
			}

			if((len<buf.length())
			&&(loop!=lastSp)
			&&(lastSp>=0)
			&&(loop>=0)
			&&(loop<buf.length())
			&&(buf.charAt(loop)!=13)
			&&(buf.charAt(loop)!=10))
			{
				amperStop=loop;
				if(buf.charAt(lastSp+1)==' ')
				{
					buf.setCharAt(lastSp,(char)13);
					buf.setCharAt(lastSp+1,(char)10);
				}
				else
				{
					buf.setCharAt(lastSp,(char)13);
					buf.insert(lastSp,(char)10);
				}
				loop=lastSp+2;
			}
			if(wrap>0)
				len=loop+wrap;
		}

		if(firstAlpha<0)
			firstAlpha=0;
		if(firstAlpha<buf.length())
			buf.setCharAt(firstAlpha,Character.toUpperCase(buf.charAt(firstAlpha)));
		return buf.toString().split("\n\r");
	}

	protected int convertEscape(final Session S, final StringBuffer str, final int index)
	{
		int enDex = index + 1;
		final char c = str.charAt(enDex);
		switch (c)
		{
		case '?':
		{
			if((S!=null)&&(S.getClientTelnetMode(Session.TELNET_ANSI)))
			{
				ColorState lastColor=S.getLastColor();
				final ColorState currColor=S.getCurrentColor();
				if((lastColor.foregroundCode()==currColor.foregroundCode())
				&&(lastColor.backgroundCode()==currColor.backgroundCode()))
					lastColor=normalColor;
				final String[] clookup = S.getColorCodes();
				String escapeSequence;
				if(lastColor.foregroundCode() < 256)
					escapeSequence=clookup[lastColor.foregroundCode()];
				else
					escapeSequence="\033[38;5;"+(lastColor.foregroundCode() & 0xff)+"m";
				if(lastColor.backgroundCode()=='.')
					escapeSequence=ColorLibrary.Color.NONE.getANSICode()+escapeSequence;
				else
				{
					String bgEscapeSequence;
					if(lastColor.backgroundCode() < 256)
					{
						bgEscapeSequence=clookup[lastColor.backgroundCode()];
						if(bgEscapeSequence==null)
							bgEscapeSequence=ColorLibrary.Color.BGBLACK.getANSICode();
						else
						if(ColorLibrary.MAP_ANSICOLOR_TO_ANSIBGCOLOR.containsKey(bgEscapeSequence))
							bgEscapeSequence=ColorLibrary.MAP_ANSICOLOR_TO_ANSIBGCOLOR.get(bgEscapeSequence);
					}
					else
						bgEscapeSequence="\033[48;5;"+(lastColor.backgroundCode() & 0xff)+"m";
					escapeSequence+=bgEscapeSequence;
				}
				S.setLastColor(S.getCurrentColor());
				S.setCurrentColor(lastColor);
				str.insert(index+2, escapeSequence);
				str.delete(index, index+2);
				return index+escapeSequence.length()-1;
			}
			str.delete(index, index+2);
			return index-1;
		}
		case ColorLibrary.COLORCODE_FANSI256:
		case ColorLibrary.COLORCODE_BANSI256:
		{
			if(((S!=null)&&(!S.getClientTelnetMode(Session.TELNET_ANSI)))
			||(enDex>str.length()-5))
			{
				str.delete(index, index+5);
				return index-1;
			}
			enDex++;
			int finalNum=-1;
			int num=str.charAt(enDex)-'0';
			if((num>=0)&&(num<=5))
			{
				int buildNum=(num*36);
				num=str.charAt(enDex+1)-'0';
				if((num>=0)&&(num<=5))
				{
					buildNum+=(num*6);
					num=str.charAt(enDex)-'0';
					if((num>=0)&&(num<=5))
					{
						finalNum=buildNum + num + 16;
					}
				}
			}
			if((finalNum < 0) && (str.charAt(enDex)==str.charAt(enDex-1)))
			{
				enDex++;
				num=CMath.hexDigit(str.charAt(enDex));
				final int num2=CMath.hexDigit(str.charAt(enDex+1));
				if((num>=0)&&(num2>=0))
					finalNum=(num*16)+num2;
			}
			if(finalNum >=0)
			{
				final boolean isFg=(c==ColorLibrary.COLORCODE_FANSI256);
				String escapeSequence;
				if(isFg)
				{
					escapeSequence="\033[38;5;"+finalNum+"m";
					if((S!=null)&&(S.getCurrentColor().backgroundCode()!='.'))
						escapeSequence=ColorLibrary.Color.NONE.getANSICode()+escapeSequence;
				}
				else
					escapeSequence="\033[48;5;"+finalNum+"m";
				str.insert(index+5, escapeSequence);
				str.delete(index, index+5);
				if(S!=null)
				{
					if(isFg)
					{
						S.setLastColor(S.getCurrentColor());
						S.setCurrentColor(CMLib.color().valueOf((char)(256 | finalNum), '.'));
					}
					else
					{
						S.setCurrentColor(CMLib.color().valueOf(S.getCurrentColor().foregroundCode(), (char)(256 | finalNum)));
					}
				}
				return index+escapeSequence.length()-1;
			}
			str.delete(index, index+5);
			return index-1;
		}
		case ColorLibrary.COLORCODE_BACKGROUND:
		{
			enDex++;
			if((S!=null)&&(!S.getClientTelnetMode(Session.TELNET_ANSI)))
			{
				str.delete(index, index+2);
				return index-1;
			}
			final char bc=str.charAt(enDex);
			final String[] clookup = (S==null)?CMLib.color().standardColorLookups():S.getColorCodes();
			final String escapeSequence=clookup[bc];
			final String bgEscapeSequence=ColorLibrary.MAP_ANSICOLOR_TO_ANSIBGCOLOR.get(escapeSequence);
			if(bgEscapeSequence != null)
			{
				str.insert(index+3, bgEscapeSequence);
				str.delete(index, index+3);
				if(S!=null)
				{
					final ColorState curColor=S.getCurrentColor();
					S.setCurrentColor(CMLib.color().valueOf(curColor.foregroundCode(), bc));
				}
				return index+bgEscapeSequence.length()-1;
			}
			else
			if(escapeSequence != null)
			{
				str.insert(index+3, escapeSequence);
				str.delete(index, index+3);
				return index+escapeSequence.length()-1;
			}
			else
			{
				str.delete(index, index+3);
				return index-1;
			}
		}
		case '>':
			/* why was this here?
			if (currentColor > 0)
			{
				if (clookup()[c] == null)
					escapeCodes = clookup()[currentColor];
				else if (clookup()[currentColor] == null)
					escapeCodes = clookup[c];
				else
					escapeCodes = clookup()[c] + clookup()[currentColor];
			}
			else
			*/
			str.delete(index, index+1);
			return index;
		case '<':
		{
			while(enDex<(str.length()-1))
			{
				if((str.charAt(enDex)!='^')||(str.charAt(enDex+1)!='>'))
					enDex++;
				else
				if((S==null)
				||(!S.getClientTelnetMode(Session.TELNET_MXP))
				||(!S.isAllowedMxp(str.substring(index,enDex+2))))
				{
					str.delete(index,enDex+2);
					enDex=index-1;
					return enDex;
				}
				else
				{
					str.delete(enDex, enDex+1);
					str.delete(index, index+1);
					return enDex-1;
				}
			}
			str.delete(index, index+1);
			return index;
		}
		case '&':
		{
			while(enDex<(str.length()-1))
			{
				if(str.charAt(enDex)!=';')
					enDex++;
				else
				if((S==null)
				||(!S.getClientTelnetMode(Session.TELNET_MXP))
				||(!S.isAllowedMxp(str.substring(index,enDex+1))))
				{
					str.delete(index,enDex+1);
					enDex=index-1;
					return enDex;
				}
				else
				{
					str.delete(index, index+1);
					return enDex;
				}
			}
			str.delete(index, index+1);
			return index;
		}
		case '"':
			str.delete(index, index+1);
			return index;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			if((S==null)||(S.getClientTelnetMode(Session.TELNET_MSP)))
			{
				final int escOrd=CMProps.Str.ESC0.ordinal() + (c - ('0'));
				final CMProps.Str escEnum=CMProps.Str.values()[escOrd];
				final String escapeSequence=CMProps.getVar(escEnum);
				str.insert(index+2, escapeSequence);
				str.delete(index, index+2);
				return index+escapeSequence.length()-1;
			}
			else
			{
				str.delete(index, index+2);
				return index-1;
			}
		case '.':
		  if((S==null)||(S.getClientTelnetMode(Session.TELNET_ANSI)))
		  {
			  final String[] clookup = (S==null)?CMLib.color().standardColorLookups():S.getColorCodes();
			  String escapeSequence=clookup[c];
			  if(escapeSequence==null)
			  	escapeSequence="";
			  str.insert(index+2, escapeSequence);
			  str.delete(index, index+2);
			  return index+escapeSequence.length()-1;
		  }
		  else
		  {
			  str.delete(index, index+2);
			  return index-1;
		  }
		default:
			if((c>=0)&&(c<256)&&((S==null)||(S.getClientTelnetMode(Session.TELNET_ANSI))))
			{
				final String[] clookup = (S==null)?CMLib.color().standardColorLookups():S.getColorCodes();
				String escapeSequence=clookup[c];
				if(escapeSequence==null)
					escapeSequence="";
				if((S!=null)&&(escapeSequence.length()>0)&&(escapeSequence.charAt(0)=='\033'))
				{
					final ColorState state=S.getCurrentColor();
					if(state.backgroundCode()!='.')
						escapeSequence=ColorLibrary.Color.NONE.getANSICode()+escapeSequence;
					S.setLastColor(state);
					S.setCurrentColor(CMLib.color().valueOf(c,'.'));
				}
				str.insert(index+2, escapeSequence);
				str.delete(index, index+2);
				return index+escapeSequence.length()-1;
			}
			else
			{
				str.delete(index, index+2);
				return index-1;
			}
		}
	}

	// no word-wrapping, text filtering or ('\','n') -> '\n' translations
	@Override
	public String colorOnlyFilter(String msg, Session S)
	{
		if(msg==null)
			return null;

		if(msg.length()==0)
			return msg;
		final StringBuffer buf=new StringBuffer(msg);
		final Session CS=S;
		//if(CS==null) CS=(Session)CMClass.getCommon("DefaultSession");
		int loop=0;

		while(buf.length()>loop)
		{
			switch(buf.charAt(loop))
			{
			case '>':
				if((S!=null)
				&&(S.getClientTelnetMode(Session.TELNET_MXP)))
				{
					buf.delete(loop,loop+1);
					buf.insert(loop,"&gt;".toCharArray());
					loop+=3;
				}
				break;
			case '"':
				if((S!=null)
				&&(S.getClientTelnetMode(Session.TELNET_MXP)))
				{
					buf.delete(loop,loop+1);
					buf.insert(loop,"&quot;".toCharArray());
					loop+=5;
				}
				break;
			case '&':
				if((S!=null)
				&&(S.getClientTelnetMode(Session.TELNET_MXP)))
				{
					if((!buf.substring(loop,loop+3).equalsIgnoreCase("lt;"))
					&&(buf.substring(loop,loop+3).equalsIgnoreCase("gt;")))
					{
						buf.delete(loop,loop+1);
						buf.insert(loop,"&amp;".toCharArray());
						loop+=4;
					}
				}
				else
				if(loop<buf.length()-3)
				{
					if(buf.substring(loop,loop+3).equalsIgnoreCase("lt;"))
						buf.replace(loop,loop+3,"<");
					else
					if(buf.substring(loop,loop+3).equalsIgnoreCase("gt;"))
						buf.replace(loop,loop+3,">");
				}
				break;
			case '<':
				if((S!=null)
				&&(S.getClientTelnetMode(Session.TELNET_MXP)))
				{
					buf.delete(loop,loop+1);
					buf.insert(loop,"&lt;".toCharArray());
					loop+=3;
				}
				break;
			case '^':
				if(loop<buf.length()-1)
					loop=convertEscape(CS, buf, loop);
				break;
			default:
				break;
			}
			loop++;
		}

		if ((S!=null)
		&&(normalColor!=null)
		&&(!normalColor.equals(S.getCurrentColor()))
		&&(S.getClientTelnetMode(Session.TELNET_ANSI)))
		{
			buf.append(S.getColorCodes()['N']);
			S.setLastColor(S.getCurrentColor());
			S.setCurrentColor(normalColor);
		}
		if(CMSecurity.isDebugging(CMSecurity.DbgFlag.OUTPUT))
			Log.debugOut("CoffeeFilter","OUTPUT: "+(((S!=null)&&(S.mob()!=null))?S.mob().Name():"")+": "+buf.toString());
		return buf.toString();
	}

	// no word-wrapping, text filtering or ('\','n') -> '\n' translations
	@Override
	public String mxpSafetyFilter(String msg, Session S)
	{
		if(msg==null)
			return null;

		if(msg.length()==0)
			return msg;
		final StringBuffer buf=new StringBuffer(msg);
		int loop=0;

		while(buf.length()>loop)
		{
			switch(buf.charAt(loop))
			{
			case '>':
				if((S!=null)
				&&(S.getClientTelnetMode(Session.TELNET_MXP)))
				{
					buf.delete(loop,loop+1);
					buf.insert(loop,"&gt;".toCharArray());
					loop+=3;
				}
				break;
			case '"':
				if((S!=null)
				&&(S.getClientTelnetMode(Session.TELNET_MXP)))
				{
					buf.delete(loop,loop+1);
					buf.insert(loop,"&quot;".toCharArray());
					loop+=5;
				}
				break;
			case '&':
				if((S!=null)
				&&(S.getClientTelnetMode(Session.TELNET_MXP)))
				{
					if((!buf.substring(loop,loop+3).equalsIgnoreCase("lt;"))
					&&(buf.substring(loop,loop+3).equalsIgnoreCase("gt;")))
					{
						buf.delete(loop,loop+1);
						buf.insert(loop,"&amp;".toCharArray());
						loop+=4;
					}
				}
				else
				if(loop<buf.length()-3)
				{
					if(buf.substring(loop,loop+3).equalsIgnoreCase("lt;"))
						buf.replace(loop,loop+3,"<");
					else
					if(buf.substring(loop,loop+3).equalsIgnoreCase("gt;"))
						buf.replace(loop,loop+3,">");
				}
				break;
			case '<':
				if((S!=null)
				&&(S.getClientTelnetMode(Session.TELNET_MXP)))
				{
					buf.delete(loop,loop+1);
					buf.insert(loop,"&lt;".toCharArray());
					loop+=3;
				}
				break;
			default:
				break;
			}
			loop++;
		}

		if(CMSecurity.isDebugging(CMSecurity.DbgFlag.OUTPUT))
			Log.debugOut("CoffeeFilter","OUTPUT: "+(((S!=null)&&(S.mob()!=null))?S.mob().Name():"")+": "+buf.toString());
		return buf.toString();
	}

	@Override
	public String getLastWord(StringBuffer buf, int lastSp, int lastSpace)
	{
		String lastWord="";
		if(lastSp>lastSpace)
		{
			lastWord=CMStrings.removeColors(buf.substring(lastSpace,lastSp)).trim().toUpperCase();
			while((lastWord.length()>0)&&(!Character.isLetterOrDigit(lastWord.charAt(0))))
				  lastWord=lastWord.substring(1);
			while((lastWord.length()>0)&&(!Character.isLetterOrDigit(lastWord.charAt(lastWord.length()-1))))
				  lastWord=lastWord.substring(0,lastWord.length()-1);
			for(int i=lastWord.length()-1;i>=0;i--)
				if(!Character.isLetterOrDigit(lastWord.charAt(i)))
				{ lastWord=lastWord.substring(i+1); break;}
		}
		else
		{
			for(int i=(lastSpace-1);((i>=0)&&(!Character.isLetterOrDigit(buf.charAt(i))));i--)
				lastWord=buf.charAt(i)+lastWord;
			lastWord=CMStrings.removeColors(lastWord).trim().toUpperCase();
		}
		return lastWord;
	}

	@Override
	public String fullOutFilter(Session S,
								MOB mob,
								Physical source,
								Environmental target,
								Environmental tool,
								final String msg,
								boolean wrapOnly)
	{
		if(msg==null)
			return null;

		if(msg.length()==0)
			return msg;

		boolean doSagain=false;
		boolean firstSdone=false;
		final StringBuffer buf=new StringBuffer(msg);

		final int wrap=(S!=null)?S.getWrap():78;
		int len=(wrap>0)?wrap:(Integer.MAX_VALUE/3);
		int loop=0;
		int lastSpace=0;
		int firstAlpha=-1;
		int amperStop = -1;

		int loopDebugCtr=0;
		int lastLoop=-1;

		while(buf.length()>loop)
		{
			if(loop==lastLoop)
			{
				//BZ: delete when this is fixed. 
				//BZ: 11/2015 - this might be fixed now!
				if(++loopDebugCtr>5) 
				{
					Log.debugOut("CoffeeFilter","LOOP: "+loop+"/"+wrap+"/!"+(buf.charAt(loop)=='\033')+"!/"+lastSpace+"/"+firstAlpha+"/"+amperStop+"/"+doSagain+"/"+firstSdone+"/"+buf.length()+"/"+loopDebugCtr);
					Log.debugOut("CoffeeFilter",buf.toString());
					break;
				}
			}
			else
			{
				lastLoop=loop;
				loopDebugCtr=0;
			}

			int lastSp=-1;
			while((loop<len)&&(buf.length()>loop))
			{
				switch(buf.charAt(loop))
				{
				case ' ':
					{
						if(lastSp>lastSpace)
							lastSpace=lastSp;
						lastSp=loop;
					}
					break;
				case (char)13:
					{
						if(((loop<buf.length()-1)&&((buf.charAt(loop+1))!=10))
						&&((loop>0)&&((buf.charAt(loop-1))!=10)))
							buf.insert(loop+1,(char)10);
						if(wrap>0)
							len=loop+wrap;
						lastSpace=loop;
					}
					break;
				case (char)10:
					{
						if(wrap>0)
							len=loop+wrap;
						lastSpace=loop;
					}
					break;
				case '`':
					buf.setCharAt(loop,'\'');
					break;
				case '!':
					if((loop<buf.length()-10)
					&&(buf.charAt(loop+1)=='!')
					&&((buf.substring(loop+2,loop+7).equalsIgnoreCase("sound"))
					   ||(buf.substring(loop+2,loop+7).equalsIgnoreCase("music"))))
					{
						final int x=buf.indexOf("(",loop+7);
						final int y=buf.indexOf(")",loop+7);
						if((x>=0)&&(y>=x))
						{
							if((S!=null)
							&&(S.getClientTelnetMode(Session.TELNET_MSP)||S.getClientTelnetMode(Session.TELNET_MXP))
							&&((source==null)
							   ||(source==mob)
							   ||(CMLib.flags().canBeHeardSpeakingBy(source,mob))))
							{
								if(S.getClientTelnetMode(Session.TELNET_MXP))
								{
									buf.setCharAt(loop+1, '<');
									buf.setCharAt(x, ' ');
									buf.setCharAt(y, '>');
									buf.deleteCharAt(loop);
									if(wrap>0)
										len=len+(y-loop);
									loop=y-1;
								}
								else
								if(S.getClientTelnetMode(Session.TELNET_MSP))
								{
									if(wrap>0)
										len=len+(y-loop)+1;
									loop=y;
								}
							}
							else
							{
								buf.delete(loop,y+1);
								loop--;
							}
						}
					}
					break;
				case '>':
					if((S!=null)
					&&(S.getClientTelnetMode(Session.TELNET_MXP)))
					{
						buf.delete(loop,loop+1);
						buf.insert(loop,"&gt;".toCharArray());
						loop+=3;
					}
					break;
				case '"':
					if((S!=null)
					&&(S.getClientTelnetMode(Session.TELNET_MXP)))
					{
						buf.delete(loop,loop+1);
						buf.insert(loop,"&quot;".toCharArray());
						loop+=5;
					}
					break;
				case '&':
					if(loop < amperStop)
						break;
					else
					if((S!=null)
					&&(S.getClientTelnetMode(Session.TELNET_MXP)))
					{
						if((!buf.substring(loop,loop+3).equalsIgnoreCase("lt;"))
						&&(!buf.substring(loop,loop+3).equalsIgnoreCase("gt;")))
						{
							buf.delete(loop,loop+1);
							buf.insert(loop,"&amp;".toCharArray());
							loop+=4;
						}
						else
							loop+=3;
					}
					else
					if(loop<buf.length()-3)
					{
						if(buf.substring(loop,loop+3).equalsIgnoreCase("lt;"))
							buf.replace(loop,loop+3,"<");
						else
						if(buf.substring(loop,loop+3).equalsIgnoreCase("gt;"))
							buf.replace(loop,loop+3,">");
					}
					break;
				case '%':
					if(loop<buf.length()-2)
					{
						final int dig1=hexStr.indexOf(buf.charAt(loop+1));
						final int dig2=hexStr.indexOf(buf.charAt(loop+2));
						if((dig1>=0)&&(dig2>=0))
						{
							buf.setCharAt(loop,(char)((dig1*16)+dig2));
							buf.deleteCharAt(loop+1);
							if((buf.charAt(loop))==13)
								buf.setCharAt(loop+1,(char)10);
							else
								buf.deleteCharAt(loop+1);
							if(buf.charAt(loop)=='\033')
								loop--; // force a retry of this char
						}
					}
					break;
				case '(':
					if((!wrapOnly)&&(loop<(buf.length()-1)))
					{
						final char c2=Character.toUpperCase(buf.charAt(loop+1));
						if(((loop<buf.length()-2)&&(buf.charAt(loop+2)==')')&&(c2=='S'))
						||((loop<buf.length()-3)&&(buf.charAt(loop+3)==')')&&(Character.toUpperCase(buf.charAt(loop+2))=='S')&&((c2=='Y')||(c2=='E'))))
						{
							final String lastWord=getLastWord(buf,lastSp,lastSpace);
							final int lastParen=(c2=='S')?loop+2:loop+3;
							if(lastWord.equals("A")
							||lastWord.equals("YOU")
							||lastWord.equals("1")
							||doSagain)
							{
								if(c2=='Y')
									buf.replace(loop,lastParen+1,CMStrings.sameCase("y",buf.charAt(loop+1)));
								else
									buf.delete(loop,lastParen+1);
								doSagain=true;
								loop--;
							}
							else
							{
								if(c2=='Y')
									buf.replace(loop,lastParen+1,CMStrings.sameCase("ies",buf.charAt(loop+1)));
								else
								{
									buf.deleteCharAt(lastParen);
									buf.deleteCharAt(loop);
								}
							}
							firstSdone=true;
						}
					}
					break;
				case '\\':
					if(loop<buf.length()-1)
					{
						switch(buf.charAt(loop+1))
						{
						case 'n':
						case 'r':
							{
							buf.setCharAt(loop,(char)13);
							if((loop>=buf.length()-2)||((loop<buf.length()-2)&&((buf.charAt(loop+2))!=10)))
								buf.setCharAt(loop+1,(char)10);
							else
							if(loop<buf.length()-2)
								buf.deleteCharAt(loop+1);
							}
							break;
						case '\'':
						case '`':
							{
							buf.setCharAt(loop,'\'');
							buf.deleteCharAt(loop+1);
							}
							break;
						}
					}
					break;
				case '<':
					if((!wrapOnly)&&((loop+1)<buf.length()))
					{
						// supported here <?-HIS-HER>, <?-HIM-HER>, <?-NAME>,
						// <?-NAMESELF>, <?-HE-SHE>, <?-IS-ARE>, <?-HAS-HAVE>
						//int endDex=loop;
						StringBuffer cmd=new StringBuffer("");
						int ldex=loop+1;
						char lc=' ';
						for(;(ldex<buf.length())&&(cmd!=null);ldex++)
						{
							lc=buf.charAt(ldex);
							if(lc=='>')
								break;
							switch(lc)
							{
								case '<':
								case '\n':
								case '\r':
									cmd=null;
									break;
								default:
									cmd.append(Character.toUpperCase(lc));
									break;
							}
						}
						if((cmd!=null)&&(ldex<buf.length())&&(buf.charAt(ldex)=='>')&&(cmd.length()>1)&&(cmd.length()<14))
						{
							Environmental regarding=null;
							switch(cmd.charAt(0))
							{
							case 'S':
								regarding = source;
								break;
							case 'T':
								regarding = target;
								break;
							case 'O':
								regarding = tool;
								break;
							}
							String replacement=null;
							final Pronoun P=getTagTable().get(cmd.substring(1));
							if(P==null)
							{
								if((S!=null)
								&&(S.getClientTelnetMode(Session.TELNET_MXP))
								&&(S.isAllowedMxp(buf.substring(loop,loop+1))))
								{
									buf.delete(loop,loop+1);
									buf.insert(loop,"&lt;".toCharArray());
								}
							}
							else
							switch(P)
							{
							case NAME:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
									{
										replacement="you";
										if(!firstSdone)
											doSagain=true;
									}
									else
									if((mob!=null)
									&&((!CMLib.flags().canSee(mob))||(!CMLib.flags().canBeSeenBy(regarding,mob)))
									&&(regarding.Name().trim().length()>0))
										replacement=((regarding instanceof MOB)?"someone":"something");
									else
									if(regarding instanceof PhysicalAgent)
										replacement=((PhysicalAgent)regarding).name(mob);
									else
										replacement=regarding.name();
								}
								break;
							case ACCOUNTNAME:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
									{
										replacement="you";
										if(!firstSdone)
											doSagain=true;
									}
									else
									if((mob!=null)
									&&((!CMLib.flags().canSee(mob))||(!CMLib.flags().canBeSeenBy(regarding,mob)))
									&&(regarding.Name().trim().length()>0))
										replacement=((regarding instanceof MOB)?"someone":"something");
									else
									if(regarding instanceof MOB)
									{
										if((((MOB)regarding).playerStats()!=null)
										&&(((MOB)regarding).playerStats().getAccount()!=null))
											replacement=((MOB)regarding).playerStats().getAccount().getAccountName();
										else
											replacement=((MOB)regarding).name(mob);
									}
									else
									if(regarding instanceof PhysicalAgent)
										replacement=((PhysicalAgent)regarding).name(mob);
									else
										replacement=regarding.name();
								}
								break;
							case NAMENOART:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
									{
										replacement="you";
										if(!firstSdone)
											doSagain=true;
									}
									else
									if((mob!=null)
									&&((!CMLib.flags().canSee(mob))||(!CMLib.flags().canBeSeenBy(regarding,mob)))
									&&(regarding.Name().trim().length()>0))
										replacement=((regarding instanceof MOB)?"someone":"something");
									else
									if(regarding instanceof PhysicalAgent)
										replacement=CMLib.english().cleanArticles(((PhysicalAgent)regarding).name(mob));
									else
										replacement=CMLib.english().cleanArticles(regarding.name());
								}
							break;
							case NAMESELF:
								{
									if(regarding==null)
										replacement="";
									else
									if(((source==target)||(target==null))&&(mob==regarding))
										replacement="yourself";
									else
									if(mob==regarding)
									{
										replacement="you";
										if(!firstSdone)
											doSagain=true;
									}
									else
									if((mob!=null)
									&&((!CMLib.flags().canSee(mob))||(!CMLib.flags().canBeSeenBy(regarding,mob)))
									&&(regarding.Name().trim().length()>0))
										replacement=((regarding instanceof MOB)?"someone":"something");
									else
									if(source==target)
										replacement=((regarding instanceof MOB)?(((MOB)regarding).charStats().himher()+"self"):"itself");
									else
									if(regarding instanceof PhysicalAgent)
										replacement=((PhysicalAgent)regarding).name(mob);
									else
										replacement=regarding.name();
								}
								break;
							case YOUPOSS:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
										replacement="your";
									else
									if((mob!=null)
									&&((!CMLib.flags().canSee(mob))||(!CMLib.flags().canBeSeenBy(regarding,mob)))
									&&(regarding.Name().trim().length()>0))
										replacement=((regarding instanceof MOB)?"someone's":"something's");
									else
									if(regarding instanceof PhysicalAgent)
										replacement=((PhysicalAgent)regarding).name(mob)+"'s";
									else
										replacement=regarding.name()+"'s";
								}
								break;
							case HISHER:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
										replacement="your";
									else
									if(regarding instanceof MOB)
										replacement=((MOB)regarding).charStats().hisher();
									else
										replacement="its";

								}
								break;
							case HIMHER:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
									{
										replacement="you";
										if(!firstSdone)
											doSagain=true;
									}
									else
									if(regarding instanceof MOB)
										replacement=((MOB)regarding).charStats().himher();
									else
										replacement="it";

								}
								break;
							case HIMHERSELF:
								{
									if(regarding==null)
										replacement="themself";
									else
									if(mob==regarding)
										replacement="yourself";
									else
									if(regarding instanceof MOB)
										replacement=((MOB)regarding).charStats().himher()+"self";
									else
										replacement="itself";

								}
								break;
							case HISHERSELF:
								{
									if(regarding==null)
										replacement="themself";
									else
									if(mob==regarding)
										replacement="yourself";
									else
									if(regarding instanceof MOB)
										replacement=((MOB)regarding).charStats().hisher()+"self";
									else
										replacement="itself";
								}
								break;
							case HESHE:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
									{
										replacement="you";
										if(!firstSdone)
											doSagain=true;
									}
									else
									if(regarding instanceof MOB)
										replacement=((MOB)regarding).charStats().heshe();
									else
										replacement="its";
								}
								break;
							case SIRMADAM:
								{
									if(regarding==null)
										replacement="";
									else
									if(regarding instanceof MOB)
										replacement=((MOB)regarding).charStats().sirmadam();
									else
										replacement="sir";
								}
								break;
							case ISARE:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
										replacement="are";
									else
										replacement="is";
								}
								break;
							case ISARE2:
								{
									final String lastWord=getLastWord(buf,lastSp,lastSpace);
									if((lastWord.equals("A")||lastWord.equals("YOU")||lastWord.equals("1")||doSagain))
										replacement="is";
									else
										replacement="are";
								}
								break;
							case HASHAVE:
								{
									if(regarding==null)
										replacement="";
									else
									if(mob==regarding)
										replacement="have";
									else
									if(regarding instanceof MOB)
										replacement="has";
								}
								break;
							}
							if(replacement!=null)
							{
								final String newReplacement=CMLib.lang().filterTranslation(replacement);
								if(newReplacement!=null)
									replacement=newReplacement;
								buf.delete(loop,ldex+1);
								buf.insert(loop,replacement.toCharArray());
								loop--;
							}
						}
						else
						if((S!=null)
						&&(S.getClientTelnetMode(Session.TELNET_MXP)))
						{
							buf.delete(loop,loop+1);
							buf.insert(loop,"&lt;".toCharArray());
							loop+=3;
						}
					}
					break;
					case '\033': // skip escapes
					{
						if((S!=null)&&(!S.getClientTelnetMode(Session.TELNET_ANSI)))
						{
							int oldLoop=loop;
							if((loop < buf.length()-1) && (buf.charAt(loop+1)=='['))
							{
								loop++; // added 2013, see comment below
								char loopc=buf.charAt(loop);
								while( (loop < buf.length()-1) && (loopc!='m') && (loopc!='z') )
								{
									loop++;
									loopc=buf.charAt(loop);
								}
								buf.delete(oldLoop,loop+1);
								loop=oldLoop-1;
							}
						}
						else
						{
							if((loop < buf.length()-1) && (buf.charAt(loop+1)=='['))
							{
								loop++; // added 2013, see comment below
								char loopc=buf.charAt(loop);
								while( (loop < buf.length()-1) && (loopc!='m') && (loopc!='z') )
								{
									len++;
									loop++;
									loopc=buf.charAt(loop);
								}
								//if(buf.charAt(loop)=='\033')
								//	loop--; // force a retry of this char. 2013: why do this?  only possible if you didn't move, and this promises less moving!
							}
						}
						break;
					}
					case '^':
					{
						if(loop<buf.length()-1)
						{
							final int oldLoop=loop;
							loop=convertEscape(S, buf, loop);
							if(wrap>0)
								len=(loop-oldLoop)+len+1;
						}
						break;
					}
					default:
					{
						if((firstAlpha < 0)&&(Character.isLetter(buf.charAt(loop))))
							firstAlpha = loop;
						break;
					}
				}
				loop++;
			}


			if((len<buf.length())
			&&(loop!=lastSp)
			&&(lastSp>=0)
			&&(loop>=0)
			&&(loop<buf.length())
			&&(buf.charAt(loop)!=13)
			&&(buf.charAt(loop)!=10))
			{
				amperStop=loop;
				if(buf.charAt(lastSp+1)==' ')
				{
					buf.setCharAt(lastSp,(char)13);
					buf.setCharAt(lastSp+1,(char)10);
				}
				else
				{
					buf.setCharAt(lastSp,(char)13);
					buf.insert(lastSp,(char)10);
				}
				loop=lastSp+2;
			}
			if(wrap>0)
				len=loop+wrap;
		}

		if(firstAlpha<0)
			firstAlpha=0;
		if(firstAlpha<buf.length())
			buf.setCharAt(firstAlpha,Character.toUpperCase(buf.charAt(firstAlpha)));
		if((S!=null)
		&&(!normalColor.equals(S.getCurrentColor()))
		&&(S.getClientTelnetMode(Session.TELNET_ANSI)))
		{
			buf.append(S.getColorCodes()['N']);
			S.setLastColor(S.getCurrentColor());
			S.setCurrentColor(normalColor);
		}

		/* fabulous debug code
		for(int i=0;i<buf.length();i+=25)
		{
			for(int x=0;x<25;x++)
			{
				if((i+x)<buf.length())
				{
					char c=buf.charAt(i+x);
					if((c!='\r')&&(c!='\n'))
						System.out.print(c);
					else
						System.out.print("?");
				}
			}
			System.out.print(" ");
			for(int x=0;x<25;x++)
			{
				if((i+x)<buf.length())
				{
					int c=(int)buf.charAt(i+x);
					int a=c/16;
					int b=c%16;
					System.out.print(("0123456789ABCDEF").charAt(a));
					System.out.print(("0123456789ABCDEF").charAt(b));
				}
			}
			System.out.print(" \n");
		}
		//*/
		if(CMSecurity.isDebugging(CMSecurity.DbgFlag.OUTPUT))
			Log.debugOut("CoffeeFilter","OUTPUT: "+(((S!=null)&&(S.mob()!=null))?S.mob().Name():"")+": "+buf.toString());
		return buf.toString();
	}


	@Override
	public String simpleInFilter(StringBuilder input)
	{
		return simpleInFilter(input, false);
	}

	@Override
	public String simpleInFilter(StringBuilder input, boolean permitMXPTags)
	{
		if(input==null)
			return null;

		int x=0;
		while(x<input.length())
		{
			final char c=input.charAt(x);
			if(c=='\'')
				input.setCharAt(x,'`');
			else
			if((c=='^')&&(x<(input.length()-1))&&(!permitMXPTags))
			{
				switch(input.charAt(x+1))
				{
				case '<':
				case '>':
				case '&':
					input.deleteCharAt(x);
					break;
				}
			}
			else
			if(c==8)
			{
				final String newStr=input.toString();
				if(x==0)
					input=new StringBuilder(newStr.substring(x+1));
				else
				{
					input=new StringBuilder(newStr.substring(0,x-1)+newStr.substring(x+1));
					x--;
				}
				x--;
			}
			x++;
		}
		return input.toString();
	}

	@Override
	public String fullInFilter(String input)
	{
		if(input==null)
			return null;
		final StringBuilder buf=new StringBuilder(input);
		for(int i=0;i<buf.length();i++)
		{
			switch(buf.charAt(i))
			{
			case (char)10:
				buf.setCharAt(i,'r');
				buf.insert(i,'\\');
				break;
			case (char)13:
				buf.setCharAt(i,'n');
				buf.insert(i,'\\');
				break;
			}
		}
		return simpleInFilter(buf,false).toString();
	}

	@Override
	public String safetyFilter(String s)
	{
		final StringBuffer s1=new StringBuffer(s);

		int x=-1;
		while((++x)<s1.length())
		{
			if(s1.charAt(x)=='\r')
			{
				s1.deleteCharAt(x);
				x--;
			}
			else
			if(s1.charAt(x)=='\n')
			{
				s1.setCharAt(x,'\\');
				s1.insert(x+1,'n');
				x++;
			}
			else
			if(s1.charAt(x)=='\'')
				s1.setCharAt(x,'`');
		}
		return s1.toString();
	}
}