using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
#if NET40_OR_GREATER || NETCOREAPP
using System.Threading.Tasks;
#endif
using SabreTools.DatFiles;
using SabreTools.IO;
using SabreTools.IO.Extensions;
using SabreTools.IO.Logging;
using SabreTools.Reports;
namespace SabreTools.DatTools
{
///
/// Helper methods for dealing with DatFile statistics
///
public class Statistics
{
#region Logging
///
/// Logging object
///
private static readonly Logger logger = new();
#endregion
///
/// Calculate statistics from a list of inputs
///
/// List of input files and folders
/// True if single DAT stats are output, false otherwise
/// True if the error that is thrown should be thrown back to the caller, false otherwise
public static List CalculateStatistics(List inputs, bool single, bool throwOnError = false)
{
// Create the output list
List stats = [];
// Make sure we have all files and then order them
List files = PathTool.GetFilesOnly(inputs);
files = files
.OrderBy(i => Path.GetDirectoryName(i.CurrentPath))
.ThenBy(i => Path.GetFileName(i.CurrentPath))
.ToList();
// Init total
DatStatistics totalStats = new("DIR: All DATs", isDirectory: true);
// Init directory-level variables
string? lastdir = null;
DatStatistics dirStats = new(displayName: null, isDirectory: true);
// Now process each of the input files
foreach (ParentablePath file in files)
{
// Get the directory for the current file
string? thisdir = Path.GetDirectoryName(file.CurrentPath);
// If we don't have the first file and the directory has changed, show the previous directory stats and reset
if (lastdir != null && thisdir != lastdir && single)
{
#if NET20 || NET35
dirStats.DisplayName = $"DIR: {lastdir}";
#else
dirStats.DisplayName = $"DIR: {WebUtility.HtmlEncode(lastdir)}";
#endif
dirStats.MachineCount = dirStats.GameCount;
stats.Add(dirStats);
dirStats = new DatStatistics(displayName: null, isDirectory: true);
}
InternalStopwatch watch = new($"Collecting statistics for '{file.CurrentPath}'");
List machines = [];
DatFile datdata = Parser.CreateAndParse(file.CurrentPath, statsOnly: true, throwOnError: throwOnError);
// Add single DAT stats (if asked)
if (single)
{
DatStatistics individualStats = datdata.Items.DatStatistics;
//DatStatistics individualStats = datdata.ItemsDB.DatStatistics;
individualStats.DisplayName = datdata.Header.GetStringFieldValue(DatHeader.FileNameKey);
individualStats.MachineCount = datdata.Items.Keys.Count;
stats.Add(individualStats);
}
// Add single DAT stats to dir
dirStats.AddStatistics(datdata.Items.DatStatistics);
//dirStats.AddStatistics(datdata.ItemsDB.DatStatistics);
dirStats.GameCount += datdata.Items.Keys.Count;
// Add single DAT stats to totals
totalStats.AddStatistics(datdata.Items.DatStatistics);
//totalStats.AddStatistics(datdata.ItemsDB.DatStatistics);
totalStats.GameCount += datdata.Items.Keys.Count;
// Make sure to assign the new directory
lastdir = thisdir;
watch.Stop();
}
// Add last directory stats
if (single)
{
#if NET20 || NET35
dirStats.DisplayName = $"DIR: {lastdir}";
#else
dirStats.DisplayName = $"DIR: {WebUtility.HtmlEncode(lastdir)}";
#endif
dirStats.MachineCount = dirStats.GameCount;
stats.Add(dirStats);
}
// Add total DAT stats
totalStats.MachineCount = totalStats.GameCount;
stats.Add(totalStats);
return stats;
}
///
/// Output the stats for a list of input dats as files in a human-readable format
///
/// List of pre-calculated statistics objects
/// Name of the output file
/// True if baddumps should be included in output, false otherwise
/// True if nodumps should be included in output, false otherwise
/// Set the statistics output format to use
/// True if the error that is thrown should be thrown back to the caller, false otherwise
/// True if the report was written correctly, false otherwise
public static bool Write(
List stats,
string reportName,
string? outDir,
bool baddumpCol,
bool nodumpCol,
StatReportFormat statDatFormat,
bool throwOnError = false)
{
// If there's no output format, set the default
if (statDatFormat == StatReportFormat.None)
{
logger.Verbose("No report format defined, defaulting to textfile");
statDatFormat = StatReportFormat.Textfile;
}
// Get the proper output file name
if (string.IsNullOrEmpty(reportName))
reportName = "report";
// Get the proper output directory name
outDir = outDir.Ensure();
InternalStopwatch watch = new($"Writing out report data to '{outDir}'");
// Get the dictionary of desired output report names
Dictionary outfiles = CreateOutStatsNames(outDir!, statDatFormat, reportName);
try
{
// Write out all required formats
#if NET452_OR_GREATER || NETCOREAPP
Parallel.ForEach(outfiles.Keys, Core.Globals.ParallelOptions, reportFormat =>
#elif NET40_OR_GREATER
Parallel.ForEach(outfiles.Keys, reportFormat =>
#else
foreach (var reportFormat in outfiles.Keys)
#endif
{
string outfile = outfiles[reportFormat];
try
{
BaseReport.Create(reportFormat, stats)?.WriteToFile(outfile, baddumpCol, nodumpCol, throwOnError);
}
catch (Exception ex) when (!throwOnError)
{
logger.Error(ex, $"Report '{outfile}' could not be written out");
}
#if NET40_OR_GREATER || NETCOREAPP
});
#else
}
#endif
}
catch (Exception ex) when (!throwOnError)
{
logger.Error(ex);
return false;
}
finally
{
watch.Stop();
}
return true;
}
///
/// Get the proper extension for the stat output format
///
/// Output path to use
/// StatDatFormat to get the extension for
/// Name of the input file to use
/// Dictionary of output formats mapped to file names
private static Dictionary CreateOutStatsNames(string outDir, StatReportFormat statDatFormat, string reportName, bool overwrite = true)
{
Dictionary output = [];
// First try to create the output directory if we need to
if (!Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
// Double check the outDir for the end delim
if (!outDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
outDir += Path.DirectorySeparatorChar;
// For each output format, get the appropriate stream writer
output.Add(StatReportFormat.None, CreateOutStatsNamesHelper(outDir, ".null", reportName, overwrite));
#if NETFRAMEWORK
if ((statDatFormat & StatReportFormat.Textfile) != 0)
#else
if (statDatFormat.HasFlag(StatReportFormat.Textfile))
#endif
output.Add(StatReportFormat.Textfile, CreateOutStatsNamesHelper(outDir, ".txt", reportName, overwrite));
#if NETFRAMEWORK
if ((statDatFormat & StatReportFormat.CSV) != 0)
#else
if (statDatFormat.HasFlag(StatReportFormat.CSV))
#endif
output.Add(StatReportFormat.CSV, CreateOutStatsNamesHelper(outDir, ".csv", reportName, overwrite));
#if NETFRAMEWORK
if ((statDatFormat & StatReportFormat.HTML) != 0)
#else
if (statDatFormat.HasFlag(StatReportFormat.HTML))
#endif
output.Add(StatReportFormat.HTML, CreateOutStatsNamesHelper(outDir, ".html", reportName, overwrite));
#if NETFRAMEWORK
if ((statDatFormat & StatReportFormat.SSV) != 0)
#else
if (statDatFormat.HasFlag(StatReportFormat.SSV))
#endif
output.Add(StatReportFormat.SSV, CreateOutStatsNamesHelper(outDir, ".ssv", reportName, overwrite));
#if NETFRAMEWORK
if ((statDatFormat & StatReportFormat.TSV) != 0)
#else
if (statDatFormat.HasFlag(StatReportFormat.TSV))
#endif
output.Add(StatReportFormat.TSV, CreateOutStatsNamesHelper(outDir, ".tsv", reportName, overwrite));
return output;
}
///
/// Help generating the outstats name
///
/// Output directory
/// Extension to use for the file
/// Name of the input file to use
/// True if we ignore existing files, false otherwise
/// String containing the new filename
private static string CreateOutStatsNamesHelper(string outDir, string extension, string reportName, bool overwrite)
{
string outfile = outDir + reportName + extension;
outfile = outfile.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", Path.DirectorySeparatorChar.ToString());
if (!overwrite)
{
int i = 1;
while (File.Exists(outfile))
{
outfile = $"{outDir}{reportName}_{i}{extension}";
outfile = outfile.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", Path.DirectorySeparatorChar.ToString());
i++;
}
}
return outfile;
}
}
}