using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Text;
using UnityEngine;

using Verse.Sound;
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 bool						useHitPoints = true;
	public int						stackLimit = 1;				//How many can be stacked in a square or in inventory	
	public List<CompProperties>		comps = new List<CompProperties>();
	public bool						destroyable = true;
	public IntVec2					size = new IntVec2(1,1);
	public bool						rotatable = true;
	public bool						smallVolume = false;

	//Misc
	public List<ThingCount>			killedLeavings = new List<ThingCount>();
	public List<ThingCount>			butcherProducts = null;
	public List<ThingCount>			smeltProducts = null;
	public bool						randomizeRotationOnSpawn = false;
	public List<DamageMultiplier>	damageMultipliers = null;
    public bool                     isBodyPartOrImplant = false;
	public List<StuffCategoryDef>	stuffCategories = null; //What categories of stuff I can be made of
	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 = new IntVec3(0,0,0);
    public bool						hasInteractionCell = false;	
	public ThingDef					filthLeaving = null;
	public bool						forceDebugSpawnable = false;

	//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; //0..1 range

	//Interface
	public bool						selectable = false;
	public bool 					neverMultiSelect = false;
	public bool						isAutoAttackableWorldObject = false;
	public bool						hasTooltip = false;
	public List<Type>				inspectorTabs = new List<System.Type>();
	[Unsaved] public List<ITab>		inspectorTabsResolved = null;
	public bool						seeThroughFog = false;
	public bool						drawGUIOverlay = false;
	public ResourceCountPriority	resourceReadoutPriority = ResourceCountPriority.Uncounted;
	public bool						resourceReadoutAlwaysShow = false;
	public bool						drawPlaceWorkersWhileSelected = false;

	//AI hints
	public bool						alwaysHaulable = false;
	public bool						designateHaulable = false;
	public List<ThingCategoryDef>	thingCategories = null;
	public bool						EverHaulable{get{return alwaysHaulable || designateHaulable;}}
	public bool						EverStoreable{get{return !thingCategories.NullOrEmpty();}}
	public bool						mineable = false;
	public bool 					combatTargetBuilding = false;	
	public bool						socialPropernessMatters = false;

	//Sounds
	public SoundDef					soundDrop;
	public SoundDef					soundPickup;
	public SoundDef					soundInteract;
	public SoundDef					soundImpactDefault; //Can be overridden by stuff

	//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;
	[Unsaved] public bool			regionBarrier = false;

	//Trade
	public Tradeability				tradeability = Tradeability.Stockable;
	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;
	public List<string>				weaponTags = null;
    public List<string>             techHediffsTags = null;
	public bool						canBeSpawningInventory = true;
	public bool						destroyOnDrop = false;
	public List<StatModifier>		equippedStatOffsets	= null; //How I'll offset pawn's stats when equipped
	
	//Used with blueprints
	public BuildableDef				entityDefToBuild = null;

	//Used by seeds
	public ThingDef					seed_PlantDefToMake = null;	
	
	//Various sub-properties
	public IngestibleProperties		ingestible = null;
	public bool						IsNutritionSource{get{return ingestible != null && ingestible.nutrition > 0;}}
	public FilthProperties			filth = null;	
	public BuildingProperties		building = null;
	public RaceProperties			race = null;
	public ApparelProperties		apparel = null;
	public bool						IsApparel{get{return apparel != null;}}
	public MoteProperties			mote = null;
	public PlantProperties			plant = null;
	public ProjectileProperties		projectile = null;
	public StuffProperties			stuffProps = null;
    public ArtifactProperties       artifact = null;

	//Constants
	private const float				SmallVolumePerUnit = 0.05f;


	//======================== Misc properties ==============================
	public float VolumePerUnit{get{return !smallVolume ? 1 : SmallVolumePerUnit;}}
	public override Color IconDrawColor{get { return graphicData.color; }}
	public override IntVec2 Size{get{return size;}}
	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 bool IsBed { get{return typeof(Building_Bed).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	MadeFromStuff		{get{return !stuffCategories.NullOrEmpty();}}
	public bool EverTransmitsPower
	{
		get
		{
			for( int i=0; i<comps.Count; i++ )
			{
				if( comps[i].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 IsWeapon { get { return !verbs.NullOrEmpty(); } }
	public bool IsRangedWeapon
	{
		get
		{
			if( verbs != null )
			{
				for( int i=0; i<verbs.Count; i++ )
				{
					if( !verbs[i].MeleeRange)
						return true;
				}
			}
			return false;
		}
	}
	public bool IsMeleeWeapon
	{
		get
		{
			if( category == ThingCategory.Item )
			{
				if( verbs != null )
				{
					for( int i=0; i<verbs.Count; i++ )
					{
						if( verbs[i].MeleeRange )
							return true;
					}
				}
			}
			return false;
		}
	}
	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 )
			{
				if( category == ThingCategory.Plant )
					return true;

				return false;
			}

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

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

			//Blueprints and frames inherit from the def they want to build
			if( IsBlueprint || IsFrame )
			{
				ThingDef 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;
		}
	}


	//========================== 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) );
	}


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

	public override void PostLoad()
	{
		if( graphicData != null )
		{
			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();

		//Resolve itabs
		for( int i=0; i<inspectorTabs.Count; i++ )
		{
			if( inspectorTabsResolved == null )
				inspectorTabsResolved = new List<ITab>();
			inspectorTabsResolved.Add( ITabManager.GetSharedInstance( inspectorTabs[i] ) );
		}

		//Calculate regionBarrier
		if( passability == Traversability.Impassable || thingClass == typeof(Building_Door) )
			regionBarrier = true;

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

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

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

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

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

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

        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( 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( ThingCount 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 && category != ThingCategory.Item )
			yield return "has >0 DeteriorationRate but category != Item";

		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( graphicData != null && graphicData.shadowData != null  )
		{
			if( castEdgeShadows )
				yield return "graphicData defines a shadowInfo by castEdgeShadows is also true";

			if( staticSunShadowHeight > 0)
				yield return "graphicData defines a shadowInfo by 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( 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;
			}
		}
	}

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

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

	public CompProperties GetCompProperties( Type compType )
	{
		for( int i=0; i<comps.Count; i++ )
		{
			if( comps[i].compClass == compType )
				return comps[i];
		}
		return null;
	}

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

	//===========================================================================
	//=========================== 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( !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 = GenDate.TicksToSeconds(verb.warmupTicks);
			if( warmup > 0 )
			{
				var warmupLabel = category == ThingCategory.Pawn ? "MeleeWarmupTime".Translate() : "WarmupTime".Translate();
				yield return new StatDrawEntry( verbStatCategory, warmupLabel, warmup.ToString("0.##") + " s", 40);
			}

            if(verb.projectileDef != null)
			{
                float dam = verb.projectileDef.projectile.damageAmountBase;
				yield return new StatDrawEntry( verbStatCategory, "Damage".Translate(), dam.ToString(), 50);
			}
  
            if(verb.projectileDef != null)
            {
                int burstShotCount = verb.burstShotCount;
                float burstShotFireRate = 60f / GenDate.TicksToSeconds(verb.ticksBetweenBurstShots);
                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() )
			{
				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 * 100f).ToString("F0") + "%");
                        
                    foreach( var s in diff.SpecialDisplayStats() )
                    {
                        yield return s;
                    }
                        
					var vg = diff.CompPropsFor( typeof(HediffComp_VerbGiver) );
                    if(vg != null)
                    {
						VerbProperties verb = vg.verbs.FirstOrDefault();

                        if( !verb.MeleeRange )
						{
                            int projDamage = verb.projectileDef.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());
						}
                    }

                    // 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));
                }
            }
            
        }
    }
}
}











