<commit_before>﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using AuthenticodeExaminer;
using Microsoft.Extensions.DependencyModel;
using NuGet.Packaging;
using NuGetPe;
using NuGetPe.AssemblyMetadata;
using PackageExplorerViewModel.Utilities;

namespace PackageExplorerViewModel
{
    public enum SymbolValidationResult
    {
        /// <summary>
        /// Everything checks out and symbols are in the package
        /// </summary>
        Valid,
        /// <summary>
        /// Valid with symbol servers
        /// </summary>
        ValidExternal,

        /// <summary>
        /// Source Link exists but has errors
        /// </summary>
        InvalidSourceLink,

        /// <summary>
        /// Missing source link data
        /// </summary>
        NoSourceLink,

        /// <summary>
        /// No symbols found
        /// </summary>
        NoSymbols,

        /// <summary>
        /// In progress
        /// </summary>
        Pending,

        /// <summary>
        /// No relevant files to validate. 
        /// </summary>
        NothingToValidate,

        /// <summary>
        /// Valid/ValidExternal except contains untracked sources
        /// </summary>
        HasUntrackedSources
    }

    public enum DeterministicResult
    {
        /// <summary>
        /// Assembly and sources are deterministic
        /// </summary>
        Valid,

        /// <summary>
        /// In Progress
        /// </summary>
        Pending,
        /// <summary>
        /// Source and assembly are not deterministic
        /// </summary>
        NonDeterministic,

        /// <summary>
        /// No relevant files to validate. 
        /// </summary>
        NothingToValidate,

        /// <summary>
        /// Valid but has untracked sources
        /// </summary>
        HasUntrackedSources
    }

    public enum HasCompilerFlagsResult
    {
        /// <summary>
        /// Symbols have compiler flag data
        /// </summary>
        Present,

        /// <summary>
        /// In Progress
        /// </summary>
        Pending,
        /// <summary>
        /// Symbols do not have compiler flag data
        /// </summary>
        Missing,
        /// <summary>
        /// No relevant files to validate. 
        /// </summary>
        NothingToValidate
    }

    public class SymbolValidator : INotifyPropertyChanged
    {
        private readonly IPackage _package;
        private readonly HttpClient _httpClient = new();
        private readonly PackageFolder _rootFolder;
        private readonly string _packagePath;


        public SymbolValidator(IPackage package, string packagePath, PackageFolder? rootFolder)
        {
            _package = package ?? throw new ArgumentNullException(nameof(package));
            _packagePath = packagePath ?? throw new ArgumentNullException(nameof(packagePath));

            // NuGet signs all its packages and stamps on the service index. Look for that.
            if (package is ISignaturePackage sigPackage)
            {
                if (sigPackage.RepositorySignature?.V3ServiceIndexUrl?.AbsoluteUri.Contains(".nuget.org/", StringComparison.OrdinalIgnoreCase) == true)
                {
                    IsPublicPackage = true;
                }
            }

            _rootFolder = rootFolder ?? PathToTreeConverter.Convert(package.GetFiles().ToList(), null);


            SourceLinkResult = SymbolValidationResult.Pending;
            DeterministicResult = DeterministicResult.Pending;
            CompilerFlagsResult = HasCompilerFlagsResult.Pending;               
        }  

        public async Task ResetToDefault()
        {
            SourceLinkResult = SymbolValidationResult.Pending;
            SourceLinkErrorMessage = null;
            DeterministicResult = DeterministicResult.Pending;
            DeterministicErrorMessage = null;
            CompilerFlagsResult = HasCompilerFlagsResult.Pending;
            HasCompilerFlagsMessage = null;

            await Validate().ConfigureAwait(false);
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        public async Task Validate()
        {
            try
            {
                // Get relevant files to check
                var files = GetFilesToCheck();

                await Task.Run(async () => await CalculateValidity(files!).ConfigureAwait(false));
            }
            catch(Exception e)
            {
                DiagnosticsClient.TrackException(e, _package, IsPublicPackage);
            }
            finally
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SourceLinkResult)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SourceLinkErrorMessage)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DeterministicResult)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DeterministicErrorMessage)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CompilerFlagsResult)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasCompilerFlagsMessage)));
            }

        }

        private IReadOnlyList<PackageFile> GetFilesToCheck()
        {
            if(_package.PackageTypes.Any(pt => "DotnetTool".Equals(pt.Name, StringComparison.OrdinalIgnoreCase)))
            {
                return GetToolFiles();
            }

            return GetLibraryFiles();
        }

        private IReadOnlyList<PackageFile> GetToolFiles()
        {

            var files = new List<PackageFile>();
            // For tool packages, we look in the tools folder for projects the user built
            // We will look for *.deps.json and read the libraries node for type: project.
            // Then we return the matching files in the same directory

            var depsFiles = _rootFolder["tools"]?.GetFiles().Where(f => f.Path.EndsWith(".deps.json", StringComparison.OrdinalIgnoreCase)) ?? Enumerable.Empty<IPackageFile>();

            foreach(PackageFile depFile in depsFiles)
            {
                using var reader = new DependencyContextJsonReader();
                var context = reader.Read(depFile.GetStream());

                var runtimeLibs = context.RuntimeLibraries.Where(rl => "project".Equals(rl.Type, StringComparison.OrdinalIgnoreCase)).ToList();


                var userFiles = (from rl in runtimeLibs
                                join f in depFile.Parent!.GetFiles().Where(pf => pf is PackageFile).Cast<PackageFile>() on $"{rl.Name}.dll".ToUpperInvariant() equals Path.GetFileName(f.Path).ToUpperInvariant()<commit_msg>Expose validated file list<commit_after>﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using AuthenticodeExaminer;
using Microsoft.Extensions.DependencyModel;
using NuGet.Packaging;
using NuGetPe;
using NuGetPe.AssemblyMetadata;
using PackageExplorerViewModel.Utilities;

namespace PackageExplorerViewModel
{
    public enum SymbolValidationResult
    {
        /// <summary>
        /// Everything checks out and symbols are in the package
        /// </summary>
        Valid,
        /// <summary>
        /// Valid with symbol servers
        /// </summary>
        ValidExternal,

        /// <summary>
        /// Source Link exists but has errors
        /// </summary>
        InvalidSourceLink,

        /// <summary>
        /// Missing source link data
        /// </summary>
        NoSourceLink,

        /// <summary>
        /// No symbols found
        /// </summary>
        NoSymbols,

        /// <summary>
        /// In progress
        /// </summary>
        Pending,

        /// <summary>
        /// No relevant files to validate. 
        /// </summary>
        NothingToValidate,

        /// <summary>
        /// Valid/ValidExternal except contains untracked sources
        /// </summary>
        HasUntrackedSources
    }

    public enum DeterministicResult
    {
        /// <summary>
        /// Assembly and sources are deterministic
        /// </summary>
        Valid,

        /// <summary>
        /// In Progress
        /// </summary>
        Pending,
        /// <summary>
        /// Source and assembly are not deterministic
        /// </summary>
        NonDeterministic,

        /// <summary>
        /// No relevant files to validate. 
        /// </summary>
        NothingToValidate,

        /// <summary>
        /// Valid but has untracked sources
        /// </summary>
        HasUntrackedSources
    }

    public enum HasCompilerFlagsResult
    {
        /// <summary>
        /// Symbols have compiler flag data
        /// </summary>
        Present,

        /// <summary>
        /// In Progress
        /// </summary>
        Pending,
        /// <summary>
        /// Symbols do not have compiler flag data
        /// </summary>
        Missing,
        /// <summary>
        /// No relevant files to validate. 
        /// </summary>
        NothingToValidate
    }

    public class SymbolValidator : INotifyPropertyChanged
    {
        private readonly IPackage _package;
        private readonly HttpClient _httpClient = new();
        private readonly PackageFolder _rootFolder;
        private readonly string _packagePath;


        public SymbolValidator(IPackage package, string packagePath, PackageFolder? rootFolder)
        {
            _package = package ?? throw new ArgumentNullException(nameof(package));
            _packagePath = packagePath ?? throw new ArgumentNullException(nameof(packagePath));

            // NuGet signs all its packages and stamps on the service index. Look for that.
            if (package is ISignaturePackage sigPackage)
            {
                if (sigPackage.RepositorySignature?.V3ServiceIndexUrl?.AbsoluteUri.Contains(".nuget.org/", StringComparison.OrdinalIgnoreCase) == true)
                {
                    IsPublicPackage = true;
                }
            }

            _rootFolder = rootFolder ?? PathToTreeConverter.Convert(package.GetFiles().ToList(), null);


            SourceLinkResult = SymbolValidationResult.Pending;
            DeterministicResult = DeterministicResult.Pending;
            CompilerFlagsResult = HasCompilerFlagsResult.Pending;               
        }  

        public async Task ResetToDefault()
        {
            SourceLinkResult = SymbolValidationResult.Pending;
            SourceLinkErrorMessage = null;
            DeterministicResult = DeterministicResult.Pending;
            DeterministicErrorMessage = null;
            CompilerFlagsResult = HasCompilerFlagsResult.Pending;
            HasCompilerFlagsMessage = null;

            await Validate().ConfigureAwait(false);
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        public async Task Validate()
        {
            try
            {
                // Get relevant files to check
                var files = GetFilesToCheck();

                await Task.Run(async () => await CalculateValidity(files!).ConfigureAwait(false));
            }
            catch(Exception e)
            {
                DiagnosticsClient.TrackException(e, _package, IsPublicPackage);
            }
            finally
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SourceLinkResult)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SourceLinkErrorMessage)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DeterministicResult)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DeterministicErrorMessage)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CompilerFlagsResult)));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasCompilerFlagsMessage)));
            }

        }

        public IReadOnlyList<PackageFile> GetAllFiles() => GetFilesToCheck();

        private IReadOnlyList<PackageFile> GetFilesToCheck()
        {
            if(_package.PackageTypes.Any(pt => "DotnetTool".Equals(pt.Name, StringComparison.OrdinalIgnoreCase)))
            {
                return GetToolFiles();
            }

            return GetLibraryFiles();
        }

        private IReadOnlyList<PackageFile> GetToolFiles()
        {

            var files = new List<PackageFile>();
            // For tool packages, we look in the tools folder for projects the user built
            // We will look for *.deps.json and read the libraries node for type: project.
            // Then we return the matching files in the same directory

            var depsFiles = _rootFolder["tools"]?.GetFiles().Where(f => f.Path.EndsWith(".deps.json", StringComparison.OrdinalIgnoreCase)) ?? Enumerable.Empty<IPackageFile>();

            foreach(PackageFile depFile in depsFiles)
            {
                using var reader = new DependencyContextJsonReader();
                var context = reader.Read(depFile.GetStream());

                var runtimeLibs = context.RuntimeLibraries.Where(rl => "project".Equals(rl.Type, StringComparison.OrdinalIgnoreCase)).ToList();


                var userFiles = (from rl in runtimeLibs
                                join f in depFile.Parent!.GetFiles().Where(pf => pf is PackageFile).Cast<PackageFile>() on $"{rl.Name}.dll".ToUpperInvariant() equals Path.GetFileName(f.Path).ToUpperInvariant()
{'fraction_non_alphanumeric': 0.09619252873563218, 'fraction_numerical': 0.00014367816091954023, 'mean_word_length': 1.930736842105263, 'pattern_counts': {'":': 0, '<': 82, '<?xml version=': 0, '>': 93, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5825975', 'n_tokens_mistral': 3385, 'n_tokens_neox': 3255, 'n_words': 814}