package com.planet_ink.coffee_mud.MOBS;
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.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.Session.InputCallback;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.DatabaseEngine;
import com.planet_ink.coffee_mud.Libraries.interfaces.DatabaseEngine.PlayerData;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.PostOffice.MailPiece;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.util.*;

/*
   Copyright 2005-2020 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 StdPostman extends StdShopKeeper implements PostOffice
{
	@Override
	public String ID()
	{
		return "StdPostman";
	}

	protected double	minimumPostage		= 1.0;
	protected double	postagePerPound		= 1.0;
	protected double	holdFeePerPound		= 1.0;
	protected double	feeForNewBox		= 50.0;
	protected int		maxMudMonthsHeld	= 12;
	private long		postalWaitTime		= -1;

	protected static Map<String,Long>	postalTimes	= new Hashtable<String,Long>();

	public StdPostman()
	{
		super();
		username="a postman";
		setDescription("He\\`s making a speedy delivery!");
		setDisplayText("The local postman is waiting to serve you.");
		CMLib.factions().setAlignment(this,Faction.Align.GOOD);
		setMoney(0);
		whatIsSoldMask=ShopKeeper.DEAL_POSTMAN;
		basePhyStats.setWeight(150);
		setWimpHitPoint(0);

		baseCharStats().setStat(CharStats.STAT_INTELLIGENCE,16);
		baseCharStats().setStat(CharStats.STAT_CHARISMA,25);

		basePhyStats().setArmor(0);

		baseState.setHitPoints(1000);

		recoverMaxState();
		resetToMaxState();
		recoverPhyStats();
		recoverCharStats();
	}

	@Override
	public double minimumPostage()
	{
		return minimumPostage;
	}

	@Override
	public void setMinimumPostage(final double d)
	{
		minimumPostage = d;
	}

	@Override
	public double postagePerPound()
	{
		return postagePerPound;
	}

	@Override
	public void setPostagePerPound(final double d)
	{
		postagePerPound = d;
	}

	@Override
	public double holdFeePerPound()
	{
		return holdFeePerPound;
	}

	@Override
	public void setHoldFeePerPound(final double d)
	{
		holdFeePerPound = d;
	}

	@Override
	public double feeForNewBox()
	{
		return feeForNewBox;
	}

	@Override
	public void setFeeForNewBox(final double d)
	{
		feeForNewBox = d;
	}

	@Override
	public int maxMudMonthsHeld()
	{
		return maxMudMonthsHeld;
	}

	@Override
	public void setMaxMudMonthsHeld(final int months)
	{
		maxMudMonthsHeld = months;
	}

	@Override
	public void addSoldType(final int mask)
	{
		setWhatIsSoldMask(CMath.abs(mask));
	}

	@Override
	public void setWhatIsSoldMask(final long newSellCode)
	{
		super.setWhatIsSoldMask(newSellCode);
		if(!isSold(ShopKeeper.DEAL_CLANPOSTMAN))
			whatIsSoldMask=ShopKeeper.DEAL_POSTMAN;
		else
			whatIsSoldMask=ShopKeeper.DEAL_CLANPOSTMAN;
	}

	@Override
	public String postalChain()
	{
		return text();
	}

	@Override
	public void setPostalChain(final String name)
	{
		setMiscText(name);
	}

	@Override
	public String postalBranch()
	{
		return CMLib.map().getExtendedRoomID(getStartRoom());
	}

	@Override
	public String getSenderName(final MOB mob, final Clan.Function func, final boolean checked)
	{
		if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
		{
			final Pair<Clan,Integer> clanPair=CMLib.clans().findPrivilegedClan(mob, func);
			if(clanPair!=null)
				return clanPair.first.clanID();
			else
			if(checked)
			{
				if(mob.clans().iterator().hasNext())
				{
					CMLib.commands().postSay(this,mob,L("I'm sorry, you aren't authorized by your clan to do that."),true,false);
					return null;
				}
				else
				{
					CMLib.commands().postSay(this,mob,L("I'm sorry, I only deal with clans."),true,false);
					return null;
				}
			}
		}
		return mob.Name();
	}

	@Override
	public void addToBox(final String mob, final Item thisThang, final String from, final String to, final long holdTime, final double COD)
	{
		final String name=thisThang.ID();
		CMLib.catalog().updateCatalogIntegrity(thisThang);
		CMLib.database().DBCreatePlayerData(mob,
				postalChain(),
				postalBranch()+";"+thisThang+Math.random(),
				from+";"
				+to+";"
				+holdTime+";"
				+COD+";"
				+name+";"
				+CMLib.coffeeMaker().getPropertiesStr(thisThang,true));
	}

	@Override
	public boolean delFromBox(final String boxName, final Item thisThang)
	{
		final List<PlayerData> V=getBoxRowPDData(boxName);
		boolean found=false;
		for(int v=V.size()-1;v>=0;v--)
		{
			final DatabaseEngine.PlayerData PD=V.get(v);
			if((PD!=null)
			&&(PD.key().startsWith(postalBranch()+";")))
			{
				final Item I=makeItem(parsePostalItemData(PD.xml()));
				if(I==null)
					continue;
				if(thisThang.sameAs(I))
				{
					found=true;
					CMLib.database().DBDeletePlayerData(PD.who(),PD.section(),PD.key());
					break;
				}
			}
		}
		return found;
	}

	@Override
	public void emptyBox(final String boxName)
	{
		CMLib.database().DBDeletePlayerData(boxName,postalChain());
	}

	@Override
	public Map<String, String> getOurOpenBoxes(final String boxName)
	{
		final Hashtable<String,String> branches=new Hashtable<String,String>();
		final List<PlayerData> V=CMLib.database().DBReadPlayerData(boxName,postalChain());
		if(V==null)
			return branches;
		for(int v=0;v<V.size();v++)
		{
			final DatabaseEngine.PlayerData PD=V.get(v);
			if(PD!=null)
			{
				final String key=PD.key();
				final int x=key.indexOf('/');
				if(x>0)
					branches.put(key.substring(0,x),key.substring(x+1));
			}
		}
		return branches;
	}

	@Override
	public void createBoxHere(final String boxName, final String forward)
	{
		if(!getOurOpenBoxes(boxName).containsKey(postalBranch()))
		{
			CMLib.database().DBCreatePlayerData(boxName,
					postalChain(),
					postalBranch()+"/"+forward,
					"50");
		}
	}

	@Override
	public void deleteBoxHere(final String boxName)
	{
		final List<PlayerData> V=getBoxRowPDData(boxName);
		if(V==null)
			return;
		for(int v=0;v<V.size();v++)
		{
			final DatabaseEngine.PlayerData PD=V.get(v);
			if((PD!=null)
			&&(PD.key().startsWith(postalBranch()+"/")))
			{
				CMLib.database().DBDeletePlayerData(PD.who(),PD.section(),PD.key());
			}
		}
	}

	public List<PlayerData> getAllLocalBoxPD(final String boxName)
	{
		final List<PlayerData> V=getBoxRowPDData(boxName);
		final List<PlayerData> mine=new Vector<PlayerData>();
		for(int v=0;v<V.size();v++)
		{
			final DatabaseEngine.PlayerData PD=V.get(v);
			if((PD!=null)
			&&(PD.key().startsWith(postalBranch()+";")))
			{
				mine.add(PD);
			}
		}
		return mine;
	}

	public List<PlayerData> getBoxRowPDData(final String mob)
	{
		return CMLib.database().DBReadPlayerData(mob,postalChain());
	}

	@Override
	public Item findBoxContents(final String boxName, final String likeThis)
	{
		final List<PlayerData> V=getBoxRowPDData(boxName);
		for(int v=0;v<V.size();v++)
		{
			final DatabaseEngine.PlayerData PD=V.get(v);
			if((PD!=null)
			&&(PD.key().startsWith(postalBranch()+";")))
			{
				final Item I=makeItem(parsePostalItemData(PD.xml()));
				if(I==null)
					continue;
				if(CMLib.english().containsString(I.Name(),likeThis))
					return I;
				I.destroy();
			}
		}
		return null;
	}

	@Override
	public MailPiece findExactBoxData(final String boxName, final Item likeThis)
	{
		final List<PlayerData> V=getBoxRowPDData(boxName);
		for(int v=0;v<V.size();v++)
		{
			final DatabaseEngine.PlayerData PD=V.get(v);
			if((PD!=null)
			&&(PD.key().startsWith(postalBranch()+";")))
			{
				final Item I=makeItem(parsePostalItemData(PD.xml()));
				if(I==null)
					continue;
				if(I.sameAs(likeThis))
					return parsePostalItemData(PD.xml());
			}
		}
		return null;
	}

	@Override
	public MailPiece parsePostalItemData(String data)
	{
		final MailPiece piece = new MailPiece();
		final String origData = data;
		for(int i=0;i<5;i++)
		{
			final int x=data.indexOf(';');
			if(x<0)
			{
				Log.errOut("StdPostman","Malformed postal data: '"+origData+"' : '"+data+"'");
				return null;
			}
			switch(i)
			{
			case 0:
				piece.from = data.substring(0, x);
				break;
			case 1:
				piece.to = data.substring(0, x);
				break;
			case 2:
				piece.time = data.substring(0, x);
				break;
			case 3:
				piece.cod = data.substring(0, x);
				break;
			case 4:
				piece.classID = data.substring(0, x);
				break;
			}
			data=data.substring(x+1);
		}
		piece.xml = data;
		return piece;
	}

	protected Item makeItem(final MailPiece data)
	{
		if(data ==  null)
			return null;
		final Item I=CMClass.getItem(data.classID);
		if(I!=null)
		{
			CMLib.coffeeMaker().setPropertiesStr(I,data.xml,true);
			I.recoverPhyStats();
			I.text();
			return I;
		}
		return null;
	}

	protected int getChargeableWeight(final Item I)
	{
		if(I==null)
			return 0;
		int chargeableWeight=0;
		if(I.phyStats().weight()>0)
			chargeableWeight=(I.phyStats().weight()-1);
		return chargeableWeight;
	}

	protected double getSimplePostage(final int chargeableWeight)
	{
		if(getStartRoom()==null)
			return 0.0;
		return minimumPostage()+CMath.mul(postagePerPound(),chargeableWeight);
	}

	protected double getHoldingCost(final MailPiece data, final int chargeableWeight)
	{
		if(data== null)
			return 0.0;
		if(getStartRoom()==null)
			return 0.0;
		double amt=0.0;
		final TimeClock TC=CMLib.time().localClock(getStartRoom());
		final long time=System.currentTimeMillis()-CMath.s_long(data.time);
		final long millisPerMudMonth=TC.getDaysInMonth()*CMProps.getMillisPerMudHour()*TC.getHoursInDay();
		if(time<=0)
			return amt;
		amt+=CMath.mul(CMath.mul(Math.floor(CMath.div(time,millisPerMudMonth)),holdFeePerPound()),chargeableWeight);
		return amt;
	}

	protected double getCODChargeForPiece(final MailPiece data)
	{
		if(data==null)
			return 0.0;
		final int chargeableWeight=getChargeableWeight(makeItem(data));
		final double COD=CMath.s_double(data.cod);
		double amt=0.0;
		if(COD>0.0)
			amt=getSimplePostage(chargeableWeight)+COD;
		return amt+getHoldingCost(data,chargeableWeight);
	}

	protected String getBranchPostableTo(final String toWhom, final String branch, final Map<String, String> allBranchBoxes)
	{
		String forward=allBranchBoxes.get(branch);
		if(forward==null)
			return null;
		if(forward.equalsIgnoreCase(toWhom))
			return branch;
		final PostOffice P=CMLib.map().getPostOffice(postalChain(),forward);
		if(P!=null)
		{
			forward=allBranchBoxes.get(P.postalBranch());
			if((forward!=null)&&forward.equalsIgnoreCase(toWhom))
				return P.postalBranch();
		}
		return null;
	}

	@Override
	public String findProperBranch(final String toWhom)
	{
		final MOB M=CMLib.players().getLoadPlayer(toWhom);
		if(M!=null)
		{
			if(M.getStartRoom()!=null)
			{
				final Map<String,String> allBranchBoxes=getOurOpenBoxes(toWhom);
				final PostOffice P=CMLib.map().getPostOffice(postalChain(), M.getStartRoom().getArea().Name());
				String branch=null;
				if(P!=null)
				{
					branch=getBranchPostableTo(toWhom,P.postalBranch(),allBranchBoxes);
					if(branch!=null)
						return branch;
					if(allBranchBoxes.size()==0)
					{
						P.createBoxHere(toWhom,toWhom);
						return P.postalBranch();
					}
				}
				branch=getBranchPostableTo(toWhom,postalBranch(),allBranchBoxes);
				if(branch!=null)
					return branch;
				for(final String tryBranch : allBranchBoxes.keySet())
				{
					branch=getBranchPostableTo(toWhom,tryBranch,allBranchBoxes);
					if(branch!=null)
						return branch;
				}
				if(P!=null)
				{
					P.deleteBoxHere(toWhom);
					P.createBoxHere(toWhom,toWhom);
					return P.postalBranch();
				}
			}
		}
		else
		if(CMLib.clans().getClanAnyHost(toWhom)!=null)
		{
			final Map<String,String> allBranchBoxes=getOurOpenBoxes(toWhom);
			String branch=getBranchPostableTo(toWhom,postalBranch(),allBranchBoxes);
			if(branch!=null)
				return branch;
			for(final String tryBranch : allBranchBoxes.keySet())
			{
				branch=getBranchPostableTo(toWhom,tryBranch,allBranchBoxes);
				if(branch!=null)
					return branch;
			}
		}
		return null;
	}

	public long postalWaitTime()
	{
		if((postalWaitTime<0)&&(getStartRoom()!=null))
			postalWaitTime=10038;
		else
		if((postalWaitTime==10038)&&(getStartRoom()!=null))
			postalWaitTime=(getStartRoom().getArea().getTimeObj().getHoursInDay())*CMProps.getMillisPerMudHour();
		return postalWaitTime;
	}

	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		if(!super.tick(ticking,tickID))
			return false;
		if(!CMProps.getBoolVar(CMProps.Bool.MUDSTARTED))
			return true;

		if((tickID==Tickable.TICKID_MOB)&&(getStartRoom()!=null))
		{
			boolean proceed=false;
			// each chain is handled by one branch,
			// since pending mail all looks the same.
			synchronized(postalTimes)
			{
				Long L=postalTimes.get(postalChain()+"/"+postalBranch());
				if((L==null)||(L.longValue()<System.currentTimeMillis()))
				{
					proceed=(L!=null);
					L=Long.valueOf(System.currentTimeMillis()+postalWaitTime());
					postalTimes.remove(postalChain()+"/"+postalBranch());
					postalTimes.put(postalChain()+"/"+postalBranch(),L);
				}
			}
			if(proceed)
			{
				List<PlayerData> postalDataV=getBoxRowPDData(postalChain());
				if(postalDataV==null)
					postalDataV=new ArrayList<PlayerData>(0);
				// first parse all the pending mail,
				// and remove it from the sorter
				final List<MailPiece> parsed=new ArrayList<MailPiece>(postalDataV.size());
				for(int v=0;v<postalDataV.size();v++)
				{
					final DatabaseEngine.PlayerData PD=postalDataV.get(v);
					if(PD.key().startsWith(postalBranch()+";"))
					{
						parsed.add(parsePostalItemData(PD.xml()));
						CMLib.database().DBDeletePlayerData(PD.who(),PD.section(),PD.key());
					}
				}
				PostOffice P=null;
				for(int v=0;v<parsed.size();v++)
				{
					final MailPiece V2=parsed.get(v);
					final String toWhom=V2.to;
					String deliveryBranch=findProperBranch(toWhom);
					if(deliveryBranch!=null)
					{
						P=CMLib.map().getPostOffice(postalChain(),deliveryBranch);
						final Item I=makeItem(V2);
						if((P!=null)&&(I!=null))
						{
							P.addToBox(toWhom,I,V2.from,V2.to,CMath.s_long(V2.time),CMath.s_double(V2.cod));
							continue;
						}
					}
					final String fromWhom=V2.from;
					deliveryBranch=findProperBranch(fromWhom);
					if(deliveryBranch!=null)
					{
						P=CMLib.map().getPostOffice(postalChain(),deliveryBranch);
						final Item I=makeItem(V2);
						if((P!=null)&&(I!=null))
							P.addToBox(fromWhom,I,V2.to,"POSTMASTER",System.currentTimeMillis(),0.0);
					}
				}
				postalDataV=CMLib.database().DBReadPlayerSectionData(postalChain());
				TimeClock TC=null;
				if(getStartRoom()!=null)
					TC=getStartRoom().getArea().getTimeObj();
				if((TC!=null)&&(maxMudMonthsHeld()>0))
				{
					for(int v=0;v<postalDataV.size();v++)
					{
						final DatabaseEngine.PlayerData V2=postalDataV.get(v);
						if(V2.key().startsWith(postalBranch()+";"))
						{
							final MailPiece data=parsePostalItemData(V2.xml());
							if((data!=null)&&(getStartRoom()!=null))
							{
								final long time=System.currentTimeMillis()-CMath.s_long(data.time);
								final long millisPerMudMonth=TC.getDaysInMonth()*CMProps.getMillisPerMudHour()*TC.getHoursInDay();
								if(time>0)
								{
									final int months=(int)Math.round(Math.floor(CMath.div(time,millisPerMudMonth)));
									if(months>maxMudMonthsHeld())
									{
										final Item I=makeItem(data);
										CMLib.database().DBDeletePlayerData(V2.who(),V2.section(),V2.key());
										if(I!=null)
											getShop().addStoreInventory(I);
									}
								}
							}
						}
					}
				}
			}
		}
		return true;
	}

	public void autoGive(final MOB src, final MOB tgt, final Item I)
	{
		CMMsg msg2=CMClass.getMsg(src,I,null,CMMsg.MSG_DROP|CMMsg.MASK_INTERMSG,null,CMMsg.MSG_DROP|CMMsg.MASK_INTERMSG,null,CMMsg.MSG_DROP|CMMsg.MASK_INTERMSG,null);
		location().send(this,msg2);
		msg2=CMClass.getMsg(tgt,I,null,CMMsg.MSG_GET|CMMsg.MASK_INTERMSG,null,CMMsg.MSG_GET|CMMsg.MASK_INTERMSG,null,CMMsg.MSG_GET|CMMsg.MASK_INTERMSG,null);
		location().send(this,msg2);
	}

	@Override
	public void executeMsg(final Environmental myHost, final CMMsg msg)
	{
		final MOB mob=msg.source();
		if(msg.amITarget(this))
		{
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_GIVE:
			case CMMsg.TYP_DEPOSIT:
				if(CMLib.flags().isAliveAwakeMobileUnbound(mob,true))
				{
					if(msg.tool() instanceof Container)
						((Container)msg.tool()).emptyPlease(true);
					final Session S=msg.source().session();
					if((!msg.source().isMonster())&&(S!=null)&&(msg.tool() instanceof Item))
					{
						autoGive(msg.source(),this,(Item)msg.tool());
						if(isMine(msg.tool()))
						{
							final StdPostman me=this;
							S.prompt(new InputCallback(InputCallback.Type.PROMPT,"",0)
							{
								@Override
								public void showPrompt()
								{
									S.promptPrint(L("Address this to whom? "));
								}

								@Override
								public void timedOut()
								{
									autoGive(me,msg.source(),(Item)msg.tool());
								}

								@Override
								public void callBack()
								{
									if((this.input!=null)&&(this.input.length()>0)
									&&((CMLib.players().getLoadPlayer(this.input)!=null)||(CMLib.clans().findClan(this.input)!=null)))
									{
										final String fromWhom=getSenderName(msg.source(),Clan.Function.DEPOSIT,false);
										final String toWhom;
										if(CMLib.players().getLoadPlayer(this.input)!=null)
											toWhom=CMLib.players().getLoadPlayer(this.input).Name();
										else
											toWhom=CMLib.clans().findClan(this.input).name();
										final double amt=getSimplePostage(getChargeableWeight((Item)msg.tool()));
										S.prompt(new InputCallback(InputCallback.Type.CHOOSE,"P","CP\n",0)
										{
											@Override
											public void showPrompt()
											{
												S.promptPrint(L("Postage on this will be @x1.\n\rWould you like to P)ay this now, or be C)harged on delivery (c/P)?",CMLib.beanCounter().nameCurrencyShort(me,amt)));
											}

											@Override
											public void timedOut()
											{
												autoGive(me,msg.source(),(Item)msg.tool());
											}

											@Override
											public void callBack()
											{
												final String choice=this.input.trim().toUpperCase();
												if(choice.startsWith("C"))
												{
													S.prompt(new InputCallback(InputCallback.Type.PROMPT,"",0)
													{
														@Override
														public void showPrompt()
														{
															S.promptPrint(L("Enter COD amount (@x1): ",CMLib.beanCounter().getDenominationName(CMLib.beanCounter().getCurrency(me),CMLib.beanCounter().getLowestDenomination(CMLib.beanCounter().getCurrency(me)))));
														}

														@Override
														public void timedOut()
														{
															autoGive(me,msg.source(),(Item)msg.tool());
														}

														@Override
														public void callBack()
														{
															final String CODstr=this.input;
															if((CODstr.length()==0)||(!CMath.isNumber(CODstr))||(CMath.s_double(CODstr)<=0.0))
															{
																CMLib.commands().postSay(me,mob,L("That is not a valid amount."),true,false);
																autoGive(me,msg.source(),(Item)msg.tool());
															}
															else
															{
																final Coins currency=CMLib.beanCounter().makeBestCurrency(CMLib.beanCounter().getCurrency(me),CMLib.beanCounter().getLowestDenomination(CMLib.beanCounter().getCurrency(me))*(CMath.s_double(CODstr)));
																final double COD=currency.getTotalValue();
																addToBox(postalChain(),(Item)msg.tool(),fromWhom,toWhom,System.currentTimeMillis(),COD);
																CMLib.commands().postSay(me,mob,L("I'll deliver that for ya right away!"),true,false);
																((Item)msg.tool()).destroy();
															}
														}
													});
												}
												else
												if((amt>0.0)&&(CMLib.beanCounter().getTotalAbsoluteShopKeepersValue(msg.source(),me)<amt))
												{
													CMLib.commands().postSay(me,mob,L("You can't afford postage."),true,false);
													autoGive(me,msg.source(),(Item)msg.tool());
												}
												else
												{
													CMLib.beanCounter().subtractMoney(mob,CMLib.beanCounter().getCurrency(me),amt);
													addToBox(postalChain(),(Item)msg.tool(),fromWhom,toWhom,System.currentTimeMillis(),0.0);
													CMLib.commands().postSay(me,mob,L("I'll deliver that for ya right away!"),true,false);
													((Item)msg.tool()).destroy();
												}
											}
										});

									}
									else
									{
										CMLib.commands().postSay(me,mob,L("That is not a valid player or clan name."),true,false);
										autoGive(me,msg.source(),(Item)msg.tool());
									}
								}
							});
						}
						else
							CMLib.commands().postSay(this,mob,L("I can't seem to deliver @x1.",msg.tool().name()),true,false);
					}
				}
				if ((CMSecurity.isAllowed(msg.source(), location(), CMSecurity.SecFlag.ORDER)
				|| (CMLib.law().doesHavePriviledgesHere(msg.source(), getStartRoom()))
				|| (CMSecurity.isAllowed(msg.source(), location(), CMSecurity.SecFlag.CMDMOBS) && (isMonster()))
				|| (CMSecurity.isAllowed(msg.source(), location(), CMSecurity.SecFlag.CMDROOMS) && (isMonster()))))
					return;
				super.executeMsg(myHost, msg);
				return;
			case CMMsg.TYP_WITHDRAW:
				if(CMLib.flags().isAliveAwakeMobileUnbound(mob,true))
				{
					final String fromWhom=getSenderName(msg.source(),Clan.Function.WITHDRAW,false);
					final Item old=(Item)msg.tool();
					MailPiece data=findExactBoxData(fromWhom,(Item)msg.tool());
					if((data==null)
					&&(!isSold(ShopKeeper.DEAL_CLANPOSTMAN))
					&&(msg.source().isMarriedToLiege()))
						data=findExactBoxData(msg.source().getLiegeID(),(Item)msg.tool());
					if(data==null)
						CMLib.commands().postSay(this,mob,L("You want WHAT? Try LIST."),true,false);
					else
					{
						if((!delFromBox(fromWhom,old))
						&&(!isSold(ShopKeeper.DEAL_CLANPOSTMAN))
						&&(msg.source().isMarriedToLiege()))
							delFromBox(msg.source().getLiegeID(),old);
						final double totalCharge=getCODChargeForPiece(data);
						if((totalCharge>0.0)&&(CMLib.beanCounter().getTotalAbsoluteShopKeepersValue(msg.source(),this)>=totalCharge))
						{

							CMLib.beanCounter().subtractMoney(msg.source(),totalCharge);
							final double COD=CMath.s_double(data.cod);
							Coins returnMoney=null;
							if(COD>0.0)
								returnMoney=CMLib.beanCounter().makeBestCurrency(this,COD);
							if(returnMoney!=null)
							{
								CMLib.commands().postSay(this,mob,L("The COD amount of @x1 has been sent back to @x2.",returnMoney.Name(),data.from),true,false);
								addToBox(postalChain(),returnMoney,data.to,data.from,System.currentTimeMillis(),0.0);
								CMLib.commands().postSay(this,mob,L("The total charge on that was a COD charge of @x1 plus @x2 postage and holding fees.",returnMoney.Name(),CMLib.beanCounter().nameCurrencyShort(this,totalCharge-COD)),true,false);
							}
							else
								CMLib.commands().postSay(this,mob,L("The total charge on that was @x1 in holding/storage fees.",CMLib.beanCounter().nameCurrencyShort(this,totalCharge)),true,false);
						}
						CMLib.commands().postSay(this,mob,L("There ya go!"),true,false);
						if(location()!=null)
							location().addItem(old,ItemPossessor.Expire.Player_Drop);
						final CMMsg msg2=CMClass.getMsg(mob,old,this,CMMsg.MSG_GET,null);
						if(location().okMessage(mob,msg2))
							location().send(mob,msg2);
					}
				}
				super.executeMsg(myHost, msg);
				return;
			case CMMsg.TYP_VALUE:
			case CMMsg.TYP_SELL:
			case CMMsg.TYP_VIEW:
				super.executeMsg(myHost,msg);
				return;
			case CMMsg.TYP_BUY:
				super.executeMsg(myHost,msg);
				return;
			case CMMsg.TYP_SPEAK:
			{
				super.executeMsg(myHost,msg);
				String str=CMStrings.getSayFromMessage(msg.targetMessage());
				if((str!=null)&&(str.trim().equalsIgnoreCase("open")))
				{
					final String theName=getSenderName(msg.source(),Clan.Function.DEPOSIT,false);
					if(getOurOpenBoxes(theName).containsKey(postalBranch()))
					{
						if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
							CMLib.commands().postSay(this,mob,L("@x1 already has a box open here!",CMStrings.capitalizeFirstLetter(theName)),true,false);
						else
							CMLib.commands().postSay(this,mob,L("You already have a box open here!"),true,false);
					}
					else
					if(feeForNewBox()>0.0)
					{
						if(CMLib.beanCounter().getTotalAbsoluteShopKeepersValue(msg.source(),this)<feeForNewBox())
						{
							CMLib.commands().postSay(this,mob,L("Too bad you can't afford it."),true,false);
							return;
						}
						CMLib.beanCounter().subtractMoney(msg.source(),CMLib.beanCounter().getCurrency(this),feeForNewBox());
						createBoxHere(theName,theName);
						if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
							CMLib.commands().postSay(this,mob,L("A box has been opened for @x1.",CMStrings.capitalizeFirstLetter(theName)),true,false);
						else
							CMLib.commands().postSay(this,mob,L("A box has been opened for you."),true,false);
					}
					else
					{
						createBoxHere(theName,theName);
						if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
							CMLib.commands().postSay(this,mob,L("A box has been opened for @x1.",CMStrings.capitalizeFirstLetter(theName)),true,false);
						else
							CMLib.commands().postSay(this,mob,L("A box has been opened for you."),true,false);
					}
				}
				else
				if((str!=null)&&(str.trim().equalsIgnoreCase("close")))
				{
					final String theName=getSenderName(msg.source(),Clan.Function.WITHDRAW,false);
					if(!getOurOpenBoxes(theName).containsKey(postalBranch()))
					{
						if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
							CMLib.commands().postSay(this,mob,L("@x1 does not have a box here!",CMStrings.capitalizeFirstLetter(theName)),true,false);
						else
							CMLib.commands().postSay(this,mob,L("You don't have a box open here!"),true,false);
					}
					else
					if(getAllLocalBoxPD(theName).size()>0)
						CMLib.commands().postSay(this,mob,L("That box has pending items which must be removed first."),true,false);
					else
					{
						deleteBoxHere(theName);
						CMLib.commands().postSay(this,mob,L("That box is now closed."),true,false);
					}
				}
				else
				if((str!=null)&&(str.toUpperCase().trim().startsWith("FORWARD")))
				{
					final String theName=getSenderName(msg.source(),Clan.Function.WITHDRAW,false);
					str=str.trim().substring(7).trim();
					if(!getOurOpenBoxes(theName).containsKey(postalBranch()))
					{
						if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
							CMLib.commands().postSay(this,mob,L("@x1 does not have a box here!",CMStrings.capitalizeFirstLetter(theName)),true,false);
						else
							CMLib.commands().postSay(this,mob,L("You don't have a box open here!"),true,false);
					}
					else
					if(str.toUpperCase().equalsIgnoreCase("STOP") || str.toUpperCase().equalsIgnoreCase("UNFORWARD") || str.toUpperCase().equalsIgnoreCase("END"))
					{
						final Map<String,String> allOpenBoxes = getOurOpenBoxes(theName);
						if(allOpenBoxes.containsKey(postalBranch()))
						{
							deleteBoxHere(theName);
							CMLib.commands().postSay(this,mob,L("Ok, mail send here will no longer be forwarded anywhere."),true,false);
						}
					}
					else
					{
						final Area A=CMLib.map().findAreaStartsWith(str);
						if(A==null)
						{
							CMLib.commands().postSay(this,mob,L("I don't know of an area called '@x1' to forward to.",str),true,false);
							final Map<String,String> allOpenBoxes = getOurOpenBoxes(theName);
							if(allOpenBoxes.containsKey(postalBranch()))
								CMLib.commands().postSay(this,mob,L("Top stop forwarding, try FORWARD STOP."),true,false);
						}
						else
						{
							final PostOffice P=CMLib.map().getPostOffice(postalChain(),A.Name());
							if(P==null)
								CMLib.commands().postSay(this,mob,L("I'm sorry, we don't have a branch in @x1.",A.name()),true,false);
							else
							if(!P.getOurOpenBoxes(theName).containsKey(P.postalBranch()))
							{
								if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
									CMLib.commands().postSay(this,mob,L("I'm sorry, @x1 does not have a box at our branch in @x2.",CMStrings.capitalizeFirstLetter(theName),A.name()),true,false);
								else
									CMLib.commands().postSay(this,mob,L("I'm sorry, you don't have a box at our branch in @x1.",A.name()),true,false);
							}
							else
							{
								deleteBoxHere(theName);
								createBoxHere(theName,P.postalBranch());
								CMLib.commands().postSay(this,mob,L("Ok, mail will now be forwarded to our branch in @x1.",A.name()),true,false);
							}
						}
					}
				}
				return;
			}
			case CMMsg.TYP_LIST:
			{
				super.executeMsg(myHost,msg);
				if(CMLib.flags().isAliveAwakeMobileUnbound(mob,true))
				{
					List<PlayerData> allPostalData=null;
					final String theName=getSenderName(msg.source(),Clan.Function.DEPOSIT_LIST,false);
					if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
						allPostalData=getAllLocalBoxPD(theName);
					else
					{
						allPostalData=getAllLocalBoxPD(theName);
						if(mob.isMarriedToLiege())
						{
							final List<PlayerData> PDV=getAllLocalBoxPD(mob.getLiegeID());
							if((PDV!=null)&&(PDV.size()>0))
								allPostalData.addAll(PDV);
						}
					}

					final TimeClock C=CMLib.time().localClock(getStartRoom());
					boolean codCharge=false;
					if(allPostalData.size()==0)
						mob.tell(L("\n\rYour postal box is presently empty."));
					else
					{
						StringBuffer str=new StringBuffer("");
						str.append(L("\n\rItems in your postal box here:\n\r"));
						str.append(L("^x[COD     ][From           ][Sent           ][Item                        ]^.^N"));
						mob.tell(str.toString());
						for(int i=0;i<allPostalData.size();i++)
						{
							final DatabaseEngine.PlayerData PD=allPostalData.get(i);
							// already filtered by getAllLocalBoxPD
							final MailPiece pieces=parsePostalItemData(PD.xml());
							final Item I=makeItem(pieces);
							if(I==null)
								continue;
							str=new StringBuffer("^N");
							if(getCODChargeForPiece(pieces)>0.0)
							{
								codCharge=true;
								str.append("["+CMStrings.padRight(""+CMLib.beanCounter().abbreviatedPrice(this,getCODChargeForPiece(pieces)),8)+"]");
							}
							else
								str.append("[        ]");
							str.append("["+CMStrings.padRight(pieces.from,15)+"]");
							final TimeClock C2=C.deriveClock(CMath.s_long(pieces.time));
							str.append("["+CMStrings.padRight(C2.getShortestTimeDescription(),15)+"]");
							str.append("["+CMStrings.padRight(I.Name(),28)+"]");
							mob.tell(str.toString()+"^T");
						}
					}
					final StringBuffer str=new StringBuffer("\n\r^N");
					if(codCharge)
						str.append(L("* COD charges above include all shipping costs.\n\r"));
					str.append(L("* This branch charges minimum @x1 postage for first pound.\n\r",CMLib.beanCounter().nameCurrencyShort(this,minimumPostage())));
					str.append(L("* An additional @x1 per pound is charged for packages.\n\r",CMLib.beanCounter().nameCurrencyShort(this,postagePerPound())));
					str.append(L("* A charge of @x1 per pound per month is charged for holding.\n\r",CMLib.beanCounter().nameCurrencyShort(this,holdFeePerPound())));
					str.append("* To forward your mail, 'say \""+name()+"\" \"forward <areaname>\"'.\n\r");
					str.append(L("* To close your box, 'say \"@x1\" close'.\n\r",name()));
					mob.tell(str.toString());
				}
				return;
			}
			default:
				break;
			}
		}
		else
		if(msg.sourceMinor()==CMMsg.TYP_RETIRE)
			emptyBox(msg.source().Name());
		super.executeMsg(myHost,msg);
	}

	@Override
	public boolean okMessage(final Environmental myHost, final CMMsg msg)
	{
		final MOB mob=msg.source();
		if((msg.targetMinor()==CMMsg.TYP_EXPIRE)
		&&(msg.target()==location())
		&&(CMLib.flags().isInTheGame(this,true)))
			return false;
		else
		if(msg.amITarget(this))
		{
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_GIVE:
			case CMMsg.TYP_DEPOSIT:
				{
					if(!CMLib.coffeeShops().ignoreIfNecessary(msg.source(),finalIgnoreMask(),this))
						return false;
					if(msg.tool()==null)
						return false;
					final String senderName=getSenderName(msg.source(),Clan.Function.DEPOSIT,true);
					if(senderName==null)
						return false;
					if(!(msg.tool() instanceof Item))
					{
						mob.tell(L("@x1 doesn't look interested.",mob.charStats().HeShe()));
						return false;
					}
					if((msg.tool() instanceof Container)
					&&(((Container)msg.tool()).getContents().size()>0))
					{
						mob.tell(this,msg.tool(),null,L("<S-HE-SHE> refuses to accept <T-NAME>.  You'll need to mail each item separately."));
						return false;
					}
					if(CMLib.flags().isEnspelled((Item)msg.tool())
					|| CMLib.flags().isOnFire((Item)msg.tool())
					||(msg.tool() instanceof CagedAnimal))
					{
						mob.tell(this,msg.tool(),null,L("<S-HE-SHE> refuses to accept <T-NAME> for delivery."));
						return false;
					}
				}
				return super.okMessage(myHost, msg);
			case CMMsg.TYP_WITHDRAW:
				{
					if(!CMLib.coffeeShops().ignoreIfNecessary(msg.source(),finalIgnoreMask(),this))
						return false;
					final String senderName=getSenderName(msg.source(),Clan.Function.WITHDRAW,true);
					if(senderName==null)
						return false;
					if((msg.tool()==null)||(!(msg.tool() instanceof Item)))
					{
						CMLib.commands().postSay(this,mob,L("What do you want? I'm busy!"),true,false);
						return false;
					}
					if((msg.tool()!=null)&&(!msg.tool().okMessage(myHost,msg)))
						return false;
					MailPiece data=findExactBoxData(senderName,(Item)msg.tool());
					if((data==null)
					&&(!isSold(ShopKeeper.DEAL_CLANPOSTMAN))
					&&(msg.source().isMarriedToLiege()))
						data=findExactBoxData(msg.source().getLiegeID(),(Item)msg.tool());
					if(data==null)
					{
						CMLib.commands().postSay(this,mob,L("You want WHAT? Try LIST."),true,false);
						return false;
					}
					final double totalCharge=getCODChargeForPiece(data);
					if(CMLib.beanCounter().getTotalAbsoluteShopKeepersValue(msg.source(),this)<totalCharge)
					{
						CMLib.commands().postSay(this,mob,L("The total charge to receive that item is @x1. You don't have enough.",CMLib.beanCounter().nameCurrencyShort(this,totalCharge)),true,false);
						return false;
					}
				}
				return super.okMessage(myHost,msg);
			case CMMsg.TYP_VALUE:
			case CMMsg.TYP_SELL:
			case CMMsg.TYP_VIEW:
				return super.okMessage(myHost,msg);
			case CMMsg.TYP_BUY:
				return super.okMessage(myHost,msg);
			case CMMsg.TYP_LIST:
			{
				if(!CMLib.coffeeShops().ignoreIfNecessary(msg.source(),finalIgnoreMask(),this))
					return false;
				final String senderName=getSenderName(msg.source(),Clan.Function.DEPOSIT_LIST,true);
				if(senderName==null)
					return false;
				if((!getOurOpenBoxes(senderName).containsKey(postalBranch()))
				&&((isSold(ShopKeeper.DEAL_CLANPOSTMAN))
				   ||(!msg.source().isMarriedToLiege())
				   ||(!getOurOpenBoxes(msg.source().getLiegeID()).containsKey(postalBranch()))))
				{
					if((!isSold(ShopKeeper.DEAL_CLANPOSTMAN))
					&&(msg.source().getStartRoom().getArea()==getStartRoom().getArea()))
					{
						createBoxHere(msg.source().Name(),msg.source().Name());
						return true;
					}
					final StringBuffer str=new StringBuffer("");
					if(isSold(ShopKeeper.DEAL_CLANPOSTMAN))
						str.append(L("@x1 does not have a postal box at this branch, I'm afraid.",CMStrings.capitalizeFirstLetter(senderName)));
					else
						str.append(L("You don't have a postal box at this branch, I'm afraid."));
					if(postalChain().length()>0)
						str.append(L("\n\rThis branch is part of the @x1 postal chain.",postalChain()));
					CMLib.commands().postSay(this,mob,str.toString()+"^T",true,false);
					mob.tell(L("Use 'say \"@x1\" open' to open a box here@x2",name(),((feeForNewBox()<=0.0)?".":(" for "+CMLib.beanCounter().nameCurrencyShort(this,feeForNewBox())+"."))));
					return false;
				}
				return true;
			}
			default:
				break;
			}
		}
		return super.okMessage(myHost,msg);
	}
}
