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

namespace Rimworld_Animations {
    public class CompExtendedAnimator : ThingComp
    {

        // CompExtendedAnimator
        // Helps manage AnimationQueue, AbsolutePosition
        //ticks of current animation
        private int animationTicks;

        private List<AnimationDef> animationQueue;
        private BaseExtendedAnimatorAnchor anchor;
        private VoiceDef voice;


        private bool isAnimating = false;

        public bool IsAnimating
        {
            get
            {
                return isAnimating;
            }
        }

        public bool IsAnchored
        {
            get
            {
                return anchor != null;
            }
        }

        private Vector3? offset;

        public Vector3? Offset
        {
            get
            {
                return offset;
            }
            set
            {
                this.offset = value;
            }
        }

        private int? rotation;

        public int? Rotation
        {
            get
            {
                return rotation;
            }
            set
            {
                this.rotation = value;
            }
        }

        public int AnimationLength
        {
            get
            {
                if (!IsAnimating) return 0;

                int groupAnimLength = 0;
                foreach(AnimationDef anim in animationQueue)
                {
                    groupAnimLength += anim.durationTicks;
                }

                return groupAnimLength;

            }
        }

        public AnimationDef CurrentAnimation { 
            
            get
            {
                return IsAnimating ? animationQueue[0] : null;
            }
        
        }

        public override void PostSpawnSetup(bool respawningAfterLoad)
        {
            if (voice == null)
            {
                AssignNewVoice();
            }
            
        }

        public Vector3 getAnchor()
        {
            return anchor.getDrawPos();
        }

        public override void CompTick()
        {
            if (isAnimating)
            {
                animationTicks++;

                //if animationticks is equal to cur. anim duration,
                if (animationTicks >= animationQueue[0].durationTicks)
                {
                    //dequeue; returns false if more animations
                    if (!PopAnimationQueue())
                    {
                        //play next if more anims still
                        PlayNextAnimation();
                    }
                    else
                    {
                        StopAnimating();
                    }
                }

                CheckAndPlaySounds();

            }
            


            base.CompTick();
        }

        //returns false if still more animations
        public bool PopAnimationQueue()
        {
            
            if (!animationQueue.Empty())
            {
                //pop queue
                animationQueue.RemoveAt(0);
            }

            return animationQueue.Empty();
        }

        public void PlayNextAnimation()
        {
            if (!animationQueue.Empty())
            {
                isAnimating = true;
                animationTicks = 0;
                pawn.Drawer.renderer.SetAnimation(animationQueue[0]);
            }
        }

        public void StopAnimating()
        {
            isAnimating = false;
            animationQueue = null;
            anchor = null;
            offset = null;
            pawn.Drawer.renderer.SetAnimation(null);

            pawn.Drawer.renderer.SetAllGraphicsDirty();
        }

        public void PlayGroupAnimation(List<AnimationDef> groupAnimation, Vector3? positionOffset, int? rotationOffset)
        {
            this.Offset = positionOffset;
            this.Rotation = rotationOffset;
            animationQueue = groupAnimation;

            //set all graphics dirty; necessary because sometimes rjw doesn't call during threesomes
            pawn.Drawer.renderer.SetAllGraphicsDirty();

            PlayNextAnimation();
        }

        public void PlayGroupAnimation(List<AnimationDef> groupAnimation, Vector3? positionOffset, int? rotationOffset, BaseExtendedAnimatorAnchor anchor)
        {
            this.anchor = anchor;
            PlayGroupAnimation(groupAnimation, positionOffset, rotationOffset);
        }

        public override void PostExposeData()
        {
            base.PostExposeData();
            Scribe_Values.Look<bool>(ref this.isAnimating, "animations_isAnimating", false);
            Scribe_Values.Look<int>(ref this.animationTicks, "animations_ticks", 0);
            Scribe_Collections.Look<AnimationDef>(ref animationQueue, "animations_queue");
            Scribe_Deep.Look<BaseExtendedAnimatorAnchor>(ref this.anchor, "animations_anchor");
            Scribe_Defs.Look<VoiceDef>(ref this.voice, "animations_voice");
            
        }

        public override List<PawnRenderNode> CompRenderNodes()
        {
            //only if pawn is animating for performance
            if (IsAnimating)
            {

                List<PawnRenderNode> animRenderNodes = new List<PawnRenderNode>();

                // for all animationpropdefs,
                foreach (AnimationPropDef animationProp in DefDatabase<AnimationPropDef>.AllDefsListForReading)
                {
                    //if animation makes use of prop,
                    if (AnimationMakesUseOfProp(animationProp))
                    {
                        PawnRenderNodeProperties props = animationProp.animPropProperties;

                        if (props.texPath.NullOrEmpty())
                        {
                            
                            props.texPath = "AnimationProps/MissingTexture/MissingTexture";
                        }
                        

                        //create new render node
                        PawnRenderNode animRenderNode = (PawnRenderNode)Activator.CreateInstance(props.nodeClass, new object[] {
                            this.pawn,
                            props,
                            pawn.Drawer.renderer.renderTree
                        });

                        animRenderNodes.Add(animRenderNode);
                    }

                }

                //return list of rendernodes that should animate
                return animRenderNodes;

            }
            else
            {
                return null;
            }

        }

        public void AssignNewVoice()
        {
            //all voice options
            List<VoiceDef> voiceOptions =
                DefDatabase<VoiceDef>.AllDefsListForReading
                .FindAll(voiceDef => voiceDef.VoiceFitsPawn(pawn));

            //all voice options, with priority (for traitdef specific voices)
            List<VoiceDef> voiceOptionsWithPriority =
                voiceOptions.FindAll(voiceDef => voiceDef.takesPriority);

            if (!voiceOptionsWithPriority.NullOrEmpty())
            {
                voice = voiceOptionsWithPriority.RandomElementByWeight(x => x.randomChanceFactor);

            }
            else if (!voiceOptions.NullOrEmpty())
            {
                voice = voiceOptions.RandomElementByWeight(x => x.randomChanceFactor);
            }
                
        }

        public void CheckAndPlaySounds()
        {

            PawnRenderNode rootNode = pawn.Drawer?.renderer?.renderTree?.rootNode;

            //check if the rootnode has sounds; if so play it
            if (rootNode?.AnimationWorker is AnimationWorker_KeyframesExtended animWorker)
            {
                SoundDef sound = animWorker.soundAtTick(rootNode.tree.AnimationTick);

                if (sound != null)
                {
                    SoundInfo soundInfo = new TargetInfo(pawn.Position, pawn.Map);

                    //temp; does not consider non-rjw animations
                    //todo: replace with value stored in comp or somewhere else?
                    soundInfo.volumeFactor = RJWSettings.sounds_sex_volume;

                    sound.PlayOneShot(soundInfo);
                }

                if (RJWAnimationSettings.playVoices)
                {
                    SoundInfo voiceInfo = new TargetInfo(pawn.Position, pawn.Map);
                    voiceInfo.volumeFactor = RJWSettings.sounds_voice_volume;

                    //play voice sounds
                    VoiceTagDef voiceTag = animWorker.voiceAtTick(rootNode.tree.AnimationTick);
                    if (voiceTag != null)
                    {
                        if (voice != null && voice.sounds.ContainsKey(voiceTag))
                        {
                            voice.sounds[voiceTag].PlayOneShot(voiceInfo);
                        }

                        else if (pawn.RaceProps.Humanlike && RJWAnimationSettings.playHumanlikeVoicesAsDefault)
                        {
                            //play default voice
                            VoiceDef pawnDefaultVoice = (pawn.gender == Gender.Male ? VoiceDefOf.Voice_HumanMale : VoiceDefOf.Voice_HumanFemale);
                            if (pawnDefaultVoice.sounds.ContainsKey(voiceTag))
                            {
                                pawnDefaultVoice.sounds[voiceTag].PlayOneShot(voiceInfo);
                            }


                        }

                    }
                }


            }

            //check rootnodes and children
            if (rootNode?.children != null)
            {
                foreach (PawnRenderNode node in rootNode?.children)
                {
                    if (node?.AnimationWorker is AnimationWorker_KeyframesExtended childrenAnimWorker)
                    {
                        

                        SoundDef sound = childrenAnimWorker.soundAtTick(node.tree.AnimationTick);
                        if (sound != null)
                        {
                            SoundInfo soundInfo = new TargetInfo(pawn.Position, pawn.Map);
                            soundInfo.volumeFactor = RJWSettings.sounds_sex_volume;
                            sound.PlayOneShot(soundInfo);
                        }

                        if (RJWAnimationSettings.playVoices)
                        {

                            SoundInfo voiceInfo = new TargetInfo(pawn.Position, pawn.Map);
                            voiceInfo.volumeFactor = RJWSettings.sounds_voice_volume;

                            //play voice sounds
                            VoiceTagDef voiceTag = childrenAnimWorker.voiceAtTick(rootNode.tree.AnimationTick);
                            if (voiceTag != null)
                            {
                                if (voice != null && voice.sounds.ContainsKey(voiceTag))
                                {
                                    voice.sounds[voiceTag].PlayOneShot(voiceInfo);
                                }
                                else if (pawn.RaceProps.Humanlike && RJWAnimationSettings.playHumanlikeVoicesAsDefault)
                                {

                                    VoiceDef pawnDefaultVoice = (pawn.gender == Gender.Male ? VoiceDefOf.Voice_HumanMale : VoiceDefOf.Voice_HumanFemale);
                                    if (pawnDefaultVoice.sounds.ContainsKey(voiceTag))
                                    {
                                        pawnDefaultVoice.sounds[voiceTag].PlayOneShot(voiceInfo);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            //do the same for all the child nodes
            
        }




        public bool AnimationMakesUseOfProp(AnimationPropDef animationProp)
        {
            // never true if not animating; anim props shouldn't be attached
            if (!IsAnimating) return false;

            //for all anims in queue (because it's only recached at start)
            foreach (AnimationDef animation in animationQueue)
            {
                foreach (PawnRenderNodeTagDef propTag in animation.animationParts.Keys)
                {

                    // if that proptag is the same as the one for animationProp,
                    if (propTag == animationProp.animPropProperties.tagDef)
                    {
                        //that prop is being used in the animation
                        return true;
                    }
                }

            }
            
           
            return false;
        }

        private Pawn pawn => base.parent as Pawn;

    }

}

1.5/Source/Comps/CompExtendedAnimator.cs