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

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

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

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

        static readonly string[] KnownCacheFolderNames =
        {
            "Temp", "tmp", "Logs", "LogFiles",
            "INetCache", "Temporary Internet Files",
            "WER", "CrashDumps"
        };

        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 List<FileInfo> StaleFiles = new List<FileInfo>();
        static List<LargeFolder> RootBreakdown = new List<LargeFolder>();
        static List<CacheFolder> DetectedCacheFolders = new List<CacheFolder>();
        static List<LargeFolder> SystemDiagResults = new List<LargeFolder>();

        static long TotalFilesScanned;
        static long TotalFoldersScanned;
        static long TotalErrorCount;
        static long TotalSizeScanned;

        static Stopwatch Timer = new Stopwatch();
        static int spinnerIndex;
        static readonly char[] SpinnerChars = { '|', '/', '-', '\\' };

        static void Main(string[] args)
        {
            Console.OutputEncoding = System.Text.Encoding.UTF8;

            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);
            DisplayXstoreInfo();
            Console.WriteLine("--------------------------------------------------\n");

            Timer.Start();

            string[] rootDirs;
            try { rootDirs = Directory.GetDirectories(rootPath); }
            catch { rootDirs = new string[0]; }

            foreach (string dir in rootDirs)
            {
                long size = AnalyzeDirectory(dir);
                RootBreakdown.Add(new LargeFolder
                {
                    Path = dir,
                    Size = size
                });
            }

            ClearProgress();
            Timer.Stop();

            DisplaySummary();
            DisplayRootBreakdown();
            DisplayLargeFolders();
            DisplayTopExtensions();
            DisplayLargestFiles();
            DisplayStaleFiles();
            DisplayCacheFolders();
            DisplaySystemDiagnostic();
            DisplayOrphanInstallers();

            ExportReport(rootPath);

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

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

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

            string folderName = Path.GetFileName(path);
            bool isKnownCache = KnownCacheFolderNames.Any(c =>
                c.Equals(folderName, StringComparison.OrdinalIgnoreCase));

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

                    bool excluded = IsExcludedFile(fi, path);

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

                    if (!excluded)
                    {
                        TrackLargestFiles(fi);
                        TrackStaleFiles(fi);
                    }
                }

                if (TotalFoldersScanned % 100 == 0)
                    UpdateProgress(path);

                foreach (string dir in Directory.GetDirectories(path))
                {
                    if (IsHardExcluded(dir)) continue;
                    totalSize += AnalyzeDirectory(dir);
                }
            }
            catch
            {
                TotalErrorCount++;
            }

            if (isKnownCache && totalSize >= 50 * ONE_MB)
            {
                DetectedCacheFolders.Add(new CacheFolder
                {
                    Path = path,
                    Size = totalSize,
                    FolderName = folderName
                });
            }

            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");
        }

        // ======================= EXCLUSIONS =======================

        static bool IsExcludedFile(FileInfo fi, string dirPath)
        {
            if (fi.Extension.Equals(".dbf", StringComparison.OrdinalIgnoreCase)
                && dirPath.StartsWith(@"C:\xstoredb", StringComparison.OrdinalIgnoreCase))
                return true;

            return false;
        }

        // ======================= 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
            }
        }

        // ======================= PROGRESSION =======================

        static void UpdateProgress(string currentDir)
        {
            spinnerIndex = (spinnerIndex + 1) % SpinnerChars.Length;
            string dir = currentDir.Length > 50
                ? "..." + currentDir.Substring(currentDir.Length - 47)
                : currentDir;

            string line = string.Format("\r {0} {1:N0} fichiers | {2:N0} dossiers | {3} GB | {4}",
                SpinnerChars[spinnerIndex],
                TotalFilesScanned,
                TotalFoldersScanned,
                ToGB(TotalSizeScanned),
                dir);

            int width = 120;
            try { width = Console.WindowWidth; } catch { }
            if (line.Length < width)
                line = line.PadRight(width - 1);
            else if (line.Length >= width)
                line = line.Substring(0, width - 1);

            Console.Write(line);
        }

        static void ClearProgress()
        {
            int width = 120;
            try { width = Console.WindowWidth; } catch { }
            Console.Write("\r" + new string(' ', width - 1) + "\r");
        }

        // ======================= RESUME =======================

        static void DisplaySummary()
        {
            Console.WriteLine("\n=== RESUME DU SCAN ===\n");
            Console.WriteLine("Machine                : {0}", Environment.MachineName);
            Console.WriteLine("Duree                  : {0}", Timer.Elapsed.ToString(@"hh\:mm\:ss"));
            Console.WriteLine("Fichiers analyses      : {0:N0}", TotalFilesScanned);
            Console.WriteLine("Dossiers analyses      : {0:N0}", TotalFoldersScanned);
            Console.WriteLine("Taille totale scannee  : {0} GB", ToGB(TotalSizeScanned));
            Console.WriteLine("Erreurs (acces refuse) : {0:N0}", TotalErrorCount);
        }

        // ======================= ARBORESCENCE RACINE =======================

        static void DisplayRootBreakdown()
        {
            Console.WriteLine("\n=== REPARTITION RACINE (top 30) ===\n");

            foreach (var item in RootBreakdown.OrderByDescending(r => r.Size).Take(30))
            {
                string name = Path.GetFileName(item.Path);
                if (string.IsNullOrEmpty(name)) name = item.Path;

                double pct = TotalSizeScanned > 0
                    ? (item.Size * 100.0 / TotalSizeScanned)
                    : 0;

                int barLen = Math.Min((int)(pct / 2), 25);
                string bar = new string('#', barLen).PadRight(25);

                Console.WriteLine("{0,-40} {1,8} GB  [{2}] {3,5:F1}%",
                    name.Length > 40 ? name.Substring(0, 37) + "..." : name,
                    ToGB(item.Size),
                    bar,
                    pct);
            }
        }

        // ======================= FICHIERS ANCIENS =======================

        static void TrackStaleFiles(FileInfo fi)
        {
            try
            {
                if (fi.Length < 100 * ONE_MB) return;
                if (fi.LastWriteTime > DateTime.Now.AddYears(-2)) return;

                StaleFiles.Add(fi);
                StaleFiles = StaleFiles
                    .OrderByDescending(f => f.Length)
                    .Take(20)
                    .ToList();
            }
            catch { }
        }

        static void DisplayStaleFiles()
        {
            if (StaleFiles.Count == 0) return;

            Console.WriteLine("\n=== GROS FICHIERS ANCIENS (>100 Mo, non modifies depuis 2+ ans) ===\n");

            foreach (var fi in StaleFiles)
            {
                string path = fi.FullName.Length > 75
                    ? "..." + fi.FullName.Substring(fi.FullName.Length - 72)
                    : fi.FullName;

                Console.WriteLine("{0,-75} {1,8} GB  {2:yyyy-MM-dd}",
                    path, ToGB(fi.Length), fi.LastWriteTime);
            }
        }

        // ======================= DOSSIERS TEMPORAIRES =======================

        static void DisplayCacheFolders()
        {
            if (DetectedCacheFolders.Count == 0) return;

            Console.WriteLine("\n=== DOSSIERS TEMPORAIRES / LOGS DETECTES (>50 Mo) ===\n");

            long total = 0;
            foreach (var cf in DetectedCacheFolders.OrderByDescending(c => c.Size).Take(30))
            {
                string path = cf.Path.Length > 70
                    ? "..." + cf.Path.Substring(cf.Path.Length - 67)
                    : cf.Path;

                Console.WriteLine("{0,-70} {1,8} GB",
                    path, ToGB(cf.Size));
                total += cf.Size;
            }

            Console.WriteLine("\nTotal temporaires/logs : {0} GB", ToGB(total));
        }

        // ======================= DIAGNOSTIC SYSTEME =======================

        static void DisplaySystemDiagnostic()
        {
            Console.WriteLine("\n=== DIAGNOSTIC ZONES SYSTEME ===\n");

            var zones = new List<string>
            {
                @"C:\Windows\SoftwareDistribution\Download",
                @"C:\Windows\Temp",
                @"C:\Windows\Logs",
                @"C:\Windows\Panther",
                @"C:\ProgramData\Microsoft\Windows\WER",
                @"C:\$Recycle.Bin"
            };

            // Temp + cache par profil utilisateur
            try
            {
                foreach (string userDir in Directory.GetDirectories(@"C:\Users"))
                {
                    string userName = Path.GetFileName(userDir);
                    if (userName.Equals("Public", StringComparison.OrdinalIgnoreCase)
                        || userName.Equals("Default", StringComparison.OrdinalIgnoreCase)
                        || userName.Equals("Default User", StringComparison.OrdinalIgnoreCase))
                        continue;

                    zones.Add(Path.Combine(userDir, @"AppData\Local\Temp"));
                    zones.Add(Path.Combine(userDir, @"AppData\Local\Microsoft\Windows\INetCache"));

                    // Firefox cache
                    string firefoxProfiles = Path.Combine(userDir, @"AppData\Local\Mozilla\Firefox\Profiles");
                    if (Directory.Exists(firefoxProfiles))
                    {
                        foreach (string profile in Directory.GetDirectories(firefoxProfiles))
                            zones.Add(Path.Combine(profile, "cache2"));
                    }
                }
            }
            catch { }

            // memory.dmp
            string memDump = @"C:\Windows\memory.dmp";
            if (File.Exists(memDump))
            {
                long dumpSize = new FileInfo(memDump).Length;
                SystemDiagResults.Add(new LargeFolder { Path = memDump, Size = dumpSize });
                Console.WriteLine("{0,-65} {1,8} GB", memDump, ToGB(dumpSize));
            }

            long grandTotal = 0;
            foreach (string zone in zones)
            {
                if (!Directory.Exists(zone)) continue;
                long size = GetDirectorySize(zone);
                if (size < ONE_MB) continue;

                SystemDiagResults.Add(new LargeFolder { Path = zone, Size = size });
                Console.WriteLine("{0,-65} {1,8} GB", 
                    zone.Length > 65 ? "..." + zone.Substring(zone.Length - 62) : zone,
                    ToGB(size));
                grandTotal += size;
            }

            Console.WriteLine("\nTotal zones systeme recuperable : {0} GB", ToGB(grandTotal));
        }

        static long GetDirectorySize(string path)
        {
            long size = 0;
            try
            {
                foreach (string file in Directory.GetFiles(path))
                {
                    try { size += new FileInfo(file).Length; } catch { }
                }
                foreach (string dir in Directory.GetDirectories(path))
                {
                    size += GetDirectorySize(dir);
                }
            }
            catch { }
            return size;
        }

        // ======================= EXPORT =======================

        static void ExportReport(string rootPath)
        {
            string fileName = Environment.MachineName + ".diskusage.log";
            string exeDir = AppDomain.CurrentDomain.BaseDirectory;
            string exportPath = Path.Combine(exeDir, fileName);

            try
            {
                var sb = new StringBuilder();
                sb.AppendLine("DiskUsageLog - Rapport d'analyse disque");
                sb.AppendLine("Machine : " + Environment.MachineName);
                sb.AppendLine("Racine  : " + rootPath);
                sb.AppendLine("Date    : " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                sb.AppendLine("Duree   : " + Timer.Elapsed.ToString(@"hh\:mm\:ss"));
                sb.AppendLine(new string('=', 80));

                sb.AppendFormat("\nFichiers analyses : {0:N0}\n", TotalFilesScanned);
                sb.AppendFormat("Dossiers analyses : {0:N0}\n", TotalFoldersScanned);
                sb.AppendFormat("Taille totale     : {0} GB\n", ToGB(TotalSizeScanned));
                sb.AppendFormat("Erreurs           : {0:N0}\n", TotalErrorCount);

                sb.AppendLine("\n--- REPARTITION RACINE ---");
                foreach (var item in RootBreakdown.OrderByDescending(r => r.Size).Take(30))
                {
                    string name = Path.GetFileName(item.Path);
                    if (string.IsNullOrEmpty(name)) name = item.Path;
                    sb.AppendFormat("{0,-60} {1} GB\n", name, ToGB(item.Size));
                }

                sb.AppendLine("\n--- DOSSIERS > 1 Go ---");
                foreach (var f in LargeFolders.OrderByDescending(f => f.Size))
                {
                    string flag = f.IsRiskArea ? " !!!" : "";
                    sb.AppendFormat("{0} : {1} GB{2}\n", f.Path, ToGB(f.Size), flag);
                }

                sb.AppendLine("\n--- TOP 20 EXTENSIONS ---");
                foreach (var ext in ExtensionStats.OrderByDescending(e => e.Value.Size).Take(20))
                {
                    sb.AppendFormat("{0,-12} {1,10} fichiers  {2} GB\n",
                        ext.Key, ext.Value.Count, ToGB(ext.Value.Size));
                }

                sb.AppendLine("\n--- TOP 10 PLUS GROS FICHIERS ---");
                foreach (var fi in LargestFiles)
                    sb.AppendFormat("{0} : {1} GB\n", fi.FullName, ToGB(fi.Length));

                if (StaleFiles.Count > 0)
                {
                    sb.AppendLine("\n--- GROS FICHIERS ANCIENS ---");
                    foreach (var fi in StaleFiles)
                        sb.AppendFormat("{0} : {1} GB (modifie {2:yyyy-MM-dd})\n",
                            fi.FullName, ToGB(fi.Length), fi.LastWriteTime);
                }

                if (DetectedCacheFolders.Count > 0)
                {
                    sb.AppendLine("\n--- DOSSIERS TEMPORAIRES / LOGS ---");
                    foreach (var cf in DetectedCacheFolders.OrderByDescending(c => c.Size).Take(30))
                        sb.AppendFormat("{0} : {1} GB\n", cf.Path, ToGB(cf.Size));
                }

                if (SystemDiagResults.Count > 0)
                {
                    sb.AppendLine("\n--- DIAGNOSTIC ZONES SYSTEME ---");
                    foreach (var sd in SystemDiagResults.OrderByDescending(s => s.Size))
                        sb.AppendFormat("{0} : {1} GB\n", sd.Path, ToGB(sd.Size));
                }

                File.WriteAllText(exportPath, sb.ToString());
                Console.WriteLine("\nRapport exporte : " + exportPath);
            }
            catch (Exception ex)
            {
                Console.WriteLine("\nErreur export : " + ex.Message);
            }
        }

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

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

        static void DisplayXstoreInfo()
        {
            try
            {
                using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\GFI"))
                {
                    if (key == null) return;

                    Console.WriteLine(" --- XSTORE ---");

                    foreach (var name in key.GetValueNames())
                    {
                        var val = key.GetValue(name);
                        if (val != null && val.ToString().Length > 0)
                            Console.WriteLine($" {name,-25}: {val}");
                    }
                }
            }
            catch { }
        }
    }

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

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

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

    class CacheFolder
    {
        public string Path;
        public long Size;
        public string FolderName;
    }
}