using RimWorld;
using AlienRace;
using Verse;
using Verse.AI;
using UnityEngine;
using System.Collections.Generic;

using RimWorld;
using System;
using System.Collections.Generic;

namespace LTF_Crystal_Lockdowner
{

    public class LockdownEffect : AbilityUser.Verb_UseAbility
    {
        public static float ageFactor = 20f;

        public virtual void Effect()
        {
            float age = CasterPawn.ageTracker.AgeBiologicalYearsFloat;
            string message = string.Empty;

            if (age < ageFactor)
            {
                message = "Must be over " + ageFactor;
            }
            else
            {
                message = this.Ability.Def.label;
            }

            CreateLockBlocks(CasterPawn);
            MoteMaker.ThrowText(CasterPawn.DrawPos, CasterPawn.Map, message);
        }

        //IntVec3
        public static IntVec3 InvertIntVec3(IntVec3 intVec3)
        {
            IntVec3 temp;
            int x, y, z;
            x = intVec3.x; y = intVec3.y; z = intVec3.z;

            x *= -1;
            z *= -1;

            temp = new IntVec3(x, y, z);
            return (temp);
        }

        public static IntVec3 Front(Rot4 rot)
        {
            return (InvertIntVec3(Behind(rot)));
        }

        public static IntVec3 Left(Rot4 rot)
        {
            return (InvertIntVec3(Right(rot)));
        }

        public static IntVec3 Right(Rot4 rot)
        {
            IntVec3 offset;
            int x, y, z;
            x = 0; y = 0; z = 0;

            if (rot == Rot4.North)
                x += 1;
            else if (rot == Rot4.South)
                x -= 1;
            else if (rot == Rot4.West)
                z -= 1;
            else if (rot == Rot4.East)
                z += 1;

            offset = new IntVec3(x, y, z);
            return (offset);
        }

        public static IntVec3 Behind(Rot4 rot)
        {
            IntVec3 offset;
            int x, y, z;
            x = 0; y = 0; z = 0;

            if (rot == Rot4.North)
                z -= 1;
            else if (rot == Rot4.South)
                z += 1;
            else if (rot == Rot4.West)
                x += 1;
            else if (rot == Rot4.East)
                x -= 1;

            offset = new IntVec3(x, y, z);
            return (offset);
        }

        public static IntVec3 Behind(Pawn pawn)
        {
            return Behind(pawn.Rotation);
        }

        public static void UseCrystalHeart(Pawn pawn)
        {
            Thing consumable = pawn.jobs.curJob.targetA.Thing;
            float age = pawn.ageTracker.AgeBiologicalYearsFloat;
            float radius = 0f;
            IntVec3 pPos = pawn.Position;
            string message = string.Empty;

            Room maybeRoom = null;
            maybeRoom = pawn.GetRoom(RegionType.Set_Passable);
            // Outdoors
            if (maybeRoom.Group.AnyRoomTouchesMapEdge)
            {
                // Denied
                if (age < LockdownEffect.ageFactor)
                {
                    message = "Must be over " + LockdownEffect.ageFactor;
                }
                // Allowed
                else
                {
                    if (consumable != null)
                        message = "Use " + consumable.def.label;

                    message += ":Lockdown";

                    CircleDyn(pawn, !IsNonViolent(pawn));
                }
            }
            // Indoors
            else
            {
                // Allowed
                if (consumable != null)
                    message = "Use " + consumable.def.label;

                message += ":Lockdown";
                EncloseRoom(pawn);
            }

            if (consumable != null)
            {
                consumable.SplitOff(1).Destroy(DestroyMode.Vanish);
            }

            MoteMaker.ThrowText(pawn.DrawPos, pawn.Map, message);

            MoteMaker.MakeWaterSplash(pPos.ToVector3Shifted(), pawn.Map, radius, 15f);
            ThrowMicroFlakes(pPos.ToVector3Shifted(), pawn.Map);

            foreach(IntVec3 puff in GenAdj.CellsAdjacent8Way(pawn))
                MoteMaker.ThrowAirPuffUp(puff.ToVector3(), pawn.Map);
        }

        public static void CreateLockBlocks(Pawn pawn)
        {
            float radius = 0f;
            IntVec3 pPos = pawn.Position;

            

            Room maybeRoom = null;
            //room.Group.AnyRoomTouchesMapEdge
            maybeRoom = pawn.GetRoom(RegionType.Set_Passable);

            if(maybeRoom.Group.AnyRoomTouchesMapEdge)
            {
                CircleDyn(pawn, !IsNonViolent(pawn));
            }
            else
            {
                EncloseRoom(pawn);
            }
            

            MoteMaker.MakeWaterSplash(pPos.ToVector3Shifted(), pawn.Map, radius, 15f);
            ThrowMicroFlakes(pPos.ToVector3Shifted(), pawn.Map);

            foreach (IntVec3 puff in GenAdj.CellsAdjacent8Way(pawn))
                MoteMaker.ThrowAirPuffUp(puff.ToVector3(), pawn.Map);
        }

        public static void BehindBlock(Rot4 rot, IntVec3 pos)
        {
            Building lockBlock = (Building)ThingMaker.MakeThing(ThingDef.Named("LTF_LockdownBlock"), null);
            GenSpawn.Spawn(lockBlock, Behind(rot), Find.VisibleMap, Rot4.North, false);
        }

        public static bool IsNonViolent(Pawn pawn) {
            
            return ( 
                (pawn != null)
                &&
                (pawn.story != null)
                && 
                (pawn.story.WorkTagIsDisabled(WorkTags.Violent))
            );
        }

        public static void EncloseRoom(Pawn pawn)
        {
            Rot4 rot = pawn.Rotation;
            IntVec3 pos = pawn.Position;
            Map map = pawn.Map;
            float pawnAge = pawn.ageTracker.AgeChronologicalYearsFloat;

            Room room = pawn.GetRoom(RegionType.Set_Passable);

            List<IntVec3> doorTiles = new List<IntVec3>();
            List<IntVec3> spawnTiles = new List<IntVec3>();
            int visitedTiles = 0;
            int okTiles = 0;
            int okishTiles = 0;

            //find doors
            foreach (Thing current in room.ContainedAndAdjacentThings)
            {
                //Log.Warning(current.def.defName + " Parsing ");          
                Building maybeBuilding = current as Building;
                if (maybeBuilding != null)
                    if (maybeBuilding.def.IsDoor)
                        doorTiles.Add(maybeBuilding.Position);

                visitedTiles += 1;
            }

            //Log.Warning("Found " + doorTiles.Count + " doors in " + visitedTiles);
            visitedTiles = 0;
            int regionN = 0;
            int roomN = 0;

            //foreach (IntVec3 current in pawn.GetRegion().Cells)
            //foreach (IntVec3 current in room.Cells)
            foreach (Region curRegion in room.Group.Regions)
            {
                foreach (Room curRoom in curRegion.Room.Group.Rooms)
                {
                    foreach (IntVec3 current in curRoom.Cells)
                        spawnTiles.Add(current);

                    roomN += 1;
                }
                regionN += 1;
            }


            foreach (IntVec3 current in spawnTiles)
            {
                foreach (IntVec3 door in doorTiles)
                {
                    if (door.AdjacentTo8WayOrInside(current))
                    {
                        if (CheckTileOnMap(current, map))
                        {
                            if (SafeToSpawn(current, map))
                            {
                                Building lockBlock = (Building)ThingMaker.MakeThing(ThingDef.Named("LTF_LockdownBlock"), null);
                                BodyOverEyesSpectrumPaint(lockBlock, pawn);
                                SnowUtility.AddSnowRadial(current, map, 1f, 1f);

                                if (Rand.Chance(1 - ageFactor / pawnAge + .25f))
                                    MakeBuildingWhiter(lockBlock, 1 - ageFactor / pawnAge);

                                GenSpawn.Spawn(lockBlock, current, Find.VisibleMap, Rot4.North, false);
                                ThrowMicroFlakes(lockBlock.TrueCenter(), pawn.Map);
                                okTiles += 1;
                            }
                        }

                        okishTiles += 1;
                    }

                }

                visitedTiles += 1;
            }


/*
            Log.Warning(
                "close to doors " + okTiles + " / " + okishTiles + " / " + visitedTiles + " / " + room.CellCount + 
                " " + regionN + " regions " + roomN + " rooms"
            );
            */

        }



        public static void CircleDyn(Pawn pawn, bool ageDependantRadius = true)
        {
            Rot4 rot = pawn.Rotation;
            IntVec3 pos = pawn.Position;
            Map map = pawn.Map;

            //Room pawnRoom = pawn.GetRoom(RegionType.Set_Passable);
            
            float pawnAge = pawn.ageTracker.AgeChronologicalYearsFloat;
            // 2 non violent
            float radius = ((ageDependantRadius) ? (pawnAge / ageFactor) : (2));
            //Protection versus "Not enough squares to get to radius 64.72919.Max is 56.40036"
            float maxRadius = 56;
            radius = ((radius > maxRadius) ? (maxRadius) : (radius));

            float snowMax = pawnAge * .7f;
            float snowAvailable = snowMax;
            float randomSnowLimit = 1f;

            // half square root 2; a²+b²=c²; tolerance = c
            float tolerance = 1.4143f / 2f;
            float AgeInstabilityFactor = 10 / pawnAge;
            if (AgeInstabilityFactor > 1)
            {
                AgeInstabilityFactor = 1;
            }
            float AgeConstancyFactor = 1 - AgeInstabilityFactor;

            bool forcedSpectrum = true;
            float WeightWhiteCounter = 1;

            foreach (IntVec3 current in GenRadial.RadialCellsAround(pos, radius, true))
            {

                // circle extremity
                if (
                    (pos == current) ||
                    (pos.DistanceTo(current) < (radius * ((radius - tolerance) / (radius + tolerance))))
                    ) {

                    //if (Rand.Chance(.5f))                        MoteMaker.ThrowAirPuffUp(current.ToVector3(), map);

                    //Log.Warning("Skipping");
                    continue;
                };


                //else
                if (CheckTileOnMap(current, map))
                {
                    Room maybeInside = null;
                    maybeInside = current.GetRoom(map, RegionType.Set_Passable);

                    if(maybeInside != null)
                        if (maybeInside.Group != null)
                            if (!maybeInside.Group.AnyRoomTouchesMapEdge)
                                continue;

                    if (SafeToSpawn(current, map))
                    {
                        Building lockBlock = (Building)ThingMaker.MakeThing(ThingDef.Named("LTF_LockdownBlock"), null);
                        if (Rand.Chance(AgeInstabilityFactor) || forcedSpectrum)
                        {
                            RandomSpectrumPaint(lockBlock, pawn);
                            if (Rand.Chance(AgeConstancyFactor))
                                forcedSpectrum = false;
                        }
                        else
                        {
                            // If eyes color, next block will spectrum
                            forcedSpectrum = BodyOverEyesSpectrumPaint(lockBlock, pawn);
                        }

                        float snowRadius = 0;
                        // Random radius snow on blocks
                        //float snowRadius = tolerance + Rand.Range(-1 * radius, radius) / 4;
                        if (snowAvailable > 0)
                        {
                            snowRadius = tolerance + Rand.Range(-2 * radius, 3 * radius) / 5;
                            snowRadius = ((snowRadius < 0) ? (0) : (snowRadius));
                        }


                        if (snowRadius > 0)
                        {
                            SnowUtility.AddSnowRadial(current, map, snowRadius, 1f);
                            snowAvailable -= 2 * (snowRadius * 3.14f);

                        }

                        //Log.Warning("available snow: " + snowAvailable);
                        //if (Rand.Chance(1/WeightWhiteCounter +.5f))
                        if (snowAvailable > 0)
                            if (MakeBuildingWhiter(lockBlock, (snowAvailable / snowMax) * WeightWhiteCounter))
                            {
                                //WeightWhiteCounter *= 2f;
                                WeightWhiteCounter *= 1 + (snowRadius / radius);
                            }
                            else
                            {
                                WeightWhiteCounter = 1;
                            }
                        else if (Rand.Chance(randomSnowLimit))
                        {

                            MakeBuildingWhiter(lockBlock, ((randomSnowLimit > .5f) ? (.5f) : (randomSnowLimit)));
                            randomSnowLimit *= .5f;
                        }


                        GenSpawn.Spawn(lockBlock, current, Find.VisibleMap, Rot4.North, false);
                        if (WeightWhiteCounter != 1)
                        {
                            //Log.Warning("flakes please");
                            ThrowMicroFlakes(lockBlock.TrueCenter(), pawn.Map);
                        }

                    }
                }

            }
        }
        

        //Colors
        public static bool MakeBuildingWhiter(Building building, float factor=1f)
        {
            //Log.Warning("want to whiten");

            if (building == null)
                return false;

            Color originalColor = building.DrawColor;
            Color newColor;

            float maxValue = Mathf.Max(originalColor.r, originalColor.g, originalColor.b);
            float minValue = Mathf.Min(originalColor.r, originalColor.g, originalColor.b);
            float maxDifference = Mathf.Max(Mathf.Abs(originalColor.r - originalColor.g), Mathf.Abs(originalColor.r - originalColor.b), Mathf.Abs(originalColor.g - originalColor.b));
//            float minDifference = Mathf.Min(Mathf.Abs(originalColor.r - originalColor.g), Mathf.Abs(originalColor.r - originalColor.b), Mathf.Abs(originalColor.g - originalColor.b));

            float randWhiter = maxDifference * Rand.Range(.3f, .75f);

            float newRed = originalColor.r;
            float newGreen = originalColor.g;
            float newBlue = originalColor.b;

            randWhiter *= factor;

            newRed += randWhiter;
            newGreen += randWhiter;
            newBlue += randWhiter;

            newRed = ((newRed > 1) ? (1) : (newRed));
            newGreen = ((newGreen > 1) ? (1) : (newGreen));
            newBlue = ((newBlue > 1) ? (1) : (newBlue));

            newColor = new Color(
                newRed,
                newGreen,
                newBlue,
                originalColor.a
            );

            if (randWhiter > 0)
            {
                building.SetColor(newColor, true);
               // Log.Warning("made it " + randWhiter + " whiter");
                return true;
            }

            return false;
        }

        public static bool BodyOverEyesSpectrumPaint(Building building, Pawn pawn)
        {
            bool ChanceResult = false;

            if (building == null || pawn == null)
                return ChanceResult;

            AlienPartGenerator.AlienComp alienComp = pawn.TryGetComp<AlienPartGenerator.AlienComp>();

            if (alienComp == null)
                return ChanceResult;
            //Color


            // The lower , more body; bigger eyes
            float AgeConstancyFactor = 10 / pawn.ageTracker.AgeChronologicalYearsFloat;

            ChanceResult = Rand.Chance(AgeConstancyFactor);

            Color color1 = alienComp.skinColor;
            Color color2 = alienComp.skinColorSecond;

            Color chosenColor = (ChanceResult) ? (color2) : (color1);

            Color spectrumColor = new Color(
                WeightedRandomColor(chosenColor.r, AgeConstancyFactor),
                WeightedRandomColor(chosenColor.g, AgeConstancyFactor),
                WeightedRandomColor(chosenColor.b, AgeConstancyFactor),
                WeightedRandomColor(chosenColor.a, AgeConstancyFactor, true)
            );

            spectrumColor.a = SomeTransparency(spectrumColor.a);
            building.SetColor(spectrumColor, true);

            return ChanceResult;
        }


        private static float WeightedRandomColor(float color, float variance, bool randIfMax=false, float min=0.65f, float max=0.9f)
        {
            float result = color + Rand.Range(variance*-1, variance);

            if (randIfMax)
            {
                result = ((result > max) ? (Rand.Range(min, max)) : (result));
            }
                

            return result;
        }

        public static void RandomSpectrumPaint(Building building, Pawn pawn)
        {
            AlienPartGenerator.AlienComp alienComp = pawn.TryGetComp<AlienPartGenerator.AlienComp>();
            //Color
            if (alienComp != null)
            {
                Color color1 = alienComp.skinColor;
                Color color2 = alienComp.skinColorSecond;

                Color spectrumColor = new Color(
                    Rand.Range(Mathf.Min(color1.r, color2.r), Mathf.Max(color1.r, color2.r)),
                    Rand.Range(Mathf.Min(color1.g, color2.g), Mathf.Max(color1.g, color2.g)),
                    Rand.Range(Mathf.Min(color1.b, color2.b), Mathf.Max(color1.b, color2.b)),
                    WeightedRandomColor(color1.a, .2f, true)
                );

                spectrumColor.a=SomeTransparency(spectrumColor.a);
                building.SetColor(spectrumColor, true);
            }
        }

        private static float SomeTransparency(float input)
        {
            return ((input > .9|| input < .6) ? (Rand.Range(.6f, .8f)) : (input));
        }
        // checkNoDestroyableItemOnTile
        public static bool SafeToSpawn(IntVec3 tile, Map map)
        {
            bool val = true;

            List<Thing> thingList = tile.GetThingList(map);
            for (int i = 0; i < thingList.Count; i++)
            {
                Building building = thingList[i] as Building;
                if (building != null)
                    return false;

                /*
                Plant plant = thingList[i] as Plant;
                if (plant != null)
                {
                    if (plant.def.defName.Contains("Tree"))
                    {
                        Log.Warning("Found " + plant.def.defName);
                        if (plant.LifeStage == PlantLifeStage.Mature)
                        {
                            val &= false;
                        }
                            
                    }
                }
                */

                Thing thing = thingList[i];
                if (thing != null)
                {
                    
                    Plant plant = thingList[i] as Plant;
                    Projectile projectile = thingList[i] as Projectile;
                    Pawn pawn = thingList[i] as Pawn;

                    if (plant != null)
                    {
                        if (plant.def.defName.Contains("Tree"))
                        {
                           // Log.Warning("Found " + plant.def.defName);
                            if (plant.LifeStage == PlantLifeStage.Mature)
                            {
                                val &= false;
                            }

                        }
                        else
                        {
                            val &= true;
                        }
                    }
                    else
                    // fragile ressources
                    if ((thing.def.IsIngestible && !thing.def.IsCorpse) ||
                        thing.def.IsLeather || 
                        thing.def.IsDrug || 
                        thing.def.IsMedicine ||
                        IsNaturalImplant(thing))
                    {
                        val &= true;
                    }
                    else
                    // rotten body
                    if (thing.def.IsCorpse && thing.IsNotFresh())
                        val &= true;
                    else
                    // Wooden Stone weapon
                    if (thing.def.IsWeapon)
                    {
                        //Log.Warning("Found weap : " + thing.def.defName);
                        if (thing.def.MadeFromStuff && (thing.Stuff != null))
                        {
                            //Log.Warning("Stuff : " + thing.Stuff.defName);
                            if (thing.Stuff.stuffProps == null)
                            {
                                Log.Warning("cant zogzog");
                            }
                            List<StuffCategoryDef> maybeWoodOrStone = thing.Stuff.stuffProps.categories;
                            foreach (StuffCategoryDef curStuff in maybeWoodOrStone)
                            {
                                //Log.Warning("cat : " + curStuff.defName);

                                if ((curStuff.defName == "Woody") ||
                                    (curStuff.defName == "Stony"))
                                {
                                    //val &= !(thing.Stuff.defName == "Jade");

                                    if (thing.Stuff.defName == "Jade")
                                    {
                                        //Log.Warning("gotta save : " + thing.def.label);
                                        val &= false;
                                    }
                                    else
                                    {
                                        
                                        //Log.Warning("allowed to destroy : " + thing.def.label);
                                        val &= true;
                                    }
                                }
                                else
                                {
                                    //Log.Warning("gotta save : " + thing.def.label);
                                    val &= false;
                                }
                            }
                        }
                        else
                        {
                            val &= false;
                        }

                    }
                    else if(thing.def.mote != null || thing.def.IsFilth || projectile != null)
                    {
                        val &= true;
                    }
                    else if (pawn != null)
                    {
                        //Fun with unwalkable but bug ?
                        //val &= true;
                        //pawn.Position.

                        /*
                        int loopBreaker = 10;
                        
                        bool cellFound = false;
                        int visitedCells = 0;
                        
                        while( !cellFound)
                        {
                            foreach(IntVec3 randTp in GenAdj.CellsAdjacent8Way(thing))
                            {
                                if (!randTp.Impassable))
                                {
                                    pawn.Position = randTp;
                                    cellFound = true;
                                }
                                loopBreaker -= 1;
                                if (loopBreaker < 0)
                                {
                                    Log.Warning(pawn.LabelShort + " you failed me");
                                    cellFound = true;
                                }
                                visitedCells += 1;
                            }
                        }
                        Log.Warning(pawn.LabelShort+" We visited " + visitedCells);
                        val &= true;
                        */
                        val &= false;
                    }
                    else
                    {
                        val &= false;
                        /*
                        if(Prefs.DevMode)
                            Log.Warning("protecting " + thing.def.defName);
                        */
                    }
                        

                    /*
                    if (thing.def.defName.Contains("Chunk"))
                        return false;

                    if (thing.def.IsWithinCategory(ThingCategoryDef.Named("ResourcesRaw")))
                        return false;
                    
                    if (thing.def.IsApparel)
                        return false;

                    if (thing.def.IsWeapon)
                        if (thing.def.stuffProps != null)
                            if(thing.def.stuffProps.smeltable)
                                return false;
                    

                        //if ( !(thing.def.IsLeather || thing.def.IsDrug || thing.def.IsMedicine) )
                        //Log.Warning("cat:" + thing.def.category + " defname:" + thing.def.defName);
                        */
                }
            }
           return val;
        }

        private static bool IsNaturalImplant(Thing implant)
        {
            if (implant == null)
                return false;

            return implant.def.IsWithinCategory(ThingCategoryDef.Named("BodyPartsNatural"));
        }
            

        public static bool CheckTileOnMap(IntVec3 tile, Map map)
        {
            return (tile.InBounds(map));
        }

        public static void ThrowMicroFlakes(Vector3 loc, Map map)
        {
            if (!loc.ShouldSpawnMotesAt(map) || map.moteCounter.SaturatedLowPriority)
            {
                return;
            }
            MoteThrown moteThrown = (MoteThrown)ThingMaker.MakeThing(ThingDef.Named("Mote_CrystalSnowFlakes"), null);
            moteThrown.Scale = Rand.Range(0.5f, 1.2f);
            moteThrown.rotationRate = Rand.Range(-12f, 36f);

            moteThrown.exactPosition = loc;
            moteThrown.exactPosition -= new Vector3(0.5f, 0f, 0.5f);
            moteThrown.exactPosition += new Vector3(Rand.Value, 0f, Rand.Value);
            moteThrown.SetVelocity((float)Rand.Range(35, 45), 1.2f);

            GenSpawn.Spawn(moteThrown, loc.ToIntVec3(), map);
        }

        // Necessary for autocomplete ability
        public override void PostCastShot(bool inResult, out bool outResult)
        {
            if (inResult)
            {
                Effect();
                outResult = true;
            }
            outResult = inResult;
        }
    }
}