context: using libsignalservice;
  using libsignalservice.util;
  using Microsoft.Extensions.Logging;
  using Signal_Windows.Lib;
  using Signal_Windows.Lib.Models;
  using Signal_Windows.Models;
  using Signal_Windows.Storage;
  using Signal_Windows.ViewModels;
  using Signal_Windows.Views;
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Diagnostics;
  using System.Runtime.InteropServices.WindowsRuntime;
  using System.Threading.Tasks;
  using Windows.ApplicationModel.DataTransfer;
  using Windows.Foundation;
  using Windows.Graphics.Imaging;
  using Windows.Storage;
  using Windows.Storage.AccessCache;
  using Windows.Storage.Pickers;
  using Windows.Storage.Streams;
  using Windows.System;
  using Windows.UI.Core;
  using Windows.UI.ViewManagement;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Input;
  using Windows.UI.Xaml.Media;
  
  // The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
  
  namespace Signal_Windows.Controls
  {
      public interface IMessageView
      {
          SignalMessage Model { get; set; }
          void HandleUpdate(SignalMessage m);
          FrameworkElement AsFrameworkElement();
      }
  
      public sealed partial class Conversation : UserControl, INotifyPropertyChanged
      {
          private readonly ILogger Logger = LibsignalLogging.CreateLogger<Conversation>();
          public event PropertyChangedEventHandler PropertyChanged;
          private SignalConversation SignalConversation;
          public VirtualizedCollection Collection;
          private CoreWindowActivationState ActivationState = CoreWindowActivationState.Deactivated;
          private int LastMarkReadRequest;
          private StorageFile SelectedFile;
  
          private string _ThreadDisplayName;
  
          public string ThreadDisplayName
          {
              get { return _ThreadDisplayName; }
              set { _ThreadDisplayName = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThreadDisplayName))); }
          }
  
          private string _ThreadUsername;
  
          public string ThreadUsername
          {
              get { return _ThreadUsername; }
              set { _ThreadUsername = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThreadUsername))); }
          }
  
          private Visibility _ThreadUsernameVisibility;
  
          public Visibility ThreadUsernameVisibility
          {
              get { return _ThreadUsernameVisibility; }
              set { _ThreadUsernameVisibility = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ThreadUsernameVisibility))); }
          }
  
          private Visibility _SeparatorVisiblity;
  
          public Visibility SeparatorVisibility
          {
              get { return _SeparatorVisiblity; }
              set { _SeparatorVisiblity = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SeparatorVisibility))); }
          }
  
          private bool _SendButtonEnabled;
          public bool SendButtonEnabled
          {
              get { return _SendButtonEnabled; }
              set
              {
                  _SendButtonEnabled = value;
                  PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SendButtonEnabled)));
              }
          }
  
          private Brush _HeaderBackground;
  
          public Brush HeaderBackground
          {
              get { return _HeaderBackground; }
              set { _HeaderBackground = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HeaderBackground))); }
          }
  
          public Brush SendButtonBackground
          {
              get { return Utils.Blue; }
          }
  
          private bool blocked;
          public bool Blocked
          {
              get { return blocked; }
              set
              {
                  blocked = value;
                  PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Blocked)));
              }
          }
  
          public bool SendMessageVisible
          {
              get { return !Blocked; }
              set { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SendMessageVisible))); }
          }
  
          private bool spellCheckEnabled;
          public bool SpellCheckEnabled
          {
              get { return spellCheckEnabled; }
              set { spellCheckEnabled = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SpellCheckEnabled))); }
          }
  
          public Conversation()
          {
              this.InitializeComponent();
              Displayname.Foreground = Utils.ForegroundIncoming;
              Separator.Foreground = Utils.ForegroundIncoming;
              Username.Foreground = Utils.ForegroundIncoming;
              SendButtonEnabled = false;
              Loaded += Conversation_Loaded;
              Unloaded += Conversation_Unloaded;
          }
  
          private void Conversation_Unloaded(object sender, RoutedEventArgs e)
          {
              Window.Current.Activated -= HandleWindowActivated;
          }
  
          private void Conversation_Loaded(object sender, RoutedEventArgs e)
          {
              Window.Current.Activated += HandleWindowActivated;
          }
  
          private void HandleWindowActivated(object sender, WindowActivatedEventArgs e)
          {
              Logger.LogTrace("HandleWindowActivated() new activation state {0}", e.WindowActivationState);
              ActivationState = e.WindowActivationState;
              MarkBottommostMessageRead();
          }
  
          public MainPageViewModel GetMainPageVm()
          {
              return DataContext as MainPageViewModel;
          }
  
          private void UpdateHeader(SignalConversation thread)
          {
              ThreadDisplayName = thread.ThreadDisplayName;
              ThreadUsername = thread.ThreadId;
              if (thread is SignalContact contact)
              {
                  HeaderBackground = contact.Color != null ? Utils.GetBrushFromColor((contact.Color)) :
                      Utils.GetBrushFromColor(Utils.CalculateDefaultColor(contact.ThreadDisplayName));
                  if (ThreadUsername != ThreadDisplayName)
                  {
                      ThreadUsernameVisibility = Visibility.Visible;
                      SeparatorVisibility = Visibility.Visible;
                  }
                  else
                  {
                      ThreadUsernameVisibility = Visibility.Collapsed;
                      SeparatorVisibility = Visibility.Collapsed;
                  }
              }
              else
              {
                  HeaderBackground = Utils.Blue;
                  ThreadUsernameVisibility = Visibility.Collapsed;
                  SeparatorVisibility = Visibility.Collapsed;
              }
          }
  
          public void Load(SignalConversation conversation)
          {
              bool conversationThreadIdChanged = SignalConversation?.ThreadId != conversation?.ThreadId;
              SignalConversation = conversation;
              if (SignalConversation is SignalContact contact)
              {
                  Blocked = contact.Blocked;
                  SendMessageVisible = !Blocked;
              }
              else
              {
                  // Need to make sure to reset the Blocked and SendMessageVisible values in case
                  // a group chat is selected. Group chats can never be blocked.
                  Blocked = false;
                  SendMessageVisible = !Blocked;
              }
              LastMarkReadRequest = -1;
              SendButtonEnabled = false;
  
              /*
               * On app resume this method (Load()) gets called with the same conversation, but new object.
               * Only load draft if it is acutally a different conversation,
               * because on mobile app gets supended during file picking and
               * the new conversation does not have the DraftFileTokens
               */
              if (conversationThreadIdChanged)
              {
                  // We don't need to wait for this
                  _ = LoadDraft();
              }
              UserInputBar.FocusTextBox();
              DisposeCurrentThread();
              UpdateHeader(conversation);
              SpellCheckEnabled = GlobalSettingsManager.SpellCheckSetting;
  
              /*
               * When selecting a small (~650 messages) conversation after a bigger (~1800 messages) one,
               * ScrollToBottom would scroll so far south that the entire screen was white. Seems like the
               * scrollbar is not properly notified that the collection changed. I tried things like throwing
               * CollectionChanged (reset) event, but to no avail. This hack works, though.
               */
              ConversationItemsControl.ItemsSource = new List<object>();
              UpdateLayout();
              Collection = new VirtualizedCollection(conversation);
              ConversationItemsControl.ItemsSource = Collection;
              UpdateLayout();
              SendButtonEnabled = conversation.CanReceive;
              ScrollToUnread();
          }
  
          public void DisposeCurrentThread()
          {
              Collection?.Dispose();
          }
  
          public T FindElementByName<T>(FrameworkElement element, string sChildName) where T : FrameworkElement
          {
              T childElement = null;
              var nChildCount = VisualTreeHelper.GetChildrenCount(element);
              for (int i = 0; i < nChildCount; i++)
              {
                  if (!(VisualTreeHelper.GetChild(element, i) is FrameworkElement child))
                      continue;
  
                  if (child is T && child.Name.Equals(sChildName))
                  {
                      childElement = (T)child;
                      break;
                  }
  
                  childElement = FindElementByName<T>(child, sChildName);
  
                  if (childElement != null)
                      break;
              }
              return childElement;
          }
  
          public void UpdateMessageBox(SignalMessage updatedMessage)
          {
              if (Collection != null)
              {
                  IMessageView m = Collection.GetMessageByDbId(updatedMessage.Id);
                  if (m != null)
                  {
                      var attachment = FindElementByName<Attachment>(m.AsFrameworkElement(), "Attachment");
                      m.HandleUpdate(updatedMessage);
                  }
              }
          }
  
          public void UpdateAttachment(SignalAttachment sa)
          {
              if (Collection != null)
              {
                  IMessageView m = Collection.GetMessageByDbId(sa.Message.Id);
                  if (m != null)
                  {
                      var attachment = FindElementByName<Attachment>(m.AsFrameworkElement(), "Attachment");
                      attachment.HandleUpdate(sa);
                  }
              }
          }
  
          public AppendResult Append(IMessageView sm)
          {
              AppendResult result = new AppendResult(false);
              bool bottom = GetBottommostIndex() == Collection.Count - 2; // -2 because we already incremented Count
              Collection.Add(sm, sm.Model.Author == null);
              if (bottom)
              {
                  UpdateLayout();
                  ScrollToBottom();
                  if (ActivationState != CoreWindowActivationState.Deactivated)
                  {
                      result = new AppendResult(true);
                  }
              }
              return result;
          }
  
          public void HandleDeleteMesage(SignalMessage message)
          {
              Collection.Remove(message);
          }
  
          private async void UserInputBar_OnEnterKeyPressed()
          {
              CoreWindow coreWindow = CoreWindow.GetForCurrentThread();
              bool shift = coreWindow.GetKeyState(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
              bool control = coreWindow.GetKeyState(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
              // If SendMessageWithEnterSetting is true then add new if shift key is pressed.
              // If SendMessageWithEnterSetting is false then send message if control key is pressed.
              if (GlobalSettingsManager.SendMessageWithEnterSetting ? shift : !control)
              {
                  UserInputBar.AddLinefeed();
              }
              else
              {
                  bool sendMessageResult = await GetMainPageVm().SendMessage(UserInputBar.InputText, SelectedFile);
                  if (sendMessageResult)
                  {
                      ResetInput();
                  }
              }
          }
  
          private void ScrollToBottom()
          {
              if (Collection.Count > 0)
              {
                  var lastUnreadMsg = ConversationItemsControl.Items[Collection.Count - 1];
                  ConversationItemsControl.ScrollIntoView(lastUnreadMsg, ScrollIntoViewAlignment.Leading);
              }
          }
  
  
          private async void UserInputBar_OnSendMessageButtonClicked()
          {
              if (string.IsNullOrEmpty(UserInputBar.InputText) && SelectedFile == null)
              {
                  var filePicker = new FileOpenPicker();
                  filePicker.FileTypeFilter.Add("*"); // Without this the file picker throws an exception, this is not documented
                  var file = await filePicker.PickSingleFileAsync();
                  SetSelectedFile(file);
                  if (SelectedFile != null)
                  {
                      UserInputBar.FocusTextBox();
                  }
              }
              else
              {
                  bool sendMessageResult = await GetMainPageVm().SendMessage(UserInputBar.InputText, SelectedFile);
                  if (sendMessageResult)
                  {
                      ResetInput();
                  }
              }
          }
  
          private static ScrollViewer GetScrollViewer(DependencyObject element)
          {
              if (element is ScrollViewer)
              {
                  return (ScrollViewer)element;
              }
  
              for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
              {
                  var child = VisualTreeHelper.GetChild(element, i);
  
                  var result = GetScrollViewer(child);
                  if (result == null)
                  {
                      continue;
                  }
                  else
                  {
                      return result;
                  }
              }
  
              return null;
          }
  
          private void ScrollToUnread()
          {
  
gt:             if (Collection.Count > 0)