package com.planet_ink.coffee_mud.core.intermud.cm1.commands;

import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.intermud.cm1.RequestHandler;
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.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.GenericBuilder;
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.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.io.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.*;

/*
 Copyright 2010-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 GetStat extends CM1Command
{
	@Override
	public String getCommandWord()
	{
		return "GETSTAT";
	}

	public GetStat(final RequestHandler req, final String parameters)
	{
		super(req, parameters);
	}

	protected static final String[]	STATTYPES	= { "SESSION", "MOB", "CHAR", "STATE", "PHYSICAL", "BASECHAR", "MAXSTATE", "BASESTATE", "BASEPHYSICAL", "PLAYERSTATS", "ITEM", "EXIT", "ROOM", "AREA" };
	protected static final String[]	TYPESTYPE	= { "P", "MP", "MP", "MP", "MPIREA", "MP", "MP", "MP", "MPIREA", "P", "I", "E", "R", "A" };

	protected static final String[]	PHYSSTATS	= { "EFFECT" };
	protected static final String[]	PHYASTATS	= { "BEHAVIOR" };
	protected static final String[]	MOBASTATS	= { "ABILITY", "FACTION", "EXPERTISE", "FOLLOWER" };
	protected static final String[]	ITEMSTATS	= { "ITEM" };
	protected static final String[]	ROOMSTATS	= { "MOB" };

	public char getTypeCode(final Physical P)
	{
		if (P instanceof MOB)
			return ((MOB) P).isMonster() ? 'M' : 'P';
		if (P instanceof Item)
			return 'I';
		if (P instanceof Room)
			return 'R';
		if (P instanceof Exit)
			return 'E';
		if (P instanceof Area)
			return 'A';
		return ' ';
	}

	public boolean isApplicableTypeCode(final String type, final Physical P)
	{
		final char c = getTypeCode(P);
		for (int i = 0; i < STATTYPES.length; i++)
		{
			if (STATTYPES[i].equalsIgnoreCase(type))
				return TYPESTYPE[i].indexOf(c) >= 0;
		}
		return false;
	}

	public String[] getApplicableStatCodes(final Physical P)
	{
		final char c = getTypeCode(P);
		final List<String> majorCodes = new LinkedList<String>();
		for (int i = 0; i < STATTYPES.length; i++)
		{
			if (TYPESTYPE[i].indexOf(c) >= 0)
				majorCodes.add(STATTYPES[i]);
		}
		return majorCodes.toArray(new String[0]);
	}

	public Modifiable getModifiable(final String type, final Physical P)
	{
		final int x = CMParms.indexOf(STATTYPES, type.toUpperCase().trim());
		if (x < 0)
			return null;
		if (!isApplicableTypeCode(type, P))
			return null;

		switch (x)
		{
		case 0:
			return ((MOB) P).session();
		case 1:
			return P;
		case 2:
			return ((MOB) P).charStats();
		case 3:
			return ((MOB) P).curState();
		case 4:
			return P.phyStats();
		case 5:
			return ((MOB) P).baseCharStats();
		case 6:
			return ((MOB) P).maxState();
		case 7:
			return ((MOB) P).baseState();
		case 8:
			return P.basePhyStats();
		case 9:
			return ((MOB) P).playerStats();
		case 10:
			return P;
		case 11:
			return P;
		case 12:
			return P;
		case 13:
			return P;
		}
		return null;
	}

	public boolean UseGenBuilder(final Physical P, final Modifiable m)
	{
		return (P != null) && (!P.isGeneric()) && ((m instanceof MOB) || (m instanceof Item));
	}

	public String[] getStatCodes(final Physical P, final Modifiable m)
	{
		SLinkedList<String> codes;
		if (!UseGenBuilder(P, m))
			codes = new SLinkedList<String>(m.getStatCodes());
		else
		if (m instanceof MOB)
			codes = new SLinkedList<String>(CMParms.toStringArray(GenericBuilder.GenMOBCode.values()));
		else
		if (m instanceof Item)
			codes = new SLinkedList<String>(CMParms.toStringArray(GenericBuilder.GenItemCode.values()));
		else
			return new String[0];
		if (m instanceof Physical)
			codes.addAll(PHYSSTATS);
		if (m instanceof PhysicalAgent)
			codes.addAll(PHYASTATS);
		if (m instanceof MOB)
			codes.addAll(MOBASTATS);
		if (m instanceof ItemPossessor)
			codes.addAll(ITEMSTATS);
		if (m instanceof Room)
			codes.addAll(ROOMSTATS);
		return codes.toArray(new String[0]);
	}

	public boolean isAStat(final Physical P, final Modifiable m, final String stat)
	{
		if (!UseGenBuilder(P, m))
			return m.isStat(stat);
		final String[] codes = getStatCodes(P, m);
		if (codes != null)
			for (final String code : codes)
				if (code.equalsIgnoreCase(stat))
					return true;
		return false;
	}

	public boolean isAuthorized(final MOB user, final PhysicalAgent target)
	{
		if (target instanceof MOB)
		{
			if (CMLib.players().playerExists(target.Name()))
				return CMSecurity.isAllowed(user, user.location(), CMSecurity.SecFlag.CMDPLAYERS);
			return CMSecurity.isAllowed(user, user.location(), CMSecurity.SecFlag.CMDMOBS);
		}
		else
		if (target instanceof Item)
			return CMSecurity.isAllowed(user, user.location(), CMSecurity.SecFlag.CMDITEMS);
		else
		if (target instanceof Room)
			return CMSecurity.isAllowed(user, user.location(), CMSecurity.SecFlag.CMDROOMS);
		else
		if (target instanceof Exit)
			return CMSecurity.isAllowed(user, user.location(), CMSecurity.SecFlag.CMDEXITS);
		else
		if (target instanceof Area)
			return CMSecurity.isAllowed(user, user.location(), CMSecurity.SecFlag.CMDAREAS);
		else
			return false;
	}

	@Override
	public void run()
	{
		try
		{
			final PhysicalAgent P = req.getTarget();
			if (P == null)
			{
				req.sendMsg("[FAIL NO TARGET]");
				return;
			}
			if (!isAuthorized(req.getUser(), P))
			{
				req.sendMsg("[FAIL UNAUTHORIZED]");
				return;
			}
			String stat = "";
			String type = parameters.toUpperCase().trim();
			String rest = "";
			// char adjuster=' ';
			int x = parameters.indexOf(' ');
			if (x > 0)
			{
				type = parameters.substring(0, x).toUpperCase().trim();
				stat = parameters.substring(x + 1).toUpperCase().trim();
				x = stat.lastIndexOf(' ');
				if (x > 0)
				{
					rest = stat.substring(x + 1).trim();
					stat = stat.substring(0, x);
				}
				if ((stat.length() > 0) && (!Character.isLetter(stat.charAt(0))))
				{
					// adjuster=stat.charAt(0);
					stat = stat.substring(1);
				}
			}
			final Modifiable mod = getModifiable(type, P);
			if (mod == null)
			{
				req.sendMsg("[FAIL " + getHelp(req.getUser(), P, "") + "]");
				return;
			}
			if ((stat.length() == 0) || (!isAStat(P, mod, stat)))
			{
				req.sendMsg("[FAIL USAGE: GETSTAT " + type + " " + CMParms.toListString(getStatCodes(P, mod)) + "]");
				return;
			}
			if (mod instanceof Physical)
			{
				switch (CMParms.indexOf(PHYSSTATS, stat))
				{
				case -1:
					break;
				case 0:
				{
					if (rest.trim().length() == 0)
						req.sendMsg("[OK " + ((Physical) mod).numEffects() + "]");
					else
					{
						final Ability A = ((Physical) mod).fetchEffect(CMath.s_int(rest));
						if (A == null)
							req.sendMsg("[FAIL NO EFFECT " + rest + "]");
						else
							req.sendMsg("[OK " + A.ID() + " " + A.text() + "]");
					}
					return;
				}
				}
			}
			if (mod instanceof PhysicalAgent)
			{
				switch (CMParms.indexOf(PHYASTATS, stat))
				{
				case -1:
					break;
				case 0:
				{
					if (rest.trim().length() == 0)
						req.sendMsg("[OK " + ((PhysicalAgent) mod).numBehaviors() + "]");
					else
					{
						final Behavior A = ((PhysicalAgent) mod).fetchBehavior(CMath.s_int(rest));
						if (A == null)
							req.sendMsg("[FAIL NO BEHAVIOR " + rest + "]");
						else
							req.sendMsg("[OK " + A.ID() + " " + A.getParms() + "]");
					}
					return;
				}
				}
			}
			if (mod instanceof MOB)
			{
				switch (CMParms.indexOf(MOBASTATS, stat))
				{
				case -1:
					break;
				case 0:
				{
					if (rest.trim().length() == 0)
						req.sendMsg("[OK " + ((MOB) mod).numAllAbilities() + "]");
					else
					{
						final Ability A = ((MOB) mod).fetchAbility(CMath.s_int(rest));
						if (A == null)
							req.sendMsg("[FAIL NO ABILITY " + rest + "]");
						else
							req.sendMsg("[OK " + A.ID() + " " + A.proficiency() + " " + A.text() + "]");
					}
					return;
				}
				case 1:
				{
					if (rest.trim().length() == 0)
					{
						final StringBuilder factions = new StringBuilder("");
						for (final Enumeration<String> f = ((MOB) mod).factions(); f.hasMoreElements();)
							factions.append(' ').append(f);
						req.sendMsg("[OK" + factions.toString() + "]");
					}
					else
					{
						final Faction F = CMLib.factions().getFaction(rest);
						if (F == null)
							req.sendMsg("[FAIL " + rest + " NOT EXIST]");
						else
						{
							final int f = ((MOB) mod).fetchFaction(F.factionID());
							if (f == Integer.MAX_VALUE)
								req.sendMsg("[FAIL NO FACTION " + F.factionID() + "]");
							else
								req.sendMsg("[OK " + f + "]");
						}
					}
					return;
				}
				case 2:
				{
					if (rest.trim().length() == 0)
					{
						int numExpertises = 0;
						for (final Enumeration<String> i = ((MOB) mod).expertises(); i.hasMoreElements(); numExpertises++)
							i.nextElement();
						req.sendMsg("[OK " + numExpertises + "]");
					}
					else
					{
						int whichExpertise = CMath.s_int(rest);
						final Enumeration<String> i = ((MOB) mod).expertises();
						String EX = null;
						while ((whichExpertise >= 0) && (i.hasMoreElements()))
						{
							EX = i.nextElement();
							whichExpertise--;
						}
						if ((whichExpertise >= 0) || (EX == null))
							req.sendMsg("[FAIL NO EXPERTISE " + rest + "]");
						else
							req.sendMsg("[OK " + EX + "]");
					}
					return;
				}
				case 3:
				{
					if (rest.trim().length() == 0)
						req.sendMsg("[OK " + ((MOB) mod).numFollowers() + "]");
					else
					{
						final MOB M = ((MOB) mod).fetchFollower(CMath.s_int(rest));
						if (M == null)
							req.sendMsg("[FAIL NO FOLLOWER " + rest + "]");
						else
							req.sendMsg("[OK " + M.Name() + "]");
					}
					return;
				}
				}
			}
			if (mod instanceof ItemPossessor)
			{
				switch (CMParms.indexOf(ITEMSTATS, stat))
				{
				case -1:
					break;
				case 0:
				{
					if (rest.trim().length() == 0)
						req.sendMsg("[OK " + ((ItemPossessor) mod).numItems() + "]");
					else
					{
						final Item I = ((ItemPossessor) mod).getItem(CMath.s_int(rest));
						if (I == null)
							req.sendMsg("[FAIL NO ITEM " + rest + "]");
						else
							req.sendMsg("[OK " + I.Name() + "]");
					}
					return;
				}
				}
			}
			if (mod instanceof Room)
			{
				switch (CMParms.indexOf(ROOMSTATS, stat))
				{
				case -1:
					break;
				case 0:
				{
					if (rest.trim().length() == 0)
						req.sendMsg("[OK " + ((Room) mod).numInhabitants() + "]");
					else
					{
						final MOB M = ((Room) mod).fetchInhabitant(CMath.s_int(rest));
						if (M == null)
							req.sendMsg("[FAIL NO MOB " + rest + "]");
						else
							req.sendMsg("[OK " + M.Name() + "]");
					}
					return;
				}
				}
			}
			if (!UseGenBuilder(P, mod))
			{
				req.sendMsg("[OK " + mod.getStat(stat) + "]");
			}
			else
			{
				final String[] codes = this.getStatCodes(P, mod);
				for (final String code : codes)
				{
					if (code.equalsIgnoreCase(stat))
					{
						if (P instanceof MOB)
							req.sendMsg("[OK " + CMLib.coffeeMaker().getGenMobStat((MOB) P, stat) + "]");
						else
						if (P instanceof Item)
							req.sendMsg("[OK " + CMLib.coffeeMaker().getGenItemStat((Item) P, stat) + "]");
					}
				}
			}
		}
		catch (final Exception ioe)
		{
			Log.errOut(className, ioe);
			req.close();
		}
	}

	@Override
	public boolean passesSecurityCheck(final MOB user, final PhysicalAgent target)
	{
		return (user != null);
	}

	@Override
	public String getHelp(final MOB user, final PhysicalAgent target, String rest)
	{
		Modifiable mod = null;
		if ((rest != null) && (rest.trim().length() > 0))
		{
			final int x = rest.indexOf(' ');
			if (x > 0)
				rest = rest.substring(0, x).toUpperCase().trim();
			if (isApplicableTypeCode(rest, target))
				mod = getModifiable(rest.toUpperCase().trim(), target);
		}
		if (mod == null)
			return "USAGE: " + getCommandWord() + " " + CMParms.toListString(getApplicableStatCodes(target));
		else
		if (rest != null)
			return "USAGE: " + getCommandWord() + " " + rest.toUpperCase().trim() + " " + CMParms.toListString(getStatCodes(target, mod));
		else
			return "USAGE: " + getCommandWord() + " " + CMParms.toListString(getStatCodes(target, mod));
	}
}
