# blender_context.py
"""
Contextual metadata acquired from internal values in a Blender file.

This module must be invoked from within Blender to work, as it relies on the bpy Blender API
module and the currently-open Blender file's data graph in order to work.

It collects data about scenes, objects, groups, and other datablocks in the Blender file,
as well as data encoded in text blocks in different formats. Overall file data is incorporated
into a PropertyGroup attached to the "WindowManager" object identified as 'WinMan' (normally,
it appears there is only ever one of these in a Blender file, but if there is more than one, this
is the one that will be used).
"""

import io
import bpy, bpy.app, bpy.props, bpy.utils
from bpy.app.handlers import persistent
from .accumulate import UnionList, RecursiveDict
import yaml

def EnumFromList(schema, listname):
    """
    Convert options from a list of strings referenced by key name.
    
    Args:
        schema (dict): definition of the property group containing the enum.
        options (list): list of options as simple strings.
    
    Returns:
        List of options as tuples, as needed by Blender.
    """
    return [(e, e.capitalize(), e.capitalize()) for e in schema[listname]]

def ExpandEnumList(schema, options):
    """
    Convert options from a direct list.
    
    Args:
        schema (dict): definition of the property group containing the enum.
        options (list): list of options. Individual options can be strings,
            pairs, or triples.
            
    Returns:
        A list of triples defining the enumerated values as needed for Blender.
    """
    blender_options = []
    for option in options:
        if type(option) is str:
            blender_options.append((option, option, option))
        elif isinstance(option, tuple) or isinstance(option, list):
            option = tuple(option[0:3] + ([option[-1]]*(3-len(option))))
            blender_options.append(option)
    return blender_options

class PropertyGroupFactory(bpy.types.PropertyGroup):
    """
    Property group factory for attachment to Blender object types.
    
    Structure of the property group returned is determined by a dictionary
    schema, which may be loaded from a YAML file.
    
    This is a "class factory", a class which returns another class when
    called.
    """
    # These values mirror the Blender documentation for the bpy.props types:
    prop_types = {
        'str':{
            'property': bpy.props.StringProperty,
            'keywords': { 'name', 'description', 'default', 'maxlen', 
                          'options', 'subtype'},
            'translate': {
                'desc': (None, 'description', None)}},
        'enum': {
            'property': bpy.props.EnumProperty,
            'keywords': { 'items', 'name', 'description', 'default', 'options'},
            'translate': {
                'desc': (None, 'description', None),
                'items_from': (EnumFromList, 'items'),
                'items': (ExpandEnumList, 'items')}},
        'int': {
            'property': bpy.props.IntProperty,
            'keywords': { 'name', 'description', 'default', 'min', 'max',
                          'soft_min', 'soft_max', 'step', 'options', 'subtype'},
            'translate': {
                'desc': (None, 'description')}},
        'float': {
            'property': bpy.props.FloatProperty,
            'keywords': { 'name', 'description', 'default', 'min', 'max',
                          'soft_min', 'soft_max', 'step', 'options', 
                          'subtype', 'precision', 'unit'},
            'translate': {
                'desc': (None, 'description')}},
        'bool': {
            'property': bpy.props.BoolProperty,
            'keywords': { 'name', 'description', 'default', 'options', 'subtype'},
            'translate': {
                'desc': (None, 'description')}}
        }
    
    def __new__(cls, name, schema):  
        class CustomPropertyGroup(bpy.types.PropertyGroup):
            pass                  
        for definition in schema[name]:
            # Translate and filter parameters
            try:
                propmap = cls.prop_types[definition['type']]
            except KeyError:
                # If no 'type' specified or 'type' not found, default to string:
                propmap = cls.prop_types['str']
                
            filtered = {}
            for param in definition:   
                if 'translate' in propmap and param in propmap['translate']:
                    translator = propmap['translate'][param][0]
                    if callable(translator):
                        # Filtered translation                        
                        filtered[propmap['translate'][param][1]] = translator(schema, definition[param])
                    else:
                        # Simple translation
                        filtered[propmap['translate'][param][1]] = definition[param]
                else:
                    filtered[param] = definition[param]
                        
            # Create the Blender Property object
            kwargs = dict((key,filtered[key]) for key in propmap['keywords'] if key in filtered)
            setattr(CustomPropertyGroup, definition['code'], propmap['property'](**kwargs))
                                                  
        bpy.utils.register_class(CustomPropertyGroup)
        return(CustomPropertyGroup)


    
        
    
        