package com.planet_ink.coffee_mud.Items.BasicTech;
import com.planet_ink.coffee_mud.Items.Basic.StdItem;
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.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.Technical.TechType;
import com.planet_ink.coffee_mud.Libraries.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 2013-2022 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 StdElecWeapon extends StdElecItem implements Weapon, Electronics
{
	@Override
	public String ID()
	{
		return "StdElecWeapon";
	}

	protected int		weaponDamageType		= Weapon.TYPE_SHOOT;
	protected int		weaponClassification	= Weapon.CLASS_RANGED;
	protected boolean	useExtendedMissString	= false;
	protected int		minRange				= 0;
	protected int		maxRange				= 10;
	protected ModeType	mode 					= ModeType.NORMAL;
	protected ModeType[]modeTypes 				= new ModeType[]{ ModeType.NORMAL };

	protected enum ModeType
	{
		STUN,
		NORMAL,
		KILL,
		DISINTEGRATE,
		MAIM,
		DISRUPT,
		LASER,
		SONIC
	}

	public StdElecWeapon()
	{
		super();

		setName("a tech gun");
		setDisplayText("a tech gun sits here.");
		setDescription("You can't tell what it is by looking at it.");

		setRawLogicalAnd(false);
		setRawProperLocationBitmap(Wearable.WORN_HELD|Wearable.WORN_WIELD);
		basePhyStats().setAttackAdjustment(0);
		basePhyStats().setDamage(20);
		basePhyStats().setLevel(1);
		setBaseValue(1500);
		setRanges(0,10);
		setMaterial(RawMaterial.RESOURCE_STEEL);
		activate(true);
		super.setPowerCapacity(1000);
		super.setPowerRemaining(1000);
		recoverPhyStats();
	}

	// this method is the reason not to make the types an editable field; the
	// murder-motel-like interaction between shields and weapons is finely
	// balanced based on what this does.
	@Override
	public int weaponDamageType()
	{
		switch(mode)
		{
		case LASER:
			return Weapon.TYPE_LASERING;
		case SONIC:
			return Weapon.TYPE_SONICING;
		case DISRUPT:
		case DISINTEGRATE:
			return Weapon.TYPE_MELTING;
		case STUN:
			return Weapon.TYPE_SONICING;
		case MAIM:
			return Weapon.TYPE_SLASHING;
		default:
			return weaponDamageType;
		}
	}

	protected ModeType getState(String s)
	{
		s=s.toUpperCase().trim();
		for(final ModeType type : modeTypes)
		{
			if(type.toString().startsWith(s))
				return type;
		}
		return null;
	}

	protected String getStateName()
	{
		return mode.name().toLowerCase();
	}

	@Override
	public void setMiscText(final String text)
	{
		miscText=text;
		if(CMath.isInteger(text))
		{
			final int x=CMath.s_int(text);
			if((x>=0)&&(x<modeTypes.length))
				mode=modeTypes[x];
		}
		else
		{
			final ModeType t=(ModeType)CMath.s_valueOf(ModeType.class,text.toUpperCase().trim());
			if((t != null)&&(CMParms.indexOf(modeTypes, t)>=0))
				mode=t;
		}
	}

	@Override
	public String text()
	{
		return mode.toString();
	}

	@Override
	public void recoverPhyStats()
	{
		super.recoverPhyStats();
		if(mode!=null)
		switch(mode)
		{
		case STUN:
			phyStats().setDamage(1);
			break;
		case NORMAL:
		case MAIM:
		case KILL:
		case LASER:
		case SONIC:
			break;
		case DISINTEGRATE:
		case DISRUPT:
			phyStats().setDamage(1+(phyStats().damage()*2));
			break;
		}
	}

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

	@Override
	public void setWeaponDamageType(final int newType)
	{
		weaponDamageType = newType;
	}

	@Override
	public void setWeaponClassification(final int newClassification)
	{
		weaponClassification = newClassification;
	}

	@Override
	public TechType getTechType()
	{
		return TechType.PERSONAL_WEAPON;
	}

	@Override
	public String secretIdentity()
	{
		return super.secretIdentity()+"\n\rAttack: "+phyStats().attackAdjustment()+", Damage: "+phyStats().damage();
	}

	@Override
	public void affectPhyStats(final Physical affected, final PhyStats affectableStats)
	{
		super.affectPhyStats(affected,affectableStats);
		if(amWearingAt(Wearable.WORN_WIELD) && activated())
		{
			if(phyStats().attackAdjustment()!=0)
				affectableStats.setAttackAdjustment(affectableStats.attackAdjustment()+phyStats().attackAdjustment());
			if(phyStats().damage()!=0)
				affectableStats.setDamage(affectableStats.damage()+phyStats().damage());
		}
	}

	@Override
	public void executeMsg(final Environmental myHost, final CMMsg msg)
	{
		if(msg.amITarget(this))
		{
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_EXAMINE:
				if(CMLib.flags().canBeSeenBy(this, msg.source()))
				{
					super.executeMsg(myHost,msg);
					msg.source().tell(L("@x1 is currently @x2 and is at @x3% power.",name(),(activated()?"activated":"deactivated"),""+Math.round(CMath.div(powerRemaining(),powerCapacity())*100.0)));
					if(CMLib.flags().canBeSeenBy(this, msg.source())&&(activated()))
						msg.source().tell(L("@x1 is currently set to @x2.",name(),getStateName()));
				}
				break;
			case CMMsg.TYP_ACTIVATE:
			{
				super.executeMsg(myHost,msg);
				if((msg.source().location()!=null)&&(!CMath.bset(msg.targetMajor(), CMMsg.MASK_CNTRLMSG)))
					msg.source().location().show(msg.source(), this, CMMsg.MSG_OK_VISUAL, L("<S-NAME> activate(s) <T-NAME>."));
				this.activate(true);
				if((msg.source().location()!=null)&&(!CMath.bset(msg.targetMajor(), CMMsg.MASK_CNTRLMSG)))
				{
					ModeType newState=mode;
					if((msg.targetMessage()!=null)&&(msg.targetMessage().length()>0))
					{
						final List<String> V=CMParms.parse(msg.targetMessage());
						if(V.size()>0)
						{
							final String s=V.get(0).toUpperCase().trim();
							newState=this.getState(s);
							if(newState==null)
								newState=mode;
						}
					}
					mode=newState;
					msg.source().location().show(msg.source(), this, CMMsg.MSG_OK_VISUAL, L("<S-NAME> set(s) <T-NAME> on @x1.",this.getStateName()));
					recoverPhyStats();
					msg.source().recoverPhyStats();
				}
				this.activate(true);
				break;
			}
			case CMMsg.TYP_DEACTIVATE:
			{
				super.executeMsg(myHost,msg);
				if((msg.source().location()!=null)&&(!CMath.bset(msg.targetMajor(), CMMsg.MASK_CNTRLMSG)))
					msg.source().location().show(msg.source(), this, CMMsg.MSG_OK_VISUAL, L("<S-NAME> deactivate(s) <T-NAME>."));
				this.activate(false);
				break;
			}
			default:
				super.executeMsg(myHost,msg);
				break;
			}
		}
		else
		if((owner() instanceof MOB) && msg.amISource((MOB)owner()) && (!amWearingAt(Wearable.IN_INVENTORY)))
		{
			super.executeMsg(myHost,msg);
			final MOB mob=(MOB)owner();
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_WEAPONATTACK:
				if((msg.tool()==this) && (weaponClassification()==Weapon.CLASS_THROWN))
					msg.addTrailerMsg(CMClass.getMsg(msg.source(),this,CMMsg.MSG_EXPIRE|CMMsg.MASK_ALWAYS,null));
				break;
			case CMMsg.TYP_DAMAGE:
				if(msg.tool() ==this)
				{
					switch(mode)
					{
					case STUN: {
						if(msg.value()>0)
						{
							final Ability A=CMClass.getAbility("CombatSleep");
							if((A!=null)&&(msg.target() instanceof Physical))
								A.invoke(mob, (Physical)msg.target(), true, 0);
							else
							if((A==null)&&(msg.target() instanceof MOB))
							{
								msg.source().location().show((MOB)msg.target(), null, CMMsg.MSG_OK_VISUAL, L("<S-NAME> go(es) unconscious!"));
								((MOB)msg.target()).basePhyStats().setDisposition(((MOB)msg.target()).basePhyStats().disposition()|PhyStats.IS_SLEEPING);
								((MOB)msg.target()).phyStats().setDisposition(((MOB)msg.target()).phyStats().disposition()|PhyStats.IS_SLEEPING);
							}
							if(mob.getVictim()==msg.source())
								mob.makePeace(true);
							if(msg.source().getVictim()==mob)
								msg.source().makePeace(true);
							msg.setValue(0);
						}
						break;
					}
					case NORMAL:
					case KILL:
					case LASER:
					case SONIC:
					{
						// this is normal...
						break;
					}
					case DISINTEGRATE:
					{
						final Environmental targ=msg.target();
						if((msg.value()>(basePhyStats().damage()*1.5))
						&&((!(targ instanceof MOB))||(msg.value()>=((MOB)targ).curState().getHitPoints())))
						{
							final Room R=CMLib.map().roomLocation(targ);
							if(R!=null)
							{
								if(targ instanceof MOB)
								{
									if((!((MOB)targ).amDead())||(((MOB)targ).curState().getHitPoints()>0))
										CMLib.combat().postDeath(msg.source(), (MOB)targ, null);
									((MOB)targ).location().show(mob,targ,CMMsg.MSG_OK_ACTION,L("<T-NAME> disintegrate(s)!"));
									if(((MOB)targ).amDead())
									{
										DeadBody corpseI=null;
										for(int i=0;i<R.numItems();i++)
										{
											final Item I=R.getItem(i);
											if((I instanceof DeadBody)
											&&(I.container()==null)
											&&(((DeadBody)I).getMobName().equals(targ.Name()))
											&&(!((DeadBody)I).isPlayerCorpse()))
											{
												corpseI=(DeadBody)I;
												break;
											}
										}
										if(corpseI!=null)
										{
											corpseI.destroy();
										}
									}
								}
								else
								if((targ instanceof Item)
								&&((!(targ instanceof DeadBody)||(!((DeadBody)targ).isPlayerCorpse()))))
								{
									((MOB)targ).location().show(mob,targ,CMMsg.MSG_OK_ACTION,L("^S<S-NAME> fire(s) <O-NAME> at <T-NAME> and it disintegrates!^?"));
									((Item)targ).destroy();
								}
								R.recoverRoomStats();
							}
						}
						break;
					}
					case MAIM:
					{
						if(msg.value()>0)
						{
							final Ability A=CMClass.getAbility("Amputation");
							if((A!=null)&&(msg.target() instanceof Physical))
								A.invoke(mob, (Physical)msg.target(), true, 0);
						}
						break;
					}
					case DISRUPT:
					{
						final Environmental targ=msg.target();
						if((msg.value()>(basePhyStats().damage()*1.5))
						&&((!(targ instanceof MOB))||(msg.value()>=((MOB)targ).curState().getHitPoints())))
						{
							final Room R=CMLib.map().roomLocation(targ);
							if(R!=null)
							{
								if(targ instanceof MOB)
								{
									if((!((MOB)targ).amDead())||(((MOB)targ).curState().getHitPoints()>0))
										CMLib.combat().postDeath(msg.source(), (MOB)targ, msg);
								}
								else
								if((targ instanceof Item)
								&&((!(targ instanceof DeadBody)||(!((DeadBody)targ).isPlayerCorpse()))))
								{
									((MOB)targ).location().show(mob,targ,CMMsg.MSG_OK_ACTION,L("^S<S-NAME> fire(s) <O-NAME> at <T-NAME> and it explodes!^?"));
									((Item)targ).destroy();
								}
								R.recoverRoomStats();
							}
						}
						break;
					}
					}
				}
				break;
			default:
				break;
			}
		}
	}

	@Override
	public boolean okMessage(final Environmental myHost, final CMMsg msg)
	{
		if(!super.okMessage(myHost, msg))
			return false;
		if((owner() instanceof MOB) && msg.amISource((MOB)owner()) && (!amWearingAt(Wearable.IN_INVENTORY)))
		{
			final MOB mob=(MOB)owner();
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_ACTIVATE:
				if((msg.source().location()!=null)&&(!CMath.bset(msg.targetMajor(), CMMsg.MASK_CNTRLMSG)))
				{
					if((msg.targetMessage()!=null)&&(msg.targetMessage().length()>0))
					{
						final List<String> V=CMParms.parse(msg.targetMessage());
						if(V.size()>0)
						{
							final String s=V.get(0).toUpperCase().trim();
							final ModeType newState=this.getState(s);
							if(newState==null)
							{
								msg.source().tell(L("'@x1' is an unknown setting on @x2",s,name(msg.source())));
								return false;
							}
						}
					}
				}
				break;
			case CMMsg.TYP_WEAPONATTACK:
				if(msg.tool() ==this)
				{
					if((powerRemaining()<=0)||(!activated))
					{
						String msgStr;
						if(weaponClassification()==Weapon.CLASS_RANGED)
							msgStr="<S-YOUPOSS> <T-NAMENOART> goes *click* and does not fire.";
						else
						if(activated())
							msgStr="<S-YOUPOSS> <T-NAMENOART> seems to be out of power.";
						else
							msgStr="<S-YOUPOSS> <T-NAMENOART> seems to be turned off.";
						final CMMsg msg2=CMClass.getMsg(mob, this, null,CMMsg.MSG_OK_VISUAL,msgStr);
						if((mob.location()!=null)&&(mob.location().okMessage(myHost, msg2)))
							mob.location().send(mob, msg2);
						return false;
					}
				}
				break;
			case CMMsg.TYP_DAMAGE: // remember 50% miss rate
				if(msg.tool() ==this)
				{
					double successFactor=0.5;
					final Manufacturer m=getFinalManufacturer();
					successFactor=m.getReliabilityPct()*successFactor;
					final long powerConsumed=Math.round(phyStats().damage()*Math.max(.33, Math.abs(2.0-m.getEfficiencyPct())));
					if(powerRemaining()>=powerConsumed)
					{
						setPowerRemaining(powerRemaining()-powerConsumed);
						if(msg.value()>0)
							msg.setValue((int)Math.round(successFactor*msg.value()));
					}
					else
					{
						setPowerRemaining(0);
					}
					if(mode == ModeType.STUN)
					{
						if(msg.value()>0)
							msg.setValue(1);
					}
				}
				break;
			}
		}
		return true;
	}

	@Override
	public void setUsesRemaining(int newUses)
	{
		if(newUses==Integer.MAX_VALUE)
			newUses=100;
		super.setUsesRemaining(newUses);
	}

	@Override
	public String missString()
	{
		return CMLib.combat().standardMissString(weaponDamageType,weaponClassification,name(),useExtendedMissString);
	}

	@Override
	public String hitString(final int damageAmount)
	{
		return CMLib.combat().standardHitString(weaponDamageType, weaponClassification,damageAmount,name());
	}

	@Override
	public int minRange()
	{
		if(CMath.bset(phyStats().sensesMask(),PhyStats.SENSE_ITEMNOMINRANGE))
			return 0;
		return minRange;
	}

	@Override
	public int maxRange()
	{
		if(CMath.bset(phyStats().sensesMask(),PhyStats.SENSE_ITEMNOMAXRANGE))
			return 100;
		return maxRange;
	}

	@Override
	public void setRanges(final int min, final int max)
	{
		minRange = min;
		maxRange = max;
	}

	@Override
	public int[] getRanges()
	{
		return new int[]{minRange, maxRange};
	}

	@Override
	public boolean subjectToWearAndTear()
	{
		return false;
	}
}
