using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Win32;

namespace DiskUsageLog
{
    class Program
    {
        const long ONE_GB = 1024L * 1024 * 1024;

        static readonly string[] HardExcludedFolders =
        {
            "WinSxS",
            "Microsoft.NET",
            "assembly"
        };

        static readonly string InstallerPath = @"C:\Windows\Installer";

        static Dictionary<string, ExtensionStat> ExtensionStats =
            new Dictionary<string, ExtensionStat>(StringComparer.OrdinalIgnoreCase);

        static List<FileInfo> LargestFiles = new List<FileInfo>();
        static List<LargeFolder> LargeFolders = new List<LargeFolder>();

        static void Main(string[] args)
        {
            string rootPath = args.Length > 0 ? args[0] : @"C:\";

            if (!Directory.Exists(rootPath))
            {
                Console.WriteLine("Chemin invalide.");
                return;
            }

            Console.WriteLine("DiskUsageLog - Analyse disque");
            Console.WriteLine("Racine analysée : " + rootPath);
            Console.WriteLine("--------------------------------------------------\n");

            foreach (string dir in Directory.GetDirectories(rootPath))
            {
                AnalyzeDirectory(dir);
            }

            DisplayLargeFolders();
            DisplayTopExtensions();
            DisplayLargestFiles();
            DisplayOrphanInstallers();

            Console.WriteLine("\nAnalyse terminée.");
            Console.ReadLine();
        }

        // ======================= ANALYSE =======================

        static long AnalyzeDirectory(string path)
        {
            long totalSize = 0;

            try
            {
                foreach (string file in Directory.GetFiles(path))
                {
                    FileInfo fi = new FileInfo(file);
                    totalSize += fi.Length;

                    if (!IsSystemCritical(path))
                        TrackExtension(fi);

                    TrackLargestFiles(fi);
                }

                foreach (string dir in Directory.GetDirectories(path))
                {
                    if (IsHardExcluded(dir)) continue;
                    totalSize += AnalyzeDirectory(dir);
                }
            }
            catch
            {
                // Accès refusé / erreur disque
            }

            if (totalSize >= ONE_GB)
            {
                LargeFolders.Add(new LargeFolder
                {
                    Path = path,
                    Size = totalSize,
                    IsRiskArea = IsRiskArea(path)
                });
            }

            return totalSize;
        }

        // ======================= REGLES =======================

        static bool IsHardExcluded(string path)
        {
            string name = Path.GetFileName(path);
            return HardExcludedFolders.Any(e =>
                e.Equals(name, StringComparison.OrdinalIgnoreCase));
        }

        static bool IsSystemCritical(string path)
        {
            return path.StartsWith(@"C:\Windows\System32",
                StringComparison.OrdinalIgnoreCase);
        }

        static bool IsRiskArea(string path)
        {
            return path.StartsWith(@"C:\Windows\Installer", StringComparison.OrdinalIgnoreCase)
                || path.StartsWith(@"C:\Windows\Temp", StringComparison.OrdinalIgnoreCase)
                || path.StartsWith(@"C:\Windows\SoftwareDistribution", StringComparison.OrdinalIgnoreCase)
                || path.Contains(@"\AppData\Local\Temp");
        }

        // ======================= EXTENSIONS =======================

        static void TrackExtension(FileInfo fi)
        {
            string ext = string.IsNullOrEmpty(fi.Extension)
                ? "[sans_ext]"
                : fi.Extension.ToLower();

            if (!ExtensionStats.ContainsKey(ext))
                ExtensionStats[ext] = new ExtensionStat();

            ExtensionStats[ext].Count++;
            ExtensionStats[ext].Size += fi.Length;
        }

        static void DisplayTopExtensions()
        {
            Console.WriteLine("\n=== TOP 20 EXTENSIONS (hors système critique) ===\n");
            Console.WriteLine("{0,-12} {1,10} {2,15}",
                "Extension", "Fichiers", "Taille (GB)");

            foreach (var ext in ExtensionStats
                .OrderByDescending(e => e.Value.Size)
                .Take(20))
            {
                Console.WriteLine("{0,-12} {1,10} {2,15}",
                    ext.Key,
                    ext.Value.Count,
                    ToGB(ext.Value.Size));
            }
        }

        // ======================= GROS FICHIERS =======================

        static void TrackLargestFiles(FileInfo fi)
        {
            LargestFiles.Add(fi);
            LargestFiles = LargestFiles
                .OrderByDescending(f => f.Length)
                .Take(10)
                .ToList();
        }

        static void DisplayLargestFiles()
        {
            Console.WriteLine("\n=== TOP 10 PLUS GROS FICHIERS ===\n");

            foreach (FileInfo fi in LargestFiles)
            {
                Console.WriteLine("{0} : {1} GB",
                    fi.FullName,
                    ToGB(fi.Length));
            }
        }

        // ======================= DOSSIERS =======================

        static void DisplayLargeFolders()
        {
            Console.WriteLine("\n=== DOSSIERS > 1 Go ===\n");

            foreach (LargeFolder f in LargeFolders
                .OrderByDescending(f => f.Size))
            {
                string flag = f.IsRiskArea ? " !!!" : "";
                Console.WriteLine("{0} : {1} GB{2}",
                    f.Path,
                    ToGB(f.Size),
                    flag);
            }

            Console.WriteLine("\n!!! = zone connue à dérive disque");
        }

        // ======================= MSI ORPHELINS =======================

        static void DisplayOrphanInstallers()
        {
            Console.WriteLine("\n=== MSI / MSP ORPHELINS POTENTIELS ===\n");

            if (!Directory.Exists(InstallerPath))
            {
                Console.WriteLine("Répertoire Windows Installer introuvable.");
                return;
            }

            var allInstallers = GetInstallerFiles();
            var referencedInstallers = GetReferencedInstallerFiles();

            var orphans = allInstallers.Values
                .Where(f => !referencedInstallers.Contains(f.FullName))
                .OrderByDescending(f => f.Length)
                .ToList();

            long total = 0;

            foreach (var fi in orphans)
            {
                Console.WriteLine("{0,-20} {1,10} GB",
                    fi.Name,
                    ToGB(fi.Length));

                total += fi.Length;
            }

            Console.WriteLine("\nTotal récupérable potentiel : {0} GB",
                ToGB(total));

            Console.WriteLine(
                "\nATTENTION : aucune suppression automatique.\nAnalyse uniquement.");
        }

        static Dictionary<string, FileInfo> GetInstallerFiles()
        {
            var dict = new Dictionary<string, FileInfo>(
                StringComparer.OrdinalIgnoreCase);

            try
            {
                foreach (string file in Directory.GetFiles(InstallerPath))
                {
                    if (file.EndsWith(".msi", StringComparison.OrdinalIgnoreCase) ||
                        file.EndsWith(".msp", StringComparison.OrdinalIgnoreCase))
                    {
                        FileInfo fi = new FileInfo(file);
                        dict[fi.FullName] = fi;
                    }
                }
            }
            catch
            {
                // Accès refusé
            }

            return dict;
        }

        static HashSet<string> GetReferencedInstallerFiles()
        {
            var result = new HashSet<string>(
                StringComparer.OrdinalIgnoreCase);

            ReadInstallerRegistry(
                @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData",
                result);

            ReadInstallerRegistry(
                @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Installer\UserData",
                result);

            return result;
        }

        static void ReadInstallerRegistry(string baseKey, HashSet<string> result)
        {
            try
            {
                using (RegistryKey root =
                    Registry.LocalMachine.OpenSubKey(baseKey))
                {
                    if (root == null) return;

                    foreach (string sid in root.GetSubKeyNames())
                    {
                        using (RegistryKey products =
                            root.OpenSubKey(sid + @"\Products"))
                        {
                            if (products == null) continue;

                            foreach (string prod in products.GetSubKeyNames())
                            {
                                using (RegistryKey installProps =
                                    products.OpenSubKey(prod + @"\InstallProperties"))
                                {
                                    if (installProps == null) continue;

                                    string localPackage =
                                        installProps.GetValue("LocalPackage") as string;

                                    if (!string.IsNullOrEmpty(localPackage))
                                        result.Add(localPackage);
                                }
                            }
                        }
                    }
                }
            }
            catch
            {
                // erreur registre
            }
        }

        // ======================= UTILS =======================

        static double ToGB(long bytes)
        {
            return Math.Round(bytes / 1024d / 1024 / 1024, 2);
        }
    }

    // ======================= MODELES =======================

    class ExtensionStat
    {
        public int Count;
        public long Size;
    }

    class LargeFolder
    {
        public string Path;
        public long Size;
        public bool IsRiskArea;
    }
}