using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Text;
using UnityEngine;
using Verse.Sound;
using Verse.AI;
using RimWorld;

namespace Verse
{

public enum FillCategory : byte
{
	None,
	Partial,
	Full,
}

public enum DrawerType : byte
{
	None,
	RealtimeOnly,
	MapMeshOnly,
	MapMeshAndRealTime
}

public enum ResourceCountPriority : byte
{
	Uncounted,

	Last,
	Middle,
	First
}

public enum Tradeability : byte
{
	Never,
	Sellable,
	Stockable,
}

public enum SurfaceType : byte
{
	None,
	Item,
	Eat
}

public class DamageMultiplier
{
	public DamageDef	damageDef = null;
	public float		multiplier = 1f;
}

public class ThingDef : BuildableDef
{
	//Basics
	public Type						thingClass;
	public ThingCategory 			category;
	public TickerType				tickerType = TickerType.Never;
	public int						stackLimit = 1;
	public IntVec2					size = new IntVec2(1,1);
	public bool						destroyable = true;
	public bool						rotatable = true;
	public bool						smallVolume = false;
	public bool						useHitPoints = true;
	public bool						receivesSignals = false;
	public List<CompProperties>		comps = new List<CompProperties>();

	//Misc
	public List<ThingCountClass>	killedLeavings = null;
	public List<ThingCountClass>	butcherProducts = null;
	public List<ThingCountClass>	smeltProducts = null;
	public bool						smeltable = false;
	public bool						randomizeRotationOnSpawn = false;
	public List<DamageMultiplier>	damageMultipliers = null;
    public bool                     isBodyPartOrImplant = false;
	public RecipeMakerProperties	recipeMaker = null;
	public ThingDef					minifiedDef = null;
	public bool						isUnfinishedThing = false;
	public bool						leaveResourcesWhenKilled = false;
	public ThingDef					slagDef = null;
	public bool						isFrame = false;
	public IntVec3					interactionCellOffset = IntVec3.Zero;
    public bool						hasInteractionCell = false;	
	public ThingDef					filthLeaving = null;
	public bool						forceDebugSpawnable = false;
	public bool						intricate = false;
	public bool						scatterableOnMapGen = true;
	public float					deepCommonality = 0;
	public int						deepCountPerCell = 150;
	public float					generateCommonality = 1f;
	public float					generateAllowChance = 1f;
	private bool					canOverlapZones = true;
	public FloatRange				startingHpRange = FloatRange.One;
	[NoTranslate]public List<string> itemGeneratorTags = null;
	public bool						alwaysFlee;
	public List<Tool>				tools;

	//Visuals
	public GraphicData				graphicData = null;
	public DrawerType				drawerType = DrawerType.RealtimeOnly;
	public bool						drawOffscreen = false;
	public ColorGenerator			colorGenerator = null;
	public float					hideAtSnowDepth = 99999f;
	public bool						drawDamagedOverlay = true;
	public bool						castEdgeShadows = false;
	public float					staticSunShadowHeight = 0;

	//Interface
	public bool						selectable = false;
	public bool 					neverMultiSelect = false;
	public bool						isAutoAttackableMapObject = false;
	public bool						hasTooltip = false;
	public List<Type>				inspectorTabs = null;
	[Unsaved] public List<InspectTabBase> inspectorTabsResolved = null;
	public bool						seeThroughFog = false;
	public bool						drawGUIOverlay = false;
	public ResourceCountPriority	resourceReadoutPriority = ResourceCountPriority.Uncounted;
	public bool						resourceReadoutAlwaysShow = false;
	public bool						drawPlaceWorkersWhileSelected = false;
	public ConceptDef				storedConceptLearnOpportunity = null;
	public float					iconDrawScale = -1f;

	//AI hints
	public bool						alwaysHaulable = false;
	public bool						designateHaulable = false;
	public List<ThingCategoryDef>	thingCategories = null;
	public bool						mineable = false;
	public bool						socialPropernessMatters = false;
	public bool						stealable = true;

	//Sounds
	public SoundDef					soundDrop;
	public SoundDef					soundPickup;
	public SoundDef					soundInteract;
	public SoundDef					soundImpactDefault;

	//Save/load
    public bool						saveCompressible = false;
	public bool						isSaveable = true;

	//Physics
	public bool						holdsRoof = false;
	public float					fillPercent = 0;
	public bool						coversFloor = false;
	public bool						neverOverlapFloors = false;
	public SurfaceType				surfaceType = SurfaceType.None;
	public bool						blockPlants = false;
	public bool						blockLight = false;
	public bool						blockWind = false;
	[Unsaved] public bool			affectsRegions = false;

	//Trade
	public Tradeability				tradeability = Tradeability.Stockable;
	[NoTranslate] public List<string> tradeTags = null;
	public bool						tradeNeverStack = false;
	public ColorGenerator			colorGeneratorInTraderStock = null;

	//Used with buildings
	public Type						blueprintClass = typeof(Blueprint_Build);
	public GraphicData				blueprintGraphicData = null;
	public TerrainDef				naturalTerrain = null;
	public TerrainDef				leaveTerrain = null;
	public List<RecipeDef>			recipes = null;
	
    //Used with equipment or races
    private List <VerbProperties>	verbs = null;
    
	//Used with equipment/inventory/artificial body parts/implants
    public float                    equippedAngleOffset = 0f;
	public EquipmentType			equipmentType = EquipmentType.None;
	public TechLevel				techLevel = TechLevel.Undefined;
	[NoTranslate] public List<string> weaponTags = null;
    [NoTranslate] public List<string> techHediffsTags = null;
	public bool						canBeSpawningInventory = true;
	public bool						destroyOnDrop = false;
	public List<StatModifier>		equippedStatOffsets	= null;
	
	//Used with blueprints
	public BuildableDef				entityDefToBuild = null;

	//Used with shells
	public ThingDef					projectileWhenLoaded;

	//Various sub-properties
	public IngestibleProperties		ingestible = null;
	public FilthProperties			filth = null;	
	public GasProperties			gas = null;	
	public BuildingProperties		building = null;
	public RaceProperties			race = null;
	public ApparelProperties		apparel = null;
	public MoteProperties			mote = null;
	public PlantProperties			plant = null;
	public ProjectileProperties		projectile = null;
	public StuffProperties			stuffProps = null;
	public SkyfallerProperties		skyfaller = null;

	//Constants
	public const int				SmallUnitPerVolume = 10;
	public const float				SmallVolumePerUnit = 0.1f;


	//======================== Misc properties ==============================
	public bool	EverHaulable{get{return alwaysHaulable || designateHaulable;}}
	public bool	EverStoreable{get{return !thingCategories.NullOrEmpty();}}
	public float VolumePerUnit{get{return !smallVolume ? 1 : SmallVolumePerUnit;}}
	public override Color IconDrawColor{get { return graphicData.color; }}
	public override IntVec2 Size{get{return size;}}
	public bool DiscardOnDestroyed{get{return race == null;}}
	public int	BaseMaxHitPoints	{get{return Mathf.RoundToInt(this.GetStatValueAbstract( StatDefOf.MaxHitPoints ));}}
	public float BaseFlammability	{get{return this.GetStatValueAbstract( StatDefOf.Flammability );}}
	public float BaseMarketValue
	{
		get
		{
			return this.GetStatValueAbstract( StatDefOf.MarketValue );
		}
		set
		{
			this.SetStatBaseValue( StatDefOf.MarketValue, value );
		}
	}
	public float BaseMass
	{
		get
		{
			return this.GetStatValueAbstract( StatDefOf.Mass );
		}
	}
	public bool PlayerAcquirable
	{
		get
		{
			return !destroyOnDrop;
		}
	}
	public bool EverTransmitsPower
	{
		get
		{
			for( int i=0; i<comps.Count; i++ )
			{
				var p = comps[i] as CompProperties_Power;

				if( p != null && p.transmitsPower )
					return true;
			}
			return false;
		}
	}
	public bool Minifiable{get{return minifiedDef != null;}}
	public bool	HasThingIDNumber
	{
		get
		{
			return category != ThingCategory.Mote;
		}
	}
	private List<RecipeDef> allRecipesCached = null;
	public List<RecipeDef> AllRecipes
	{
		get
		{
			if( allRecipesCached == null )
			{
				allRecipesCached = new List<RecipeDef>();
				if( recipes != null )
				{
					for(int i=0; i<recipes.Count; i++ )
					{
						allRecipesCached.Add(recipes[i]);
					}
				}

				var recipeDefs = DefDatabase<RecipeDef>.AllDefsListForReading;
				for( int i=0; i<recipeDefs.Count; i++ )
				{
					if( recipeDefs[i].recipeUsers != null )
					{
						if( recipeDefs[i].recipeUsers.Contains(this) )
							allRecipesCached.Add(recipeDefs[i]);
					}
				}
			}

			return allRecipesCached;
		}
	}
	public bool ConnectToPower
	{
		get
		{
			if( EverTransmitsPower )
				return false;

			for( int i=0; i<comps.Count; i++ )
			{
				if( comps[i].compClass == typeof(CompPowerBattery) )
					return true;

				if( comps[i].compClass == typeof(CompPowerTrader) )
					return true;
			}
			return false;
		}
	}
	public bool CoexistsWithFloors
	{
		get
		{
			return !neverOverlapFloors && !coversFloor;
		}
	}
	public FillCategory Fillage
	{
		get
		{
			if( fillPercent < 0.01f )
				return FillCategory.None;
			else if( fillPercent > 0.99f )
				return FillCategory.Full;
			else
				return FillCategory.Partial;
		}
	}
	public bool MakeFog{get{return Fillage == FillCategory.Full;}}
	public bool CanOverlapZones
	{
		get
		{
			// buildings which support plants can't overlap zones,
			// (so there is no growing zone and a building which supports plants on the same cell)
			if( building != null && building.SupportsPlants )
				return false;

			//Nothing impassable can overlap a zone, except plants
			if( passability == Traversability.Impassable && category != ThingCategory.Plant )
				return false;

			if( surfaceType >= SurfaceType.Item )
				return false;

			if( typeof(ISlotGroupParent).IsAssignableFrom(thingClass) )
				return false;

			if( !canOverlapZones )
				return false;

			//Blueprints and frames inherit from the def they want to build
			if( IsBlueprint || IsFrame )
			{
				var thingDefToBuild = entityDefToBuild as ThingDef;
				if( thingDefToBuild != null )
					return thingDefToBuild.CanOverlapZones;
			}

			return true;
		}
	}
	public bool	CountAsResource{get{return resourceReadoutPriority != ResourceCountPriority.Uncounted;}}
	public bool BlockPlanting
	{
		get
		{
			//Nothing that supports plants blocks planting
			if( building != null && building.SupportsPlants )
				return false;
			
			if( blockPlants )
				return true;

			//All plants block each other
			if( category == ThingCategory.Plant )
				return true;

			if( Fillage > FillCategory.None )
				return true;

			//This includes things like power conduits
			//if( category == EntityCategory.Building )
			//	return true;

			if( this.IsEdifice() )
				return true;

			return false;
		}
	}
	private static List<VerbProperties> EmptyVerbPropertiesList = new List<VerbProperties>();
	public List<VerbProperties> Verbs
	{
		get
		{
			if( verbs != null )
				return verbs;
			return EmptyVerbPropertiesList;
		}
	}
	public bool CanHaveFaction
	{
		get
		{
			if( IsBlueprint || IsFrame )
				return true;

			switch( category )
			{
				case ThingCategory.Pawn: return true;
				case ThingCategory.Building: return true;
			}

			return false;
		}
	}
	public bool Claimable
	{
		get
		{
			return building != null && building.claimable;
		}
	}
	public ThingCategoryDef FirstThingCategory
	{
		get
		{
			if( thingCategories.NullOrEmpty() )
				return null;

			return thingCategories[0];
		}
	}
	public float MedicineTendXpGainFactor
	{
		get
		{
			return Mathf.Clamp( this.GetStatValueAbstract(StatDefOf.MedicalPotency)*0.7f, LearnRates.NoMedicineTendXpFactor, 1.0f );
		}
	}
	public bool CanEverDeteriorate
	{
		get
		{
			if( !useHitPoints )
				return false;

			return category == ThingCategory.Item || this == ThingDefOf.BurnedTree;
		}
	}
	/// <summary>
	/// Returns true if this thing affects regions (e.g. is a wall), i.e. whether the regions should be rebuilt whenever this thing is spawned or despawned.
	/// </summary>
	public bool AffectsRegions
	{
		get
		{
			// see RegionTypeUtility.GetExpectedRegionType()
			return passability == Traversability.Impassable || IsDoor;
		}
	}
	/// <summary>
	/// Returns true if this thing affects reachability (e.g. is a wall), i.e. whether the reachability cache should be cleared whenever this thing is spawned or despawned.
	/// </summary>
	public bool AffectsReachability
	{
		get
		{
			// see TouchPathEndModeUtility.IsCornerTouchAllowed()

			//Things which affect regions always affect reachability
			if( AffectsRegions )
				return true;

			if( passability == Traversability.Impassable || IsDoor )
				return true;

			//Makes occupied cells reachable diagonally
			if( TouchPathEndModeUtility.MakesOccupiedCellsAlwaysReachableDiagonally(this) )
				return true;

			return false;
		}
	}

	//Properties: IsKindOfThing bools
	public bool IsApparel{get{return apparel != null;}}
	public bool IsBed { get{return typeof(Building_Bed).IsAssignableFrom(thingClass);} }
	public bool IsCorpse { get{return typeof(Corpse).IsAssignableFrom(thingClass);} }
	public bool IsFrame { get{return isFrame;}}
	public bool IsBlueprint { get{return entityDefToBuild != null && category == ThingCategory.Ethereal;}}
	public bool IsStuff				{get{return stuffProps != null;}}
	public bool IsMedicine { get{return statBases.StatListContains(StatDefOf.MedicalPotency);}}
	public bool IsDoor{get{return typeof(Building_Door).IsAssignableFrom(thingClass);}}
	public bool IsFilth{get{return filth != null;}}
	public bool IsIngestible{get{return ingestible != null;}}
	public bool IsNutritionGivingIngestible{get{return IsIngestible && ingestible.nutrition > 0;}}
	public bool IsWeapon { get { return category == ThingCategory.Item && (!verbs.NullOrEmpty() || !tools.NullOrEmpty()); } }
	public bool IsCommsConsole{get{return typeof(Building_CommsConsole).IsAssignableFrom(thingClass);}}
	public bool IsOrbitalTradeBeacon{get{return typeof(Building_OrbitalTradeBeacon).IsAssignableFrom(thingClass);}}
	public bool IsFoodDispenser{get{return typeof(Building_NutrientPasteDispenser).IsAssignableFrom(thingClass);}}
	public bool IsDrug{get{return ingestible != null && ingestible.drugCategory != DrugCategory.None;}}
	public bool IsPleasureDrug{get{return IsDrug && ingestible.joy > 0;}}
	public bool IsNonMedicalDrug{get{return IsDrug && ingestible.drugCategory != DrugCategory.Medical;}}
	public bool IsTable{get{return surfaceType == SurfaceType.Eat && HasComp(typeof(CompGatherSpot));}}
	public bool IsWorkTable{get{return typeof(Building_WorkTable).IsAssignableFrom(thingClass);}}
	public bool IsShell{get{return projectileWhenLoaded != null;}}
	public bool IsArt{get{return IsWithinCategory(ThingCategoryDefOf.Art);}}
	public bool IsAddictiveDrug
	{
		get
		{
			var compDrug = GetCompProperties<CompProperties_Drug>();
			return compDrug != null && compDrug.addictiveness > 0;
		}
	}
	public bool IsMeat
	{
		get
		{
			return category == ThingCategory.Item
				&& thingCategories != null
				&& thingCategories.Contains(ThingCategoryDefOf.MeatRaw);
		}
	}
	public bool IsLeather
	{
		get
		{
			return category == ThingCategory.Item
				&& thingCategories != null
				&& thingCategories.Contains(ThingCategoryDefOf.Leathers);
		}
	}
	public bool IsRangedWeapon
	{
		get
		{
			if( !IsWeapon )
				return false;
			
			if( !verbs.NullOrEmpty() )
			{
				for( int i = 0; i < verbs.Count; i++ )
				{
					if( !verbs[i].MeleeRange )
						return true;
				}
			}

			return false;
		}
	}
	public bool IsMeleeWeapon
	{
		get
		{
			return IsWeapon && !IsRangedWeapon;
		}
	}
	public bool IsBuildingArtificial
	{
		get
		{
			// check for frame to handle special case: floor frames are not buildings
			return (category == ThingCategory.Building || IsFrame)
				&& !(building != null && (building.isNaturalRock || building.isResourceRock));
		}
	}

	private Dictionary<ThingDef, Thing> concreteExamplesInt;
	public Thing GetConcreteExample(ThingDef stuff = null)
	{
		if( concreteExamplesInt == null )
			concreteExamplesInt = new Dictionary<ThingDef, Thing>();

		if( stuff == null )
			stuff = ThingDefOf.Steel;
		
		if( !concreteExamplesInt.ContainsKey(stuff) )
		{
			if( this.race == null )
				concreteExamplesInt[stuff] = ThingMaker.MakeThing(this, MadeFromStuff ? stuff : null);	// We can't store null keys in a dictionary, so we store null stuff under "steel", then pass the right parameter in here.
			else
				concreteExamplesInt[stuff] = PawnGenerator.GeneratePawn(DefDatabase<PawnKindDef>.AllDefsListForReading.Where(pkd => pkd.race == this).FirstOrDefault());
		}

		return concreteExamplesInt[stuff];
	}

	public List<Verb> GetConcreteExampleVerbs(Def def, ThingDef stuff = null)
	{
		List<Verb> verbs = null;
		ThingDef td = def as ThingDef;
		if( td != null )
		{
			var example = td.GetConcreteExample(stuff);
			if( example is Pawn )
				verbs = (example as Pawn).verbTracker.AllVerbs;
			else if( example is ThingWithComps )
				verbs = (example as ThingWithComps).GetComp<CompEquippable>().AllVerbs;
		}

		HediffDef hd = def as HediffDef;
		if( hd != null )
		{
			var example = hd.ConcreteExample;
			verbs = example.TryGetComp<HediffComp_VerbGiver>().VerbTracker.AllVerbs;
		}
		
		return verbs;
	}
	
	//========================== Comp stuff ================================

	public CompProperties CompDefFor<T>() where T:ThingComp
	{
		return comps.FirstOrDefault( c=> c.compClass == typeof(T) );
	}

	public CompProperties CompDefForAssignableFrom<T>() where T:ThingComp
	{
		return comps.FirstOrDefault( c=> typeof(T).IsAssignableFrom(c.compClass) );
	}

	public bool HasComp(Type compType)
	{
		for( int i = 0; i < comps.Count; i++ )
		{
			if( comps[i].compClass == compType )
				return true;
		}
		return false;
	}

	public T GetCompProperties<T>() where T : CompProperties
	{
		for( int i = 0; i < comps.Count; i++ )
		{
			var c = comps[i] as T;

			if( c != null )
				return c;
		}

		return null;
	}

	//========================== Loading and resolving ================================

	public override void PostLoad()
	{
		if( graphicData != null )
		{
			LongEventHandler.ExecuteWhenFinished(() =>
				{
					if( graphicData.shaderType == ShaderType.None )
						graphicData.shaderType = ShaderType.Cutout;

					graphic = graphicData.Graphic;
				});
		}

		//Hack: verb inherits my label
		if( verbs != null && verbs.Count == 1 )
			verbs[0].label = label;

		base.PostLoad();

		//Avoid null refs on things that didn't have a building properties defined
		if( category == ThingCategory.Building && building == null )
			building = new BuildingProperties();

		if( building != null )
			building.PostLoadSpecial(this);

		if( plant != null )
			plant.PostLoadSpecial(this);
	}

	public override void ResolveReferences()
	{
		base.ResolveReferences();

		if( building != null )
			building.ResolveReferencesSpecial();

		if( graphicData != null )
			graphicData.ResolveReferencesSpecial();

		if( race != null )
			race.ResolveReferencesSpecial();

		//Default sounds
		if( soundImpactDefault == null )
			soundImpactDefault = SoundDefOf.BulletImpactGround;	
		if( soundDrop == null )
			soundDrop = SoundDefOf.Standard_Drop;
		if( soundPickup == null )
			soundPickup = SoundDefOf.Standard_Pickup;
		if( soundInteract == null )
			soundPickup = SoundDefOf.Standard_Pickup;

		//Resolve itabs
		if( inspectorTabs != null && inspectorTabs.Any() )
		{
			inspectorTabsResolved = new List<InspectTabBase>();

			for( int i = 0; i < inspectorTabs.Count; i++ )
			{
				try
				{
					inspectorTabsResolved.Add(InspectTabManager.GetSharedInstance(inspectorTabs[i]));
				}
				catch( Exception e )
				{
					Log.Error("Could not instantiate inspector tab of type " + inspectorTabs[i] + ": " + e);
				}
			}
		}

        if (comps != null)
        {
            for (int i = 0; i < comps.Count; i++)
            {
                comps[i].ResolveReferences(this);
            }
        }
	}


	public override IEnumerable<string> ConfigErrors()
	{
		foreach( string str in base.ConfigErrors() )
		{
			yield return str;
		}

		if( label.NullOrEmpty() )
			yield return "no label";

		if( graphicData != null )
		{
			foreach( var err in graphicData.ConfigErrors(this) )
			{
				yield return err;
			}
		}

		if( projectile != null )
		{
			foreach( var err in projectile.ConfigErrors(this) )
			{
				yield return err;
			}
		}
		
		if( statBases != null )
		{
			foreach( var statBase in statBases )
			{
				if( statBases.Where( st=>st.stat == statBase.stat ).Count() > 1 )
					yield return defName + " defines the stat base " + statBase.stat + " more than once.";
			}
		}

		if( char.IsNumber(defName[defName.Length-1]) )
			yield return defName + " ends with a numerical digit, which is not allowed on ThingDefs.";

		if( thingClass == null )
			yield return defName + " has null thingClass.";

		if( comps.Count > 0 && !typeof(ThingWithComps).IsAssignableFrom( thingClass ) )	
			yield return defName + " has components but it's thingClass is not a ThingWithComps";

		if( ConnectToPower && drawerType == DrawerType.RealtimeOnly && IsFrame )
			yield return defName + " connects to power but does not add to map mesh. Will not create wire meshes.";

		if( costList != null )
		{
			foreach( ThingCountClass cost in costList )
			{
				if( cost.count == 0 )
					yield return defName + " cost in " + cost.thingDef + " is zero.";
			}
		}

		if( thingCategories != null )
		{
			var doubleCat = thingCategories.FirstOrDefault( cat=>thingCategories.Count(c=>c==cat) > 1 );
			if( doubleCat != null )
				yield return defName + " has duplicate thingCategory " + doubleCat + ".";
		}

		if( Fillage == FillCategory.Full && category != ThingCategory.Building )
			yield return defName + " gives full cover but is not a building.";

		if( comps.Any(c=>c.compClass == typeof(CompPowerTrader) ) && drawerType == DrawerType.MapMeshOnly )
			yield return defName + " has PowerTrader comp but does not draw real time. It won't draw a needs-power overlay.";
	
		if( equipmentType != EquipmentType.None )
		{
			if( techLevel == TechLevel.Undefined )
				yield return defName + " has no tech level.";

			if( !comps.Any(c=>c.compClass == typeof(CompEquippable) ) )
				yield return "is equipment but has no CompEquippable";
		}

		if( thingClass == typeof(Bullet) && projectile.damageDef == null )
			yield return defName + " is a bullet but has no damageDef.";

		if( destroyOnDrop && !menuHidden )
			yield return defName + " has destroyOnDrop but not menuHidden.";

		if( stackLimit > 1 && !drawGUIOverlay )
			yield return defName + " has stackLimit > 1 but also has drawGUIOverlay = false.";

		if( damageMultipliers != null )
		{
			foreach( DamageMultiplier mult in damageMultipliers )
			{
				if( damageMultipliers.Where( m=>m.damageDef == mult.damageDef ).Count() > 1 )
				{
					yield return defName + " has multiple damage multipliers for damageDef " + mult.damageDef;
					break;
				}
			}
		}

		if( Fillage == FillCategory.Full )
		{
			if( !this.IsEdifice() )
				yield return "fillPercent is 1.00 but is not edifice";

		//	if( !blockLight )
		//		yield return "fillPercent is 1.00 but does not block light";
		}

		if( MadeFromStuff && constructEffect != null )
			yield return defName + " is madeFromStuff but has a defined constructEffect (which will always be overridden by stuff's construct animation).";

		if( MadeFromStuff && stuffCategories.NullOrEmpty() )
			yield return "madeFromStuff but has no stuffCategories.";

		if( costList.NullOrEmpty() && costStuffCount <= 0 && recipeMaker != null )
			yield return "has a recipeMaker but no costList or costStuffCount.";

		if( this.GetStatValueAbstract( StatDefOf.DeteriorationRate ) > 0.00001f && !CanEverDeteriorate )
			yield return "has >0 DeteriorationRate but can't deteriorate.";

		if( drawerType == DrawerType.MapMeshOnly && comps.Any( c=>c.compClass == typeof(CompForbiddable) ) )
			yield return "drawerType=MapMeshOnly but has a CompForbiddable, which must draw in real time.";

		if( smeltProducts != null && smeltable )
			yield return "has smeltProducts but has smeltable=false";

		if( equipmentType != EquipmentType.None && verbs.NullOrEmpty() && tools.NullOrEmpty() )
			yield return "is equipment but has no verbs or tools";

		if( graphicData != null && graphicData.shadowData != null  )
		{
			if( castEdgeShadows )
				yield return "graphicData defines a shadowInfo but castEdgeShadows is also true";

			if( staticSunShadowHeight > 0)
				yield return "graphicData defines a shadowInfo but staticSunShadowHeight > 0";
		}

		if( race != null && verbs != null )
		{
			for( int i=0; i<verbs.Count; i++ )
			{
				if( verbs[i].linkedBodyPartsGroup != null && !race.body.AllParts.Any(part=>part.groups.Contains(verbs[i].linkedBodyPartsGroup) ) )
				{
					yield return "has verb with linkedBodyPartsGroup " + verbs[i].linkedBodyPartsGroup + " but body " + race.body + " has no parts with that group.";
				}
			}
		}

		if( race != null && tools != null )
		{
			for( int i=0; i<tools.Count; i++ )
			{
				if( tools[i].linkedBodyPartsGroup != null && !race.body.AllParts.Any(part=>part.groups.Contains(tools[i].linkedBodyPartsGroup) ) )
				{
					yield return "has tool with linkedBodyPartsGroup " + tools[i].linkedBodyPartsGroup + " but body " + race.body + " has no parts with that group.";
				}
			}
		}

		if( building != null )
		{
			foreach( var err in building.ConfigErrors(this) )
			{
				yield return err;
			}
		}

		if( apparel != null )
		{
			foreach( var err in apparel.ConfigErrors(this) )
			{
				yield return err;
			}
		}

		if( comps != null )
		{
			for( int i=0; i<comps.Count; i++ )
			{
				foreach( var err in comps[i].ConfigErrors(this) )
				{
					yield return err;
				}
			}
		}

		if( race != null )
		{
			foreach( var e in race.ConfigErrors() )
			{
				yield return e;
			}
		}

		if( ingestible != null )
		{
			foreach( var e in ingestible.ConfigErrors(this) )
			{
				yield return e;
			}
		}

		if( plant != null )
		{
			foreach( var e in plant.ConfigErrors() )
			{
				yield return e;
			}
		}

		if( recipes != null && race != null )
		{
			foreach( var r in recipes )
			{
				if( r.requireBed != race.FleshType.requiresBedForSurgery )
					yield return string.Format("surgery bed requirement mismatch; flesh-type {0} is {1}, recipe {2} is {3}", race.FleshType, race.FleshType.requiresBedForSurgery, r, r.requireBed);
			}
		}

		if( tools != null )
		{
			var dupeTool = tools.SelectMany(lhs => tools.Where(rhs => lhs != rhs && lhs.Id == rhs.Id)).FirstOrDefault();
			if( dupeTool != null )
				yield return string.Format("duplicate thingdef tool id {0}", dupeTool.Id);
		}
	}

	public static ThingDef Named(string defName)
	{
		return DefDatabase<ThingDef>.GetNamed(defName);
	}
        
    //========================== Misc ================================

    public string LabelAsStuff
    {
        get
        {
            if (!stuffProps.stuffAdjective.NullOrEmpty())
            {
                return stuffProps.stuffAdjective;
            }
            else
            {
                return label;
            }
        }
    }

	public bool IsWithinCategory(ThingCategoryDef category)
	{
		if( thingCategories == null )
		{
			return false;
		}

		for( int i = 0; i < thingCategories.Count; ++i )
		{
			if( thingCategories[i] == category || thingCategories[i].Parents.Contains(category) )
				return true;
		}

		return false;
	}

	//===========================================================================
	//=========================== Info card stats ===============================
	//===========================================================================

	public override IEnumerable <StatDrawEntry> SpecialDisplayStats()
    {
		if(apparel != null)
        {
            string coveredParts = apparel.GetCoveredOuterPartsString(BodyDefOf.Human);
            yield return new StatDrawEntry( StatCategoryDefOf.Apparel, "Covers".Translate(), coveredParts, 100);   
        }

		if( IsMedicine && MedicineTendXpGainFactor != 1f )
			yield return new StatDrawEntry(StatCategoryDefOf.Basics, "MedicineXpGainFactor".Translate(), MedicineTendXpGainFactor.ToStringPercent());

		if( fillPercent > 0 && fillPercent < 1.0f
			&& (category == ThingCategory.Item || category == ThingCategory.Building || category == ThingCategory.Plant) )
		{
			var sde = new StatDrawEntry(StatCategoryDefOf.Basics, "CoverEffectiveness".Translate(), this.BaseBlockChance().ToStringPercent());
			sde.overrideReportText = "CoverEffectivenessExplanation".Translate();
			yield return sde;
		}

		if( constructionSkillPrerequisite > 0 )
			yield return new StatDrawEntry(StatCategoryDefOf.Basics, "ConstructionSkillRequired".Translate(), constructionSkillPrerequisite.ToString(), overrideReportText: "ConstructionSkillRequiredExplanation".Translate());

        if( !verbs.NullOrEmpty() )
        {
            VerbProperties verb = verbs.Where(x => x.isPrimary).First();

			//Verbs can be native verbs held by pawns, or weapon verbs
			StatCategoryDef verbStatCategory = category == ThingCategory.Pawn
				? verbStatCategory = StatCategoryDefOf.PawnCombat
				: verbStatCategory = StatCategoryDefOf.Weapon;

			float warmup = verb.warmupTime;
			if( warmup > 0 )
			{
				var warmupLabel = category == ThingCategory.Pawn ? "MeleeWarmupTime".Translate() : "WarmupTime".Translate();
				yield return new StatDrawEntry( verbStatCategory, warmupLabel, warmup.ToString("0.##") + " s", 40);
			}

			//NOTE: this won't work with custom projectiles, e.g. mortars
            if(verb.defaultProjectile != null)
			{
				float dam = verb.defaultProjectile.projectile.damageAmountBase;
				yield return new StatDrawEntry( verbStatCategory, "Damage".Translate(), dam.ToString(), 50);
			}
  
            if(verb.LaunchesProjectile)
            {
                int burstShotCount = verb.burstShotCount;
                float burstShotFireRate = 60f / verb.ticksBetweenBurstShots.TicksToSeconds();
                float range = verb.range;
                    
                if(burstShotCount > 1)
                {
                    yield return new StatDrawEntry( verbStatCategory, "BurstShotCount".Translate(), burstShotCount.ToString(), 20);
                    yield return new StatDrawEntry( verbStatCategory, "BurstShotFireRate".Translate(),
                                                                burstShotFireRate.ToString("0.##") + " rpm", 19);
                }
                yield return new StatDrawEntry( verbStatCategory, "Range".Translate(), range.ToString("0.##"), 10);
            }
        }
            
        if( plant != null )
        {
			foreach( var s in plant.SpecialDisplayStats() )
			{
				yield return s;
			}  
        }
            
        if( ingestible != null )
        {
			foreach( var s in ingestible.SpecialDisplayStats(this) )
			{
				yield return s;
			}
        }
            
        if( race != null )
        {
			foreach( var s in race.SpecialDisplayStats(this) )
			{
				yield return s;
			}
        }
                   
        if(isBodyPartOrImplant)
        {
            //We have to iterate through all recipes to see where this body part item or implant is used
            foreach(RecipeDef def in DefDatabase <RecipeDef>.AllDefs.Where(x => x.IsIngredient(this)))
            {
                HediffDef diff = def.addsHediff;
                if(diff != null)
                {
                    if(diff.addedPartProps != null)
                        yield return new StatDrawEntry( StatCategoryDefOf.Basics, "BodyPartEfficiency".Translate(), diff.addedPartProps.partEfficiency.ToStringByStyle(ToStringStyle.PercentZero) );
                        
                    foreach( var s in diff.SpecialDisplayStats() )
                    {
                        yield return s;
                    }
                        
					var vg = diff.CompProps<HediffCompProperties_VerbGiver>();
                    if(vg != null )
                    {
						if( !vg.verbs.NullOrEmpty() )
						{
							var verb = vg.verbs[0];

							if( !verb.MeleeRange )
							{
								//NOTE: this won't work with custom projectiles
								if( verb.defaultProjectile != null )
								{
									int projDamage = verb.defaultProjectile.projectile.damageAmountBase;
									yield return new StatDrawEntry(StatCategoryDefOf.Basics, "Damage".Translate(), projDamage.ToString());
								}
							}
							else
							{
								int meleeDamage = verb.meleeDamageBaseAmount;
								yield return new StatDrawEntry(StatCategoryDefOf.Weapon, "Damage".Translate(), meleeDamage.ToString());
							}
						}
						else if( !vg.tools.NullOrEmpty() )
						{
							var tool = vg.tools[0];
							yield return new StatDrawEntry(StatCategoryDefOf.Weapon, "Damage".Translate(), tool.power.ToString());
						}
                    }

                    // now, check thought defs
                    var thought = DefDatabase<ThoughtDef>.AllDefs.FirstOrDefault(x => x.hediff == diff);

                    if(thought != null && thought.stages != null && thought.stages.Any())
                        yield return new StatDrawEntry(StatCategoryDefOf.Basics, "MoodChange".Translate(), (thought.stages.First().baseMoodEffect).ToStringByStyle(ToStringStyle.Integer, ToStringNumberSense.Offset));
                }
            }
        }

		for( int i = 0; i < comps.Count; i++ )
		{
			foreach( var s in comps[i].SpecialDisplayStats() )
			{
				yield return s;
			}
		}
    }

}
}











