package com.planet_ink.coffee_mud.Items.BasicTech;
import com.planet_ink.coffee_mud.Items.Basic.StdBoardable;
import com.planet_ink.coffee_mud.Items.Basic.StdPortal;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.interfaces.ItemPossessor.Expire;
import com.planet_ink.coffee_mud.core.interfaces.ItemPossessor.Move;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.core.exceptions.CMException;
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.Items.interfaces.TechComponent.ShipDir;
import com.planet_ink.coffee_mud.Items.interfaces.Technical.TechCommand;
import com.planet_ink.coffee_mud.Items.interfaces.Technical.TechType;
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 com.planet_ink.coffee_mud.Libraries.interfaces.*;

/*
   Copyright 2013-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 GenSpaceShip extends StdBoardable implements Electronics, SpaceShip
{
	@Override
	public String ID()
	{
		return "GenSpaceShip";
	}

	protected Manufacturer	cachedManufact  = null;
	protected String	 	manufacturer	= "RANDOM";
	public long[]   		coordinates 	= new long[3];
	public double[] 		direction   	= new double[2];
	public double			roll			= 0.0;
	public double 			speed			= 0;
	protected SpaceObject	spaceTarget 	= null;
	protected double[]		facing			= new double[2];
	protected Set<ShipFlag>	shipFlags		= new SHashSet<ShipFlag>();
	protected volatile double	speedTick	= 0;

	public GenSpaceShip()
	{
		super();
		setName("the space ship [NEWNAME]");
		setDisplayText("the space ship [NEWNAME] is here.");
		setMaterial(RawMaterial.RESOURCE_STEEL);
		this.doorName="hatch";
	}

	@Override
	public boolean isGeneric()
	{
		return true;
	}

	@Override
	public void recoverPhyStats()
	{
		super.recoverPhyStats();
	}

	@Override
	protected String getAreaClassType()
	{
		return "StdSpaceShip";
	}


	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		if(!super.tick(ticking, tickID))
			return false;
		if(tickID==Tickable.TICKID_AREA) // ticking from the area
		{
			this.speedTick = 0.0;
		}
		return true;
	}

	@Override
	protected Room createFirstRoom()
	{
		final Room R=CMClass.getLocale("MetalRoom");
		R.setDisplayText(L("The Cockpit"));
		return R;
	}

	@Override
	public Area getShipArea()
	{
		if((!destroyed)&&(area==null))
		{
			final Area area=super.getShipArea();
			if(area != null)
				area.setTheme(Area.THEME_TECHNOLOGY);
			return area;
		}
		return super.getShipArea();
	}

	@Override
	public void dockHere(final Room R)
	{
		if((CMSecurity.isDebugging(DbgFlag.SPACESHIP))&&(getIsDocked()==null))
			Log.debugOut("SpaceShip "+name()+" is docking at '"+R.displayText()+"' ("+R.roomID()+")");
		if(R instanceof LocationRoom)
			setCoords(CMLib.map().moveSpaceObject(((LocationRoom)R).coordinates(), ((LocationRoom)R).getDirectionFromCore(), radius()));
		CMLib.map().delObjectInSpace(getShipSpaceObject());
		setSpeed(0);
		super.dockHere(R);
	}

	@Override
	public Room unDock(final boolean moveToOutside)
	{
		if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
			Log.debugOut("SpaceShip "+name()+" is undocking"+(moveToOutside?" into space":""));
		final Room R=getIsDocked();
		final Room exitRoom = super.unDock(moveToOutside);
		if(R instanceof LocationRoom)
		{
			setDirection(Arrays.copyOf(((LocationRoom)R).getDirectionFromCore(),2));
			direction()[0]+=CMLib.dice().plusOrMinus(Math.PI/10.0);
			if(direction[0] > 2*Math.PI)
				direction[0] -= 2*Math.PI;
			if(direction()[0]<0)
				direction()[0]+=(Math.PI*2);
			direction()[1]+=CMath.abs(CMLib.dice().plusOrMinus(Math.PI/10.0));
			if(direction[1] > Math.PI)
				direction[1] -= Math.PI;
			setFacing(Arrays.copyOf(direction(),2));
		}
		if(moveToOutside)
		{
			final SpaceObject o = getShipSpaceObject();
			final SpaceObject planetO = CMLib.map().getSpaceObject(R, true);
			final long[] newCoordinates = CMLib.map().moveSpaceObject(((LocationRoom)R).coordinates(), direction(), radius()+radius());
			if((o != null)&&(R instanceof LocationRoom))
			{
				CMLib.map().addObjectToSpace(o,newCoordinates);
				final double gravity = CMLib.tech().getGravityForce(o, planetO);
				setShipFlag(SpaceShip.ShipFlag.IN_THE_AIR,(gravity > 0.0));
			}
		}
		return exitRoom;
	}

	@Override
	public void renameShip(final String newName)
	{
		final Area area=this.getShipArea();
		if(area instanceof BoardableShip)
		{
			final String oldName=area.Name();
			String registryNum=area.getBlurbFlag("REGISTRY");
			if(registryNum==null)
				registryNum="";
			super.renameShip(newName);
			CMLib.tech().unregisterElectronics(null, oldName+registryNum);
			registryNum=Double.toString(Math.random());
			area.addBlurbFlag("REGISTRY Registry#"+registryNum.substring(registryNum.indexOf('.')+1));
		}
	}

	@Override
	public boolean okMessage(final Environmental myHost, final CMMsg msg)
	{
		if(msg.amITarget(this))
		{
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_DAMAGE:
			{
				final double myMass=getMass();
				final double hardness = RawMaterial.CODES.HARDNESS(material()) * SpaceObject.Distance.Kilometer.dm;
				msg.setValue((int)Math.round((usesRemaining() * (msg.value() / myMass)) / hardness));
				if(!okAreaMessage(msg,false))
					return false;
				return true;
			}
			}
		}
		return super.okMessage(myHost, msg);
	}

	@Override
	public void executeMsg(final Environmental myHost, final CMMsg msg)
	{
		super.executeMsg(myHost,msg);

		if(msg.amITarget(this))
		{
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_ACTIVATE:
				if((CMath.bset(msg.targetMajor(), CMMsg.MASK_CNTRLMSG))&&(msg.targetMessage()!=null))
				{
					final String[] parts=msg.targetMessage().split(" ");
					final TechCommand command=TechCommand.findCommand(parts);
					if(command!=null)
					{
						final Object[] parms=command.confirmAndTranslate(parts);
						if(parms!=null)
						{
							if(command==Technical.TechCommand.ACCELERATION)
							{
								final TechComponent.ShipDir dir=(TechComponent.ShipDir)parms[0];
								final double amount=((Double)parms[1]).doubleValue();
								final boolean isConst = ((Boolean)parms[2]).booleanValue();
								double finalAcceleration = 0;
								Room dockR = getIsDocked();
								if(amount != 0)
								{
									switch(dir)
									{
									case STARBOARD:
										if(dockR==null)
										{
											finalAcceleration = -CMath.mul(amount,0.017);
											facing[0] += finalAcceleration;
											if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
												Log.debugOut("SpaceShip "+name()+" turns "+dir.toString()+" "+Math.toDegrees(finalAcceleration)+" to "+facing[0]);
										}
										break;
									case PORT:
										if(dockR==null)
										{
											finalAcceleration = CMath.mul(amount,0.017);
											facing[0] += finalAcceleration;
											if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
												Log.debugOut("SpaceShip "+name()+" turns "+dir.toString()+" "+Math.toDegrees(finalAcceleration)+" to "+facing[0]);
										}
										break;
									case DORSEL:
										if(dockR==null)
										{
											finalAcceleration = -CMath.mul(amount,0.017);
											facing[1] += finalAcceleration;
											if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
												Log.debugOut("SpaceShip "+name()+" turns "+dir.toString()+" "+Math.toDegrees(finalAcceleration)+" to "+facing[1]);
										}
										break;
									case VENTRAL:
										if(dockR==null)
										{
											finalAcceleration = CMath.mul(amount,0.017);
											facing[1] += finalAcceleration;
											if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
												Log.debugOut("SpaceShip "+name()+" turns "+dir.toString()+" "+Math.toDegrees(finalAcceleration)+" to "+facing[1]);
										}
										break;
									case FORWARD:
									case AFT: // breaking thrusters
									{
										if(dockR!=null)
										{
											if(dir == ShipDir.FORWARD)
											{
												if(amount > SpaceObject.ACCELERATION_G)
												{
													unDock(true);
													dockR=null;
												}
											}
										}
										// this will move it, but will also update speed and direction -- all good!
										final double inAirFactor=(shipFlags.contains(ShipFlag.IN_THE_AIR))?(1.0-getOMLCoeff()):1.0;
										//TODO: calculate inertia gforce damage here, and send the message
										//^^ this should be LIKE acceleration, except it can be modified by antigrav stuff
										if(!isConst)
										{
											// a non-constant thruster means the ship attains speed in one burst,
											// and slows in one burst as well.  It is therefore right and good
											// to eliminate all speed, do a complicated gforce calc, and re-speed
											this.setSpeed(0);
										}
										//force/mass is the Gs felt by the occupants.. not force-mass
										finalAcceleration = amount*inAirFactor;
										if((dockR==null) && ((finalAcceleration-this.speedTick) > 0))
										{
											final double prevSpeed = speed();
											final double[] moveDir = (dir == ShipDir.FORWARD) ? facing() : CMLib.map().getOppositeDir(facing());
											CMLib.map().moveSpaceObject(this,moveDir,finalAcceleration-this.speedTick); // have to do this to know new speed
											if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
												Log.debugOut("SpaceShip "+name()+" accelerates "+dir.toString()+" " +(finalAcceleration-this.speedTick));
											this.speedTick += (finalAcceleration-this.speedTick);
											if((speed() < prevSpeed) && (this.speed() < 0.5)) // enough slowing down!
												setSpeed(0.0);
										}
										break;
									}
									}
									final String code=Technical.TechCommand.ACCELERATED.makeCommand(dir,Double.valueOf(finalAcceleration));
									final MOB mob=CMClass.getFactoryMOB();
									try
									{
										final CMMsg msg2=CMClass.getMsg(mob, this, this, CMMsg.NO_EFFECT, null, CMMsg.MSG_ACTIVATE|CMMsg.MASK_CNTRLMSG, code, CMMsg.NO_EFFECT,null);
										this.sendComputerMessage(mob, msg2);
									}
									finally
									{
										mob.destroy();
									}
									facing[0]=Math.abs(facing[0]%(2*Math.PI));
									facing[1]=Math.abs(facing[1]);
									if(facing[1] > Math.PI)
										facing[1] -= Math.PI;
								}
							}
						}
					}
				}
				break;
			case CMMsg.TYP_DAMAGE: // kinetic damage taken to the outside of the ship
			{
				final long myMass=getMass();
				if((msg.value() > 0)&&(myMass>0))
				{
					//TODO: apply non-collision damage to internal systems instead of hull
					//  only a portions is applied to the hull.
					int hullDamage=0;
					final int baseDamage = msg.value();
					switch(msg.sourceMinor())
					{
					case CMMsg.TYP_COLLISION:
						hullDamage = msg.value();
						break;
					case CMMsg.TYP_ELECTRIC:
					case CMMsg.TYP_ACID:
					case CMMsg.TYP_COLD:
					case CMMsg.TYP_FIRE:
					case CMMsg.TYP_GAS:
					case CMMsg.TYP_LASER:
					case CMMsg.TYP_PARALYZE:
					case CMMsg.TYP_POISON:
					case CMMsg.TYP_SONIC:
					case CMMsg.TYP_UNDEAD:
					case CMMsg.TYP_WATER:
						if(CMLib.dice().getRandomizer().nextDouble() + CMath.div(baseDamage,100) > this.getFinalManufacturer().getReliabilityPct())
							hullDamage=1;
						if(hullDamage < usesRemaining())
						{
							final List<Electronics> list = CMLib.tech().getMakeRegisteredElectronics(CMLib.tech().getElectronicsKey(getShipArea()));
							for(final Iterator<Electronics> i=list.iterator();i.hasNext();)
							{
								final Electronics E=i.next();
								if((E.amDestroyed())
								||(((E.subjectToWearAndTear())&&(E.usesRemaining()>0)))
								||(E instanceof ElecPanel)
								||(E instanceof Software)
								||((E instanceof TechComponent) && (!((TechComponent)E).isInstalled()))
								||((!(E instanceof TechComponent)) && (!E.activated())))
									i.remove();
							}
							if(list.size()>0)
							{
								final Electronics damagedE = list.get(CMLib.dice().roll(1, list.size(), -1));
								final Room R=CMLib.map().roomLocation(damagedE);
								final CMMsg msg2=(CMMsg)msg.copyOf();
								if((R!=null)&&(R.okMessage(msg.source(), msg2)))
									R.send(msg.source(), msg2);
							}
						}
						break;
					}
					if(hullDamage >= usesRemaining())
					{
						if(baseDamage>0)
							msg.setOthersMessage(L("For a split second, you hear a loud deafening crash and feel an incredible jolt."));
						else
							msg.setOthersMessage(L("You hear a loud deafening crash and feel a massive jolt."));
						sendAreaMessage(msg,false);
						if(hullDamage>0)
							destroyThisShip();
					}
					else
					{
						if(hullDamage>0)
							setUsesRemaining(usesRemaining() - hullDamage);
						if(baseDamage > 75)
							msg.setOthersMessage(L("You hear a booming crash and feel a crushing jolt."));
						else
						if(baseDamage > 50)
							msg.setOthersMessage(L("You hear a loud crash and feel a hard jolt."));
						else
						if(baseDamage > 25)
							msg.setOthersMessage(L("You hear a noise and feel a small jolt."));
						else
							msg.setOthersMessage(L("You hear a bump and feel a short rattle."));
						sendAreaMessage(msg,false);
					}
				}
				break;
			}
			case CMMsg.TYP_COLLISION:
			{
				final MOB mob=msg.source();
				final double previousSpeed = speed();
				if((msg.tool() instanceof SpaceObject) // we hit something very very big
				&&(((SpaceObject)msg.tool()).getMass() >= (100 * SpaceObject.Distance.Kilometer.dm)))
				{
					if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
						Log.debugOut("SpaceShip "+name()+" just hit "+msg.tool().Name()+"!!!");
					stopThisShip(mob);
				}

				final long myMass=getMass();
				if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
					Log.debugOut("SpaceShip "+name()+" just collided with "+msg.tool().Name()+" of mass "+myMass+" at speed "+previousSpeed);

				// this only works because Areas don't move.
				// the only way to hit one is to be moving towards it.
				if((previousSpeed > (SpaceObject.ACCELERATION_DAMAGED))
				&&(msg.tool() instanceof SpaceObject))
				{
					final SpaceObject O=(SpaceObject)msg.tool();
					// This is technically wrong. Imagine taping a huge object from behind because you
					// are going just a tiny bit faster, even though you are both going very fast.
					// However, the odds of that happening are nothing.  Forget it.
					double absorbedDamage;
					if(O instanceof ShipWarComponent)
						absorbedDamage = ((ShipWarComponent)O).phyStats().damage();
					else
						absorbedDamage = Math.floor((previousSpeed * myMass) + (O.speed() * O.getMass()));
					if(absorbedDamage > Integer.MAX_VALUE / 10)
						absorbedDamage = Integer.MAX_VALUE / 10;

					// the item should modify the source message type, otherwise, all the damage is absorbed
					// by the hull as collision damage.  Damage may be 0 here, and the weapon may generate more,
					// or convert the collision damage into some other kind.
					final CMMsg sMsg=(CMMsg)msg.copyOf();
					sMsg.setTargetCode(CMMsg.MSG_DAMAGE);
					sMsg.setValue((int)Math.round(absorbedDamage));
					if(O.okMessage(O, sMsg)  && okMessage(this,sMsg))
					{
						O.executeMsg(O, sMsg);
						if(sMsg.value() > 0)
							executeMsg(this,sMsg);
					}
				}

				if((!amDestroyed()) && (msg.tool() instanceof Area))
				{
					final List<LocationRoom> landingPoints=CMLib.map().getLandingPoints(this, msg.tool());
					final LocationRoom LR = landingPoints.size()==0 ? null : landingPoints.get(0);
					stopThisShip(mob);
					if(LR!=null)
					{
						if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
							Log.debugOut(Name()+" Landed and Stopped, and docking at "+CMLib.map().getExtendedRoomID(LR));
						//final CMMsg kMsg=CMClass.getMsg(msg.source(),getShipArea(),this,CMMsg.MSG_OK_ACTION,L("The ship comes to a resting stop."));
						dockHere(LR); // set location and so forth
					}
					else
					{
						if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
							Log.debugOut(Name()+" Landed and Stopped, but nowhere to dock. :(");
						// we landed, but there was nowhere to dock!
					}
				}
				else
				if(!amDestroyed())
				{
					if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
						Log.debugOut(Name()+" Collided with weird thing: "+msg.tool().ID());
				}
				else
				if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
					Log.debugOut(Name()+" was destroyed.");
				sendComputerMessage(mob,msg);
				break;
			}
			default:
				break;
			}
		}
	}

	@Override
	protected LocationRoom findNearestDocks(final Room R)
	{
		final List<LocationRoom> docks=new XVector<LocationRoom>();
		if(R!=null)
		{
			if((R.domainType()==Room.DOMAIN_OUTDOORS_SPACEPORT)
			&&(R instanceof LocationRoom))
				return (LocationRoom)R;

			TrackingLibrary.TrackingFlags flags;
			flags = CMLib.tracking().newFlags()
					.plus(TrackingLibrary.TrackingFlag.NOEMPTYGRIDS)
					.plus(TrackingLibrary.TrackingFlag.NOAIR)
					.plus(TrackingLibrary.TrackingFlag.NOHOMES)
					.plus(TrackingLibrary.TrackingFlag.UNLOCKEDONLY)
					.plus(TrackingLibrary.TrackingFlag.NOWATER);
			final List<Room> rooms=CMLib.tracking().getRadiantRooms(R, flags, 25);
			for(final Room R2 : rooms)
			{
				if((R2.domainType()==Room.DOMAIN_OUTDOORS_SPACEPORT)
				&&(R2 instanceof LocationRoom)
				&&(R.getArea().inMyMetroArea(R2.getArea())))
					docks.add((LocationRoom)R2);
			}
			if(docks.size()==0)
			{
				for(final Enumeration<Room> r=R.getArea().getMetroMap();r.hasMoreElements();)
				{
					final Room R2=r.nextElement();
					if((R2.domainType()==Room.DOMAIN_OUTDOORS_SPACEPORT)
					&&(R2 instanceof LocationRoom))
						docks.add((LocationRoom)R2);
				}
			}
			if(docks.size()==0)
			{
				for(final Room R2 : rooms)
				{
					if((R2.domainType()==Room.DOMAIN_OUTDOORS_SPACEPORT)
					&&(R2 instanceof LocationRoom))
						docks.add((LocationRoom)R2);
				}
			}
		}
		if(docks.size()==0)
			return null;
		return docks.get(CMLib.dice().roll(1, docks.size(), -1));
	}

	protected void stopThisShip(final MOB mob)
	{
		if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
			Log.debugOut("SpaceShip "+name()+" is now STOPPED!");
		setSpeed(0); // if you collide with something massive, your speed ENDS
		final List<Electronics> electronics=CMLib.tech().getMakeRegisteredElectronics(CMLib.tech().getElectronicsKey(getShipArea()));
		for(final Electronics E : electronics)
		{
			if(E instanceof ShipEngine)
			{
				final CMMsg msg=CMClass.getMsg(mob, E, this, CMMsg.NO_EFFECT, null, CMMsg.MSG_DEACTIVATE, null, CMMsg.NO_EFFECT,null);
				if(E.okMessage(mob, msg))
				{
					E.executeMsg(mob, msg);
					if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
						Log.debugOut("SpaceShip "+name()+" deactivated "+E.Name()+"!");
					((ShipEngine)E).setThrust(0.0);
				}
			}
		}
	}

	@Override
	public BoundedCube getBounds()
	{
		return new BoundedObject.BoundedCube(coordinates(),radius());
	}

	@Override
	public long powerCapacity()
	{
		return 0;
	}

	@Override
	public void setPowerCapacity(final long capacity)
	{
	}

	@Override
	public long powerRemaining()
	{
		return 0;
	}

	@Override
	public int powerNeeds()
	{
		return 0;
	}

	@Override
	public void setPowerRemaining(final long remaining)
	{
	}

	@Override
	public void activate(final boolean truefalse)
	{
	}

	protected void sendComputerMessage(final MOB mob, final CMMsg msg)
	{
		final Area ship=getShipArea();
		if(ship!=null)
		{
			final List<Electronics> electronics = CMLib.tech().getMakeRegisteredElectronics(CMLib.tech().getElectronicsKey(ship));
			for(final Electronics E : electronics)
			{
				if(E instanceof Computer)
				{
					if(E.owner() instanceof Room)
					{
						if(((Room)E.owner()).okMessage(mob, msg))
							((Room)E.owner()).send(mob, msg);
					}
					else
					if(E.okMessage(mob, msg))
						E.executeMsg(mob, msg);
				}
			}
		}
	}

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

	@Override
	public void setShipFlag(final ShipFlag flag, final boolean setFlag)
	{
		if(shipFlags.contains(flag))
		{
			if(!setFlag)
				shipFlags.remove(flag);
		}
		else
		{
			if(setFlag)
				shipFlags.add(flag);
		}
	}

	@Override
	public boolean getShipFlag(final ShipFlag flag)
	{
		return shipFlags.contains(flag);
	}

	@Override
	public SpaceObject getShipSpaceObject()
	{
		return this;
	}

	@Override
	public boolean activated()
	{
		return true;
	}

	@Override
	public int techLevel()
	{
		return phyStats().ability();
	}

	@Override
	public void setTechLevel(final int lvl)
	{
		basePhyStats.setAbility(lvl); recoverPhyStats();
	}

	@Override
	public String getManufacturerName()
	{
		return manufacturer;
	}

	@Override
	public void setManufacturerName(final String name)
	{
		cachedManufact=null;
		if(name!=null)
			manufacturer=name;
	}

	@Override
	public long getMass()
	{
		return basePhyStats().weight()+((area instanceof SpaceShip)?((SpaceShip)area).getMass(): 1000);
	}

	@Override
	public Manufacturer getFinalManufacturer()
	{
		if(cachedManufact==null)
		{
			cachedManufact=CMLib.tech().getManufacturerOf(this,getManufacturerName().toUpperCase().trim());
			if(cachedManufact==null)
				cachedManufact=CMLib.tech().getDefaultManufacturer();
		}
		return cachedManufact;
	}

	@Override
	public long[] coordinates()
	{
		return coordinates;
	}

	@Override
	public double[] direction()
	{
		return direction;
	}

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

	@Override
	public void setRoll(final double dir)
	{
		roll =dir;
	}

	@Override
	public double[] facing()
	{
		return facing;
	}

	@Override
	public void setFacing(final double[] dir)
	{
		if(dir!=null)
			this.facing=dir;
	}

	@Override
	public SpaceObject knownTarget()
	{
		return spaceTarget;
	}

	@Override
	public void setKnownTarget(final SpaceObject O)
	{
		spaceTarget=O;
	}

	@Override
	public void setCoords(final long[] coords)
	{
		if((coords!=null)&&(coords.length==3))
			CMLib.map().moveSpaceObject(this,coords);
	}

	@Override
	public void setDirection(final double[] dir)
	{
		if(dir!=null)
			direction=dir;
	}

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

	@Override
	public void setSpeed(final double v)
	{
		if(Double.isNaN(v))
		{
			Log.errOut("Bad Speed: "+v);
			return;
		}
		speed=v;
	}

	@Override
	public SpaceObject knownSource()
	{
		return (area instanceof SpaceObject)?((SpaceObject)area).knownSource():null;
	}

	@Override
	public void setKnownSource(final SpaceObject O)
	{
		if (area instanceof SpaceObject)
			((SpaceObject)area).setKnownSource(O);
	}

	@Override
	public long radius()
	{
		return (area instanceof SpaceObject)?((SpaceObject)area).radius():50;
	}

	@Override
	public void setRadius(final long radius)
	{
		if (area instanceof SpaceObject)
			((SpaceObject)area).setRadius(radius);
	}

	@Override
	public double getOMLCoeff()
	{
		return (area instanceof SpaceShip)?((SpaceShip)area).getOMLCoeff()
				:SpaceObject.ATMOSPHERIC_DRAG_STREAMLINE + ((SpaceObject.ATMOSPHERIC_DRAG_BRICK-SpaceObject.ATMOSPHERIC_DRAG_STREAMLINE)/2.0);
	}

	@Override
	public void setOMLCoeff(final double coeff)
	{
		if (area instanceof SpaceShip)
			((SpaceShip)area).setOMLCoeff(coeff);
	}

	private final static String[] MYCODES={"HASLOCK","HASLID","CAPACITY","CONTAINTYPES","RESETTIME","RIDEBASIS","MOBSHELD",
											"POWERCAP","ACTIVATED","POWERREM","MANUFACTURER","AREA","COORDS","RADIUS",
											"ROLL","DIRECTION","SPEED","FACING","OWNER","PRICE","DEFCLOSED","DEFLOCKED",
											"EXITNAME","TECHLEVEL",
											"PUTSTR","MOUNTSTR","DISMOUNTSTR","STATESTR","STATESUBJSTR","RIDERSTR"
										  };

	@Override
	public String getStat(final String code)
	{
		if(CMLib.coffeeMaker().getGenItemCodeNum(code)>=0)
			return CMLib.coffeeMaker().getGenItemStat(this,code);
		switch(getCodeNum(code))
		{
		case 0:
			return "" + hasALock();
		case 1:
			return "" + hasADoor();
		case 2:
			return "" + capacity();
		case 3:
			return "" + containTypes();
		case 4:
			return "" + openDelayTicks();
		case 5:
			return "" + rideBasis();
		case 6:
			return "" + riderCapacity();
		case 7:
			return "" + powerCapacity();
		case 8:
			return "" + activated();
		case 9:
			return "" + powerRemaining();
		case 10:
			return getManufacturerName();
		case 11:
			return CMLib.coffeeMaker().getAreaObjectXML(getShipArea(), null, null, null, true).toString();
		case 12:
			return CMParms.toListString(coordinates());
		case 13:
			return "" + radius();
		case 14:
			return "" + roll();
		case 15:
			return CMParms.toListString(direction());
		case 16:
			return "" + speed();
		case 17:
			return CMParms.toListString(facing());
		case 18:
			return getOwnerName();
		case 19:
			return "" + getPrice();
		case 20:
			return "" + defaultsClosed();
		case 21:
			return "" + defaultsLocked();
		case 22:
			return "" + doorName();
		case 23:
			return "" + techLevel();
		case 24:
			return this.getPutString();
		case 25:
			return this.getMountString();
		case 26:
			return this.getDismountString();
		case 27:
			return this.getStateString();
		case 28:
			return this.getStateStringSubject();
		case 29:
			return this.getRideString();
		default:
			return CMProps.getStatCodeExtensionValue(getStatCodes(), xtraValues, code);
		}
	}

	@Override
	public void setStat(final String code, final String val)
	{
		if(CMLib.coffeeMaker().getGenItemCodeNum(code)>=0)
			CMLib.coffeeMaker().setGenItemStat(this,code,val);
		else
		switch(getCodeNum(code))
		{
		case 0:
			setDoorsNLocks(hasADoor(), isOpen(), defaultsClosed(), CMath.s_bool(val), false, CMath.s_bool(val) && defaultsLocked());
			break;
		case 1:
			setDoorsNLocks(CMath.s_bool(val), isOpen(), CMath.s_bool(val) && defaultsClosed(), hasALock(), isLocked(), defaultsLocked());
			break;
		case 2:
			setCapacity(CMath.s_parseIntExpression(val));
			break;
		case 3:
			setContainTypes(CMath.s_parseBitLongExpression(Container.CONTAIN_DESCS, val));
			break;
		case 4:
			setOpenDelayTicks(CMath.s_parseIntExpression(val));
			break;
		case 5:
			break;
		case 6:
			break;
		case 7:
			setPowerCapacity(CMath.s_parseIntExpression(val));
			break;
		case 8:
			activate(CMath.s_bool(val));
			break;
		case 9:
			setPowerRemaining(CMath.s_parseLongExpression(val));
			break;
		case 10:
			setManufacturerName(val);
			break;
		case 11:
			setShipArea(val);
			break;
		case 12:
			setCoords(CMParms.toLongArray(CMParms.parseCommas(val, true)));
			coordinates[0] = coordinates[0] % SpaceObject.Distance.GalaxyRadius.dm;
			coordinates[1] = coordinates[1] % SpaceObject.Distance.GalaxyRadius.dm;
			coordinates[2] = coordinates[2] % SpaceObject.Distance.GalaxyRadius.dm;
			break;
		case 13:
			setRadius(CMath.s_long(val));
			break;
		case 14:
			setRoll(CMath.s_double(val));
			break;
		case 15:
			setDirection(CMParms.toDoubleArray(CMParms.parseCommas(val, true)));
			break;
		case 16:
			setSpeed(CMath.s_double(val));
			break;
		case 17:
			setFacing(CMParms.toDoubleArray(CMParms.parseCommas(val, true)));
			break;
		case 18:
			setOwnerName(val);
			break;
		case 19:
			setPrice(CMath.s_int(val));
			break;
		case 20:
			setDoorsNLocks(hasADoor(), isOpen(), CMath.s_bool(val), hasALock(), isLocked(), defaultsLocked());
			break;
		case 21:
			setDoorsNLocks(hasADoor(), isOpen(), defaultsClosed(), hasALock(), isLocked(), CMath.s_bool(val));
			break;
		case 22:
			doorName = val;
			break;
		case 23:
			setTechLevel(CMath.s_parseIntExpression(val));
			break;
		case 24:
			setPutString(val);
			break;
		case 25:
			setMountString(val);
			break;
		case 26:
			setDismountString(val);
			break;
		case 27:
			setStateString(val);
			break;
		case 28:
			setStateStringSubject(val);
			break;
		case 29:
			setRideString(val);
			break;
		default:
			CMProps.setStatCodeExtensionValue(getStatCodes(), xtraValues, code, val);
			break;
		}
	}

	@Override
	protected int getCodeNum(final String code)
	{
		for(int i=0;i<MYCODES.length;i++)
		{
			if(code.equalsIgnoreCase(MYCODES[i]))
				return i;
		}
		return -1;
	}

	private static String[] codes=null;

	@Override
	public String[] getStatCodes()
	{
		if(codes!=null)
			return codes;
		final String[] MYCODES=CMProps.getStatCodesList(GenSpaceShip.MYCODES,this);
		final String[] superCodes=CMParms.toStringArray(GenericBuilder.GenItemCode.values());
		codes=new String[superCodes.length+MYCODES.length];
		int i=0;
		for(;i<superCodes.length;i++)
			codes[i]=superCodes[i];
		for(int x=0;x<MYCODES.length;i++,x++)
			codes[i]=MYCODES[x];
		return codes;
	}

	@Override
	public boolean sameAs(final Environmental E)
	{
		if(!(E instanceof GenSpaceShip))
			return false;
		final String[] codes=getStatCodes();
		for(int i=0;i<codes.length;i++)
		{
			if((!E.getStat(codes[i]).equals(getStat(codes[i])))
			&&(!codes[i].equals("AREA"))
			&&(!codes[i].equals("ABILITY")))
				return false;
		}
		final Area eA = ((GenSpaceShip)E).getShipArea();
		final Area A = this.getShipArea();
		final Enumeration<Room> er = eA.getProperMap();
		final Enumeration<Room> r = A.getProperMap();
		for(;r.hasMoreElements();)
		{
			final Room R=r.nextElement();
			if(!er.hasMoreElements())
				return false;
			final Room eR = er.nextElement();
			if(!R.sameAs(eR))
				return false;
			final Enumeration<Item> i=R.items();
			final Enumeration<Item> ei = eR.items();
			for(;i.hasMoreElements();)
			{
				final Item I=i.nextElement();
				if(!ei.hasMoreElements())
					return false;
				final Item eI=ei.nextElement();
				if(!I.sameAs(eI))
					return false;
			}
			if(ei.hasMoreElements())
				return false;
			final Enumeration<MOB> m=R.inhabitants();
			final Enumeration<MOB> em = eR.inhabitants();
			for(;m.hasMoreElements();)
			{
				final MOB M=m.nextElement();
				if(!em.hasMoreElements())
					return false;
				final MOB eM=em.nextElement();
				if(!M.sameAs(eM))
					return false;
			}
			if(em.hasMoreElements())
				return false;
		}
		if(er.hasMoreElements())
			return false;
		return true;
	}
}
