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

namespace SCE.Patches
{
    [HarmonyPatch]
	public static class AfterSexPatches
	{
        static Type thisType = typeof(AfterSexPatches);

        // Signal that sex has finished to RitualOutcomeComps, StageEndTriggers, etc.
        [HarmonyPatch(typeof(SexUtility), "Aftersex")]
        [HarmonyPostfix]
        static void SendSexFinishedSignal(SexProps props)
        {
            var pawns = new List<Pawn>() { props.pawn, props.partner };
            var signal = new Signal(SignalTags.SexFinished, pawns.Named(SignalArgNames.Pawns));
            Find.SignalManager.SendSignal(signal);
        }

        // Send a signal on orgasm. Unused for now, but it makes too much sense to remove.
        //[HarmonyPatch(typeof(JobDriver_Sex), "Orgasm")]
        //[HarmonyPrefix]
        static void SignalOrgasm(JobDriver_Sex __instance)
        {
            if (__instance.sex_ticks <= __instance.orgasmstick)
            {
                var signal = new Signal(SignalTags.Orgasm, __instance.pawn.Named(SignalArgNames.Pawn));
                Find.SignalManager.SendSignal(signal);
            }
        }

        // Prevent RJW from setting rapist guilt - let precepts handle it instead
        [HarmonyPatch(typeof(AfterSexUtility), "think_about_sex_Victim")]
        [HarmonyPrefix]
        static void UnsetRapistGuilt(ref bool guilty)
        {
            guilty = false;
        }

        public static readonly Dictionary<Pawn, bool> ideoHandlingThoughtsFor = new();

        public static bool skipVictimThoughts;

        public static bool skipWhoringThoughts;

	    [HarmonyPatch(typeof(AfterSexUtility), nameof(AfterSexUtility.think_about_sex), 
            new[] { typeof(Pawn), typeof(Pawn), typeof(bool), typeof(SexProps), typeof(bool) })]
        [HarmonyTranspiler]
        static IEnumerable<CodeInstruction> InsertSexThoughts(ILGenerator generator, IEnumerable<CodeInstruction> instructionEnumerable)
        {
            var instructions = instructionEnumerable.ToList();

            Label? resumeOriginalMethod = null;
            bool foundResumeLabel = false;
            bool finished = false;

            MethodInfo tryGainMemory = AccessTools.Method(typeof(MemoryThoughtHandler), nameof(MemoryThoughtHandler.TryGainMemory),
                new[] {typeof(ThoughtDef), typeof(Pawn), typeof(Precept)}
            );
            MethodInfo getCanBeAwake = AccessTools.Property(typeof(PawnCapacitiesHandler), 
                                            nameof(PawnCapacitiesHandler.CanBeAwake)).GetMethod;


            for (int i = 0; i < instructions.Count; i++)
            {
                var instruction = instructions[i];
                yield return instruction;

                // Find the label the original method jumps to if not giving a memory for getting raped while
                // unconscious, i.e. the point at which the think_about_sex_XYZ submethods are called.
                if (!foundResumeLabel && instruction.Calls(getCanBeAwake))
                {
                    instructions[i+1].Branches(out resumeOriginalMethod);
                    foundResumeLabel = true;
                }
                // Expected C# code:
                //   if (RecordSexEventaAndMemories(pawn, props))
                //   {
                //       return;
                //   }
                //   thoughtGain = (pawn, partner, isReceiving, props, whoring, masochist, zoophile, guilty);
                //   ...
                if (resumeOriginalMethod.HasValue && !finished && instruction.opcode == OpCodes.Ret && instructions[i-1].Calls(tryGainMemory))
                {
                    yield return new CodeInstruction(OpCodes.Ldarg_0);
                    yield return new CodeInstruction(OpCodes.Ldarg_3);
                    yield return CodeInstruction.Call(typeof(AfterSexPatches), nameof(RecordSexEvents));
                    yield return new CodeInstruction(OpCodes.Brfalse, resumeOriginalMethod.Value);
                    yield return new CodeInstruction(OpCodes.Ret);
                }
            }
        }

        public static bool RecordSexEvents(Pawn pawn, SexProps props)
        {
            var eventsToFire = EventUtility.GetSexEventsFor(pawn, props);

            if (!eventsToFire.Any())
            {
                return false;
            }

            eventsToFire.Do(ev => Find.HistoryEventsManager.RecordEvent(ev));

            if (pawn.Ideo == null)
            {
                return false;
            }

            bool skipDefaultMemories = false;

            foreach (var ev in eventsToFire)
            {
                if (pawn.Ideo.Notify_MemberHadSex(ev))
                {
                    skipDefaultMemories = true;
                }
            }

            return skipDefaultMemories;
        }

        // Reduces certainty slightly if a pawn reaches ahegao from sex that goes against their ideo
        // TODO: Add a setting to disable this
        [HarmonyPatch(typeof(SexUtility), "SatisfyPersonal")]
        [HarmonyTranspiler]
        static IEnumerable<CodeInstruction> AhegaoCertaintyEffect(IEnumerable<CodeInstruction> instructions)
        {
            MethodInfo tryGainMemory = AccessTools.Method(typeof(MemoryThoughtHandler), nameof(MemoryThoughtHandler.TryGainMemory),
                new[] {typeof(ThoughtDef), typeof(Pawn), typeof(Precept)});

            bool loadedAhegaoThought = false;
            bool done = false;

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

                if (done)
                {
                    continue;
                }

                // Check for DefDatabase<ThoughtDef>.GetNamed("RJW_Ahegao")
                if (instruction.OperandIs("RJW_Ahegao"))
                {
                    loadedAhegaoThought = true;
                }
                // Inject our code right after the pawn has gained the ahegao thought
                else if (loadedAhegaoThought && instruction.Calls(tryGainMemory))
                {
                    // ApplyAhegaoCertaintyReduction(props);
                    yield return new CodeInstruction(OpCodes.Ldarg_0);
                    yield return CodeInstruction.Call(thisType, nameof(ApplyAhegaoCertaintyReduction));
                    done = true;
                }
            }
        }

        public static void ApplyAhegaoCertaintyReduction(SexProps props)
        {
            const float certaintyOffsetMajor = -0.03f;
            const float certaintyOffsetModerate = -0.01f;
            //const float certaintyOffsetMild = -0.01f;

            var pawn = props.pawn;
            var ideo = pawn.Ideo;
            if (ideo == null)
            {
                return;
            }
            var partner = props.partner;

            float certaintyOffset = 0;

            bool married = pawn.relations.DirectRelationExists(PawnRelationDefOf.Spouse, partner)
                || pawn.CurJobDef == SCEJobDefOf.MarryLewd || pawn.CurJobDef == SCEJobDefOf.BeLewdlyMarried;

            if (ideo.HasPrecept(SCEPreceptDefOf.Lovin_Prohibited))
            {
                certaintyOffset += certaintyOffsetMajor;
            }
            else if (ideo.HasPrecept(SCEPreceptDefOf.Lovin_Horrible))
            {
                certaintyOffset += married ? certaintyOffsetModerate : certaintyOffsetMajor;
            }

            if (xxx.is_animal(partner))
            {
                if (ideo.HasPrecept(SCEPreceptDefOf.Bestiality_Abhorrent))
                {
                    certaintyOffset += certaintyOffsetMajor;
                }
                else if (ideo.HasPrecept(SCEPreceptDefOf.Bestiality_Horrible))
                {
                    certaintyOffset += certaintyOffsetModerate;
                }
                else if (ideo.IsVeneratedAnimal(partner))
                {
                    if (ideo.HasPrecept(SCEPreceptDefOf.Bestiality_NoVenerated))
                    {
                        certaintyOffset += certaintyOffsetMajor;
                    }
                }
                else if (ideo.HasPrecept(SCEPreceptDefOf.Bestiality_VeneratedOnly))
                {
                    certaintyOffset += certaintyOffsetModerate;
                }
            }
            else
            {
                if (props.isRapist)
                {
                    if (ideo.HasPrecept(SCEPreceptDefOf.Rape_Abhorred))
                    {
                        certaintyOffset += certaintyOffsetMajor;
                    }
                    else if (ideo.HasPrecept(SCEPreceptDefOf.Rape_Horrible)
                        || (pawn.gender != Gender.Male && ideo.HasPrecept(SCEPreceptDefOf.Rape_MaleOnly))
                        || (pawn.gender != Gender.Female && ideo.HasPrecept(SCEPreceptDefOf.Rape_FemaleOnly))
                        || (partner.gender == ideo.SupremeGender && pawn.gender != partner.gender))
                    {
                        certaintyOffset += certaintyOffsetModerate;
                    }
                }
                else if (props.isRape && pawn.gender == ideo.SupremeGender && partner.gender != pawn.gender)
                {
                    certaintyOffset += certaintyOffsetModerate;
                }

                if (!married && ideo.HasPrecept(SCEPreceptDefOf.Lovin_SpouseOnly_Strict))
                {
                    certaintyOffset += certaintyOffsetModerate;
                }
            }

            if (props.isWhoring && ideo.HasPrecept(SCEPreceptDefOf.Prostitution_Abhorred))
            {
                certaintyOffset += certaintyOffsetModerate;
            }

            // Using GetMostImportantRelation like this means that SibcestOnly will forbid sex with a parent-sibling but 
            // ParentcestOnly will allow it. This kind of makes sense from a fluff perspective but is difficult to predict here.
            PawnRelationDef relation = pawn.GetMostImportantBloodRelation(partner);
            if (relation != null)
            {
                double incestCertaintyOffset = -0.05 / Math.Max(Math.Sqrt(relation.romanceChanceFactor), 0.01);
                certaintyOffset += (float) Math.Round(incestCertaintyOffset, 3);
            }

            if (certaintyOffset != 0f)
            {
                certaintyOffset *= pawn.GetStatValue(StatDefOf.CertaintyLossFactor) * (ideo.GetRole(pawn)?.def.certaintyLossFactor ?? 1f);
                pawn.ideo.OffsetCertainty(certaintyOffset);
            }
        }
    }
}