#!/usr/bin/env python3

import pathlib
import shutil
import sys
import uuid
import time
import pickle
import json
import datetime
import magic
import mimetypes
import hashlib
import math
import random
import enum

class SortFinaleCLI:
    __Version__ = "3.1.4"
    __doc__ = __Help__ = \
f"""
Name:
    SortFinaleCLI

Version:
    {__Version__}

Author: 
    Manuel Baumi <koenigm7771@gmail.com>

Description:
    Python Module/CLI to sort and copy files per extension/suffix and more.
    Able to run as CLI from Terminal using Flags or be imported as package.

Arguments:
    None

Flags:
    All Integers default to 0. <- not implemented.

    Conventions:
        Mandatory Values.
        [2] and [3] can be used interchangeable.

        [1] -F <Value>
        [2] --flag=<Value>
        [3] --flag <Value>
    
    <FlagShortcut> <FlagLong>           <Values>        <req/opt>  <Description>
    -D     --debug                      <0|1>           required : Whether to print Records.
    -L     --logfile                    <0|Path>        optional : Whether to append Records to Logfile.
    -I     --source                     <Path>          required : Source Directory.
    -O     --destination                <Path>          required : Destination Direcrory. Will be created if not existing.
    -S     --sort-by                    SortByLevel     required : How to sort the Files into teh Destination.
    -s     --suffix-check:enable        <0|1>           optional : Whether to Enable RuleOut:SuffixCheck.
    -W     --suffix-check:whitelist     <Suffix,...>    required (if --suffix-check:enable and not --suffix-check:blacklist) : Only Files with Suffix/File Type will be copied.
    -B     --suffix-check:blacklist     <Suffix,...>    required (if --suffix-check:enable and not --suffix-check:whitelist) : Only Files without Suffix/File Type will be copied.
           --suffix-check:check-real    CheckRealLevels optional : Whether to detect (guess) SUffix in RuleOut:SuffixCheck
    -c     --continously                <Seconds>       optional : Whether to run once or run and sleep amount Seconds.
    -d     --dry-run                    <0|1>           optional : Whether to run everything and copy File.
           --sort-by:on-no-suffix       <str|int>       required (if SortByLevel 1->4) : When File has no Suffix in Name or no Suffix has benn detected, Name of the Directory in which the File will be copied will be value.
           --suffix-check:raise-error   <0|Path>        optional : If Path of a .pkl File specified, Pickle File will be loaded in and will raise Pickled Error when not in WHitelist or in Blacklist.
           --uuid                       UUID?           optional : Prefixes File Name with generated UUID and _.
           --skip-hidden                <0|1>           optional : Whether to skip hidden Files and Files with hidden Parents.
           --no-dir-dot                 <0|1>           optional : Whether to create Suffix Directories not with '.' Prefix.
           --no-dir-suffix              <0|1>           optional : Whether to create Suffix Directories not with '.d' Suffix.
    -h     --help                       <0|1>           optional : Logs Help, then stops. Recommended to --debug 0.
    -v     --version                    <0|1>           optional : Logs Version, then stops. Recommended to --debug 0.
           --on-conflict                ConflictLevels  optional : What to do if File already in Destination.
           --skip-duplicates            <0|1>           optional : Whether to skip duplicate Files by comparing sha512 hashes. If enabled will be performance intensive.

Values:
        <Value> : <Description>

    SortByLevel:
        0 : Destination will be Destination Path and File Parents Path relative to Source and File Name. Does not Sort. (GetNewPath:Parents)
        1 : Destination will be Destination Path and detected Suffix and File Name. (GetNewPath:Suffix)
        2 : Destination will be Destination Path and detected Suffix, but within the Suffix Directory Files will be sorted into Directories each conataining up to 1000 Files. (GetNewPath:StructuredSuffix)
        3 : Same as 1 but uses Suffix already specified in File Name. (GetNewPath:Suffix)
        4 : Same as 2 but uses Suffix already specified in File Name. (GetNewPath:StructuredSuffix)
        5 : Copies Files without Suffix into one Place.
        6 : Copies Files into one place.
        
    UUID?:
        1                     : Uses uuid1.
        4                     : Uses uuid4.
        3:<Namespace>:<Name>  : Uses uuid3 with provided Namespace and Name.
        5:<Namespace>:<Name>  : Uses uuid5 with provided Namespace and Name.

    CheckRealLevels:
        0 : Only use Suffixes in File Name.
        1 : Append multiple guessed Suffixes to Suffixes from File Name.
        2 : Only use detected Suffixes.
        3 : Append one guessed Suffix to Suffixes from File Name.
        4 : Only use one detected Suffix.

    ConflictLevels:
        0           : Skips the Conflict Check entirely.
        1           : Override File Destination.
        2:<a>:<b>   : Prefix with random Numbers (floor) between a and b.
        3           : Skip.
        """

    class FlagError(ValueError): pass

    class c_Logger:
        class VARS(enum.Enum):
            LEVEL_DEBUG         = "DEBUG"
            LEVEL_INFO          = "INFO"
            LEVEL_WARN          = "WARN"
            LEVEL_ERROR         = "ERROR"
            LEVEL_FATAL         = "FATAL"
            STATUS_START        = "..."
            STATUS_FINISHED     = "!"

        def __init__(self, Logfile, Print):
            self.Logfile = Logfile
            self.Print = Print
        
        def __call__(self, Record, Level, Status, Nested, Variables = {}):
            Record = f"@ {Level.value :5} | {Status.value :3} | {datetime.datetime.now(datetime.UTC).isoformat()} | ({Nested}) {"-" * Nested + ">" :<4} | {Record} | {json.dumps(Variables)}"

            if self.Print:
                print(Record)
            if self.Logfile:
                with open(self.Logfile, "at") as F:
                    F.write(Record + "\n")
        
        def Error(self, Error, Record):
            Error = Error(Record)
            self(Error, self.VARS.LEVEL_ERROR, self.VARS.STATUS_FINISHED, 1, Variables = {"Error": str(pickle.dumps(Error))})
            raise Error
        
        # Error for Catching
        def Exception(self, Exception):
            self(
                str(Exception), self.VARS.LEVEL_ERROR, self.VARS.STATUS_FINISHED, 1, 
                Variables = {
                    "Traceback": repr(Exception.__traceback__),
                    "Class": str(type(Exception)), 
                    "Name": type(Exception).__name__,
                    "Pickle": str(pickle.dumps(self.Exception)),
                    "Arguments": list(map(lambda I: str(I), Exception.args))
                }
            )
            raise Exception
        
        def g_Scrape(self):
            with open(self.Logfile, "rt") as F:
                _Record = ""
                while True:
                    Read = F.read(1)
                    if Read == "\n":
                        yield _Record
                        _Record = ""
                    else:
                        _Record += Read
        @staticmethod
        def SplitScrapedRecord(Record):
            Splits = Record.split(" | ")

            return Splits
        
        @staticmethod
        def FormatSplittedScrapedRecord(*Splits):
            return Splits[0].replace("@", "").lstrip().rstrip(), Splits[1], datetime.datetime.fromisoformat(Splits[2]), Splits[3][1], Splits[4], json.dumps(Splits[5])

    def __init__(self, Flags = sys.argv):
        self.Flags = self._BaseConfig()
        self.Flags = self.ParseFlags(Flags)

        self.Logger = self.c_Logger(self.Flags.get("--logfile"), self.Flags.get("--debug"))

        if self.Flags.get("--help"): 
            self.Help(1, 0, 1)
            sys.exit()
        
        if self.Flags.get("--version"): 
            self.Version(1, 0, 1)
            sys.exit()

        self._CheckForMissingFlags()

        self.Logger(f"Starting with Flags!", self.Logger.VARS.LEVEL_INFO, self.Logger.VARS.STATUS_START, 0, Variables=self.Flags)

        self._InitVariables()

        self._InitSuffixCheck()
    
    def Help(self, Print, Return, Log):
        if Print: print(self.__doc__)
        if Log: self.Logger(self.__doc__, "INFO")
        if Return: return self.__doc__

    def Version(self, Print, Return, Log):
        if Print: print(self.__Version__)
        if Log: self.Logger(self.__Version__, "INFO")
        if Return: return self.__Version__
    
    @staticmethod
    def _BaseConfig():
        Flags = {}
        for Key in []:
            Flags[Key] = 0
        return Flags
    
    def ParseFlags(self, Flags):
        Flags_ = {}
        
        for Flag in Flags:
            if Flag.startswith("--") and "=" not in Flag:
                if Flags.count(Flag) > 1: raise self.FlagError(f"{Flag} specified multiple times!")

                Key, Value = Flag, Flags[Flags.index(Flag) + 1]            
                Flags_[Key] = Value
            if Flag.startswith("--") and "=" in Flag:
                if Flags.count(Flag) > 1: raise self.FlagError(f"{Flag} specified multiple times!")

                Key, Value = Flag.split("=", 1)           
                Flags_[Key] = Value
            if Flag.startswith("-") and Flag[1] != "-":
                if Flags.count(Flag) > 1: raise self.FlagError(f"{Flag} specified multiple times!")

                Key, Value = Flag, Flags[Flags.index(Flag) + 1]  
                Key = self.GetLongNameOfFlagShortcut(Key)
                Flags_[Key] = Value
                
        for Key, Value in Flags_.items():
            if Value.isdigit():
                Flags_[Key] = int(Value)
                
        return Flags_

    def _CheckForMissingFlags(self):
        MissingFlags = []
        for Flag in ["--source", "--destination", "--debug", "--sort-by"]:
            if Flag not in self.Flags:
                MissingFlags.append(Flag)
        if MissingFlags and len(MissingFlags) == 1:
            self.Logger.Error(self.FlagError, f"Missing Flag '{MissingFlags[0]}'!")
        elif MissingFlags and len(MissingFlags) > 1:
            self.Logger.Error(self.FlagError, f"Missing Flags '{", ".join(MissingFlags)}'!")
        
    @staticmethod
    def GetLongNameOfFlagShortcut(Flag):
        return {
            "-I": "--source",
            "-O": "--destination",
            "-D": "--debug",
            "-L": "--logfile",
            "-s": "--suffix-check:enable",
            "-W": "--suffix-check:whitelist",
            "-B": "--suffix-check:blacklist",
            "-c": "--continously",
            "-S": "--sort-by",
            "-d": "--dry-run",
            "-h": "--help",
            "-v": "--version"
        }[Flag]
    
    def _InitVariables(self):
        self.Source = pathlib.Path(self.Flags["--source"])
        self.Destination = pathlib.Path(self.Flags["--destination"])

        self.Logger(f"Starting with Source='{self.Source}' and Destination='{self.Destination}'!", self.Logger.VARS.LEVEL_INFO, self.Logger.VARS.STATUS_START, 0, Variables={"Source": str(self.Source), "Destination": str(self.Destination)})

        if not self.Source.exists():
            raise ValueError(f"Source '{self.Source}' not existing!")
        
        self._SleepInterval = self.Flags.get("--continously")

        self._ExistingDestinationFiles  = []
        self._ExistingSourceFilesHashes = []

        if self.Flags.get("--sort-by") in [1, 2, 3, 4]:
            if not self.Flags.get("--sort-by:on-no-suffix"):
                self.Logger.Error(self.FlagError, f"(GetNewDestination:Suffix/StructuredSuffix) Requires --sort-by:on-no-suffix for Files with no suffix!")

        self._RunNum = 0

    def _InitSuffixCheck(self):
        if self.Flags.get("--suffix-check:enable"):
            if self.Flags.get("--suffix-check:whitelist"): self.SuffixCheck_Whitelist = list(map(lambda S: "." + S, self.Flags.get("--suffix-check:whitelist").split(",")))
            else: self.SuffixCheck_Whitelist = []

            if self.Flags.get("--suffix-check:blacklist"): self.SuffixCheck_Blacklist = list(map(lambda S: "." + S, self.Flags.get("--suffix-check:blacklist").split(",")))
            else: self.SuffixCheck_Blacklist = []

            if self.Flags.get("--suffix-check:blacklist") and self.Flags.get("--suffix-check:whitelist"):
                self.Logger.Error(self.FlagError, "(RuleOut:SuffixCheck) Specify Either Whitelist or Blacklist; Not Both!")

            if self.Flags.get("--suffix-check:raise-error"):
                with open(self.Flags.get("--suffix-check:raise-error"), "rb") as F:
                    self.SuffixCheck_Error = pickle.load(F)
            else: self.SuffixCheck_Error = None
    
    def __call__(self):
        if self._SleepInterval is None:
            self.Logger("Executing Main once!", self.Logger.VARS.LEVEL_INFO, self.Logger.VARS.STATUS_START, 0)
            self.Main()
        else:
            while True:
                self.Logger(f"Executing Main for the '{self._RunNum}' time!", self.Logger.VARS.LEVEL_INFO, self.Logger.VARS.STATUS_START, 0, Variables={"RunNum": self._RunNum, "SleepInterval": self._SleepInterval})

                self.Main()

                time.sleep(self._SleepInterval)
        
    def Main(self):
        self._RunNum += 1
        self.Logger("Starting Main Iterator!", self.Logger.VARS.LEVEL_INFO, self.Logger.VARS.STATUS_START, 1)
        i_FileNumber = 0
        StructuredSuffixFileNumber = 0
        
        if self.Flags.get("--on-conflict"):
            for File in self.Destination.rglob("*"):
                self._ExistingDestinationFiles.append(str(File.relative_to(self.Destination)))
            self.Logger(f"Orginal Files of Destination if any (see parsed JSON) relative to Destination!", self.Logger.VARS.LEVEL_INFO, self.Logger.VARS.STATUS_FINISHED, 1, Variables=self._ExistingDestinationFiles)

        for File in self.Source.rglob("*"):
            try:
                self.Logger(
                    f"{i_FileNumber} {File.name} ({str(File.absolute())})",
                    self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_START, 1,
                    Variables={"FileNumber": i_FileNumber, "Name": File.name, "Path": str(File.absolute()), "Suffixes": File.suffixes}
                )
                i_FileNumber += 1

                self.Logger("Starting RuleOut(s)!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_START, 1)

                _RuleOut = self.RuleOut_Filetype(File)
                if isinstance(_RuleOut, str):
                    self.Logger(f"Skipping; Is '{_RuleOut}'!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"RuleOut": _RuleOut})
                    continue
                else: self.Logger("Passed RuleOut:Filetype; Is correct Filetype!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1)

                _RuleOut = self.RuleOut_SuffixCheck(File)
                if _RuleOut:
                    self.Logger(f"Skipping; Suffix is bad!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"RuleOut": _RuleOut})
                    continue
                else: self.Logger("Passed RuleOut:SuffixCheck; Suffix is good!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1)

                _RuleOut = self.RuleOut_Hidden(File)
                if _RuleOut:
                    self.Logger(f"Skipping; Hidden!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"RuleOut": _RuleOut})
                    continue
                else: self.Logger("Passed RuleOut:Hidden!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1)

                _RuleOut = self.RuleOut_Duplicates(File)
                if _RuleOut:
                    self.Logger(f"Skipping; Duplicate!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"RuleOut": _RuleOut})
                    continue
                else: self.Logger("Passed RuleOut:Duplicates!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1)

                self.Logger("Finished RuleOut(s)!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1)

                match self.Flags.get("--sort-by"):
                    case 0:
                        Destination = self.GetNewPath_Parents(File)
                    case 1:
                        Destination = self.GetNewPath_Suffix(File, self.GetMimeSuffixSuffixes(File)[1])
                    case 2:
                        Destination = self.GetNewPath_StructuredSuffix(File, self.GetMimeSuffixSuffixes(File)[1], StructuredSuffixFileNumber)
                    case 3:
                        Destination = self.GetNewPath_Suffix(File, File.suffix)
                    case 4:
                        Destination = self.GetNewPath_StructuredSuffix(File, File.suffix, StructuredSuffixFileNumber)
                    case 5:
                        if not File.name.startswith("."):
                            Destination = self.Destination / File.name.split(".")[0]
                        else:
                            Destination = self.Destination / File.name[1:].split(".")[0]
                    case 6:
                        Destination = self.Destination / File.name
                self.Logger(f"Created new Destination Path '{Destination}'!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"Destination": str(Destination)})

                match self.Flags.get("--uuid"):
                    case 4:
                        UUID = f"{uuid.uuid4()}"
                        Destination = Destination.parent / f"{UUID}_{Destination.name}"
                        self.Logger(f"Prefixing with UUID '{UUID}'!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"UUID?": self.Flags.get("--uuid"), "UUID": UUID})
                    case 1:
                        UUID = f"{uuid.uuid1()}"
                        Destination = Destination.parent / f"{UUID}_{Destination.name}"
                        self.Logger(f"Prefixing with UUID ' {UUID}'!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"UUID?": self.Flags.get("--uuid"), "UUID": UUID})
                    case 3:
                        Namespace, Name = self.Flags.get("--uuid").split(":")[1:3]
                        UUID = f"{uuid.uuid3(Namespace, Name)}"
                        Destination = Destination.parent / f"{UUID}_{Destination.name}"
                        self.Logger(f"Prefixing with UUID ' {UUID}'!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"UUID?": self.Flags.get("--uuid"), "UUID": UUID, "Namespace": Namespace, "Name": Name})
                    case 5:
                        Namespace, Name = self.Flags.get("--uuid").split(":")[1:3]
                        UUID = f"{uuid.uuid5(Namespace, Name)}"
                        Destination = Destination.parent / f"{UUID}_{Destination.name}"
                        self.Logger(f"Prefixing with UUID ' {UUID}'!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"UUID?": self.Flags.get("--uuid"), "UUID": UUID, "Namespace": Namespace, "Name": Name})

                if self.Flags.get("--naming-convention"):
                    if self.Flags.get("--uuid"):
                        UUID, Name = Destination.name.split("_", 1)
                    else:
                        UUID, Name = "", Destination.name

                    Stats = File.stat(follow_symlinks=False)

                    Destination = Destination.parent / self.Flags.get("--naming-convention").format(
                        UUID=UUID, Name=Name,
                        **{Attr: getattr(Stats, Attr) for Attr in dir(Stats) if Attr.startswith("st")},
                        **{Attr: getattr(File, Attr, None) for Attr in dir(File)},
                    )

                Destination = self.OnConflict(Destination)
                if not Destination: continue
                
                self.Copy(File, Destination)

                StructuredSuffixFileNumber += 1
            except PermissionError as E:
                self.Logger(f"Skipping File due to Permission Error; Mabe run as root and then 'sudo chmod -R 777 <Destination>'? '{E}'!", self.Logger.VARS.LEVEL_WARN, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"Exception": str(pickle.dumps(E))})
            except FileNotFoundError as E:
                self.Logger(f"File Not Found; Maybe /proc/*? '{E}'!", self.Logger.VARS.LEVEL_WARN, self.Logger.VARS.STATUS_FINISHED, 1, Variables={"Exception": str(pickle.dumps(E))})
            except Exception as E:
                self.Logger.Exception(E)

    def RuleOut_Hidden(self, File):
        if not self.Flags.get("--skip-hidden"): return 0
        
        _Hidden = 0
        for Parent in File.parents:
            if str(Parent.name).startswith("."):
                _Hidden += 1
        if _Hidden:
            self.Logger(f"Hidden File ({str(File.absolute())}); Hidden within '{_Hidden}'!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 2, Variables={"HowHidden": _Hidden})
            return 1
    
    @staticmethod
    def RuleOut_Filetype(File: pathlib.Path):
        if File.is_block_device()   : return "Blockdevice"
        if File.is_fifo()           : return "FIFO"
        if File.is_dir()            : return "Directory"
        if File.is_symlink()        : return "Symlink"
        if File.is_socket()         : return "Socket"
        if File.is_char_device()    : return "Character Device"
        
        return 0
    
    def RuleOut_SuffixCheck(self, File):
        if not self.Flags.get("--suffix-check:enable"): return 0
        if not self.Flags.get("--suffix-check:check-real") and not File.suffixes: return 0

        Suffixes = File.suffixes

        if self.Flags.get("--suffix-check:check-real"):
            Mime, Suffix, Suffixes = self.GetMimeSuffixSuffixes(File)
            self.Logger(f"Detected Mime '{Mime}', guessed Suffix '{Suffix}' and guessed Suffixes '{Suffixes}'!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 2, Variables={"Mime": Mime, "Suffix": Suffix, "Suffixes": Suffixes})

            match self.Flags.get("--suffix-check:check-real"):
                case 1:
                    Suffixes += Suffixes
                case 2:
                    Suffixes = Suffixes
                case  3:
                    Suffixes.append(Suffix)
                case 4:
                    Suffixes = Suffix,

        if self.SuffixCheck_Whitelist:
            for Suffix in Suffixes:
                if Suffix in self.SuffixCheck_Whitelist: return 0
                elif Suffix not in self.SuffixCheck_Whitelist and self.SuffixCheck_Error: self.Logger.Error(self.SuffixCheck_Error, "Invoked Specified Error Not in Whitelist!")
                else: return 1
        if self.SuffixCheck_Blacklist:
            for Suffix in Suffixes:
                if Suffix in self.SuffixCheck_Blacklist: return 1
                if Suffix in self.SuffixCheck_Blacklist and self.SuffixCheck_Error: self.Logger.Error(self.SuffixCheck_Error, "Invoked Specified Error Is in Blacklist!")
                else: return 0
    
    def GetNewPath_Parents(self, File):
        Destination = self.Destination / File.relative_to(self.Source)

        return Destination
    
    def GetNewPath_Suffix(self, File, Suffix):
        if not Suffix: Suffix = self.Flags.get("--sort-by:on-no-suffix")

        if self.Flags.get("--no-dir-dot"):  Suffix = Suffix.replace(".", "")

        Destination = self.Destination / f"{Suffix}{".d" if not self.Flags.get("--no-dir-suffix") else ""}" / File.name

        return Destination

    def GetNewPath_StructuredSuffix(self, File, Suffix, FileNumber):
        if not Suffix: Suffix = self.Flags.get("--sort-by:on-no-suffix")

        if self.Flags.get("--no-dir-dot"):  Suffix = Suffix.replace(".", "")

        Destination = self.Destination / f"{Suffix}{".d" if not self.Flags.get("--no-dir-suffix") else ""}" / str(FileNumber // 1000) / File.name

        return Destination
    
    def Copy(self, File, Destination):
        if self.Flags.get("--dry-run"): return

        Destination.parent.mkdir(parents=True, exist_ok=True)

        shutil.copy2(File, Destination)

        self.Logger("Copied!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 2, Variables={"File": str(File), "Destination": str(Destination)})
    
    @staticmethod
    def GetMimeSuffixSuffixes(File):
        Mime = magic.from_file(File, mime=True)
        GuessedSuffix = mimetypes.guess_extension(Mime)
        GuessedSuffixes = mimetypes.guess_all_extensions(Mime)

        return Mime, GuessedSuffix, GuessedSuffixes

    def OnConflict(self, Destination):
        if not self.Flags.get("--on-conflict"): return Destination
        
        if str(Destination.relative_to(self.Destination)) in self._ExistingDestinationFiles:
            match self.Flags.get("--on-conflict"):
                case 1:
                    return Destination
                case 2:
                    a, b = int(self.Flags.get("--on-conflict").split(":")[1]), int(self.Flags.get("--on-conflict").split(":")[2])
                    while Destination.relative_to(self.Destination) in self._ExistingDestinationFiles:
                        Destination = Destination.parent / f"{math.floor(random.randint(a, b))}_{Destination.name}"
                    return Destination
                case 3: return 0
    
    def RuleOut_Duplicates(self, File):
        if not self.Flags.get("--skip-duplicates"): return 0

        Hash = hashlib.sha512(File.read_bytes()).hexdigest()

        if Hash in self._ExistingSourceFilesHashes:
            self.Logger(f"File '{File.relative_to(self.Source)}' is a duplicate of another File!", self.Logger.VARS.LEVEL_DEBUG, self.Logger.VARS.STATUS_FINISHED, 2, Variables={"Hash": Hash, "File": str(File.absolute())})
            return 1
        
        self._ExistingSourceFilesHashes.append(Hash)
            
if __name__ == "__main__":
    SortFinaleCLI()()