using CodeWalker.GameFiles;
using CodeWalker.Rendering;
using CodeWalker.Utils;
using SharpDX;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CodeWalker.World
{
    public partial class CutsceneForm : Form
    {
        private WorldForm WorldForm;
        private GameFileCache GameFileCache;
        private AudioDatabase AudioDatabase;

        private Cutscene Cutscene = null;

        private bool AnimateCamera = true;
        private bool EnableSubtitles = true;
        private bool EnableAudio = true;
        private bool Playing = false;
        private bool PositionScrolled = false;
        private float Volume = 0.5f;

        class CutsceneDropdownItem
        {
            public RpfEntry RpfEntry { get; set; }

            public override string ToString()
            {
                return RpfEntry?.Path ?? "";
            }
        }

        public CutsceneForm(WorldForm worldForm)
        {
            WorldForm = worldForm;
            GameFileCache = WorldForm.GameFileCache;
            AudioDatabase = new AudioDatabase();
            InitializeComponent();
        }



        public void UpdateAnimation(float elapsed)
        {
            if (Cutscene != null)
            {
                if (Playing)
                {
                    var newt = Cutscene.PlaybackTime + elapsed;
                    Cutscene.Update(newt);

                    if (Cutscene.PlaybackTime != newt)
                    {
                        if (EnableAudio) // handle looping cutscenes, loop the audio also
                        {
                            BeginInvoke(new Action(() => PlayAudio(Cutscene.PlaybackTime)));
                        }
                    }
                }

                if (AnimateCamera && (Cutscene.CameraObject != null))
                {
                    var pos = Cutscene.CameraObject.Position;
                    var rot = Cutscene.CameraObject.Rotation;

                    WorldForm.SetCameraTransform(pos, rot);

                    if (Cutscene.CameraClipUpdate)
                    {
                        //// disabled this because it seems to be causing some rendering issues
                        //WorldForm.SetCameraClipPlanes(Cutscene.CameraNearClip, Cutscene.CameraFarClip);
                        Cutscene.CameraClipUpdate = false;
                    }
                }

            }
        }

        public void GetVisibleYmaps(Camera camera, Dictionary<MetaHash, YmapFile> ymaps)
        {
            //use a temporary ymap for entities?

            var renderer = WorldForm?.Renderer;
            if (renderer == null) return;

            if (Cutscene == null) return;
            Cutscene.Render(renderer);

        }





        private void SelectCutscene(CutsceneDropdownItem dditem)
        {
            Cursor = Cursors.WaitCursor;
            Task.Run(() =>
            {
                CutFile cutFile = null;
                Cutscene cutscene = null;

                if (GameFileCache.IsInited)
                {
                    if (!AudioDatabase.IsInited)
                    {
                        AudioDatabase.Init(GameFileCache);
                    }

                    var entry = dditem?.RpfEntry as RpfFileEntry;
                    if (entry != null)
                    {

                        cutFile = new CutFile(entry);
                        GameFileCache.RpfMan.LoadFile(cutFile, entry);

                        cutscene = new Cutscene();
                        cutscene.Init(cutFile, GameFileCache, WorldForm, AudioDatabase);

                    }
                }

                CutsceneLoaded(cutscene);

            });
        }
        private void CutsceneLoaded(Cutscene cs)
        {
            if (InvokeRequired)
            {
                try
                {
                    Invoke(new Action(() => { CutsceneLoaded(cs); }));
                }
                catch
                { }
                return;
            }

            DisposeAudio();

            Cutscene = cs;

            if (cs != null)
            {
                cs.EnableSubtitles = EnableSubtitles;

                if (Playing)
                {
                    PlayAudio(cs.PlaybackTime);
                }
            }

            LoadTreeView(cs);

            TimeTrackBar.Maximum = (int)(cs.Duration * 10.0f);
            TimeTrackBar.Value = 0;
            UpdateTimeLabel();

            Cursor = Cursors.Default;
        }


        private void LoadTreeView(Cutscene cs)
        {
            CutsceneTreeView.Nodes.Clear();

            var cutFile = cs?.CutFile;
            var cf = cutFile?.CutsceneFile2;
            if (cf != null)
            {
                var csnode = CutsceneTreeView.Nodes.Add(cutFile.FileEntry?.Name);
                csnode.Tag = cs;

                if (cs.SceneObjects != null)
                {
                    var objsnode = csnode.Nodes.Add("Objects");
                    objsnode.Name = "Objects";

                    foreach (var obj in cs.SceneObjects.Values)
                    {
                        var objnode = objsnode.Nodes.Add(obj.ToString());
                        objnode.Tag = obj;
                    }
                }
                if (cf.pCutsceneEventList != null)
                {
                    var evtsnode = csnode.Nodes.Add("Events");
                    evtsnode.Name = "Events";

                    foreach (var evt in cf.pCutsceneEventList)
                    {
                        var evtnode = evtsnode.Nodes.Add(evt.ToString());
                        evtnode.Tag = evt;
                    }
                }
                if (cf.pCutsceneLoadEventList != null)
                {
                    var ldesnode = csnode.Nodes.Add("Load Events");
                    ldesnode.Name = "Load Events";

                    foreach (var lev in cf.pCutsceneLoadEventList)
                    {
                        var ldenode = ldesnode.Nodes.Add(lev.ToString());
                        ldenode.Tag = lev;
                    }
                }



                csnode.Expand();
                CutsceneTreeView.SelectedNode = csnode;
            }

        }



        private void UpdateTimeTrackBar()
        {
            var tim = Cutscene?.PlaybackTime ?? 0.0f;
            var itim = (int)(tim * 10.0f);
            TimeTrackBar.Value = itim;
        }
        private void UpdateTimeLabel()
        {
            var tim = Cutscene?.PlaybackTime ?? 0.0f;
            var dur = Cutscene?.Duration ?? 0.0f;
            TimeLabel.Text = tim.ToString("0.00") + " / " + dur.ToString("0.00");
        }



        private void PlayAudio(float playTime = 0.0f)
        {
            StopAudio();
            if (!EnableAudio) return;
            var sp = Cutscene?.SoundPlayer;
            if (sp != null)
            {
                sp.SetVolume(Volume);
                sp.Play(Cutscene.SoundStartOffset + playTime);
            }
        }
        private void StopAudio()
        {
            var sp = Cutscene?.SoundPlayer;
            if (sp != null)
            {
                sp.Stop();
            }
        }
        private void PauseAudio()
        {
            if (!EnableAudio) return;
            var sp = Cutscene?.SoundPlayer;
            if (sp != null)
            {
                sp.Pause();
            }
        }
        private void ResumeAudio()
        {
            if (!EnableAudio) return;
            var sp = Cutscene?.SoundPlayer;
            if (sp != null)
            {
                sp.Resume();
            }
        }
        private void DisposeAudio()
        {
            var sp = Cutscene?.SoundPlayer;
            if (sp != null)
            {
                sp.Stop();
                sp.DisposeAudio();
            }
        }


        private void CutsceneForm_Load(object sender, EventArgs e)
        {
            if (!GameFileCache.IsInited) return;//what to do here?

            var rpfman = GameFileCache.RpfMan;
            var rpflist = rpfman.AllRpfs; //loadedOnly ? gfc.ActiveMapRpfFiles.Values.ToList() :

            var dditems = new List<CutsceneDropdownItem>();
            foreach (var rpf in rpflist)
            {
                foreach (var entry in rpf.AllEntries)
                {
                    if (entry.NameLower.EndsWith(".cut"))
                    {
                        var dditem = new CutsceneDropdownItem();
                        dditem.RpfEntry = entry;
                        dditems.Add(dditem);
                    }
                }
            }

            CutsceneComboBox.Items.Clear();
            CutsceneComboBox.Items.AddRange(dditems.ToArray());


        }

        private void CutsceneForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            DisposeAudio();
        }

        private void CutsceneForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            WorldForm?.OnCutsceneFormClosed();
        }

        private void CutsceneComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            var item = CutsceneComboBox.SelectedItem as CutsceneDropdownItem;
            SelectCutscene(item);
        }

        private void CutsceneTreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            InfoPropertyGrid.SelectedObject = CutsceneTreeView.SelectedNode?.Tag;
        }

        private void AnimateCameraCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            AnimateCamera = AnimateCameraCheckBox.Checked;

            if (!AnimateCamera)
            {
                //WorldForm?.ResetCameraClipPlanes();
            }
            else
            {
                if (Cutscene != null)
                {
                    Cutscene.CameraClipUpdate = true;
                }
            }
        }

        private void SubtitlesCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            EnableSubtitles = SubtitlesCheckBox.Checked;
            if (Cutscene != null)
            {
                Cutscene.EnableSubtitles = EnableSubtitles;
            }
        }

        private void AudioCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            EnableAudio = AudioCheckBox.Checked;
            if (!EnableAudio)
            {
                StopAudio();
            }
            else
            {
                if (Playing && (Cutscene != null))
                {
                    PlayAudio(Cutscene.PlaybackTime);
                }
            }
        }

        private void PlayStopButton_Click(object sender, EventArgs e)
        {
            if (Playing)
            {
                Playing = false;
                PlayStopButton.Text = "Play";
                PlaybackTimer.Enabled = false;
                StopAudio();
            }
            else
            {
                Playing = true;
                PlayStopButton.Text = "Stop";
                PlaybackTimer.Enabled = true;
                PlayAudio(Cutscene.PlaybackTime);
            }
        }

        private void PlaybackTimer_Tick(object sender, EventArgs e)
        {
            if (Playing)
            {
                UpdateTimeTrackBar();
                UpdateTimeLabel();
            }
        }

        private void TimeTrackBar_Scroll(object sender, EventArgs e)
        {
            PositionScrolled = true;

            var t = TimeTrackBar.Value / 10.0f;

            if (Cutscene != null)
            {
                Cutscene.Update(t);

                if (Playing)
                {
                    PlayAudio(t);
                }
            }

            if (!Playing)
            {
                UpdateTimeLabel();
            }
        }

        private void TimeTrackBar_MouseUp(object sender, MouseEventArgs e)
        {
            if (PositionScrolled)
            {
                PositionScrolled = false;
                return;
            }
            PositionScrolled = false;

            var f = Math.Min(Math.Max((e.X - 13.0f) / (TimeTrackBar.Width - 26.0f), 0.0f), 1.0f);
            var t = f * (TimeTrackBar.Maximum / 10.0f);

            if (Cutscene != null)
            {
                Cutscene.Update(t);

                if (Playing)
                {
                    StopAudio();
                    PlayAudio(t);
                }
            }

            if (!Playing)
            {
                UpdateTimeTrackBar();
                UpdateTimeLabel();
            }
        }

        private void VolumeTrackBar_Scroll(object sender, EventArgs e)
        {
            Volume = VolumeTrackBar.Value / 100.0f;
            var sp = Cutscene?.SoundPlayer;
            if (sp != null)
            {
                sp.SetVolume(Volume);
            }
        }
    }


    [TypeConverter(typeof(ExpandableObjectConverter))] public class Cutscene
    {
        public CutFile CutFile { get; set; } = null;
        private GameFileCache GameFileCache = null;
        private WorldForm WorldForm = null;
        private AudioDatabase AudioDB = null;

        public float[] CameraCutList { get; set; } = null;
        public YcdFile[] Ycds { get; set; } = null;


        public float Duration { get; set; } = 0.0f;
        public float PlaybackTime { get; set; } = 0.0f;
        private bool Seeking = false;

        public bool EnableSubtitles { get; set; } = true;


        public Dictionary<int, CutObject> Objects { get; set; } = null;
        public Dictionary<int, CutsceneObject> SceneObjects { get; set; } = null;
        public CutEvent[] LoadEvents { get; set; } = null;
        public CutEvent[] PlayEvents { get; set; } = null;
        public CutConcatData[] ConcatDatas { get; set; } = null;

        public int NextLoadEvent { get; set; } = 0;
        public int NextPlayEvent { get; set; } = 0;
        public int NextCameraCut { get; set; } = 0;
        public int NextConcatData { get; set; } = 0;

        public Gxt2File Gxt2File { get; set; } = null;

        public Vector3 Position { get; set; }
        public Quaternion Rotation { get; set; }


        public CutsceneObject CameraObject = null;
        public float CameraNearClip { get; set; } = 0.5f;
        public float CameraFarClip { get; set; } = 12000.0f;
        public bool CameraClipUpdate = false;//signal to the form to update the camera clip planes
        public Quaternion CameraRotationOffset = Quaternion.RotationAxis(Vector3.UnitX, -1.57079632679f) * Quaternion.RotationAxis(Vector3.UnitZ, 3.141592653f);

        public AudioPlayer SoundPlayer { get; set; } = null;
        public float SoundStartOffset { get; set; }



        public void Init(CutFile cutFile, GameFileCache gfc, WorldForm wf, AudioDatabase adb)
        {
            CutFile = cutFile;
            GameFileCache = gfc;
            WorldForm = wf;
            AudioDB = adb;

            var csf = cutFile?.CutsceneFile2;
            if (csf == null) return;
            if (gfc == null) return;


            Duration = csf.fTotalDuration;
            CameraCutList = csf.cameraCutList;
            Position = csf.vOffset;
            Rotation = Quaternion.RotationAxis(Vector3.UnitZ, csf.fRotation);
            Objects = csf.ObjectsDict;
            LoadEvents = RecastArray<CutEvent>(csf.pCutsceneLoadEventList);
            PlayEvents = RecastArray<CutEvent>(csf.pCutsceneEventList);
            ConcatDatas = csf.concatDataList;


            LoadYcds();
            CreateSceneObjects();
            RaiseEvents(0.0f);
        }

        private void LoadYcds()
        {
            int cutListCount = (CameraCutList?.Length ?? 0) + 1;
            var shortName = CutFile.FileEntry?.GetShortNameLower() ?? "";
            Ycds = new YcdFile[cutListCount];
            if (!string.IsNullOrEmpty(shortName))
            {
                for (int i = 0; i < cutListCount; i++)
                {
                    var ycdname = shortName + "-" + i.ToString();
                    var ycdhash = JenkHash.GenHash(ycdname);
                    var ycd = GameFileCache.GetYcd(ycdhash);
                    while ((ycd != null) && (!ycd.Loaded))
                    {
                        Thread.Sleep(1);//bite me
                        ycd = GameFileCache.GetYcd(ycdhash);
                    }
                    if (ycd != null)
                    {
                        ycd.BuildCutsceneMap(i);
                    }
                    Ycds[i] = ycd;
                }
            }
        }


        public void Update(float newTime)
        {
            if (newTime > Duration)
            {
                newTime = 0.0f; //stop or loop?
            }

            if (newTime >= PlaybackTime)
            {
                RaiseEvents(newTime);
            }
            else
            {
                //reset playback to beginning, and seek to newTime
                Seeking = true;
                RaiseEvents(Duration);//raise all events up to the end first
                PlaybackTime = 0.0f;
                NextLoadEvent = 0;
                NextPlayEvent = 0;
                NextCameraCut = 0;
                NextConcatData = 0;
                RaiseEvents(newTime);
                Seeking = false;
            }

            PlaybackTime = newTime;

            int cutIndex = 0;
            float cutStart = 0.0f;
            for (cutIndex = 0; cutIndex < CameraCutList?.Length; cutIndex++)
            {
                var cutTime = CameraCutList[cutIndex];
                if (cutTime > newTime) break;
                cutStart = cutTime;
            }

            float cutOffset = newTime - cutStart;//offset into the current cut


            void updateObjectTransform(CutsceneObject obj, ClipMapEntry cme, ushort boneTag, byte posTrack, byte rotTrack)
            {
                if (cme != null)
                {
                    if (cme.Clip is ClipAnimation canim)
                    {
                        if (canim.Animation != null)
                        {
                            var t = canim.GetPlaybackTime(cutOffset);
                            var f = canim.Animation.GetFramePosition(t);
                            var p = canim.Animation.FindBoneIndex(boneTag, posTrack);
                            var r = canim.Animation.FindBoneIndex(boneTag, rotTrack);
                            if (p >= 0) obj.Position = canim.Animation.EvaluateVector4(f, p, true).XYZ();
                            if (r >= 0) obj.Rotation = canim.Animation.EvaluateQuaternion(f, r, true);
                        }
                    }
                    else if (cme.Clip is ClipAnimationList alist)
                    {
                        if (alist.Animations?.Data != null)
                        {
                            foreach (var anim in alist.Animations.Data)
                            {
                                var t = anim.GetPlaybackTime(cutOffset);
                                var f = anim.Animation.GetFramePosition(t);
                                var p = anim.Animation.FindBoneIndex(boneTag, posTrack);
                                var r = anim.Animation.FindBoneIndex(boneTag, rotTrack);
                                if (p >= 0) obj.Position = anim.Animation.EvaluateVector4(f, p, true).XYZ();
                                if (r >= 0) obj.Rotation = anim.Animation.EvaluateQuaternion(f, r, true);
                            }
                        }
                    }
                }
            }




            var ycd = (cutIndex < (Ycds?.Length ?? 0)) ? Ycds[cutIndex] : null;
            if (ycd?.CutsceneMap != null)
            {
                ClipMapEntry cme = null;

                if (CameraObject != null)
                {
                    ycd.CutsceneMap.TryGetValue(CameraObject.Name, out cme);

                    updateObjectTransform(CameraObject, cme, 0, 7, 8);


                    if (cme != null)
                    {
                        var pos = Position;
                        var rot = Rotation;
                        pos = pos + rot.Multiply(CameraObject.Position);
                        rot = rot * CameraObject.Rotation * CameraRotationOffset;
                        CameraObject.Position = pos;
                        CameraObject.Rotation = rot;
                    }

                }

                if (SceneObjects != null)
                {
                    foreach (var obj in SceneObjects.Values)
                    {
                        if (obj.Enabled == false) continue;

                        var pos = Position;
                        var rot = Rotation;
                        var animate = (obj.Ped != null) || (obj.Prop != null) || (obj.Vehicle != null) || (obj.Weapon != null);
                        if (animate)
                        {
                            ycd.CutsceneMap.TryGetValue(obj.AnimHash, out cme);
                            if (cme != null)
                            {
                                cme.OverridePlayTime = true;
                                cme.PlayTime = cutOffset;
                                updateObjectTransform(obj, cme, 0, 5, 6); //using root animation bone ids
                                pos = pos + rot.Multiply(obj.Position);
                                rot = rot * obj.Rotation;
                            }
                            obj.AnimClip = cme;
                        }
                        if (obj.Ped != null)
                        {
                            obj.Ped.Position = pos;
                            obj.Ped.Rotation = rot;
                            obj.Ped.UpdateEntity();
                            obj.Ped.AnimClip = cme;
                        }
                        if (obj.Prop != null)
                        {
                            obj.Prop.Position = pos;
                            obj.Prop.Orientation = rot;
                        }
                        if (obj.Vehicle != null)
                        {
                            obj.Vehicle.Position = pos;
                            obj.Vehicle.Rotation = rot;
                            obj.Vehicle.UpdateEntity();
                        }
                        if (obj.Weapon != null)
                        {
                            obj.Weapon.Position = pos;
                            obj.Weapon.Rotation = rot;
                            obj.Weapon.UpdateEntity();
                        }
                    }
                }




            }


        }


        public void Render(Renderer renderer)
        {

            if (SceneObjects != null)
            {
                foreach (var obj in SceneObjects.Values)
                {
                    if (obj.Enabled == false) continue;

                    if (obj.Ped != null)
                    {
                        renderer.RenderPed(obj.Ped);
                    }
                    if (obj.Prop != null)
                    {
                        renderer.RenderArchetype(obj.Prop.Archetype, obj.Prop, null, true, obj.AnimClip);
                    }
                    if (obj.Vehicle != null)
                    {
                        renderer.RenderVehicle(obj.Vehicle, obj.AnimClip);
                    }
                    if (obj.Weapon != null)
                    {
                        renderer.RenderWeapon(obj.Weapon, obj.AnimClip);
                    }
                }
                foreach (var obj in SceneObjects.Values)
                {
                    if (obj.Enabled == false) continue;

                    if (obj.HideEntity != null)
                    {
                        renderer.RenderHideEntity(obj.HideEntity);
                    }
                }
            }

        }





        private void RaiseEvents(float upToTime)
        {

            int i;
            for (i = NextLoadEvent; i < LoadEvents?.Length; i++)
            {
                var e = LoadEvents[i];
                if (e != null)
                {
                    if (e.fTime > upToTime) break;
                    RaiseEvent(e);
                }
            }
            NextLoadEvent = i;

            for (i = NextPlayEvent; i < PlayEvents?.Length; i++)
            {
                var e = PlayEvents[i];
                if (e != null)
                {
                    if (e.fTime > upToTime) break;
                    RaiseEvent(e);
                }
            }
            NextPlayEvent = i;

            for (i = NextCameraCut; i < CameraCutList?.Length; i++)
            {
                var c = CameraCutList[i];
                if (c > upToTime) break;
            }
            NextCameraCut = i;

            for (i = NextConcatData; i < ConcatDatas?.Length; i++)
            {
                var c = ConcatDatas[i];
                if (c.fStartTime > upToTime) break;
                if (c.cSceneName == 0) break;

                Position = c.vOffset;
                Rotation = Quaternion.RotationAxis(Vector3.UnitZ, c.fRotation * 0.0174532925f); //is this right?
            }
            NextConcatData = i;


        }
        private void RaiseEvent(CutEvent e)
        {

            switch (e.iEventId)
            {
                case CutEventType.LoadScene: LoadScene(e); break;
                case CutEventType.LoadAnimation: LoadAnimation(e); break;
                case CutEventType.LoadAudio: LoadAudio(e); break;
                case CutEventType.LoadModels: LoadModels(e); break;
                case CutEventType.LoadRayfireDes: LoadRayfireDes(e); break;
                case CutEventType.LoadParticles: LoadParticles(e); break;
                case CutEventType.LoadOverlays: LoadOverlays(e); break;
                case CutEventType.LoadGxt2: LoadGxt2(e); break;
                case CutEventType.UnloadModels: UnloadModels(e); break;
                case CutEventType.UnloadRayfireDes: UnloadRayfireDes(e); break;
                case CutEventType.EnableScreenFade: EnableScreenFade(e); break;
                case CutEventType.EnableHideObject: EnableHideObject(e); break;
                case CutEventType.EnableFixupModel: EnableFixupModel(e); break;
                case CutEventType.EnableBlockBounds: EnableBlockBounds(e); break;
                case CutEventType.EnableAnimation: EnableAnimation(e); break;
                case CutEventType.EnableParticleEffect: EnableParticleEffect(e); break;
                case CutEventType.EnableOverlay: EnableOverlay(e); break;
                case CutEventType.EnableAudio: EnableAudio(e); break;
                case CutEventType.EnableCamera: EnableCamera(e); break;
                case CutEventType.EnableLight: EnableLight(e); break;
                case CutEventType.DisableScreenFade: DisableScreenFade(e); break;
                case CutEventType.DisableHideObject: DisableHideObject(e); break;
                case CutEventType.DisableBlockBounds: DisableBlockBounds(e); break;
                case CutEventType.DisableAnimation: DisableAnimation(e); break;
                case CutEventType.DisableParticleEffect: DisableParticleEffect(e); break;
                case CutEventType.DisableOverlay: DisableOverlay(e); break;
                case CutEventType.DisableAudio: DisableAudio(e); break;
                case CutEventType.DisableCamera: DisableCamera(e); break;
                case CutEventType.DisableLight: DisableLight(e); break;
                case CutEventType.Subtitle: Subtitle(e); break;
                case CutEventType.PedVariation: PedVariation(e); break;
                case CutEventType.CameraCut: CameraCut(e); break;
                case CutEventType.CameraShadowCascade: CameraShadowCascade(e); break;
                case CutEventType.CameraUnk1: CameraUnk1(e); break;
                case CutEventType.CameraUnk2: CameraUnk2(e); break;
                case CutEventType.CameraUnk3: CameraUnk3(e); break;
                case CutEventType.CameraUnk4: CameraUnk4(e); break;
                case CutEventType.CameraUnk5: CameraUnk5(e); break;
                case CutEventType.CameraUnk6: CameraUnk6(e); break;
                case CutEventType.CameraUnk7: CameraUnk7(e); break;
                case CutEventType.CameraUnk8: CameraUnk8(e); break;
                case CutEventType.DecalUnk1: DecalUnk1(e); break;
                case CutEventType.DecalUnk2: DecalUnk2(e); break;
                case CutEventType.PropUnk1: PropUnk1(e); break;
                case CutEventType.Unk1: Unk1(e); break;
                case CutEventType.Unk2: Unk2(e); break;
                case CutEventType.VehicleUnk1: VehicleUnk1(e); break;
                case CutEventType.PedUnk1: PedUnk1(e); break;
                default: break;
            }
            
        }

        private void LoadScene(CutEvent e)
        {
            var args = e.EventArgs as CutLoadSceneEventArgs;
            if (args == null)
            { return; }


            Position = args.vOffset;
            Rotation = Quaternion.RotationAxis(Vector3.UnitZ, args.fRotation * 0.0174532925f);//is this right?

        }
        private void LoadAnimation(CutEvent e)
        {
            var args = e.EventArgs as CutNameEventArgs;
            if (args == null)
            { return; }

        }
        private void LoadAudio(CutEvent e)
        {
            var args = e.EventArgs as CutNameEventArgs;
            if (args == null)
            { return; }

            var obje = e as CutObjectIdEvent;
            if (obje == null)
            { return; }

            var obj = obje.Object as CutAudioObject;
            if (obj == null)
            { return; }

            if (Seeking) return;

            if (SceneObjects.TryGetValue(obje.iObjectId, out CutsceneObject audobj))
            {
                if (audobj.SoundPlayer != null)
                {
                    SoundStartOffset = obj.fOffset;
                    if (SoundPlayer != audobj.SoundPlayer)
                    {
                        if (SoundPlayer != null)
                        {
                            SoundPlayer.Stop();
                            SoundPlayer.DisposeAudio();
                            SoundPlayer = null;
                        }
                        SoundPlayer = audobj.SoundPlayer;
                    }
                }
                else
                { }
            }
            else
            { }
        }
        private void LoadModels(CutEvent e)
        {
            var args = e.EventArgs as CutObjectIdListEventArgs;
            if (args == null)
            { return; }

            if (args.iObjectIdList == null) return;

            foreach (var objid in args.iObjectIdList)
            {
                CutsceneObject obj = null;
                SceneObjects.TryGetValue(objid, out obj);
                if (obj != null)
                {
                    obj.Enabled = true;
                }
            }
        }
        private void LoadRayfireDes(CutEvent e)
        {
        }
        private void LoadParticles(CutEvent e)
        {
            var args = e.EventArgs as CutObjectIdListEventArgs;
            if (args == null)
            { return; }

        }
        private void LoadOverlays(CutEvent e)
        {
            var args = e.EventArgs as CutObjectIdListEventArgs;
            if (args == null)
            { return; }

        }
        private void LoadGxt2(CutEvent e)
        {
            if (GameFileCache == null)
            { return; }
            if (Gxt2File != null)
            { }

            var args = e.EventArgs as CutFinalNameEventArgs;
            if (args == null)
            { return; }

            var namel = args.cName?.ToLowerInvariant();
            var namehash = JenkHash.GenHash(namel);

            RpfFileEntry gxt2entry = null;
            GameFileCache.Gxt2Dict.TryGetValue(namehash, out gxt2entry);

            if (gxt2entry != null) //probably should do this load async
            {
                Gxt2File = GameFileCache.RpfMan.GetFile<Gxt2File>(gxt2entry);

                if (Gxt2File != null)
                {
                    for (int i = 0; i < Gxt2File.TextEntries.Length; i++)
                    {
                        var te = Gxt2File.TextEntries[i];
                        GlobalText.Ensure(te.Text, te.Hash);
                    }
                }
            }

        }
        private void UnloadModels(CutEvent e)
        {
            var args = e.EventArgs as CutObjectIdListEventArgs;
            if (args == null)
            { return; }

            if (args.iObjectIdList == null) return;

            foreach (var objid in args.iObjectIdList)
            {
                CutsceneObject obj = null;
                SceneObjects.TryGetValue(objid, out obj);
                if (obj != null)
                {
                    obj.Enabled = false;
                }
            }
        }
        private void UnloadRayfireDes(CutEvent e)
        {
        }
        private void EnableHideObject(CutEvent e)
        {
            var oe = e as CutObjectIdEvent;
            if (oe == null) return;

            CutsceneObject cso = null;
            SceneObjects.TryGetValue(oe.iObjectId, out cso);
            if (cso != null)
            {
                cso.Enabled = true;
            }
        }
        private void EnableFixupModel(CutEvent e)
        {
        }
        private void EnableBlockBounds(CutEvent e)
        {
            var oe = e as CutObjectIdEvent;
            if (oe == null) return;

        }
        private void EnableScreenFade(CutEvent e)
        {
        }
        private void EnableAnimation(CutEvent e)
        {
            var oe = e as CutObjectIdEvent;
            if (oe == null) return;

        }
        private void EnableParticleEffect(CutEvent e)
        {
        }
        private void EnableOverlay(CutEvent e)
        {
        }
        private void EnableAudio(CutEvent e)
        {
            var args = e.EventArgs as CutNameEventArgs;
            if (args == null)
            { return; }

        }
        private void EnableCamera(CutEvent e)
        {
            var oe = e as CutObjectIdEvent;
            if (oe == null) return;

        }
        private void EnableLight(CutEvent e)
        {
        }
        private void DisableHideObject(CutEvent e)
        {
            var oe = e as CutObjectIdEvent;
            if (oe == null) return;

            CutsceneObject cso = null;
            SceneObjects.TryGetValue(oe.iObjectId, out cso);
            if (cso != null)
            {
                cso.Enabled = false;
            }
        }
        private void DisableBlockBounds(CutEvent e)
        {
            var oe = e as CutObjectIdEvent;
            if (oe == null) return;

        }
        private void DisableScreenFade(CutEvent e)
        {
        }
        private void DisableAnimation(CutEvent e)
        {
            var oe = e as CutObjectIdEvent;
            if (oe == null) return;

        }
        private void DisableParticleEffect(CutEvent e)
        {
        }
        private void DisableOverlay(CutEvent e)
        {
        }
        private void DisableAudio(CutEvent e)
        {
            var args = e.EventArgs as CutNameEventArgs;
            if (args == null)
            { return; }

        }
        private void DisableCamera(CutEvent e)
        {
            var oe = e as CutObjectIdEvent;
            if (oe == null) return;

        }
        private void DisableLight(CutEvent e)
        {
        }
        private void Subtitle(CutEvent e)
        {
            var args = e.EventArgs as CutSubtitleEventArgs;
            if (args == null)
            { return; }

            if (!EnableSubtitles) return;
            if (Seeking) return; //don't raise subtitle events while seeking backwards...

            if (WorldForm != null)
            {
                var txt = args.cName.ToString();
                var dur = args.fSubtitleDuration;

                txt = txt.Replace("~z~", "");
                txt = txt.Replace("~c~~n~", "\n - ");
                txt = txt.Replace("~n~", "\n");
                txt = txt.Replace("~c~", " - ");
                txt = txt.Replace("~t~", " - ");

                WorldForm.ShowSubtitle(txt, dur);
            }
        }
        private void PedVariation(CutEvent e)
        {
            var args = e.EventArgs as CutObjectVariationEventArgs;
            if (args == null)
            { return; }

            var oe = e as CutObjectIdEvent;
            if (oe == null)
            { return; }

            if (Seeking) return; //this gets a bit messy when seeking backwards

            CutsceneObject cso = null;
            SceneObjects.TryGetValue(oe.iObjectId, out cso);

            if (cso?.Ped != null)
            {
                int comp = args.iComponent;
                int drbl = args.iDrawable;
                int texx = args.iTexture;

                Task.Run(() =>
                {
                    cso.Ped.SetComponentDrawable(comp, drbl, 0, texx, GameFileCache);
                });
            }


        }
        private void CameraCut(CutEvent e)
        {
            var args = e.EventArgs as CutCameraCutEventArgs;
            if (args == null)
            { return; }

            var oe = e as CutObjectIdEvent;
            if (oe == null)
            { return; }


            CutsceneObject obj = null;
            SceneObjects.TryGetValue(oe.iObjectId, out obj);
            if (obj == null)
            { return; }


            var pos = Position;
            var rot = Rotation * Quaternion.RotationAxis(Vector3.UnitX, 1.57079632679f);
            obj.Position = pos + rot.Multiply(args.vPosition);
            obj.Rotation = rot * args.vRotationQuaternion;

            CameraNearClip = (args.fNearDrawDistance > 0) ? Math.Min(args.fNearDrawDistance, 0.5f) : 0.5f;
            CameraFarClip = (args.fFarDrawDistance > 0) ? Math.Max(args.fFarDrawDistance, 1000.0f) : 12000.0f;
            CameraClipUpdate = true;
            CameraObject = obj;
        }
        private void CameraShadowCascade(CutEvent e)
        {
        }
        private void CameraUnk1(CutEvent e)
        {
        }
        private void CameraUnk2(CutEvent e)
        {
        }
        private void CameraUnk3(CutEvent e)
        {
        }
        private void CameraUnk4(CutEvent e)
        {
        }
        private void CameraUnk5(CutEvent e)
        {
        }
        private void CameraUnk6(CutEvent e)
        {
        }
        private void CameraUnk7(CutEvent e)
        {
        }
        private void CameraUnk8(CutEvent e)
        {
        }
        private void DecalUnk1(CutEvent e)
        {
        }
        private void DecalUnk2(CutEvent e)
        {
        }
        private void PropUnk1(CutEvent e)
        {
        }
        private void Unk1(CutEvent e)
        {
        }
        private void Unk2(CutEvent e)
        {
        }
        private void VehicleUnk1(CutEvent e)
        {
        }
        private void PedUnk1(CutEvent e)
        {
        }



        private T[] RecastArray<T>(object[] arr) where T : class
        {
            if (arr == null) return null;
            var r = new T[arr.Length];
            for (int i = 0; i < arr.Length; i++)
            {
                r[i] = arr[i] as T;
            }
            return r;
        }


        private void CreateSceneObjects()
        {
            SceneObjects = new Dictionary<int, CutsceneObject>();

            if (Objects == null) return;


            var refCounts = new Dictionary<MetaHash, int>();

            foreach (var obj in Objects.Values)
            {
                var sobj = new CutsceneObject();
                sobj.Init(obj, GameFileCache, AudioDB);
                SceneObjects[sobj.ObjectID] = sobj;

                if (sobj.AnimHash != 0)
                {
                    int refcount = 0;
                    var hash = sobj.AnimHash;
                    refCounts.TryGetValue(hash, out refcount);
                    if (refcount > 0)
                    {
                        var newstr = hash.ToString() + "^" + refcount.ToString();
                        sobj.AnimHash = JenkHash.GenHash(newstr);
                    }
                    refcount++;
                    refCounts[hash] = refcount;
                }

            }
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class CutsceneObject
    {
        public int ObjectID { get; set; }
        public CutObject CutObject { get; set; }
        public MetaHash Name { get; set; }

        public Vector3 Position { get; set; }
        public Quaternion Rotation { get; set; }

        public Ped Ped { get; set; }
        public YmapEntityDef Prop { get; set; }
        public Vehicle Vehicle { get; set; }
        public Weapon Weapon { get; set; }
        public YmapEntityDef HideEntity { get; set; }

        public MetaHash AnimHash { get; set; }
        public ClipMapEntry AnimClip { get; set; }

        public Dat54Sound SoundInfo { get; set; }
        public AwcStream[] SoundStreams { get; set; }
        public AudioPlayer SoundPlayer { get; set; }

        public bool Enabled { get; set; } = false;


        public void Init(CutObject obj, GameFileCache gfc, AudioDatabase adb)
        {
            CutObject = obj;
            ObjectID = obj?.iObjectId ?? -1;

            if (obj is CutNamedObject nobj)
            {
                Name = nobj.cName;
            }

            if (obj is CutAnimationManagerObject anim)
            {
            }
            else if (obj is CutAssetManagerObject ass)
            {
            }
            else if (obj is CutCameraObject cam)
            {
            }
            else if (obj is CutAudioObject aud)
            {
                InitAudio(aud, gfc, adb);
            }
            else if (obj is CutPedModelObject ped)
            {
                InitPed(ped, gfc);
            }
            else if (obj is CutPropModelObject prop)
            {
                InitProp(prop, gfc);
            }
            else if (obj is CutVehicleModelObject veh)
            {
                InitVehicle(veh, gfc);
            }
            else if (obj is CutWeaponModelObject weap)
            {
                InitWeapon(weap, gfc);
            }
            else if (obj is CutHiddenModelObject hid)
            {
                InitHiddenModel(hid, gfc);
            }
            else if (obj is CutFixupModelObject fix)
            {
            }
            else if (obj is CutRayfireObject rayf)
            {
            }
            else if (obj is CutParticleEffectObject eff)
            {
            }
            else if (obj is CutAnimatedParticleEffectObject aeff)
            {
            }
            else if (obj is CutLightObject light)
            {
            }
            else if (obj is CutAnimatedLightObject alight)
            {
            }
            else if (obj is CutDecalObject dec)
            {
            }
            else if (obj is CutOverlayObject ovr)
            {
            }
            else if (obj is CutSubtitleObject sub)
            {
            }
            else if (obj is CutBlockingBoundsObject blk)
            {
            }
            else if (obj is CutScreenFadeObject fad)
            {
            }
            else
            { }
        }

        private void InitAudio(CutAudioObject aud, GameFileCache gfc, AudioDatabase adb)
        {

            //how to know the correct name/hash to use?
            //sound name in the format: cutscenes_name_mastered_only
            var name = aud.cName.ToCleanString().ToLowerInvariant().Replace(".wav", "");
            var soundname = "cutscenes_" + name + "_mastered";
            var soundname2 = "cutscenes_" + name + "_mastered_only";
            uint soundhash = JenkHash.GenHash(soundname);
            uint soundhash2 = JenkHash.GenHash(soundname2);

            if (adb?.SoundsDB != null)
            {
                if (adb.SoundsDB.TryGetValue(soundhash, out Dat54Sound snd))
                {
                    SoundInfo = snd;
                }
                else if (adb.SoundsDB.TryGetValue(soundhash2, out Dat54Sound snd2))
                {
                    SoundInfo = snd2;
                }
            }


            if (SoundInfo is Dat54StreamingSound strsnd)
            {
                int dur = strsnd.Duration;
                MetaHash awchash = 0;
                AwcFile awc = null;

                var streaminfs = new List<Dat54SimpleSound>();
                var streamlist = new List<AwcStream>();

                foreach (var chan in strsnd.ChildSounds)
                {
                    if (chan is Dat54SimpleSound chansnd)
                    {
                        var chanawchash = chansnd.ContainerName;
                        if (chanawchash != awchash)
                        {
                            awchash = chanawchash;
                            if (adb.ContainerDB.TryGetValue(awchash, out RpfFileEntry awcentry))
                            {
                                awc = new AwcFile();
                                gfc.RpfMan.LoadFile(awc, awcentry);
                            }
                            else
                            { }
                        }

                        if (awc?.StreamDict != null)
                        {
                            var chanhash = chansnd.FileName & 0x1FFFFFFF;
                            if (awc.StreamDict.TryGetValue(chanhash, out AwcStream chanstream))
                            {
                                streaminfs.Add(chansnd);
                                streamlist.Add(chanstream);
                            }
                            else
                            { }
                        }
                        else
                        { }
                    }
                    else
                    { }
                }

                var streams = streamlist.ToArray();

                SoundPlayer = new AudioPlayer();
                SoundPlayer.LoadAudio(streams);
                for (int i = 0; i < streaminfs.Count; i++)
                {
                    var streaminf = streaminfs[i];
                    var left = 1.0f;
                    var right = 1.0f;
                    switch (streaminf.Header?.Pan ?? 0)
                    {
                        case 0://center/default
                            left = 1.0f;
                            right = 1.0f;
                            break;
                        case 0x133: // 307://left channel
                            left = 1.0f;
                            right = 0.0f;
                            break;
                        case 0x35: // 53://right channel
                            left = 0.0f;
                            right = 1.0f;
                            break;
                        default:
                            break;
                    }
                    SoundPlayer.SetOutputMatrix(i, left, right);
                }

            }
            else if (SoundInfo != null)
            { }
            if (SoundInfo == null)
            { }


        }

        private void InitPed(CutPedModelObject ped, GameFileCache gfc)
        {

            Ped = new Ped();
            Ped.Init(ped.StreamingName, gfc);
            Ped.LoadDefaultComponents(gfc);

            //if (ped.StreamingName == JenkHash.GenHash("player_zero"))
            //{
            //    //for michael, switch his outfit so it's not glitching everywhere (until it's fixed?)
            //    Ped.SetComponentDrawable(3, 27, 0, 0, gfc);
            //    Ped.SetComponentDrawable(4, 19, 0, 0, gfc);
            //    Ped.SetComponentDrawable(6, null, null, gfc);
            //}

            AnimHash = ped.StreamingName;
        }

        private void InitProp(CutPropModelObject prop, GameFileCache gfc)
        {

            Prop = new YmapEntityDef();
            Prop.SetArchetype(gfc.GetArchetype(prop.StreamingName));

            AnimHash = prop.StreamingName;
        }

        private void InitVehicle(CutVehicleModelObject veh, GameFileCache gfc)
        {
            var name = veh.StreamingName.ToString();

            Vehicle = new Vehicle();
            Vehicle.Init(name, gfc);

            AnimHash = veh.StreamingName;
        }

        private void InitWeapon(CutWeaponModelObject weap, GameFileCache gfc)
        {
            var name = weap.StreamingName.ToString();

            Weapon = new Weapon();
            Weapon.Init(name, gfc);

            AnimHash = weap.StreamingName;
        }

        private void InitHiddenModel(CutHiddenModelObject hid, GameFileCache gfc)
        {

            HideEntity = new YmapEntityDef();
            HideEntity._CEntityDef.archetypeName = hid.cName;
            HideEntity.SetPosition(hid.vPosition);

        }


        public override string ToString()
        {
            return CutObject?.ToString() ?? (ObjectID.ToString() + ": " + Name.ToString());
        }
    }




}
