package com.planet_ink.coffee_mud.Libraries;
import java.util.*;
import java.util.concurrent.TimeUnit;

import com.planet_ink.coffee_mud.Areas.interfaces.Area;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.CatalogLibrary.CataData;
import com.planet_ink.coffee_mud.MOBS.interfaces.MOB;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMLib.Library;
import com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.core.interfaces.*;

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

	private volatile long nextSweepTime = System.currentTimeMillis();

	public final SLinkedList<Session> all=new SLinkedList<Session>();

	private final static Filterer<Session> localOnlineFilter=new Filterer<Session>()
	{
		@Override
		public boolean passesFilter(final Session obj)
		{
			if((obj!=null) && (!obj.isStopped()) && (((obj.getStatus())==Session.SessionStatus.MAINLOOP)))
			{
				final MOB M=obj.mob();
				return ((M!=null)&&M.amActive()&&(CMLib.flags().isInTheGame(M,true)));
			}
			return false;
		}
	};

	@Override
	public Iterator<Session> sessions()
	{
		return all.iterator();
	}

	@Override
	public Iterable<Session> allIterable()
	{
		return all;
	}

	@Override
	public Iterator<Session> localOnline()
	{
		return new FilteredIterator<Session>(all.iterator(),localOnlineFilter);
	}

	@Override
	public Iterable<Session> localOnlineIterable()
	{
		return new FilteredIterable<Session>(all,localOnlineFilter);
	}

	@Override
	public int numSessions()
	{
		return all.size();
	}

	@Override
	public Iterable<Session> allIterableAllHosts()
	{
		final MultiIterable<Session> allIterable=new MultiIterable<Session>();
		final WorldMap map=CMLib.map();
		//allIterable.add(CMLib.sessions().allIterable(), CMLib.sessions().numSessions());
		for(final Enumeration<CMLibrary> pl=CMLib.libraries(CMLib.Library.SESSIONS); pl.hasMoreElements(); )
		{
			final SessionsList list=(SessionsList)pl.nextElement();
			if(map == CMLib.library(CMLib.getLibraryThreadID(Library.SESSIONS, list), Library.MAP))
				allIterable.add(list.allIterable(),list.numSessions());
		}
		return allIterable;
	}

	@Override
	public int numLocalOnline()
	{
		return getCount(localOnline());
	}

	protected int getCount(final Iterator<Session> i)
	{
		int xt=0;
		for(;i.hasNext();)
		{
			i.next();
			xt++;
		}
		return xt;
	}

	protected Session getAllSessionAt(final Iterator<Session> i, final int index)
	{
		int xt=0;
		Session S;
		for(;i.hasNext();)
		{
			S=i.next();
			if(xt==index)
				return S;
			xt++;
		}
		return null;
	}

	@Override
	public synchronized void add(final Session s)
	{
		if(!all.contains(s))
			all.add(s);
	}

	@Override
	public synchronized void remove(final Session s)
	{
		all.remove(s);
	}

	@Override
	public synchronized boolean isSession(final Session s)
	{
		return all.contains(s);
	}

	@Override
	public void stopSessionAtAllCosts(final Session S)
	{
		if(S==null)
			return;
		S.stopSession(true,true,false);
		CMLib.s_sleep(100);
		if(all.contains(S))
		{
			CMLib.s_sleep(100);
			S.run();
			CMLib.s_sleep(100);
			S.stopSession(true,true,false);
			CMLib.s_sleep(100);
			if(all.contains(S))
			{
				S.stopSession(true,true,true);
				remove(S);
			}
		}
	}

	protected void sessionCheck()
	{
		setThreadStatus(serviceClient,"checking player sessions.");
		for(final Session S : all)
		{
			final long time=System.currentTimeMillis()-S.getInputLoopTime();
			if(time>0)
			{
				if((S.mob()!=null)||((S.getStatus())==Session.SessionStatus.ACCOUNT_MENU)||((S.getStatus())==Session.SessionStatus.CHARCREATE))
				{
					long check=60000;

					if((S.getPreviousCMD()!=null)
					&&(S.getPreviousCMD().size()>0)
					&&(S.getPreviousCMD().get(0).equalsIgnoreCase("IMPORT")
					   ||S.getPreviousCMD().get(0).equalsIgnoreCase("EXPORT")
					   ||S.getPreviousCMD().get(0).equalsIgnoreCase("CHARGEN")
					   ||S.getPreviousCMD().get(0).equalsIgnoreCase("MERGE")))
						check=check*600;
					else
					if((S.mob()!=null)&&(CMSecurity.isAllowed(S.mob(),S.mob().location(),CMSecurity.SecFlag.CMDROOMS)))
						check=check*15;
					else
					if((S.getStatus())==Session.SessionStatus.LOGIN)
						check=check*5;

					if(time>(check*10))
					{
						final String roomID=S.mob()!=null?CMLib.map().getExtendedRoomID(S.mob().location()):"";
						if((S.getPreviousCMD()==null)
						||(S.getPreviousCMD().size()==0)
						||(S.getStatus()==Session.SessionStatus.LOGIN)
						||(S.getStatus()==Session.SessionStatus.ACCOUNT_MENU)
						||(S.getStatus()==Session.SessionStatus.HANDSHAKE_MCCP)
						||(S.getStatus()==Session.SessionStatus.HANDSHAKE_OPEN)
						||(S.getStatus()==Session.SessionStatus.CHARCREATE))
							Log.sysOut(serviceClient.getName(),"Kicking out: "+((S.mob()==null)?"Unknown":S.mob().Name())+", idle: "+CMLib.time().date2EllapsedTime(time, TimeUnit.MILLISECONDS, true)+", status: "+S.getStatus());
						else
						{
							Log.errOut(serviceClient.getName(),"KILLING DEAD Session: "+((S.mob()==null)?"Unknown":S.mob().Name())+" ("+roomID+"), out for "+CMLib.time().date2EllapsedTime(time, TimeUnit.MILLISECONDS, true));
							Log.errOut(serviceClient.getName(),"STATUS  was :"+S.getStatus()+", LASTCMD was :"+((S.getPreviousCMD()!=null)?S.getPreviousCMD().toString():""));
							if(S instanceof Thread)
								CMLib.threads().debugDumpStack("Sessions",(Thread)S);
						}
						setThreadStatus(serviceClient,"killing session ");
						stopSessionAtAllCosts(S);
						setThreadStatus(serviceClient,"checking player sessions.");
					}
					else
					if(time>check)
					{
						if((S.mob()==null)||(S.mob().Name()==null)||(S.mob().Name().length()==0))
							stopSessionAtAllCosts(S);
						else
						if((S.getPreviousCMD()!=null)&&(S.getPreviousCMD().size()>0))
						{
							final String roomID=S.mob()!=null?CMLib.map().getExtendedRoomID(S.mob().location()):"";
							final String statusMsg;
							if(((S.getStatus())!=Session.SessionStatus.LOGIN)
							||((S.getPreviousCMD()!=null)&&(S.getPreviousCMD().size()>0)))
								statusMsg = "STATUS  is :"+S.getStatus()+", LASTCMD was :"+((S.getPreviousCMD()!=null)?S.getPreviousCMD().toString():"");
							else
								statusMsg = "STATUS  is :"+S.getStatus()+", no last command available.";
							if((S.isLockedUpWriting())
							&&(CMLib.flags().isInTheGame(S.mob(),true)))
							{
								Log.errOut(serviceClient.getName(),"LOGGED OFF Session: "+((S.mob()==null)?"Unknown":S.mob().Name())+" ("+roomID+"), out for "+CMLib.time().date2EllapsedTime(time, TimeUnit.MILLISECONDS, true)+": "+S.isLockedUpWriting()+"\r\n"+statusMsg);
								stopSessionAtAllCosts(S);
							}
							else
								Log.warnOut(serviceClient.getName(),"Suspect Session: "+((S.mob()==null)?"Unknown":S.mob().Name())+" ("+roomID+"), out for "+CMLib.time().date2EllapsedTime(time, TimeUnit.MILLISECONDS, true)+"\r\n"+statusMsg);
						}
					}
				}
				else
				if(time>(300000))
				{
					final String roomID=S.mob()!=null?CMLib.map().getExtendedRoomID(S.mob().location()):"";
					if((S.getStatus()==Session.SessionStatus.LOGIN)
					||(S.getStatus()==Session.SessionStatus.HANDSHAKE_MCCP)
					||(S.getStatus()==Session.SessionStatus.HANDSHAKE_OPEN))
						Log.sysOut(serviceClient.getName(),"Kicking out login session after "+CMLib.time().date2EllapsedTime(time, TimeUnit.MILLISECONDS, true)+".");
					else
					{
						Log.errOut(serviceClient.getName(),"KILLING DEAD Session: "+((S.mob()==null)?"Unknown":S.mob().Name())+" ("+roomID+"), out for "+CMLib.time().date2EllapsedTime(time, TimeUnit.MILLISECONDS, true));
						if(S instanceof Thread)
							CMLib.threads().debugDumpStack("Sessions",(Thread)S);
					}
					if(S.getStatus()!=Session.SessionStatus.HANDSHAKE_MCCP)
					{
						if(((S.getStatus())!=Session.SessionStatus.LOGIN)
						||((S.getPreviousCMD()!=null)&&(S.getPreviousCMD().size()>0)))
							Log.errOut(serviceClient.getName(),"STATUS was :"+S.getStatus()+", LASTCMD was :"+((S.getPreviousCMD()!=null)?S.getPreviousCMD().toString():""));
					}
					setThreadStatus(serviceClient,"killing session ");
					stopSessionAtAllCosts(S);
					setThreadStatus(serviceClient,"checking player sessions");
				}
			}
		}
	}

	@Override
	public boolean activate()
	{
		nextSweepTime = System.currentTimeMillis()+MudHost.TIME_UTILTHREAD_SLEEP;
		if(serviceClient==null)
		{
			name="THSessions"+Thread.currentThread().getThreadGroup().getName().charAt(0);
			serviceClient=CMLib.threads().startTickDown(this, Tickable.TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK, 100, 1);
		}
		return true;
	}

	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		tickStatus=Tickable.STATUS_ALIVE;
		try
		{
			final double numThreads=all.size();
			if(numThreads>0.0)
			{
				for(final Session S : all)
				{
					if(!S.isRunning())
					{
						CMLib.threads().executeRunnable(S.getGroupName(), S);
					}
				}
			}
			if(System.currentTimeMillis() >= nextSweepTime)
			{
				nextSweepTime = System.currentTimeMillis()+MudHost.TIME_UTILTHREAD_SLEEP;
				if((!CMSecurity.isDisabled(CMSecurity.DisFlag.UTILITHREAD))
				&&(!CMSecurity.isDisabled(CMSecurity.DisFlag.SESSIONTHREAD)))
				{
					isDebugging=CMSecurity.isDebugging(DbgFlag.UTILITHREAD);
					sessionCheck();
				}
			}
		}
		finally
		{
			tickStatus=Tickable.STATUS_NOT;
			setThreadStatus(serviceClient,"sleeping");
		}
		return true;
	}

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

	@Override
	public MOB findPlayerOnline(final String srchStr, final boolean exactOnly)
	{
		final Session S=findPlayerSessionOnline(srchStr, exactOnly);
		if(S==null)
			return null;
		return S.mob();
	}

	@Override
	public Session findPlayerSessionOnline(final String srchStr, final boolean exactOnly)
	{
		// then look for players
		for(final Session S : localOnlineIterable())
		{
			if(S.mob().Name().equalsIgnoreCase(srchStr))
				return S;
		}
		for(final Session S : localOnlineIterable())
		{
			if(S.mob().name().equalsIgnoreCase(srchStr))
				return S;
		}
		// keep looking for players
		if(!exactOnly)
		{
			for(final Session S : localOnlineIterable())
			{
				if(CMLib.english().containsString(S.mob().Name(),srchStr))
					return S;
			}
			for(final Session S : localOnlineIterable())
				if(CMLib.english().containsString(S.mob().name(),srchStr))
					return S;
		}
		return null;
	}

	@Override
	public void moveSessionToCorrectThreadGroup(final Session session, final int theme)
	{
		final int themeDex=CMath.firstBitSetIndex(theme);
		if((themeDex>=0)&&(themeDex<Area.THEME_BIT_NAMES.length))
		{
			final ThreadGroup privateGroup=CMProps.getPrivateOwner(Area.THEME_BIT_NAMES[themeDex]+"PLAYERS");
			if((privateGroup!=null)
			&&(privateGroup.getName().length()>0)
			&&(!privateGroup.getName().equals(session.getGroupName())))
			{
				if(session.getGroupName().length()>0)
				{
					if(CMLib.library(session.getGroupName().charAt(0), CMLib.Library.SESSIONS)
					!= CMLib.library(privateGroup.getName().charAt(0), CMLib.Library.SESSIONS))
					{
						((Sessions)CMLib.library(session.getGroupName().charAt(0), CMLib.Library.SESSIONS)).remove(session);
						((Sessions)CMLib.library(privateGroup.getName().charAt(0), CMLib.Library.SESSIONS)).add(session);
					}
				}
				session.setGroupName(privateGroup.getName());
			}
		}
	}

}
