from Scripts.datasets import chipset_data
from Scripts.datasets import gpu_data
from Scripts.datasets import os_data
from Scripts.datasets import pci_data
from Scripts import codec_layouts
from Scripts import utils

class CompatibilityChecker:
    def __init__(self):
        self.utils = utils.Utils()

    def is_low_end_intel_cpu(self, processor_name):
        return any(cpu_branding in processor_name for cpu_branding in ("Celeron", "Pentium"))

    def check_cpu_compatibility(self, processor_name, simd_features):
        if "SSE4" not in simd_features:
            self.max_supported_macos_version = self.min_supported_macos_version = (-1, -1, -1)
            self.unsupported_devices.append("CPU: {}".format(processor_name))
            return
        
        if "SSE4.2" not in simd_features:
            self.min_supported_macos_version = (18, 0, 0)
            if "SSE4.1" in simd_features:
                self.max_supported_macos_version = (21, 99, 99)

    def check_gpu_compatibility(self, motherboard_chipset, processor_name, simd_features, gpu_info):
        supported_gpus = {}
        is_supported_discrete_gpu = False

        for gpu_name, gpu_props in gpu_info.items():
            gpu_manufacturer = gpu_props.get("Manufacturer")
            gpu_codename = gpu_props.get("Codename")
            device_id = gpu_props.get("Device ID")[5:]
            device_type = gpu_props.get("Device Type")
            is_supported_gpu = True

            if "Integrated GPU" in device_type:
                if "Intel" in gpu_manufacturer:
                    if  self.is_low_end_intel_cpu(processor_name) or \
                        device_id in ("0102", "0106", "010A", "0152", "0156") or \
                        self.utils.contains_any(chipset_data.IntelChipsets, motherboard_chipset, start=116, end=130):
                        is_supported_gpu = False
                        if not is_supported_discrete_gpu:
                            self.max_supported_macos_version = self.min_supported_macos_version = (-1, -1, -1)
                    
                    self.min_supported_macos_version = max((17, 0, 0), self.min_supported_macos_version)
                    if device_id.startswith("01") and not device_id[-2] in ("5", "6"):
                        self.max_supported_macos_version = max((17, 99, 99), self.max_supported_macos_version if is_supported_discrete_gpu else (-1, -1, -1))
                    elif device_id.startswith("01"):
                        self.max_supported_macos_version = max((20, 99, 99), self.max_supported_macos_version if is_supported_discrete_gpu else (-1, -1, -1))
                    elif device_id.startswith(("04", "0A", "0C", "0D", "0B", "16")):
                        self.max_supported_macos_version = max((21, 99, 99), self.max_supported_macos_version if is_supported_discrete_gpu else (-1, -1, -1))
                    elif device_id.startswith(("09", "19", "59", "87C0")):
                        self.max_supported_macos_version = max((22, 99, 99), self.max_supported_macos_version if is_supported_discrete_gpu else (-1, -1, -1))
                    elif device_id.startswith(("3E", "87", "9B")):
                        self.min_supported_macos_version = max((17, 0, 0), self.min_supported_macos_version if is_supported_discrete_gpu else (-1, -1, -1))
                        self.max_supported_macos_version = self.utils.parse_darwin_version(os_data.get_latest_darwin_version())
                    elif device_id.startswith("8A"):
                        self.min_supported_macos_version = max((19, 4, 0), self.min_supported_macos_version)
                        self.max_supported_macos_version = self.utils.parse_darwin_version(os_data.get_latest_darwin_version())
                elif "AMD" in gpu_manufacturer:
                    is_supported_gpu = device_id in ("15D8", "15DD", "15E7", "1636", "1638", "164C")
                    if is_supported_gpu:
                        self.max_supported_macos_version = self.utils.parse_darwin_version(os_data.get_latest_darwin_version())
                        self.min_supported_macos_version = max((19, 0, 0), self.min_supported_macos_version)
            elif "Discrete GPU" in device_type:
                if "AMD" in gpu_manufacturer:
                    is_supported_discrete_gpu = True

                    if "Navi 2" in gpu_codename:
                        if not "AVX2" in simd_features:
                            self.max_supported_macos_version = min((21, 99, 99), self.max_supported_macos_version)
                        else:
                            if gpu_codename in ("Navi 23", "Navi 22"):
                                self.min_supported_macos_version = max((21, 2, 0), self.min_supported_macos_version)
                            elif "Navi 21" in gpu_codename:
                                self.min_supported_macos_version = max((20, 5, 0), self.min_supported_macos_version)
                            else:
                                self.max_supported_macos_version = self.min_supported_macos_version = (-1, -1, -1)
                                is_supported_discrete_gpu = is_supported_gpu = False
                    elif "Navi 1" in gpu_codename:
                        self.min_supported_macos_version = max((19, 0, 0), self.min_supported_macos_version)
                    elif "Vega 20" in gpu_codename:
                        self.min_supported_macos_version = max((18, 6, 0), self.min_supported_macos_version)
                    elif "Vega 10" in gpu_codename or "Polaris" in gpu_codename or "Baffin" in gpu_codename or "Ellesmere" in gpu_codename or device_id == "699F":
                        self.min_supported_macos_version = max((17, 0, 0), self.min_supported_macos_version)
                    elif self.utils.contains_any(gpu_data.AMDCodenames, gpu_codename):
                        self.max_supported_macos_version = (21, 99, 99)
                    else:
                        self.max_supported_macos_version = self.min_supported_macos_version = (-1, -1, -1)
                        is_supported_discrete_gpu = is_supported_gpu = False
                elif "NVIDIA" in gpu_manufacturer:
                    is_supported_discrete_gpu = True

                    if "Kepler" in gpu_codename:
                        self.max_supported_macos_version = (20, 99, 99)
                    elif "Pascal" in gpu_codename or "Maxwell" in gpu_codename or "Fermi" in gpu_codename or "Tesla" in gpu_codename:
                        self.max_supported_macos_version = (17, 99, 99)
                        self.min_supported_macos_version = (17, 0, 0)
                    else:
                        self.max_supported_macos_version = self.min_supported_macos_version = (-1, -1, -1)
                        is_supported_discrete_gpu = is_supported_gpu = False

            if not is_supported_gpu:
                self.unsupported_devices["{}: {}".format(device_type, gpu_name)] = gpu_props
            else:
                supported_gpus[gpu_name] = gpu_props

        return supported_gpus

    def check_audio_compatibility(self, audio_info):
        supported_audio = {}
        
        for audio_device, audio_props in audio_info.items():
            codec_id = audio_props.get("Device ID")
            if "USB" in audio_props.get("Bus Type") or \
                codec_id.startswith("8086") or \
                codec_id.startswith("1002") or \
                codec_id in codec_layouts.data:
                if codec_id in codec_layouts.data:
                    supported_audio = {**{audio_device: audio_props}, **supported_audio}
                else:
                    supported_audio[audio_device] = audio_props
            else:
                self.unsupported_devices["Audio: {}".format(audio_device)] = audio_props
        
        return supported_audio

    def check_biometric_compatibility(self, hardware_report):
        biometric = hardware_report.get("Biometric", {})
        if biometric:
            for biometric_device, biometric_props in biometric.items():
                self.unsupported_devices["Biometric: {}".format(biometric_device)] = biometric_props
            
            del hardware_report["Biometric"]

    def check_network_compatibility(self, network_info):
        supported_network = {}
        
        for device_name, device_props in network_info.items():
            bus_type = device_props.get("Bus Type")
            device_id = device_props.get("Device ID")
            is_device_supported = device_id in pci_data.NetworkIDs

            if bus_type.startswith("PCI"):
                if device_id in ("8086-125B", "8086-125C", "8086-125D", "8086-3102"):
                    self.min_supported_macos_version = (19, 0, 0)

            if not is_device_supported:
                self.unsupported_devices["Network: {}".format(device_name)] = device_props
            else:
                supported_network[device_name] = device_props

        return supported_network

    def check_storage_compatibility(self, storage_controller_info):
        supported_storage = {}

        for controller_name, controller_props in storage_controller_info.items():
            if "PCI" in controller_props.get("Bus Type"):
                device_id = controller_props.get("Device ID")
                if device_id in pci_data.IntelVMDIDs:
                    self.utils.request_input("Disable Intel RST VMD in the BIOS before exporting the hardware report and try again with the new report")
                    self.utils.exit_program()
                elif device_id in pci_data.UnsupportedNVMeSSDIDs:
                    self.unsupported_devices["Storage: {}".format(pci_data.UnsupportedNVMeSSDIDs[device_id])] = controller_props
                else:
                    supported_storage[controller_name] = controller_props
        
        return supported_storage

    def check_sd_controller_compatibility(self, hardware_report):
        if not hardware_report.get("SD Controller"):
            return
        
        supported_sd_controller = {}

        for controller_name, controller_props in hardware_report.get("SD Controller", {}).items():
            if controller_props.get("Device ID") not in pci_data.RealtekCardReaderIDs:
                self.unsupported_devices["SD Controller: {}".format(controller_name)] = controller_props
            else:
                supported_sd_controller[controller_name] = controller_props

        if supported_sd_controller:
            hardware_report["SD Controller"] = supported_sd_controller
        else:
            del hardware_report["SD Controller"]

    def check_compatibility(self, hardware_report):
        self.utils.head("Compatibility Checker")
        print("")

        self.max_supported_macos_version = self.utils.parse_darwin_version(os_data.get_latest_darwin_version())
        self.min_supported_macos_version = self.utils.parse_darwin_version(os_data.get_lowest_darwin_version())
        self.unsupported_devices = {}

        self.check_cpu_compatibility(
            hardware_report.get("CPU").get("Processor Name"),
            hardware_report.get("CPU").get("SIMD Features")
        )

        if self.max_supported_macos_version != (-1, -1, -1):
            hardware_report["GPU"] = self.check_gpu_compatibility(
                hardware_report.get("Motherboard").get("Chipset"), 
                hardware_report.get("CPU").get("Processor Name"),
                hardware_report.get("CPU").get("SIMD Features"), 
                hardware_report.get("GPU")
            )
            if hardware_report.get("GPU"):
                hardware_report["Sound"] = self.check_audio_compatibility(hardware_report.get("Sound"))
                self.check_biometric_compatibility(hardware_report)
                hardware_report["Network"] = self.check_network_compatibility(hardware_report.get("Network"))
                hardware_report["Storage Controllers"] = self.check_storage_compatibility(hardware_report.get("Storage Controllers"))
                self.check_sd_controller_compatibility(hardware_report)

        if self.max_supported_macos_version[0] == -1:
            self.utils.request_input("Your hardware is not compatible with macOS!")
            self.utils.exit_program()

        return (".".join(str(item) for item in self.min_supported_macos_version), ".".join(str(item) for item in self.max_supported_macos_version)), self.unsupported_devices