<commit_before>﻿﻿using System;
using System.Diagnostics;
using System.Threading;
using AGS.API;

namespace AGS.Engine
{
    public class AGSBoundingBoxWithChildrenComponent : AGSComponent, IBoundingBoxWithChildrenComponent, ILockStep
    {
        private IBoundingBoxComponent _boundingBox;
        private AGSBoundingBox _preUnlockBoundingBox, _preUnlockPreCropBoundingBox, _boundingBoxWithChildren, _preCropBoundingBoxWithChildren;
        private int _shouldFireOnUnlock, _pendingLocks;
        private IInObjectTreeComponent _tree;
        private readonly IGameState _state;
        private readonly IGameEvents _events;
        private bool _isDirty;
        private EntityListSubscriptions<IObject> _subscriptions;
        private bool _includeSelf = true;

        public AGSBoundingBoxWithChildrenComponent(IGameState state, IGameEvents events)
        {
            _state = state;
            _events = events;
            EntitiesToSkip = new AGSConcurrentHashSet<string>();
            OnBoundingBoxWithChildrenChanged = new AGSEvent();
            events.OnRepeatedlyExecute.Subscribe(onRepeatedlyExecute);
        }

        [Property(Browsable = false)]
        public ref AGSBoundingBox BoundingBoxWithChildren { get { return ref _boundingBoxWithChildren; } }

        [Property(Browsable = false)]
        public ref AGSBoundingBox PreCropBoundingBoxWithChildren { get { return ref _preCropBoundingBoxWithChildren; } }

        public IBlockingEvent OnBoundingBoxWithChildrenChanged { get; private set; }

        public IConcurrentHashSet<string> EntitiesToSkip { get; private set; }

        public bool IncludeSelf 
        { 
            get => _includeSelf; 
            set 
            { 
                _includeSelf = value;
                onSomethingChanged();
            }
        }

        [Property(Browsable = false)]
        public ILockStep LockStep { get { return this;} }

        public bool DebugPrintouts { get; set; }

        public override void Init()
        {
            base.Init();
            Entity.Bind<IBoundingBoxComponent>(c =>
            {
                _boundingBox = c;
                c.OnBoundingBoxesChanged.Subscribe(onBoundingBoxChanged);
                onSomethingChanged();
            }, c => { c.OnBoundingBoxesChanged.Unsubscribe(onBoundingBoxChanged); _boundingBox = null; });
            Entity.Bind<IInObjectTreeComponent>(c => { _tree = c; subscribeTree(c.TreeNode); onSomethingChanged(); },
                                       c => { unsubscribeTree(c.TreeNode); _tree = null; });
        }

        public override void Dispose()
        {
            base.Dispose();
            _events.OnRepeatedlyExecute.Unsubscribe(onRepeatedlyExecute);
        }

        public void Lock()
        {
            Interlocked.Increment(ref _pendingLocks);
        }

        public void PrepareForUnlock()
        {
            if (!_isDirty) return;
            bool shouldFire = recalculateBeforeUnlock();
            _shouldFireOnUnlock += (shouldFire ? 1 : 0);
            if (shouldFire)
            {
                Interlocked.Increment(ref _shouldFireOnUnlock);
                _boundingBoxWithChildren = _preUnlockBoundingBox;
                _preCropBoundingBoxWithChildren = _preUnlockPreCropBoundingBox;
            }
        }

        public void Unlock()
        {
            if (Interlocked.Decrement(ref _pendingLocks) > 0) return;
            if (Interlocked.Exchange(ref _shouldFireOnUnlock, 0) >= 1)
            {
                OnBoundingBoxWithChildrenChanged.Invoke();
            }
        }

        private void onRepeatedlyExecute()
        {
            if (!_isDirty) return;
            refresh();
        }

        private void onSomethingChanged()
        {
            _isDirty = true;
        }

        private void onBoundingBoxChanged()
        {
            onSomethingChanged();
        }

        private void onObjBoundingBoxChanged()
        {
            onSomethingChanged();
        }

        private void onObjLabelSizeChanged()
        {
            onSomethingChanged();
        }

        private void subscribeTree(ITreeNode<IObject> node)
        {
            var visibleSubscription = new EntitySubscription<IVisibleComponent>(onVisiblePropertyChanged, 
                propertyNames: nameof(IVisibleComponent.UnderlyingVisible));
            var boundingBoxSubscription = new EntitySubscription<IBoundingBoxComponent>(null,
                c => c.OnBoundingBoxesChanged.Subscribe(onObjBoundingBoxChanged), c => c.OnBoundingBoxesChanged.Unsubscribe(onObjBoundingBoxChanged));
            var textSubscription = new EntitySubscription<ITextComponent>(null, c =>
            {
                c.OnLabelSizeChanged.Subscribe(onObjLabelSizeChanged);
            }, c =>
            {
                c.OnLabelSizeChanged.Unsubscribe(onObjLabelSizeChanged);
            });
            _subscriptions = new EntityListSubscriptions<IObject>(node.Children, true, onSomethingChanged, 
                                                                  visibleSubscription, boundingBoxSubscription);
        }

        private void unsubscribeTree(ITreeNode<IObject> node)
        {
            _subscriptions?.Unsubscribe();
        }

        private void onVisiblePropertyChanged() => onSomethingChanged();

        private void refresh()
        {
            bool shouldFire = recalculate();
            if (shouldFire) OnBoundingBoxWithChildrenChanged.Invoke();
        }

        private bool recalculate()
        {
            if (_pendingLocks > 0) return false;
            var lastBox = BoundingBoxWithChildren;
            var lastPreBox = PreCropBoundingBoxWithChildren;
            _isDirty = false;
            _boundingBoxWithChildren = getBoundingBox(_tree, _boundingBox, boxes => boxes.ViewportBox, _includeSelf, DebugPrintouts ? $"Box ({Entity.ID})" : null);
            _preCropBoundingBoxWithChildren = getBoundingBox(_tree, _boundingBox, boxes => boxes.PreCropViewportBox, _includeSelf, DebugPrintouts ? $"Box Pre Crop ({Entity.ID})" : null);
            if (DebugPrintouts)
            {
                Debug.WriteLine($"Pre crop for {Entity.ID}: {_preCropBoundingBoxWithChildren.ToString()}");
            }
            return (!lastBox.Equals(BoundingBoxWithChildren) || !lastPreBox.Equals(PreCropBoundingBoxWithChildren));
        }

        private bool recalculateBeforeUnlock()
        {
            var lastBox = BoundingBoxWithChildren;
            var lastPreBox = PreCropBoundingBoxWithChildren;
            _preUnlockBoundingBox = getBoundingBox(_tree, _boundingBox, boxes => boxes.ViewportBox, _includeSelf, DebugPrintouts ? $"Box before unlock ({Entity.ID})" : null);
            _preUnlockPreCropBoundingBox = getBoundingBox(_tree, _boundingBox, boxes => boxes.PreCropViewportBox, _includeSelf, DebugPrintouts ? $"Box Pre Crop before unlock ({Entity.ID})" : null);
            if (DebugPrintouts)
            {
                Debug.WriteLine($"Pre crop (before unlock) for ${Entity.ID}: {_preUnlockBoundingBox.ToString()}");
            }
            _isDirty = false;
            return (!lastBox.Equals(_preUnlockBoundingBox) || !lastPreBox.Equals(_preUnlockPreCropBoundingBox));
        }

        private AGSBoundingBox getBoundingBox(IInObjectTreeComponent tree, IBoundingBoxComponent box,<commit_msg>fix typo<commit_after>﻿﻿using System;
using System.Diagnostics;
using System.Threading;
using AGS.API;

namespace AGS.Engine
{
    public class AGSBoundingBoxWithChildrenComponent : AGSComponent, IBoundingBoxWithChildrenComponent, ILockStep
    {
        private IBoundingBoxComponent _boundingBox;
        private AGSBoundingBox _preUnlockBoundingBox, _preUnlockPreCropBoundingBox, _boundingBoxWithChildren, _preCropBoundingBoxWithChildren;
        private int _shouldFireOnUnlock, _pendingLocks;
        private IInObjectTreeComponent _tree;
        private readonly IGameState _state;
        private readonly IGameEvents _events;
        private bool _isDirty;
        private EntityListSubscriptions<IObject> _subscriptions;
        private bool _includeSelf = true;

        public AGSBoundingBoxWithChildrenComponent(IGameState state, IGameEvents events)
        {
            _state = state;
            _events = events;
            EntitiesToSkip = new AGSConcurrentHashSet<string>();
            OnBoundingBoxWithChildrenChanged = new AGSEvent();
            events.OnRepeatedlyExecute.Subscribe(onRepeatedlyExecute);
        }

        [Property(Browsable = false)]
        public ref AGSBoundingBox BoundingBoxWithChildren { get { return ref _boundingBoxWithChildren; } }

        [Property(Browsable = false)]
        public ref AGSBoundingBox PreCropBoundingBoxWithChildren { get { return ref _preCropBoundingBoxWithChildren; } }

        public IBlockingEvent OnBoundingBoxWithChildrenChanged { get; private set; }

        public IConcurrentHashSet<string> EntitiesToSkip { get; private set; }

        public bool IncludeSelf 
        { 
            get => _includeSelf; 
            set 
            { 
                _includeSelf = value;
                onSomethingChanged();
            }
        }

        [Property(Browsable = false)]
        public ILockStep LockStep { get { return this;} }

        public bool DebugPrintouts { get; set; }

        public override void Init()
        {
            base.Init();
            Entity.Bind<IBoundingBoxComponent>(c =>
            {
                _boundingBox = c;
                c.OnBoundingBoxesChanged.Subscribe(onBoundingBoxChanged);
                onSomethingChanged();
            }, c => { c.OnBoundingBoxesChanged.Unsubscribe(onBoundingBoxChanged); _boundingBox = null; });
            Entity.Bind<IInObjectTreeComponent>(c => { _tree = c; subscribeTree(c.TreeNode); onSomethingChanged(); },
                                       c => { unsubscribeTree(c.TreeNode); _tree = null; });
        }

        public override void Dispose()
        {
            base.Dispose();
            _events.OnRepeatedlyExecute.Unsubscribe(onRepeatedlyExecute);
        }

        public void Lock()
        {
            Interlocked.Increment(ref _pendingLocks);
        }

        public void PrepareForUnlock()
        {
            if (!_isDirty) return;
            bool shouldFire = recalculateBeforeUnlock();
            _shouldFireOnUnlock += (shouldFire ? 1 : 0);
            if (shouldFire)
            {
                Interlocked.Increment(ref _shouldFireOnUnlock);
                _boundingBoxWithChildren = _preUnlockBoundingBox;
                _preCropBoundingBoxWithChildren = _preUnlockPreCropBoundingBox;
            }
        }

        public void Unlock()
        {
            if (Interlocked.Decrement(ref _pendingLocks) > 0) return;
            if (Interlocked.Exchange(ref _shouldFireOnUnlock, 0) >= 1)
            {
                OnBoundingBoxWithChildrenChanged.Invoke();
            }
        }

        private void onRepeatedlyExecute()
        {
            if (!_isDirty) return;
            refresh();
        }

        private void onSomethingChanged()
        {
            _isDirty = true;
        }

        private void onBoundingBoxChanged()
        {
            onSomethingChanged();
        }

        private void onObjBoundingBoxChanged()
        {
            onSomethingChanged();
        }

        private void onObjLabelSizeChanged()
        {
            onSomethingChanged();
        }

        private void subscribeTree(ITreeNode<IObject> node)
        {
            var visibleSubscription = new EntitySubscription<IVisibleComponent>(onVisiblePropertyChanged, 
                propertyNames: nameof(IVisibleComponent.UnderlyingVisible));
            var boundingBoxSubscription = new EntitySubscription<IBoundingBoxComponent>(null,
                c => c.OnBoundingBoxesChanged.Subscribe(onObjBoundingBoxChanged), c => c.OnBoundingBoxesChanged.Unsubscribe(onObjBoundingBoxChanged));
            var textSubscription = new EntitySubscription<ITextComponent>(null, c =>
            {
                c.OnLabelSizeChanged.Subscribe(onObjLabelSizeChanged);
            }, c =>
            {
                c.OnLabelSizeChanged.Unsubscribe(onObjLabelSizeChanged);
            });
            _subscriptions = new EntityListSubscriptions<IObject>(node.Children, true, onSomethingChanged, 
                                                                  visibleSubscription, boundingBoxSubscription);
        }

        private void unsubscribeTree(ITreeNode<IObject> node)
        {
            _subscriptions?.Unsubscribe();
        }

        private void onVisiblePropertyChanged() => onSomethingChanged();

        private void refresh()
        {
            bool shouldFire = recalculate();
            if (shouldFire) OnBoundingBoxWithChildrenChanged.Invoke();
        }

        private bool recalculate()
        {
            if (_pendingLocks > 0) return false;
            var lastBox = BoundingBoxWithChildren;
            var lastPreBox = PreCropBoundingBoxWithChildren;
            _isDirty = false;
            _boundingBoxWithChildren = getBoundingBox(_tree, _boundingBox, boxes => boxes.ViewportBox, _includeSelf, DebugPrintouts ? $"Box ({Entity.ID})" : null);
            _preCropBoundingBoxWithChildren = getBoundingBox(_tree, _boundingBox, boxes => boxes.PreCropViewportBox, _includeSelf, DebugPrintouts ? $"Box Pre Crop ({Entity.ID})" : null);
            if (DebugPrintouts)
            {
                Debug.WriteLine($"Pre crop for {Entity.ID}: {_preCropBoundingBoxWithChildren.ToString()}");
            }
            return (!lastBox.Equals(BoundingBoxWithChildren) || !lastPreBox.Equals(PreCropBoundingBoxWithChildren));
        }

        private bool recalculateBeforeUnlock()
        {
            var lastBox = BoundingBoxWithChildren;
            var lastPreBox = PreCropBoundingBoxWithChildren;
            _preUnlockBoundingBox = getBoundingBox(_tree, _boundingBox, boxes => boxes.ViewportBox, _includeSelf, DebugPrintouts ? $"Box before unlock ({Entity.ID})" : null);
            _preUnlockPreCropBoundingBox = getBoundingBox(_tree, _boundingBox, boxes => boxes.PreCropViewportBox, _includeSelf, DebugPrintouts ? $"Box Pre Crop before unlock ({Entity.ID})" : null);
            if (DebugPrintouts)
            {
                Debug.WriteLine($"Pre crop (before unlock) for {Entity.ID}: {_preUnlockBoundingBox.ToString()}");
            }
            _isDirty = false;
            return (!lastBox.Equals(_preUnlockBoundingBox) || !lastPreBox.Equals(_preUnlockPreCropBoundingBox));
        }

        private AGSBoundingBox getBoundingBox(IInObjectTreeComponent tree, IBoundingBoxComponent box,
{'fraction_non_alphanumeric': 0.07938340350400652, 'fraction_numerical': 0.0008148852369957897, 'mean_word_length': 2.246693121693122, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5833044', 'n_tokens_mistral': 3863, 'n_tokens_neox': 3744, 'n_words': 734}