using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Linq;
using Verse;
using RimWorld;
using HarmonyLib;
using rjw;

namespace SCE.Patches
{
    [HarmonyPatch(typeof(SexAppraiser))]
    public static class SexAppraiserPatches
    {
        public const float AnimalFuckabilityNonZoophilePenalty = 0.45f;
        public const float AnimalFuckabilityVenerationBonus = 1.2f;

        [HarmonyPatch("GetOpinionFactor")]
        [HarmonyPostfix]
        static void IncestAdjustment(ref float __result, Pawn fucker, Pawn fucked)
        {
            if (fucker.Ideo == null || !fucker.Ideo.HasPrecept(SCEPreceptDefOf.Incest_FreeApproved))
            {
                return;
            }
            var rel = fucker.GetMostImportantBloodRelation(fucked);
            if (rel != null && rel.romanceChanceFactor > 0)
            {
                float romanceChanceFactor = rel.romanceChanceFactor;
                if (romanceChanceFactor >= 0.1f)
                {
                    __result *= 1f + (0.05f / romanceChanceFactor);
                }
                else
                {
                    __result *= 1.1f + (0.01f / romanceChanceFactor);
                }
            }
        }

        // Counteract the is_zoophile fuckability bonus if fucked is an animal with the wrong veneration status
        [HarmonyPatch("GetBodyFactor")]
        [HarmonyPostfix]
        static void PrimaryBestialityIdeoAdjustment(ref float __result, Pawn fucker, Pawn fucked)
        {
            if (fucker.Ideo == null || !xxx.is_human(fucker))
            {
                return;
            }

            if (fucker.story?.traits?.HasTrait(xxx.zoophile) == true)
            {
                return;
            }

            if (fucker.Ideo.SelectivelyForbidsSexWithAnimal(fucked.def))
            {
                __result *= AnimalFuckabilityNonZoophilePenalty;
            }
        }

        // Increase effective petness of venerated animals if fucker's ideo has either Bestiality_Revered or _VeneratedOnly
        [HarmonyPatch("would_fuck_animal")]
        [HarmonyTranspiler]
        static IEnumerable<CodeInstruction> SecondaryBestialityIdeoAdjustment(IEnumerable<CodeInstruction> unmodifiedInstructions)
        {
            var instructions = unmodifiedInstructions.ToList();

            int insertAt = -1;

            // Find the instruction at which fucked.raceProps.petness is loaded and set the following instruction
            // as our entry point.
            for (int i = 0; i < instructions.Count; i++)
            {
                if (instructions[i].LoadsField(typeof(RaceProperties).GetField("petness")))
                {
                    insertAt = i + 1;
                    break;
                }
            }

            if (insertAt == -1)
            {
                Log.Error($"Could not patch would_fuck_animal: petness is not used");
                return unmodifiedInstructions;
            }

            // Offset petness based on ideo and veneration status
            // Expected C# code: 
            //    float petness = target.RaceProps.petness;
            // -> float petness = target.RaceProps.petness + GetVenerationPetnessOffset(fucker, target);
            List<CodeInstruction> instructionsToAdd = new()
            {
                new CodeInstruction(OpCodes.Ldarg_0),
                new CodeInstruction(OpCodes.Ldarg_1),
                CodeInstruction.Call(typeof(SexAppraiserPatches), nameof(GetVenerationPetnessOffset)),
                new CodeInstruction(OpCodes.Add),
            };

            instructions.InsertRange(insertAt, instructionsToAdd);

            return instructions.AsEnumerable();
        }

        static float GetVenerationPetnessOffset(Pawn pawn, Pawn animal)
        {
            var ideo = pawn.Ideo;
            if (ideo != null && ideo.IsVeneratedAnimal(animal))
            {
                if (ideo.HasPrecept(SCEPreceptDefOf.Bestiality_Revered))
                {
                    return 0.3f;
                }
                else if (ideo.HasPrecept(SCEPreceptDefOf.Bestiality_VeneratedOnly))
                {
                    return 0.2f;
                }
            }
            return 0;
        }

        [HarmonyPatch("would_rape")]
        [HarmonyTranspiler]
        static IEnumerable<CodeInstruction> RapeIdeoAdjustment(IEnumerable<CodeInstruction> instructions)
        {
            bool foundRapeFactorLoc = false;
            
            MethodInfo opinionOf = typeof(Pawn_RelationsTracker).GetMethod("OpinionOf");

            foreach (var instruction in instructions)
            {
                yield return instruction;

                // Add ideo modifiers to rape factor
                // Expected C# code: 
                //    float rapeFactor = 0.3f
                // -> float rapeFactor = 0.3f + IdeoRapeFactorOffset(rapist, rapee);
                if (!foundRapeFactorLoc)
                {
                    if (instruction.opcode == OpCodes.Ldc_R4 && (float) instruction.operand == 0.3f)
                    {
                        yield return new CodeInstruction(OpCodes.Ldarg_0);
                        yield return new CodeInstruction(OpCodes.Ldarg_1);
                        yield return CodeInstruction.Call(typeof(SexAppraiserPatches), "IdeoRapeFactorOffset");
                        yield return new CodeInstruction(OpCodes.Add);

                        foundRapeFactorLoc = true;
                    }
                }
                // Change effective opinion based on precepts and slave status
                // Expected C# code: 
                //    float opinion = (float) rapist.relations.OpinionOf(rapee);
                // -> float opinion = (float) (rapist.relations.OpinionOf(rapee) + IdeoRapeOpinionOffset(rapist, rapee));
                else if (instruction.Calls(opinionOf))
                {
                    yield return new CodeInstruction(OpCodes.Ldarg_0);
                    yield return new CodeInstruction(OpCodes.Ldarg_1);
                    yield return CodeInstruction.Call(typeof(SexAppraiserPatches), "IdeoRapeOpinionOffset");
                }
            }
            
            if (!foundRapeFactorLoc)
            {
                Log.Error("Failed to patch in IdeoRapeFactorOffset.");
            }
        }

        static float IdeoRapeFactorOffset(Pawn pawn, Pawn partner)
        {
            var ideo = pawn.Ideo;
            if (ideo == null)
            {
                return 0;
            }

            float result = 0;

            if (ideo.HasPrecept(SCEPreceptDefOf.Rape_Abhorred))
            {
                result -= 0.25f;
            }
            else if (ideo.HasPrecept(SCEPreceptDefOf.Rape_Horrible))
            {
                result -= 0.1f;
            }
            else if (ideo.HasPrecept(SCEPreceptDefOf.Rape_Honourable))
            {
                result += 0.2f;
            }

            return result;
        }

        static int IdeoRapeOpinionOffset(int originalOpinion, Pawn pawn, Pawn partner)
        {
            if (pawn.Ideo == null)
            {
                return 0;
            }

            int offset = 0;

            if (!pawn.Ideo.AcceptsRape(pawn, partner) || pawn.Ideo.IsVeneratedAnimal(partner))
            {
                offset += 20;
            }

            if (partner.IsSlave)
            {
                offset -= 20;
            }

            return Math.Min(offset + originalOpinion, 100);
        }
    }
}