/
com/planet_ink/coffee_mud/Abilities/
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/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/Areas/interfaces/
com/planet_ink/coffee_mud/Behaviors/
com/planet_ink/coffee_mud/Behaviors/interfaces/
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/Exits/interfaces/
com/planet_ink/coffee_mud/Items/Armor/
com/planet_ink/coffee_mud/Items/Basic/
com/planet_ink/coffee_mud/Items/MiscMagic/
com/planet_ink/coffee_mud/Items/Software/
com/planet_ink/coffee_mud/Items/Weapons/
com/planet_ink/coffee_mud/Libraries/
com/planet_ink/coffee_mud/Libraries/interfaces/
com/planet_ink/coffee_mud/Locales/
com/planet_ink/coffee_mud/Locales/interfaces/
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/smtp/
com/planet_ink/coffee_mud/core/threads/
com/planet_ink/siplet/applet/
lib/
resources/fakedb/
resources/quests/holidays/
web/
web/admin.templates/
web/admin/grinder/
web/admin/images/
web/pub.templates/
web/pub/images/mxp/
web/pub/sounds/
package com.planet_ink.coffee_mud.Libraries;
import com.planet_ink.coffee_mud.core.exceptions.BadEmailAddressException;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
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.io.IOException;
import java.util.*;
/*
   Copyright 2000-2010 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.
*/
@SuppressWarnings("unchecked")
public class CMPlayers extends StdLibrary implements PlayerLibrary
{
    public String ID(){return "CMPlayers";}
    public Vector playersList = new Vector();
    
    private ThreadEngine.SupportThread thread=null;
    
    public ThreadEngine.SupportThread getSupportThread() { return thread;}
    
    public int numPlayers() { return playersList.size(); }
    public void addPlayer(MOB newOne)
    {
        synchronized(playersList)
        {
            if(getPlayer(newOne.Name())!=null) return;
            if(playersList.contains(newOne)) return;
            playersList.add(newOne);
        }
    }
    public void delPlayer(MOB oneToDel) { synchronized(playersList){playersList.remove(oneToDel);} }
    public MOB getPlayer(String calledThis)
    {
        MOB M = null;
        synchronized(playersList)
        {
            for (Enumeration p=players(); p.hasMoreElements();)
            {
                M = (MOB)p.nextElement();
                if (M.Name().equalsIgnoreCase(calledThis))
                    return M;
            }
        }
        return null;
    }

    public MOB getLoadPlayer(String last)
    {
        if(!CMProps.getBoolVar(CMProps.SYSTEMB_MUDSTARTED))
            return null;
        MOB M=null;
        synchronized(playersList)
        {
            M=getPlayer(last);
            if(M!=null) return M;

            for(Enumeration p=players();p.hasMoreElements();)
            {
                MOB mob2=(MOB)p.nextElement();
                if(mob2.Name().equalsIgnoreCase(last))
                { return mob2;}
            }

            MOB TM=CMClass.getMOB("StdMOB");
            if(CMLib.database().DBUserSearch(TM,last))
            {
                M=CMClass.getMOB("StdMOB");
                M.setName(TM.Name());
                CMLib.database().DBReadPlayer(M);
                CMLib.database().DBReadFollowers(M,false);
                if(M.playerStats()!=null)
                    M.playerStats().setLastUpdated(M.playerStats().lastDateTime());
                M.recoverEnvStats();
                M.recoverCharStats();
                Ability A=null;
        		for(int a=0;a<M.numLearnedAbilities();a++)
        		{
        			A=M.fetchAbility(a);
        			if(A!=null) A.autoInvocation(M);
        		}
            }
            TM.destroy();
        }
        return M;
    }

    
	public Enumeration players() { return (Enumeration)DVector.s_enum(playersList); }

    public void obliteratePlayer(MOB deadMOB, boolean quiet)
    {
        if(getPlayer(deadMOB.Name())!=null)
        {
           deadMOB=getPlayer(deadMOB.Name());
           delPlayer(deadMOB);
        }
        for(int s=0;s<CMLib.sessions().size();s++)
        {
            Session S=CMLib.sessions().elementAt(s);
            if((!S.killFlag())&&(S.mob()!=null)&&(S.mob().Name().equals(deadMOB.Name())))
               deadMOB=S.mob();
        }
        CMMsg msg=CMClass.getMsg(deadMOB,null,CMMsg.MSG_RETIRE,(quiet)?null:"A horrible death cry is heard throughout the land.");
        Room deadLoc=deadMOB.location();
        if(deadLoc!=null)
            deadLoc.send(deadMOB,msg);
        try
        {
            for(Enumeration r=CMLib.map().rooms();r.hasMoreElements();)
            {
                Room R=(Room)r.nextElement();
                if((R!=null)&&(R!=deadLoc))
                {
                    if(R.okMessage(deadMOB,msg))
                        R.sendOthers(deadMOB,msg);
                    else
                    {
                        addPlayer(deadMOB);
                        return;
                    }
                }
            }
        }catch(NoSuchElementException e){}
        StringBuffer newNoPurge=new StringBuffer("");
        Vector protectedOnes=Resources.getFileLineVector(Resources.getFileResource("protectedplayers.ini",false));
        boolean somethingDone=false;
        if((protectedOnes!=null)&&(protectedOnes.size()>0))
        {
            for(int b=0;b<protectedOnes.size();b++)
            {
                String B=(String)protectedOnes.elementAt(b);
                if(!B.equalsIgnoreCase(deadMOB.name()))
                    newNoPurge.append(B+"\n");
                else
                    somethingDone=true;
            }
            if(somethingDone)
            {
                Resources.updateResource("protectedplayers.ini",newNoPurge);
                Resources.saveFileResource("::protectedplayers.ini");
            }
        }

        CMLib.database().DBDeleteMOB(deadMOB);
        if(deadMOB.session()!=null)
            deadMOB.session().logoff(false,false,false);
        Log.sysOut("Scoring",deadMOB.name()+" has been deleted.");
        deadMOB.destroy();
    }
    
    public int savePlayers()
    {
        int processed=0;
        for(Enumeration p=players();p.hasMoreElements();)
        {
            MOB mob=(MOB)p.nextElement();
            if(!mob.isMonster())
            {
                thread.status("just saving "+mob.Name());
                CMLib.database().DBUpdatePlayerStatsOnly(mob);
                if((mob.Name().length()==0)||(mob.playerStats()==null))
                    continue;
                thread.status("saving "+mob.Name()+", "+mob.inventorySize()+"items");
                CMLib.database().DBUpdatePlayerItems(mob);
                thread.status("saving "+mob.Name()+", "+mob.numLearnedAbilities()+"abilities");
                CMLib.database().DBUpdatePlayerAbilities(mob);
                thread.status("saving "+mob.numFollowers()+" followers of "+mob.Name());
                CMLib.database().DBUpdateFollowers(mob);
                mob.playerStats().setLastUpdated(System.currentTimeMillis());
                processed++;
            }
            else
            if((mob.playerStats()!=null)
            &&((mob.playerStats().lastUpdated()==0)
               ||(mob.playerStats().lastUpdated()<mob.playerStats().lastDateTime())))
            {
                thread.status("just saving "+mob.Name());
                CMLib.database().DBUpdatePlayerStatsOnly(mob);
                if((mob.Name().length()==0)||(mob.playerStats()==null))
                    continue;
                thread.status("just saving "+mob.Name()+", "+mob.inventorySize()+" items");
                CMLib.database().DBUpdatePlayerItems(mob);
                thread.status("just saving "+mob.Name()+", "+mob.numLearnedAbilities()+" abilities");
                CMLib.database().DBUpdatePlayerAbilities(mob);
                mob.playerStats().setLastUpdated(System.currentTimeMillis());
                processed++;
            }
        }
        return processed;
    }
    
	public String getThinSortValue(ThinPlayer player, int code) 
	{
		switch(code) {
		case 0: return player.name;
		case 1: return player.charClass;
		case 2: return player.race;
		case 3: return Integer.toString(player.level);
		case 4: return Integer.toString(player.age);
		case 5: return Long.toString(player.last);
		case 6: return player.email;
		case 7: return player.ip;
		}
		return player.name;
	}
	
	public int getThinSortCode(String codeName, boolean loose) 
	{
		int x=CMParms.indexOf(THIN_SORT_CODES,codeName);
		if(x<0)x=CMParms.indexOf(THIN_SORT_CODES2,codeName);
		if(!loose) return x;
		if(x<0)
			for(int s=0;s<THIN_SORT_CODES.length;s++)
				if(THIN_SORT_CODES[s].startsWith(codeName))
					x=s;
		if(x<0)
			for(int s=0;s<THIN_SORT_CODES2.length;s++)
				if(THIN_SORT_CODES2[s].startsWith(codeName))
					x=s;
		return x;
	}
	
    public Enumeration thinPlayers(String sort, Hashtable cache)
    {
		Vector V=cache==null?null:(Vector)cache.get("PLAYERLISTVECTOR"+sort);
		if(V==null)
		{
			V=CMLib.database().getExtendedUserList();
			int code=getThinSortCode(sort,false);
			if((sort.length()>0)
			&&(code>=0)
			&&(V.size()>1))
			{
				Vector unV=V;
				V=new Vector();
				while(unV.size()>0)
				{
					ThinPlayer M=(ThinPlayer)unV.firstElement();
					String loweStr=getThinSortValue(M,code);
					ThinPlayer lowestM=M;
					for(int i=1;i<unV.size();i++)
					{
						M=(ThinPlayer)unV.elementAt(i);
						String val=getThinSortValue(M,code);
						if((CMath.isNumber(val)&&CMath.isNumber(loweStr)))
						{
							if(CMath.s_long(val)<CMath.s_long(loweStr))
							{
								loweStr=val;
								lowestM=M;
							}
						}
						else
						if(val.compareTo(loweStr)<0)
						{
							loweStr=val;
							lowestM=M;
						}
					}
					unV.removeElement(lowestM);
					V.addElement(lowestM);
				}
			}
			if(cache!=null)
				cache.put("PLAYERLISTVECTOR"+sort,V);
		}
		return DVector.s_enum(V);
    }

    private boolean autoPurge()
    {
        if(CMSecurity.isDisabled("AUTOPURGE"))
        	return true;
        
        long[] levels=new long[2001];
        long[] prePurgeLevels=new long[2001];
        for(int i=0;i<levels.length;i++) levels[i]=0;
        for(int i=0;i<prePurgeLevels.length;i++) prePurgeLevels[i]=0;
        String mask=CMProps.getVar(CMProps.SYSTEM_AUTOPURGE);
        Vector maskV=CMParms.parseCommas(mask.trim(),false);
        long purgePoint=0;
        for(int mv=0;mv<maskV.size();mv++)
        {
            Vector V=CMParms.parse(((String)maskV.elementAt(mv)).trim());
            if(V.size()<2) continue;
            long val=CMath.s_long((String)V.elementAt(1));
            if(val<=0) continue;
            long prepurge=0;
            if(V.size()>2)
                prepurge=CMath.s_long((String)V.elementAt(2));
            String cond=((String)V.firstElement()).trim();
            int start=0;
            int finish=levels.length-1;
            if(cond.startsWith("<="))
                finish=CMath.s_int(cond.substring(2).trim());
            else
            if(cond.startsWith(">="))
                start=CMath.s_int(cond.substring(2).trim());
            else
            if(cond.startsWith("=="))
            {
                start=CMath.s_int(cond.substring(2).trim());
                finish=start;
            }
            else
            if(cond.startsWith("="))
            {
                start=CMath.s_int(cond.substring(1).trim());
                finish=start;
            }
            else
            if(cond.startsWith(">"))
                start=CMath.s_int(cond.substring(1).trim())+1;
            else
            if(cond.startsWith("<"))
                finish=CMath.s_int(cond.substring(1).trim())-1;

            if((start>=0)&&(finish<levels.length)&&(start<=finish))
            {
                long realVal=System.currentTimeMillis()-(val*TimeManager.MILI_DAY);
                purgePoint=realVal+(prepurge*TimeManager.MILI_DAY);
                for(int s=start;s<=finish;s++)
                {
                    if(levels[s]==0) levels[s]=realVal;
                    if(prePurgeLevels[s]==0) prePurgeLevels[s]=purgePoint;
                }
            }
        }
        thread.status("autopurge process");
        Vector allUsers=CMLib.database().getExtendedUserList();
        Vector protectedOnes=Resources.getFileLineVector(Resources.getFileResource("protectedplayers.ini",false));
        if(protectedOnes==null) protectedOnes=new Vector();

        for(int u=0;u<allUsers.size();u++)
        {
        	ThinPlayer user=(ThinPlayer)allUsers.elementAt(u);
            String name=user.name;
            int level=user.level;
            long userLastLoginDateTime=user.last;
            long purgePriorDateTime=Long.MAX_VALUE;
            long warnPriorDateTime=Long.MAX_VALUE;
            if(level>levels.length)
            {
                purgePriorDateTime=levels[levels.length-1];
                warnPriorDateTime=prePurgeLevels[prePurgeLevels.length-1];
            }
            else
            if(level>=0)
            {
                purgePriorDateTime=levels[level];
                warnPriorDateTime=prePurgeLevels[level];
            }
            else
                continue;
            if(CMSecurity.isDebugging("AUTOPURGE"))
                Log.debugOut(thread.getName(),name+" last on "+CMLib.time().date2String(userLastLoginDateTime)+" will be warned on "+CMLib.time().date2String(warnPriorDateTime)+" and purged on "+CMLib.time().date2String(purgePriorDateTime));
            if((userLastLoginDateTime>purgePriorDateTime)&&(userLastLoginDateTime<warnPriorDateTime))
            {
                boolean protectedOne=false;
                for(int p=0;p<protectedOnes.size();p++)
                {
                    String P=(String)protectedOnes.elementAt(p);
                    if(P.equalsIgnoreCase(name))
                    {
                        protectedOne=true;
                        break;
                    }
                }
                if(!protectedOne)
                {
                    Vector warnedOnes=Resources.getFileLineVector(Resources.getFileResource("warnedplayers.ini",false));
                    long foundWarningDateTime=-1;
                    StringBuffer warnStr=new StringBuffer("");
                    if((warnedOnes!=null)&&(warnedOnes.size()>0))
                        for(int b=0;b<warnedOnes.size();b++)
                        {
                            String B=((String)warnedOnes.elementAt(b)).trim();
                            if(B.trim().length()>0)
                            {
                                if(B.toUpperCase().startsWith(name.toUpperCase()+" "))
                                {
                                    int lastSpace=B.lastIndexOf(" ");
                                    foundWarningDateTime=CMath.s_long(B.substring(lastSpace+1).trim());
                                }
                                warnStr.append(B+"\n");
                            }
                        }
                    if((foundWarningDateTime<0)||(foundWarningDateTime<purgePriorDateTime))
                    {
                        MOB M=getLoadPlayer(name);
                        if((M!=null)&&(M.playerStats()!=null))
                        {
                            warnStr.append(M.name()+" "+M.playerStats().getEmail()+" "+System.currentTimeMillis()+"\n");
                            Resources.updateResource("warnedplayers.ini",warnStr);
                            Resources.saveFileResource("::warnedplayers.ini");
                            if(CMSecurity.isDebugging("AUTOPURGE"))
                                Log.debugOut(thread.getName(),name+" is now warned.");
                            warnPrePurge(M,userLastLoginDateTime-purgePriorDateTime);
                        }
                    }
                    else
                    if(CMSecurity.isDebugging("AUTOPURGE"))
                        Log.debugOut(thread.getName(),name+" has already been warned on "+CMLib.time().date2String(foundWarningDateTime));
                }
                else
                if(CMSecurity.isDebugging("AUTOPURGE"))
                    Log.debugOut(thread.getName(),name+" is protected from purge warnings.");
            }

            if(userLastLoginDateTime<purgePriorDateTime)
            {
                boolean protectedOne=false;
                for(int p=0;p<protectedOnes.size();p++)
                {
                    String P=(String)protectedOnes.elementAt(p);
                    if(P.equalsIgnoreCase(name))
                    { protectedOne=true; break; }
                }
                if(!protectedOne)
                {
                    MOB M=getLoadPlayer(name);
                    if((M!=null)&&(!CMSecurity.isASysOp(M))&&(!CMSecurity.isAllowedAnywhere(M, "NOPURGE")))
                    {
                        obliteratePlayer(M,true);
                        Log.sysOut(thread.getName(),"AutoPurged user "+name+". Last logged in "+(CMLib.time().date2String(userLastLoginDateTime))+".");
                    }
                }
                else
                if(CMSecurity.isDebugging("AUTOPURGE"))
                    Log.debugOut(thread.getName(),name+" is protected from purging.");
            }
        }
        return true;
    }

    private void warnPrePurge(MOB mob, long timeLeft)
    {
        // check for valid recipient
        if(mob==null) return;

        if((mob.playerStats()==null)
        ||(mob.playerStats().getEmail().length()==0)) // no email addy to forward TO
            return;

        //  timeLeft is in millis
        String from="AutoPurgeWarning";
        String to=mob.Name();
        String subj=CMProps.SYSTEM_MUDNAME+" Autopurge Warning: "+to;
        String textTimeLeft="";
        if(timeLeft>(1000*60*60*24*2))
        {
            int days=(int)CMath.div((double)timeLeft,1000*60*60*24);
            textTimeLeft = days + " days";
        }
        else
        {
            int hours=(int)CMath.div((double)timeLeft,1000*60*60);
            textTimeLeft = hours + " hours";
        }
        String msg="Your character, "+to+", is going to be autopurged by the system in "+textTimeLeft+".  If you would like to keep this character active, please re-login.  This is an automated message, please do not reply.";

        SMTPLibrary.SMTPClient SC=null;
        try
        {
            if(CMProps.getVar(CMProps.SYSTEM_SMTPSERVERNAME).length()>0)
                SC=CMLib.smtp().getClient(CMProps.getVar(CMProps.SYSTEM_SMTPSERVERNAME),SMTPLibrary.DEFAULT_PORT);
            else
                SC=CMLib.smtp().getClient(mob.playerStats().getEmail());
        }
        catch(BadEmailAddressException be)
        {
            Log.errOut(thread.getName(),"Unable to notify "+to+" of impending autopurge.  Invalid email address.");
            return;
        }
        catch(java.io.IOException ioe)
        {
            return;
        }

        String replyTo="AutoPurge";
        String domain=CMProps.getVar(CMProps.SYSTEM_MUDDOMAIN).toLowerCase();
        try
        {
            SC.sendMessage(from+"@"+domain,
                           replyTo+"@"+domain,
                           mob.playerStats().getEmail(),
                           mob.playerStats().getEmail(),
                           subj,
                           CMLib.coffeeFilter().simpleOutFilter(msg));
        }
        catch(java.io.IOException ioe)
        {
            Log.errOut(thread.getName(),"Unable to notify "+to+" of impending autopurge.");
        }
    }

    public boolean activate() {
        if(thread==null)
            thread=new ThreadEngine.SupportThread("THPlayers"+Thread.currentThread().getThreadGroup().getName().charAt(0), 
                    MudHost.TIME_SAVETHREAD_SLEEP, this, CMSecurity.isDebugging("SAVETHREAD"));
        if(!thread.started)
            thread.start();
        return true;
    }
    
    public boolean shutdown() {
        playersList.clear();
        thread.shutdown();
        return true;
    }
    
    public void forceTick()
    {
        if(thread.status.equalsIgnoreCase("sleeping"))
        {
            thread.interrupt();
            return;
        }
    }

    public void run()
    {
        if((!CMSecurity.isDisabled("SAVETHREAD"))
        &&(!CMSecurity.isDisabled("PLAYERTHREAD")))
        {
            thread.status("checking player titles.");
            for(Enumeration e=players();e.hasMoreElements();)
            {
                MOB M=(MOB)e.nextElement();
                if(M.playerStats()!=null)
                {
                    if((CMLib.titles().evaluateAutoTitles(M))&&(!CMLib.flags().isInTheGame(M,true)))
                        CMLib.database().DBUpdatePlayerStatsOnly(M);
                }
            }
            autoPurge();
            if(!CMSecurity.isSaveFlag("NOPLAYERS"))
                savePlayers();
            thread.status("not saving players");
        }
    }
}