context: using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Globalization;
  using System.Reactive.Linq;
  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Controls.Primitives;
  using System.Windows.Media;
  using TailBlazer.Controls;
  
  namespace TailBlazer.Infrastucture.Virtualisation
  
  
      //TODO: 1) Clamp offset.X. 
  {
      public static class MeasureEx
      {public static Size MeasureString(this Control source, string candidate)
          {
              var formattedText = new FormattedText(
                  candidate,
                  CultureInfo.CurrentUICulture,
                  FlowDirection.LeftToRight,
                  new Typeface(source.FontFamily, source.FontStyle, source.FontWeight, source.FontStretch),
                  source.FontSize,
                  Brushes.Black);
  
              return new Size(formattedText.Width, formattedText.Height);
          }
  
      }
  
      /// <summary>
      /// This is adapted (butchered!) from VirtualWrapPanel in https://github.com/samueldjack/VirtualCollection
      /// 
      /// See http://blog.hibernatingrhinos.com/12515/implementing-a-virtualizingwrappanel
      /// </summary>
      public class LinesScrollPanel : VirtualizingPanel, IScrollInfo
      {
          private const double ScrollLineAmount = 16.0;
          private Size _extentSize;
          private ExtentInfo _extentInfo = new ExtentInfo();
          private Size _viewportSize;
          private Point _offset;
          private ItemsControl _itemsControl;
          private readonly Dictionary<UIElement, Rect> _childLayouts = new Dictionary<UIElement, Rect>();
          private IRecyclingItemContainerGenerator _itemsGenerator;
          private bool _isInMeasure;
  
          public static readonly DependencyProperty ItemHeightProperty =
              DependencyProperty.Register("ItemHeight", typeof(double), typeof(LinesScrollPanel), new PropertyMetadata(1.0, OnRequireMeasure));
  
          private static readonly DependencyProperty VirtualItemIndexProperty =
              DependencyProperty.RegisterAttached("VirtualItemIndex", typeof(int), typeof(LinesScrollPanel), new PropertyMetadata(-1));
  
          public static readonly DependencyProperty TotalItemsProperty =
              DependencyProperty.Register("TotalItems", typeof(int), typeof(LinesScrollPanel), new PropertyMetadata(default(int), OnRequireMeasure));
  
          public static readonly DependencyProperty StartIndexProperty =
              DependencyProperty.Register("StartIndex", typeof(int), typeof(LinesScrollPanel), new PropertyMetadata(default(int), OnStartIndexChanged));
  
          public static readonly DependencyProperty ScrollReceiverProperty = DependencyProperty.Register(
              "ScrollReceiver", typeof(IScrollReceiver), typeof(LinesScrollPanel), new PropertyMetadata(default(IScrollReceiver)));
  
  
          public static readonly DependencyProperty HorizontalScrollChangedProperty = DependencyProperty.Register(
              "HorizontalScrollChanged", typeof (TextScrollDelegate), typeof (LinesScrollPanel), new PropertyMetadata(default(TextScrollDelegate)));
  
          public TextScrollDelegate HorizontalScrollChanged
          {
              get { return (TextScrollDelegate) GetValue(HorizontalScrollChangedProperty); }
              set { SetValue(HorizontalScrollChangedProperty, value); }
          }
  
  
          //For Horizonal scroll we need
          //1. Max number of chars of all the lines []
          //2. Starting Character
          //3. Number of visible characters required
          //4. Plus we need to be supplied 
  
          //We need 2 calcs - First visible char + number of visible chars (+ overflow)
  
          public static readonly DependencyProperty CharacterWidthProperty = DependencyProperty.Register(
              "CharacterWidth", typeof (double), typeof (LinesScrollPanel), new PropertyMetadata(default(double), OnCharactersChanged));
  
          public double CharacterWidth
          {
              get { return (double) GetValue(CharacterWidthProperty); }
              set { SetValue(CharacterWidthProperty, value); }
          }
  
  
          public static readonly DependencyProperty TotalCharactersProperty = DependencyProperty.Register(
              "TotalCharacters", typeof(int), typeof(LinesScrollPanel), new PropertyMetadata(default(int), OnCharactersChanged));
  
          public int TotalCharacters
          {
              get { return (int)GetValue(TotalCharactersProperty); }
              set { SetValue(TotalCharactersProperty, value); }
          }
  
          public IScrollReceiver ScrollReceiver
          {
              get { return (IScrollReceiver)GetValue(ScrollReceiverProperty); }
              set { SetValue(ScrollReceiverProperty, value); }
          }
  
          public int StartIndex
          {
              get { return (int)GetValue(StartIndexProperty); }
              set { SetValue(StartIndexProperty, value); }
          }
  
          public int TotalItems
          {
              get { return (int)GetValue(TotalItemsProperty); }
              set { SetValue(TotalItemsProperty, value); }
          }
  
          private static int GetVirtualItemIndex(DependencyObject obj)
          {
              return (int)obj.GetValue(VirtualItemIndexProperty);
          }
  
          private static void SetVirtualItemIndex(DependencyObject obj, int value)
          {
              obj.SetValue(VirtualItemIndexProperty, value);
          }
  
          public double ItemHeight
          {
              get { return (double)GetValue(ItemHeightProperty); }
              set { SetValue(ItemHeightProperty, value); }
          }
  
          public double ItemWidth => _viewportSize.Width;
  
          public LinesScrollPanel()
          {
              if (!DesignerProperties.GetIsInDesignMode(this))
                  Dispatcher.BeginInvoke(new Action(Initialize));
          }
  
          private void Initialize()
          {
              _itemsControl = ItemsControl.GetItemsOwner(this);
              _itemsGenerator = (IRecyclingItemContainerGenerator)ItemContainerGenerator;
  
              InvalidateMeasure();
          }
  
          private static void OnStartIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
          {
              var panel = (LinesScrollPanel)d;
              panel.CallbackStartIndexChanged(Convert.ToInt32(e.NewValue));
              panel.InvalidateMeasure();
          }
  
          private static void OnCharactersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
          {
              var panel = (LinesScrollPanel)d;
              panel.InvalidateScrollInfo();
              panel.InvalidateMeasure();
          }
  
          private static void OnRequireMeasure(DependencyObject d, DependencyPropertyChangedEventArgs e)
          {
              var panel = (LinesScrollPanel)d;
              panel.InvalidateMeasure();
              panel.InvalidateScrollInfo();
  
          }
  
          protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
          {
              base.OnItemsChanged(sender, args);
              InvalidateMeasure();
          }
  
          protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
          {
              base.OnRenderSizeChanged(sizeInfo);
              
              if (sizeInfo.WidthChanged)
                  CalculateHorizonalScrollInfo();
  
              if (!sizeInfo.HeightChanged) return;
  
              var items = (int)(sizeInfo.NewSize.Height / ItemHeight);
              InvokeSizeCommand(items);
          }
          
         protected override Size MeasureOverride(Size availableSize)
          {
              if (_itemsControl == null)
              {
                  return new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width,
                      double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height);
              }
  
  
              _isInMeasure = true;
              _childLayouts.Clear();
              _extentInfo = GetExtentInfo(availableSize);
  
              EnsureScrollOffsetIsWithinConstrains(_extentInfo);
              var layoutInfo = GetLayoutInfo(availableSize, ItemHeight, _extentInfo);
  
              RecycleItems(layoutInfo);
  
              // Determine where the first item is in relation to previously realized items
              var generatorStartPosition = _itemsGenerator.GeneratorPositionFromIndex(layoutInfo.FirstRealizedItemIndex);
  
              var visualIndex = 0;
              double widestWidth = 0;
              var currentX = 0;//layoutInfo.FirstRealizedItemLeft;
              var currentY = layoutInfo.FirstRealizedLineTop;
       
              ////1. Calc width, Call back available chars + first char
              //var width = TotalCharacters * CharacterWidth + 22;
  
              using (_itemsGenerator.StartAt(generatorStartPosition, GeneratorDirection.Forward, true))
              {
                  var children = new List<UIElement>();
  
                  for (var itemIndex = layoutInfo.FirstRealizedItemIndex; itemIndex <= layoutInfo.LastRealizedItemIndex; itemIndex++, visualIndex++)
                  {
                      bool newlyRealized;
  
                      var child = (UIElement)_itemsGenerator.GenerateNext(out newlyRealized);
                      if (child == null) continue;
  
                      children.Add(child);
  
                      SetVirtualItemIndex(child, itemIndex);
  
                      if (newlyRealized)
                      {
                          InsertInternalChild(visualIndex, child);
                      }
                      else
                      {
                          // check if item needs to be moved into a new position in the Children collection
                          if (visualIndex < Children.Count)
                          {
                              if (Equals(Children[visualIndex], child)) continue;
                              var childCurrentIndex = Children.IndexOf(child);
                              if (childCurrentIndex >= 0)
                              {
                                  RemoveInternalChildRange(childCurrentIndex, 1);
                              }
  
                              InsertInternalChild(visualIndex, child);
                          }
                          else
                          {
                              // we know that the child can't already be in the children collection
                              // because we've been inserting children in correct visualIndex order,
                              // and this child has a visualIndex greater than the Children.Count
                              AddInternalChild(child);
                          }
                      }
                  }
  
                  //part 2: do the measure
                  foreach (var child in children)
                  {
                      //TODO: Widest = Chars + Additional space = 20
                      //[ideally should scroll from where the text begins]
  
                      _itemsGenerator.PrepareItemContainer(child);
                      child.Measure(new Size(_viewportSize.Width, ItemHeight));
                      widestWidth = Math.Max(widestWidth, child.DesiredSize.Width); 
                  }
  
              //    Console.WriteLine("Widest={0} Calc={1}", widestWidth, width);
  
                  //part 3: Create the elements
                  foreach (var child in children)
                  {
                      _childLayouts.Add(child, new Rect(currentX, currentY, Math.Max(_viewportSize.Width, _viewportSize.Width), ItemHeight));
                      currentY += ItemHeight;
                  }
              }
              RemoveRedundantChildren();
  
              UpdateScrollInfo(availableSize, _extentInfo);
  
              //NotifyHorizonalScroll(_extentInfo);
              _isInMeasure = false;
  
              return new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width, double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height);
          }
  
          private void EnsureScrollOffsetIsWithinConstrains(ExtentInfo extentInfo)
          {
              _offset.Y = Clamp(_offset.Y, 0, extentInfo.MaxVerticalOffset);
              _offset.X = Clamp(_offset.X, 0, extentInfo.MaxHorizontalOffset);
          }
  
  
  
          private void RecycleItems(ItemLayoutInfo layoutInfo)
          {
              foreach (UIElement child in Children)
              {
                  var virtualItemIndex = GetVirtualItemIndex(child);
  
                  if (virtualItemIndex < layoutInfo.FirstRealizedItemIndex || virtualItemIndex > layoutInfo.LastRealizedItemIndex)
                  {
                      var generatorPosition = _itemsGenerator.GeneratorPositionFromIndex(virtualItemIndex);
                      if (generatorPosition.Index >= 0)
                      {
                          try
                          {
                              _itemsGenerator.Recycle(generatorPosition, 1);
                          }
                          catch (ArgumentException)
                          {
                              //I have seen the following exception which appears to be a non-issue
                              // GeneratorPosition '0,10' passed to Remove does not have Offset equal to 0.
                          }
  
                      }
                  }
                  SetVirtualItemIndex(child, -1);
              }
          }
  
          protected override Size ArrangeOverride(Size finalSize)
          {
              foreach (UIElement child in Children)
              {
                  child.Arrange(_childLayouts[child]);
              }
              return finalSize;
          }
          private void UpdateScrollInfo(Size availableSize, ExtentInfo extentInfo)
          {
              _viewportSize = availableSize;
              _extentSize = new Size(extentInfo.Width, extentInfo.Height);
  
              InvalidateScrollInfo();
          }
  
          private void InvalidateScrollInfo()
          {
              ScrollOwner?.InvalidateScrollInfo();
          }
  
          private void RemoveRedundantChildren()
          {
              // iterate backwards through the child collection because we're going to be
              // removing items from it
              for (var i = Children.Count - 1; i >= 0; i--)
              {
                  var child = Children[i];
  
                  // if the virtual item index is -1, this indicates
                  // it is a recycled item that hasn't been reused this time round
                  if (GetVirtualItemIndex(child) == -1)
                  {
                      RemoveInternalChildRange(i, 1);
                  }
              }
          }
  
          private ItemLayoutInfo GetLayoutInfo(Size availableSize, double itemHeight, ExtentInfo extentInfo)
          {
              if (_itemsControl == null)
  
gt:                 return new ItemLayoutInfo();