import os
import re
import pickle
from types import MappingProxyType
from typing import Dict, List

import bpy

from logger import log
from utils import md5, EDMPath
from enums import BpyShaderNode
from serializer_tools import MatDesc
from materials.material_interface import IMaterial
from materials.material_default import DefaultMaterial
from materials.material_deck import DeckMaterial
from materials.material_fake_omni import OmniFakeLightsMaterial
from materials.material_fake_spot import SpotFakeLightsMaterial


## unmutable dictionary of all possible materials.
MATERIALS: Dict[str, IMaterial] = MappingProxyType(
    {
        DefaultMaterial.name: DefaultMaterial,
        DeckMaterial.name: DeckMaterial,
        OmniFakeLightsMaterial.name: OmniFakeLightsMaterial,
        SpotFakeLightsMaterial.name: SpotFakeLightsMaterial
    }
)
    

def filter_materials(edm_group_name: str) -> List[bpy.types.Material]:
    out: List[bpy.types.Material] = []
    for mat in bpy.data.materials:
        use_nodes = mat.use_nodes and mat.node_tree
        if not use_nodes:
            continue

        for bpy_node in mat.node_tree.nodes:
            if bpy_node.bl_idname in (BpyShaderNode.NODE_GROUP, BpyShaderNode.NODE_GROUP_EDM, BpyShaderNode.NODE_GROUP_DEFAULT, BpyShaderNode.NODE_GROUP_DECK, BpyShaderNode.NODE_GROUP_FAKE_OMNI, BpyShaderNode.NODE_GROUP_FAKE_SPOT) and bpy_node.node_tree and bpy_node.node_tree.name == edm_group_name:
                out.append(mat)
                
    return out


def filter_materials_re(edm_group_regex) -> List[bpy.types.Material]:
    out: List[bpy.types.Material] = []
    if not edm_group_regex:
        return out
    if not hasattr(bpy.data, "materials"):
        return out
    for mat in bpy.data.materials:
        use_nodes = mat.use_nodes and mat.node_tree
        if not use_nodes:
            continue

        for bpy_node in mat.node_tree.nodes:
            if bpy_node.bl_idname in (BpyShaderNode.NODE_GROUP, BpyShaderNode.NODE_GROUP_EDM, BpyShaderNode.NODE_GROUP_DEFAULT, BpyShaderNode.NODE_GROUP_DECK, BpyShaderNode.NODE_GROUP_FAKE_OMNI, BpyShaderNode.NODE_GROUP_FAKE_SPOT) and bpy_node.node_tree and re.match(edm_group_regex, bpy_node.node_tree.name):
                out.append(mat)
                
    return out


def check_if_referenced_file(blend_file_name):
    bf = os.path.splitext(os.path.basename(blend_file_name))[0].lower()

    for m in MATERIALS.keys():
        if m.lower() == bf:
            return True
    return False


def check_md5(material_name: str, material_desc: MatDesc):
    node_tree_name = re.compile(f'[A-Za-z0-9_.-]*{material_name.value}[A-Za-z0-9_.-]*')
    mat_list: List[bpy.types.Material] = filter_materials_re(node_tree_name)
    if mat_list:
        blend_file_name: str = 'data/' + str(material_name.value) + '.blend'
        blend_file_path: str = os.path.join(EDMPath.full_plugin_path, blend_file_name)
        blend_file_md5: str = md5(blend_file_path)
        if hasattr(material_desc, 'blend_file_md5') and material_desc.blend_file_md5 != blend_file_md5:
            log.fatal(f"Hash of material file {blend_file_path} is invalid.")


def build_material_descriptions() -> Dict[str, MatDesc]:    
    material_descs: Dict[str, MatDesc] = {}

    for name, mat in MATERIALS.items():
        try:
            pickle_file_name: str = os.path.join(EDMPath.full_plugin_path, mat.description_file_name)
            with open(pickle_file_name, 'rb') as f:
                material_descs[name] = pickle.load(f)
            
        except OSError as e:
            log.error(f"Can't open material description file: {mat.description_file_name}. Reason: {e}.")
            continue
    return material_descs