<commit_before>﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Threading;

namespace SotAMapper
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MapDataMgr _mapDataMgr;
        private PlayerDataWatcher _playerDataWatcher;

        private Map _lastMap;
        private PlayerData _lastPlayerData;

        private Dictionary<object, MapItem> _uiElementToMapItemMap; 

        public MainWindow()
        {
            InitializeComponent();

            // build window title, use embedded assembly version so it can be set
            // in one place.
            var ver = Assembly.GetExecutingAssembly().GetName().Version;
            Title = "SotAMapper v" + ver.Major + "." + ver.Minor;

            RenderMap(null, null);

            // load up all available map files
            _mapDataMgr = new MapDataMgr();
            _mapDataMgr.Load();

            // start watching SotA log file for player /loc data
            _playerDataWatcher = new PlayerDataWatcher();
            _playerDataWatcher.PlayerDataChanged += OnPlayerDataChanged;
            _playerDataWatcher.Start();
        }

        /// <summary>
        /// Called whenever new player data is available.
        /// </summary>
        public void OnPlayerDataChanged(PlayerData newPlayerData)
        {
            //Debug.WriteLine(">>> player data changed: " + newPlayerData);

            // ensure current map matches current player data (may be null if there is no map
            // data for current player location)
            var currentMap = _mapDataMgr.GetMap(newPlayerData.MapName);

            // store for later use by size changed event handler
            _lastMap = currentMap;
            _lastPlayerData = newPlayerData;

            // trigger re-render on UI thread
            Dispatcher.BeginInvoke((Action)(() => RenderMap(currentMap, newPlayerData)));
        }

        /// <summary>
        /// When window size changes, need to re-render as everything is based on
        /// window size
        /// </summary>
        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            RenderMap(_lastMap, _lastPlayerData);
        }

        /// <summary>
        /// Render map data on WPF canvas
        /// </summary>
        private void RenderMap(Map map, PlayerData playerData)
        {
            _uiElementToMapItemMap = new Dictionary<object, MapItem>();
            double canvasX = 0, canvasY = 0;
            TextBlock label = null;

            // margin for edge text directly in canvas units
            double windowMargin = 5;

            // first, clear everything off the canvas
            MainCanvas.Children.Clear();

            // do we have valid data to render?

            bool validData = true;

            if ((map == null) || (playerData == null))
                validData = false;

            IEnumerable<MapCoord> otherData = null;
            if (playerData != null)
                otherData = new List<MapCoord> {playerData.Loc};
            var conv = new MapCanvasConverter(map, MainCanvas, otherData);
            if (!conv.Init())
                validData = false;

            // color/brush used for rendering text and other annotations
            var lineColor = Color.FromRgb(236, 142, 0);
            var lineBrush = new SolidColorBrush(lineColor);

            // no valid data, just show help text
            if (!validData)
            {
                var helpText =
                    "* * * NO DATA * * *\n\n" +
                    "Type /loc in game and verify reported map name matches a file in \"data/maps\"\n\n" +
                    "For example, if /loc outputs the below:\n" +
                    "Area: Soltown (Novia_R1_City_Soltown) Loc: (-15.7, 28.0, 23,2)\n" +
                    "there should be a file, \"data/maps/Novia_R1_City_Soltown.csv\" with map data\n\n" +
                    "Player location on map will update automatically, however it is necessary to\n" +
                    "manually use the /loc command once each time when entering a map to sync current map.\n\n" +                    
                    "Map items are rendered as text labels unless a PNG file exists in \"data/icons\" which\n" +
                    "matches the name of the item in the map .csv file\n\n" +
                    "The /loctrack command shows location on screen and makes it easier to build map files";
                label = new TextBlock();
                label.Text = helpText;
                label.Foreground = lineBrush;
                label.FontSize = 18;
                label.TextAlignment = TextAlignment.Center;             
                label.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                label.Arrange(new Rect(label.DesiredSize));
                Canvas.SetLeft(label, (MainCanvas.ActualWidth / 2.0) - (label.ActualWidth / 2.0));
                Canvas.SetTop(label, conv.CanvasMarginY);
                MainCanvas.Children.Add(label);
            }

            // otherwise render map data
            else
            {
                // render all map items
                foreach (var mapItem in map.Items)
                {
                    conv.ConvertMapToCanvas(mapItem.Coord, out canvasX, out canvasY);

                    // if an icon exits for this item use that
                    var iconPath = System.IO.Path.Combine(Globals.IconDir, mapItem.Name + ".png");
                    if (File.Exists(iconPath))
                    {
                        var img = new Image();
                        var bmpImg = new BitmapImage();
                        bmpImg.BeginInit();
                        bmpImg.UriSource = new Uri(iconPath);
                        bmpImg.EndInit();
                        img.Stretch = Stretch.Fill;
                        img.Source = bmpImg;
                        img.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                        img.Arrange(new Rect(img.DesiredSize));
                        Canvas.SetLeft(img, canvasX - (img.ActualWidth / 2.0));
                        Canvas.SetTop(img, canvasY - (img.ActualHeight / 2.0));
                        MainCanvas.Children.Add(img);

                        _uiElementToMapItemMap[img] = mapItem;
                        img.MouseEnter += new MouseEventHandler(OnMouseEnter);
                        img.MouseLeave += new MouseEventHandler(OnMouseLeave);
                    }<commit_msg>now shows error messages in red which describe why the "no data" state was reached<commit_after>﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Threading;

namespace SotAMapper
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MapDataMgr _mapDataMgr;
        private PlayerDataWatcher _playerDataWatcher;

        private Map _lastMap;
        private PlayerData _lastPlayerData;

        private Dictionary<object, MapItem> _uiElementToMapItemMap; 

        public MainWindow()
        {
            InitializeComponent();

            // build window title, use embedded assembly version so it can be set
            // in one place.
            var ver = Assembly.GetExecutingAssembly().GetName().Version;
            Title = "SotAMapper v" + ver.Major + "." + ver.Minor;

            RenderMap(null, null);

            // load up all available map files
            _mapDataMgr = new MapDataMgr();
            _mapDataMgr.Load();

            // start watching SotA log file for player /loc data
            _playerDataWatcher = new PlayerDataWatcher();
            _playerDataWatcher.PlayerDataChanged += OnPlayerDataChanged;
            _playerDataWatcher.Start();
        }

        /// <summary>
        /// Called whenever new player data is available.
        /// </summary>
        public void OnPlayerDataChanged(PlayerData newPlayerData)
        {
            //Debug.WriteLine(">>> player data changed: " + newPlayerData);

            // ensure current map matches current player data (may be null if there is no map
            // data for current player location)
            var currentMap = _mapDataMgr.GetMap(newPlayerData.MapName);

            // store for later use by size changed event handler
            _lastMap = currentMap;
            _lastPlayerData = newPlayerData;

            // trigger re-render on UI thread
            Dispatcher.BeginInvoke((Action)(() => RenderMap(currentMap, newPlayerData)));
        }

        /// <summary>
        /// When window size changes, need to re-render as everything is based on
        /// window size
        /// </summary>
        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            RenderMap(_lastMap, _lastPlayerData);
        }

        /// <summary>
        /// Render map data on WPF canvas
        /// </summary>
        private void RenderMap(Map map, PlayerData playerData)
        {
            _uiElementToMapItemMap = new Dictionary<object, MapItem>();
            double canvasX = 0, canvasY = 0;
            TextBlock label = null;

            // margin for edge text directly in canvas units
            double windowMargin = 5;

            // first, clear everything off the canvas
            MainCanvas.Children.Clear();

            // holds error strings for rendering in case we don't have valid data to render
            var errors = new List<string>();

            // do we have valid data to render?
            bool validData = true;

            if (playerData == null)
            {
                errors.Add("no player data, try using /loc");
                validData = false;
            }
            else
            {
                if ((playerData.MapName?.Length ?? 0) == 0)
                {
                    errors.Add("unable to determine player map, try using /loc");
                    validData = false;
                }

                if (playerData.Loc == null)
                {
                    errors.Add("unable to determine player position, try using /loc");
                    validData = false;
                }
            }

            if (map == null)
            {
                if ((playerData?.MapName?.Length ?? 0) > 0)
                {
                    errors.Add("no .csv file for current map, compare /loc output to data/maps files");
                    validData = false;
                }
            }
            else
            {
                if ((map.MinLoc == null) || (map.MaxLoc == null))
                {
                    errors.Add("empty map .csv file, please add some entries");
                    validData = false;
                }
            }

            if ((map == null) ||
                (map?.MinLoc == null) ||
                (map?.MaxLoc == null) ||
                (playerData == null) ||
                ((playerData?.MapName?.Length ?? 0) == 0) ||
                (playerData?.Loc == null))
            {
                validData = false;
            }

            IEnumerable<MapCoord> otherData = null;
            if (playerData != null)
                otherData = new List<MapCoord> {playerData.Loc};
            var conv = new MapCanvasConverter(map, MainCanvas, otherData);
            if (!conv.Init())
                validData = false;

            // color/brush used for rendering text and other annotations
            var lineColor = Color.FromRgb(236, 142, 0);
            var lineBrush = new SolidColorBrush(lineColor);

            // color/brush used for error messages
            var errColor = Color.FromRgb(255, 0, 0);
            var errBrush = new SolidColorBrush(errColor);

            // no valid data, just show help text
            if (!validData)
            {
                var helpText =
                    "* * * NO DATA * * *\n" +
                    "Type /loc in game and verify reported map name matches a file in \"data/maps\"\n\n" +
                    "For example, if /loc outputs the below:\n" +
                    "Area: Soltown (Novia_R1_City_Soltown) Loc: (-15.7, 28.0, 23,2)\n" +
                    "there should be a file, \"data/maps/Novia_R1_City_Soltown.csv\" with map data\n\n" +
                    "Player location on map will update automatically, however it is necessary to\n" +
                    "manually use the /loc command once each time when entering a map to sync current map.\n\n" +                    
                    "Map items are rendered as text labels unless a PNG file exists in \"data/icons\" which\n" +
                    "matches the name of the item in the map .csv file\n\n" +
                    "The /loctrack command shows location on screen and makes it easier to build map files";
                label = new TextBlock();
                label.Text = helpText;
                label.Foreground = lineBrush;
                label.FontSize = 18;
                label.TextAlignment = TextAlignment.Center;             
                label.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                label.Arrange(new Rect(label.DesiredSize));
                Canvas.SetLeft(label, (MainCanvas.ActualWidth / 2.0) - (label.ActualWidth / 2.0));
                Canvas.SetTop(label, conv.CanvasMarginY);
                MainCanvas.Children.Add(label);

                var errorsCombined = string.Join("\n", errors);
                label = new TextBlock();
                label.Text = errorsCombined;
                label.Foreground = errBrush;
                label.FontSize = 18;
                label.TextAlignment = TextAlignment.Center;
                label.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                label.Arrange(new Rect(label.DesiredSize));
                Canvas.SetLeft(label, (MainCanvas.ActualWidth / 2.0) - (label.ActualWidth / 2.0));
                Canvas.SetTop(label, MainCanvas.ActualHeight - conv.CanvasMarginY - label.ActualHeight);
                MainCanvas.Children.Add(label);
            }

            // otherwise render map data
            else
            {
                // render all map items
                foreach (var mapItem in map.Items)
                {
                    conv.ConvertMapToCanvas(mapItem.Coord, out canvasX, out canvasY);

                    // if an icon exits for this item use that
                    var iconPath = System.IO.Path.Combine(Globals.IconDir, mapItem.Name + ".png");
                    if (File.Exists(iconPath))
                    {
                        var img = new Image();
                        var bmpImg = new BitmapImage();
                        bmpImg.BeginInit();
                        bmpImg.UriSource = new Uri(iconPath);
                        bmpImg.EndInit();
                        img.Stretch = Stretch.Fill;
                        img.Source = bmpImg;
                        img.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                        img.Arrange(new Rect(img.DesiredSize));
                        Canvas.SetLeft(img, canvasX - (img.ActualWidth / 2.0));
                        Canvas.SetTop(img, canvasY - (img.ActualHeight / 2.0));
                        MainCanvas.Children.Add(img);

                        _uiElementToMapItemMap[img] = mapItem;
                        img.MouseEnter += new MouseEventHandler(OnMouseEnter);
                        img.MouseLeave += new MouseEventHandler(OnMouseLeave);
                    }
{'fraction_non_alphanumeric': 0.09093650123620575, 'fraction_numerical': 0.00476391485255985, 'mean_word_length': 1.5624227441285539, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7359332', 'n_tokens_mistral': 4296, 'n_tokens_neox': 4026, 'n_words': 1205}