using Celeste.Mod.Entities;
using Celeste.Mod.LakeSideCode.FishDefs;
using FMOD.Studio;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Monocle;
using System;
using System.Collections;
using System.Collections.Generic;

namespace Celeste.Mod.LakeSideCode.Triggers
{
    [CustomEntity("LakeSideCode/TerminalTrigger")]
    [Tracked]
    public class TerminalTrigger : Trigger
    {
        // there was originally an effect where the terminals moved the camera
        // i'm leaving the code here commented out just in case
        //private Vector2 CameraOffset;
        private int TerminalID;
        private string TeleportRoom;
        private Vector2 TeleportCoordinates;

        private TalkComponent talk;

        // node is used to determine where the selection screen should pop 
        // out of when terminal is used
        private Vector2 nodePosition;

        private bool hasTeleported = false;

        private static string lastTerminalMusic = "";
        public static string TerminalMusic
        {
            get
            {
                Session session = (Engine.Scene as Level)?.Session;
                if (session != null && session.GetFlag("LS_Nightime"))
                {
                    return "event:/Lakeside_TerminalComplete";
                }
                else
                {
                    return "event:/Lakeside_TerminalIncomplete";
                }
            }
        }
        private static int lastTerminalMusicTimelinePosition;

        public TerminalTrigger(EntityData data, Vector2 offset) : base(data, offset)
        {
            //CameraOffset = new Vector2(data.Float("cameraXOffset", 0) * 8f, data.Float("cameraYOffset", -50) * 8f);
            nodePosition = data.NodesOffset(offset)[0];
            TerminalID = data.Int("terminalID", 1);
            TeleportRoom = data.Attr("teleportRoom");
            TeleportCoordinates = new Vector2(data.Float("teleportX"), data.Float("teleportY"));
            Add(talk = new TalkComponent(new Rectangle(0, 0, data.Width, data.Height), new Vector2(data.Width / 2, 0f), OnInteract));
            talk.PlayerMustBeFacing = false;
        }

        private void OnInteract(Player player)
        {
            if(player.Holding != null) return;

            Coroutine routine = new Coroutine(InteractRoutine(player));
            routine.RemoveOnComplete = true;
            Add(routine);
        }

        private IEnumerator InteractRoutine(Player player)
        {
            // keep the terminal alive even if player teleports
            Tag = Tags.Persistent | Tags.Global;

            player.StateMachine.State = Player.StDummy;
            player.ForceCameraUpdate = true;

            Level level = SceneAs<Level>();
            level.CanRetry = false;
            level.Session.SetFlag($"lakeside_terminal_{TerminalID}");

            StartTerminalMusic();

            //Vector2 cameraTarget = level.Camera.Position + CameraOffset;
            //Vector2 cameraStart = level.Camera.Position;
            //Add(new Coroutine(CutsceneEntity.CameraTo(cameraTarget, 2f, Ease.CubeInOut)));
            //yield return 1.25f;

            yield return 0.25f;

            FishTerminalMenu menu = new FishTerminalMenu(this);
            level.Add(menu);
            while (!menu.Finished)
            {
                yield return null;
            }

            //Add(new Coroutine(CutsceneEntity.CameraTo(cameraStart, 0.8f, Ease.CubeOut)));
            //yield return 0.8f;

            if(hasTeleported) {
                yield return 0.8f;
            }

            EndTerminalMusic();

            yield return 0.2f;

            level.CanRetry = true;

            player.ForceCameraUpdate = false;
            player.StateMachine.State = Player.StNormal;

            Tag = 0;
            // clean up if the player has teleported
            if (hasTeleported)
            {
                RemoveSelf();
                // menu.Add(new Coroutine(menu.ClosingCoroutine()));
            }
        }

        private static void StartTerminalMusic()
        {
            Audio.SetAltMusic(TerminalMusic);
            // if the same track as last time, resume where the track left off
            if (lastTerminalMusic == TerminalMusic)
            {
                Audio.currentAltMusicEvent.setTimelinePosition(lastTerminalMusicTimelinePosition);
            }
            lastTerminalMusic = TerminalMusic;

            // make ambient noises quieter
            if (Level.DialogSnapshot == null)
            {
                Level.DialogSnapshot = Audio.CreateSnapshot(Snapshots.DIALOGUE, start: false);
            }
            Audio.ResumeSnapshot(Level.DialogSnapshot);
        }

        private static void EndTerminalMusic()
        {
            // make note of where the track currently is
            Audio.currentAltMusicEvent.getTimelinePosition(out lastTerminalMusicTimelinePosition);
            Audio.currentAltMusicEvent.getDescription(out var desc);
            desc.getLength(out int length);
            lastTerminalMusicTimelinePosition += 700; // roughly account for fadeout time
            lastTerminalMusicTimelinePosition %= length; // make sure this doesn't put it past the end of the track

            Audio.SetAltMusic(null);

            Audio.EndSnapshot(Level.DialogSnapshot);
        }

        public static void Load()
        {
            Everest.Events.LevelLoader.OnLoadingThread += LevelLoader_OnLoadingThread;
        }

        public static void Unload()
        {
            Everest.Events.LevelLoader.OnLoadingThread -= LevelLoader_OnLoadingThread;
        }

        private static void LevelLoader_OnLoadingThread(Level level)
        {
            // reset this whenever the level is loaded/restarted/etc
            lastTerminalMusicTimelinePosition = 0;
        }

        /*
        to whoever is responsible for taking this over: i'm sorry xd
        
        basic overview:
         - the menu opens to the "selection" screen, where there's a grid of 
           12 entries
         - most of these are fish, and selecting one opens the "info" screen
           that shows some info about the selected fish
         - the last option is the fast travel (teleport) option, which warps 
           the player to a different room and is only available once terminals 
           1 and 2 have both been activated
         - the "Opening" and "Closing" states are just transitions where the
           screens pop up or shrink down

        yes this is hardcoded to all hell cus i'm lazy and this isn't gonna
        be a public entity anyway

        hav fun
          - SSM24
        */
        private class FishTerminalMenu : Entity
        {
            // used to signal to the parent terminal's coroutine
            public bool Finished = false;

            private TerminalTrigger parent;

            private StateMachine state;
            private const int StOpening = 0;
            private const int StSelection = 1;
            private const int StInfo = 2;
            private const int StClosing = 3;
            private const int StInfoOpening = 4;
            private const int StInfoClosing = 5;

            // the current selected index on the selection screen
            private int selectedIndex = 0;
            // the index currently showing on the info screen
            private int infoSelectedIndex;

            private const float screenCenterX = 960;
            private const float screenCenterY = 540;

            private static readonly Color BgColor = Color.Black * 0.85f;
            private static readonly Color OutlineColor = Calc.HexToColor("F0F0F0");

            // bunch of consts for the layout of the info screen
            // you get none of these for the selection screen cus i only thought of doing it like this after that was done :)
            private const int infoTotalWidth = 1100;
            private const int infoTotalHeight = 800;
            private const int infoTitleBoxSize = 135;
            private const int infoGapSize = 50;
            private const int infoDescMarginH = 50;
            private const int infoDescMarginV = 50;
            private const float infoNameScale = 1.6f;
            private const float infoDescScale = 0.70f;

            // rendertargets so we can manipulate the entire menus all at once after being drawn
            private VirtualRenderTarget selectionTarget;
            private float selectionScale;
            private float selectionOffsetX;
            private float selectionOffsetY;
            private float selectionAlpha;
            private VirtualRenderTarget infoTarget;
            private float infoScale;
            private float infoOffsetX;
            private float infoOffsetY;

            private FancyText.Text infoFishName;
            private FancyText.Text infoFishDescription;

            // wiggle effects for the controls display
            private Wiggler selectionSelectWiggle;
            private Wiggler selectionBackWiggle;
            private Wiggler infoBackWiggle;

            // used to restrict control when teleport is underway
            private bool isTeleporting;

            // used to extend the length of the black screen during teleport
            private bool showBlackScreen;

            private bool isLocked = false;

            private bool triedToTeleport = false;

            private readonly string[] fish =
            {
                "bass",
                "trout",
                "spring",
                "stone",
                "stoneeater",
                "blahaj",
                "bomb",
                "leaf",
                "angelguppy",
                "cooked",
                "fortnite",
                "teleport"
            };

            // i _told_ you this was hardcoded to all hell :)
            private bool TeleportAvailable
            {
                get
                {
                    Level level = SceneAs<Level>();
                    // return level.Session.GetFlag("lakeside_terminal_1") && level.Session.GetFlag("lakeside_terminal_2");
                    return level.Session.GetFlag("lakeside_terminal_2");
                }
            }
            // gets the current screen coordinates of the terminal's node
            private Vector2 NodeScreenOffset
            {
                get
                {
                    Level level = SceneAs<Level>();
                    Vector2 cameraPos = level.Camera.Position.Floor();
                    return (parent.nodePosition - cameraPos) * 6f - new Vector2(screenCenterX, screenCenterY);
                }
            }
            private string InfoSelectedFish => fish[infoSelectedIndex];
            // the selection screen position of the fish showing on the info screen
            private Vector2 InfoSelectedFishScreenOffset
            {
                get
                {
                    int row = infoSelectedIndex / 4;
                    int column = infoSelectedIndex % 4;
                    // this stuff _really_ should probably just be a method but i never got around to it :v
                    return new Vector2((column - 1.5f) * 210f + selectionOffsetX, (row - 1f) * 175f + selectionOffsetY);
                }
            }

            public FishTerminalMenu(TerminalTrigger parent)
            {
                Tag = Tags.HUD | Tags.Persistent | Tags.Global;

                this.parent = parent;
                this.depth = Depths.Top;

                state = new StateMachine();
                state.SetCallbacks(StOpening, null, OpeningCoroutine);
                state.SetCallbacks(StSelection, SelectionUpdate, null, SelectionBegin);
                state.SetCallbacks(StInfo, InfoUpdate, null, InfoBegin);
                state.SetCallbacks(StClosing, null, ClosingCoroutine);
                // yes reusing update methods is on purpose
                state.SetCallbacks(StInfoOpening, InfoUpdate, InfoOpeningCoroutine, InfoOpeningBegin);
                state.SetCallbacks(StInfoClosing, SelectionUpdate, InfoClosingCoroutine);
                Add(state);

                Add(new BeforeRenderHook(BeforeRender));

                Add(selectionSelectWiggle = Wiggler.Create(0.4f, 4f));
                Add(selectionBackWiggle = Wiggler.Create(0.4f, 4f));
                Add(infoBackWiggle = Wiggler.Create(0.4f, 4f));

            }

            public override void Added(Scene scene)
            {
                base.Added(scene);
                state.State = StOpening;
            }

            public override void Update()
            {
                base.Update();
                // close terminal as a failsafe if player dies or somehow regains control
                if (Scene.Tracker.GetEntity<Player>() is not Player player || player.StateMachine.State != Player.StDummy)
                {
                    state.State = StClosing;
                }
            }

            #region Main state logic

            private void SelectionBegin()
            {
                selectionScale = 1f;
                selectionOffsetX = 0f;
                selectionOffsetY = 0f;
                selectionAlpha = 1f;
            }

            private int SelectionUpdate()
            {
                UpdateSelectedFish();

                if (isTeleporting)
                {
                    return state.State;
                }

                if (Input.MenuConfirm.Pressed)
                {
                    infoSelectedIndex = selectedIndex;
                    selectionSelectWiggle.Start();
                    if (infoSelectedIndex == 11)
                    {
                        if (TeleportAvailable) {
                            Teleport();
                        } else {
                            Audio.Play("event:/Lakeside_MenuCantInteract");
                            triedToTeleport = true;
                        }
                    }
                    else
                    {
                        Audio.Play("event:/Lakeside_MenuForward");
                        return StInfoOpening;
                    }
                }
                else if (Input.MenuCancel.Pressed)
                {
                    selectionBackWiggle.Start();
                    return StClosing;
                }
                return state.State;
            }

            private void UpdateSelectedFish()
            {
                if(isLocked) return;

                bool IsInvalid(int index) => Utils.Mod(index, 12) == 11 && !TeleportAvailable;

                int prevIndex = selectedIndex;
                if (Input.MenuRight.Pressed)
                {
                    if (selectedIndex % 4 == 3)
                    {
                        selectedIndex -= 3;
                    }
                    else
                    {
                        selectedIndex++;
                    }
                    // if (IsInvalid(selectedIndex))
                    // {
                    //     selectedIndex -= 3;
                    // }
                }
                else if (Input.MenuLeft.Pressed)
                {
                    if (selectedIndex % 4 == 0)
                    {
                        selectedIndex += 3;
                    }
                    else
                    {
                        selectedIndex--;
                    }
                    // if (IsInvalid(selectedIndex))
                    // {
                    //     selectedIndex -= 1;
                    // }
                }
                if (Input.MenuUp.Pressed)
                {
                    selectedIndex -= 4;

                    if (IsInvalid(selectedIndex))
                    {
                        selectedIndex -= 4;
                    }
                }
                else if (Input.MenuDown.Pressed)
                {
                    selectedIndex += 4;

                    // if (IsInvalid(selectedIndex))
                    // {
                    //     selectedIndex += 4;
                    // }
                }
                selectedIndex = Utils.Mod(selectedIndex, fish.Length);
                if (selectedIndex != prevIndex)
                {
                    triedToTeleport = false;
                    Audio.Play("event:/Lakeside_MenuScroll");
                }
            }

            private void Teleport()
            {
                Finished = true;
                isTeleporting = true;
                parent.hasTeleported = true;
                isLocked = true;
                Level level = SceneAs<Level>();
                EventInstance fadeOut = Audio.Play(SFX.game_04_snowball_spawn);
                fadeOut.setVolume(0.5f);
                level.DoScreenWipe(wipeIn: false, onComplete: () =>
                {
                    Player player = level.Tracker.GetEntity<Player>();
                    level.Remove(player);
                    level.UnloadLevel();
                    level.Session.Level = parent.TeleportRoom;
                    level.Session.RespawnPoint = level.GetSpawnPoint(level.LevelOffset + parent.TeleportCoordinates);
                    string altMusic = level.Session.LevelData.AltMusic;
                    // prevent alt music from being changed
                    level.Session.LevelData.AltMusic = TerminalMusic;
                    level.LoadLevel(Player.IntroTypes.Transition);
                    level.Session.LevelData.AltMusic = altMusic;
                    level.Add(player);
                    player.Position = level.LevelOffset + parent.TeleportCoordinates;
                    Engine.Scene.OnEndOfFrame += () =>
                    {
                        //level.Camera.Position = player.CameraTarget;
                        level.Camera.Position = level.GetFullCameraTargetAt(player, player.Position);
                        parent = level.Tracker.GetNearestEntity<TerminalTrigger>(player.Position);
                        player.Facing = (Facings)Math.Sign(parent.CenterX - player.CenterX);
                    };

                    showBlackScreen = true;
                    Alarm.Set(this, 0.25f, onComplete: () =>
                    {
                        showBlackScreen = false;
                        // TODO: play another sound here? not sure which to use though
                        level.DoScreenWipe(wipeIn: true, onComplete: () =>
                        {
                            isTeleporting = false;
                            isLocked = false;
                        });
                    });

                });
            }

            private void InfoBegin()
            {
                infoScale = 1f;
                infoOffsetX = 0f;
                infoOffsetY = 0f;
            }

            private int InfoUpdate()
            {
                if (Input.MenuCancel.Pressed)
                {
                    Audio.Play("event:/Lakeside_MenuBackwards");
                    infoBackWiggle.Start();
                    return StInfoClosing;
                }
                return state.State;
            }

            #endregion

            #region Transition state logic

            private IEnumerator OpeningCoroutine()
            {
                selectionAlpha = 1f;
                Tween tween = Tween.Create(Tween.TweenMode.Oneshot, Ease.CubeInOut, 0.4f, start: true);
                tween.OnUpdate = t =>
                {
                    selectionScale = t.Eased;
                    selectionOffsetX = MathHelper.Lerp(NodeScreenOffset.X, 0f, t.Eased);
                    selectionOffsetY = MathHelper.Lerp(NodeScreenOffset.Y, 0f, t.Eased);
                };
                Add(tween);
                yield return tween.Wait();
                state.State = StSelection;
            }

            public IEnumerator ClosingCoroutine()
            {
                Finished = true;

                Tween tween = Tween.Create(Tween.TweenMode.Oneshot, Ease.CubeInOut, 0.33f, start: true);
                tween.OnUpdate = t =>
                {
                    selectionScale = t.Inverted;
                    selectionOffsetX = MathHelper.Lerp(NodeScreenOffset.X, 0f, t.Inverted);
                    selectionOffsetY = MathHelper.Lerp(NodeScreenOffset.Y, 0f, t.Inverted);
                };
                Add(tween);
                yield return tween.Wait();

                RemoveSelf();
            }

            private void InfoOpeningBegin()
            {
                infoFishName = FancyText.Parse(Dialog.Get($"lakeside_fish_{InfoSelectedFish}_name"), 9999, -1, 1f, Color.White);
                int lineWidth = (int)((infoTotalWidth - infoDescMarginH * 2) / infoDescScale);
                infoFishDescription = FancyText.Parse(Dialog.Get($"lakeside_fish_{InfoSelectedFish}_desc"), lineWidth, -1, 1f, OutlineColor);
            }

            private IEnumerator InfoOpeningCoroutine()
            {
                Tween tween = Tween.Create(Tween.TweenMode.Oneshot, Ease.CubeInOut, 0.4f, start: true);
                tween.OnUpdate = (t) =>
                {
                    infoScale = t.Eased;
                    infoOffsetX = InfoSelectedFishScreenOffset.X * t.Inverted;
                    infoOffsetY = InfoSelectedFishScreenOffset.Y * t.Inverted;
                    //selectionAlpha = 1f - (t.Eased * 0.75f);
                    selectionAlpha = MathHelper.Lerp(1f, 0.25f, t.Eased);
                };
                Add(tween);
                yield return tween.Wait();
                state.State = StInfo;
            }

            private IEnumerator InfoClosingCoroutine()
            {
                Tween tween = Tween.Create(Tween.TweenMode.Oneshot, Ease.CubeInOut, 0.4f, start: true);
                tween.OnUpdate = (t) =>
                {
                    infoScale = t.Inverted;
                    infoOffsetX = InfoSelectedFishScreenOffset.X * t.Eased;
                    infoOffsetY = InfoSelectedFishScreenOffset.Y * t.Eased;
                    selectionAlpha = 1f - t.Inverted * 0.75f;
                    selectionAlpha = MathHelper.Lerp(1f, 0.25f, t.Inverted);
                };
                Add(tween);
                yield return tween.Wait();
                state.State = StSelection;
            }

            #endregion

            #region Rendering

            private void BeforeRender()
            {
                if (Scene.Paused)
                {
                    return;
                }

                SelectionTargetRender();
                switch (state.State)
                {
                    case StInfoOpening:
                    case StInfo:
                    case StInfoClosing:
                        InfoTargetRender();
                        break;
                }
            }

            public override void Render()
            {
                base.Render();
                if (Scene.Paused)
                {
                    return;
                }

                SelectionRender();
                switch (state.State)
                {
                    case StInfoOpening:
                    case StInfo:
                    case StInfoClosing:
                        InfoRender();
                        break;
                }

                if (showBlackScreen)
                {
                    int b = 100;
                    
                    // Draw.Rect(0, 0, 1920, 1080, Color.Black);
                    Draw.Rect(0, 0, 1920 + b, 1920 + b, Color.Black);
                }
            }

            private void SelectionTargetRender()
            {
                if (selectionTarget == null)
                {
                    selectionTarget = VirtualContent.CreateRenderTarget("lakeside-terminal-selection-target", 1920, 1080);
                }
                Engine.Graphics.GraphicsDevice.SetRenderTarget(selectionTarget);
                Engine.Graphics.GraphicsDevice.Clear(Color.Transparent);
                Draw.SpriteBatch.Begin();

                // main box
                Utils.DrawOutlineRectCentered(screenCenterX, screenCenterY, 960, 600, BgColor, OutlineColor, 6f);

                // fish boxes
                for (int column = 0; column < 4; column++)
                {
                    for (int row = 0; row < 3; row++)
                    {
                        int index = row * 4 + column;
                        bool selected = index == selectedIndex;

                        float positionX = screenCenterX + (column - 1.5f) * 210f;
                        float positionY = screenCenterY + (row - 1f) * 175f;

                        bool isTeleportTile = index == 11;
                        if (selected)
                        {
                            Utils.DrawRectCentered(positionX, positionY, 110, 110, OutlineColor);
                            // draw fish name when selected

                            if(isTeleportTile) {
                                ActiveFont.Draw(Dialog.Get(triedToTeleport ? "LAKESIDE_FISH_CANT_TELEPORT" : "LAKESIDE_FISH_TELEPORT_NAME"),
                                    new Vector2(positionX, positionY - 80f), new Vector2(0.5f), new Vector2(0.6f), OutlineColor);
                            } else {
                                ActiveFont.Draw(Dialog.Get($"lakeside_fish_{fish[selectedIndex]}_name"),
                                    new Vector2(positionX, positionY - 80f), new Vector2(0.5f), new Vector2(0.6f), OutlineColor);
                            }
                        }
                        else
                        {
                            // background of fish box
                            string bgFilename = index == 11 ? "teleportBackground" : "fishBackground";
                            MTexture background = GFX.Gui[$"Phobscodestuff/Lakesidecodestuff/terminal/{bgFilename}"];
                            background.DrawCentered(new Vector2(positionX, positionY), Color.White);
                        }
                        Color outlineColor = isTeleportTile ? Calc.HexToColor("bdd6ff") : OutlineColor;
                        // fish box outline
                        Utils.DrawHollowRectCentered(positionX, positionY, 110, 110, outlineColor, 6f);
                    }
                }

                // draw controls at bottom left
                Vector2 position = new Vector2(490, 875);
                float scale = 0.5f;
                int gap = 32;
                string cancelLabel = Dialog.Clean("lakeside_terminal_back");
                string confirmLabel = Dialog.Clean("lakeside_terminal_select");
                float cancelWidth = ReverseButtonUI.Width(cancelLabel, Input.MenuCancel);
                float confirmWidth = ReverseButtonUI.Width(confirmLabel, Input.MenuConfirm);
                ReverseButtonUI.Render(position, confirmLabel, Input.MenuConfirm, scale, selectionSelectWiggle.Value * 0.05f);
                position.X += scale * confirmWidth + gap;
                ReverseButtonUI.Render(position, cancelLabel, Input.MenuCancel, scale, selectionBackWiggle.Value * 0.05f);

                // different sampler state (PointClamp instead of LinearClamp) for drawing gameplay sprites
                Draw.SpriteBatch.End();
                Draw.SpriteBatch.Begin(SpriteSortMode.Deferred,
                    BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise);

                // putting the fish in the fish boxes
                // bit of code duplication because we only want to switch sampler states once
                for (int column = 0; column < 4; column++)
                {
                    for (int row = 0; row < 3; row++)
                    {
                        float positionX = screenCenterX + (column - 1.5f) * 210f;
                        float positionY = screenCenterY + (row - 1f) * 175f;
                        int index = row * 4 + column;

                        bool unselectable = index == 11 && !TeleportAvailable;

                        // granny/fish are placeholders for when fish sprites are missing
                        // can probably be removed at this point
                        string placeholderPath = index != 11 ? "characters/oldlady/idle00" : "characters/bird/fly00";
                        // todo: consider caching these? probably better for performance
                        MTexture fishSprite = GFX.Game.GetOrDefault(
                            $"objects/Phobscodestuff/Lakesidecodestuff/fish/{fish[index]}", GFX.Game[placeholderPath]);
                        Color color = Color.White;
                        // if (unselectable)
                        // {
                        //     color = Calc.HexToColor("404040");
                        // }
                        if (index != 11 && ShowBlackSprite(fish[index]))
                        {
                            color = Color.Black;
                        }
                        // else
                        // {
                        //     color = Color.White;
                        // }
                        fishSprite.Draw(new Vector2(positionX, positionY), fishSprite.Center, color, 3f);
                    }
                }

                Draw.SpriteBatch.End();
            }

			private bool ShowBlackSprite(string s) {
				FishType? ft = s switch {
					"bass" => FishType.Bass,
					"trout" => FishType.Trout,
					"spring" => FishType.Spring,
					"stone" => FishType.Stone,
					"stoneeater" => FishType.StoneEater,
					"blahaj" => FishType.Blahaj,
					"bomb" => FishType.Bomb,
					"leaf" => FishType.Leaf,
					"cooked" => FishType.Cooked,
					"fortnite" => FishType.Mythic,
					_ => null
			    };
                if (ft == null) {
                    if (s == "angelguppy") {
                        return !LakeSideCodeModule.Session.CatchCounter.ContainsKey(FishType.Angel)
                            || !LakeSideCodeModule.Session.CatchCounter.ContainsKey(FishType.Devil);
                    }
                    else return false;  // Teleport
                }
                return !LakeSideCodeModule.Session.CatchCounter.ContainsKey(ft.Value);
			}

			private void SelectionRender()
            {
                if (selectionTarget != null)
                {
                    Vector2 center = new Vector2(selectionTarget.Width, selectionTarget.Height) / 2f;
                    Vector2 screenCenter = new Vector2(screenCenterX, screenCenterY);
                    Vector2 position = screenCenter + new Vector2(selectionOffsetX, selectionOffsetY);

                    Color color = Calc.HsvToColor(0f, 0f, selectionAlpha) * (1f - (1f - selectionAlpha) / 2);
                    Draw.SpriteBatch.Draw(selectionTarget,
                        position, selectionTarget.Bounds, color, 0f, center, selectionScale, SpriteEffects.None, 0f);
                }
            }

            private void InfoTargetRender()
            {
                if (infoTarget == null)
                {
                    infoTarget = VirtualContent.CreateRenderTarget("lakeside-terminal-info-target", infoTotalWidth, infoTotalHeight + 50);
                }
                Engine.Graphics.GraphicsDevice.SetRenderTarget(infoTarget);
                Engine.Graphics.GraphicsDevice.Clear(Color.Transparent);
                Draw.SpriteBatch.Begin();

                // make sure nothing is drawn outside render target bounds
                float left = 3f;
                float top = 3f;
                float width = infoTotalWidth - 6f;
                float height = infoTotalHeight - 6f;

                // fish box
                MTexture boxBackground = GFX.Gui["Phobscodestuff/Lakesidecodestuff/terminal/fishBackground"];
                boxBackground.DrawJustified(new Vector2(left, top), Vector2.Zero, Color.White, infoTitleBoxSize / 110f);
                Utils.DrawHollowRect(left, top, infoTitleBoxSize, infoTitleBoxSize, OutlineColor, 6f);
                // title box
                float titleBoxWidth = width - infoGapSize - infoTitleBoxSize;
                Utils.DrawOutlineRect(left + infoTitleBoxSize + infoGapSize, top, titleBoxWidth, infoTitleBoxSize, BgColor, OutlineColor, 6f);
                // description box
                Utils.DrawOutlineRect(left, top + infoTitleBoxSize + infoGapSize, width, height - infoGapSize - infoTitleBoxSize, BgColor, OutlineColor, 6f);

                // fish name
                Vector2 titleCenter = new Vector2(titleBoxWidth / 2 + infoTitleBoxSize + infoGapSize, infoTitleBoxSize / 2f);
                infoFishName.Draw(titleCenter, Vector2.One / 2f, new Vector2(infoNameScale), 1f);

                // fish description
                Vector2 descPosition = new Vector2(left + infoDescMarginH, top + infoTitleBoxSize + infoGapSize + infoDescMarginV);
                infoFishDescription.Draw(descPosition, Vector2.Zero, new Vector2(infoDescScale), 1f);

                // controls at bottom
                Vector2 position = new Vector2(left + 10, top + height + 35);
                float scale = 0.5f;
                string cancelLabel = Dialog.Clean("lakeside_terminal_back");
                ReverseButtonUI.Render(position, cancelLabel, Input.MenuCancel, scale, infoBackWiggle.Value * 0.05f);

                // different sampler state (PointClamp instead of LinearClamp) for drawing gameplay sprites
                Draw.SpriteBatch.End();
                Draw.SpriteBatch.Begin(SpriteSortMode.Deferred,
                    BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise);

                // fish sprite (granny sprite is placeholder again)
                Vector2 spriteCenter = new Vector2(infoTitleBoxSize / 2f);
                MTexture fishSprite = GFX.Game.GetOrDefault($"objects/Phobscodestuff/Lakesidecodestuff/fish/{InfoSelectedFish}",
                    GFX.Game["characters/oldlady/idle00"]);
                fishSprite.DrawCentered(spriteCenter, ShowBlackSprite(InfoSelectedFish) ? Color.Black : Color.White, 4f);

                Draw.SpriteBatch.End();
            }

            private void InfoRender()
            {
                if (infoTarget != null)
                {
                    Vector2 center = new Vector2(infoTarget.Width, infoTarget.Height) / 2f;
                    Vector2 screenCenter = new Vector2(screenCenterX, screenCenterY);
                    Vector2 position = screenCenter + new Vector2(infoOffsetX, infoOffsetY);
                    Draw.SpriteBatch.Draw(infoTarget,
                        position, infoTarget.Bounds, Color.White, 0f, center, infoScale, SpriteEffects.None, 0f);
                }
            }

            #endregion

            public override void Removed(Scene scene)
            {
                Dispose();
                base.Removed(scene);
            }

            public override void SceneEnd(Scene scene)
            {
                Dispose();
                base.SceneEnd(scene);
            }

            private void Dispose()
            {
                selectionTarget?.Dispose();
                selectionTarget = null;
                infoTarget?.Dispose();
                infoTarget = null;
            }
        }
    }
}
