# SPDX-FileCopyrightText: 2017-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later

bl_info = {
    "name": "Material Library",
    "author": "Mackraken",
    "version": (0, 6, 0),
    "blender": (2, 80, 0),
    "location": "Properties > Material",
    "description": "Material Library VX",
    "warning": "",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/materials/material_library.html",
    "tracker_url": "",
    "category": "Material",
}


import bpy
import json
import os
from pathlib import Path

from bpy.app.handlers import persistent
from bpy.props import (
    StringProperty, IntProperty, BoolProperty,
    PointerProperty, CollectionProperty
)
from bpy.types import (
    Panel, Menu, AddonPreferences, Operator,
    PropertyGroup, UIList,
    Scene
)

from rna_prop_ui import PropertyPanel

dev = False

user_path = Path(bpy.utils.resource_path('USER')).parent
matlib_path = os.path.join(user_path, "matlib")

if not os.path.exists(matlib_path):
    import shutil
    os.mkdir(matlib_path)
    addon_path = os.path.dirname(__file__)
    shutil.copy2(os.path.join(addon_path, "categories.txt"), matlib_path)
    shutil.copy2(os.path.join(addon_path, "templates.blend"), matlib_path)
    shutil.copy2(os.path.join(addon_path, "sample_materials.blend"), matlib_path)

##debug print variables
def dd(*args, dodir=False):
    if dev:
        if dodir:
            print(dir(*args))
        print(*args)

#Regular Functions
def winpath(path):
    return path.replace("\\", "\\\\")

def update_search_index(self, context):
    search = self.search
    for i, it in enumerate(self.materials):
        if it.name==search:
            self.mat_index = i
            break

def check_path(path):
    #isabs sometimes returns true on relpaths
    if path and os.path.exists(path) and os.path.isfile(path) and os.path.isabs(path):
        try:
            if bpy.data.filepath and bpy.path.relpath(bpy.data.filepath) == bpy.path.relpath(path):
                return False
        except:
            pass
            #paths are on different drives. No problem then
        return True
    return False

def update_lib_index(self, context):
    self.load_library()

def update_cat_index(self, context):
    dd("cat index:", self.current_category, self.filter)

    if self.filter:
        self.filter = True


def update_filter(self, context):

    dd("filter:", self.filter, self.cat_index, self.current_category)
    #  index = self.cat_index
    #
    #  if self.filter:
    #    cat = self.current_category
    #  else:
    #    cat = ""
    #
    #  self.current_library.filter = cat
    self.update_list()

def check_index(collection, index):
    count = len(collection)
    return count>0 and index<count and index>=0

def send_command(cmd, output="sendmat.py"):
    bin = winpath(bpy.app.binary_path)
    scriptpath = winpath(os.path.join(bpy.app.tempdir, output))

    with open(scriptpath, "w") as f:
        f.write(cmd)

    import subprocess

    if output == "createlib.py":
        code = subprocess.call([bin, "-b", "-P", scriptpath])
    else:
        libpath = winpath(bpy.context.scene.matlib.current_library.path)
        code = subprocess.call([bin, "-b", libpath, "-P", scriptpath])

    #code returns 0 if ok, 1 if not
    return abs(code-1)

def list_materials(path, sort=False):
    list = []
    with bpy.data.libraries.load(path) as (data_from, data_to):
        for mat in data_from.materials:
            list.append(mat)

    if sort: list = sorted(list)
    return list

#category properties (none atm)
class EmptyGroup(PropertyGroup):
    pass
# bpy.utils.register_class(EmptyGroup)

class matlibMaterials(PropertyGroup):
    category: StringProperty()
# bpy.utils.register_class(matlibMaterials)

#bpy.types.Scene.matlib_categories = CollectionProperty(type=EmptyGroup)

### CATEGORIES
class Categories():

    #cats = bpy.context.scene.matlib.categories

    def __init__(self, cats):
        self.cats = cats

    def save(self):
        scn = bpy.context.scene
        cats = set([cat.name for cat in self.cats])
        libpath = bpy.context.scene.matlib.current_library.path

        cmd = """
print(30*"+")
import bpy
if not hasattr(bpy.context.scene, "matlib_categories"):
  class EmptyProps(bpy.types.PropertyGroup):
    pass
  bpy.utils.register_class(EmptyProps)
  bpy.types.Scene.matlib_categories = bpy.props.CollectionProperty(type=EmptyProps)
cats = bpy.context.scene.matlib_categories
for cat in cats:
  cats.remove(0)
"""
        for cat in cats:
            cmd += """
cat = cats.add()
cat.name = "%s" """ % cat.capitalize()
        cmd +='''
bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % winpath(libpath)

        return send_command(cmd, "save_categories.py")

    def read(self, pull=True):
        #mandar a imprimir el listado
        catfile = winpath(os.path.join(matlib_path, "categories.txt"))
        cmd = """
import bpy, json
class EmptyProps(bpy.types.PropertyGroup):
  pass
bpy.utils.register_class(EmptyProps)
bpy.types.Scene.matlib_categories = bpy.props.CollectionProperty(type=EmptyProps)
cats = []
for cat in bpy.context.scene.matlib_categories:
  materials = []
  for mat in bpy.data.materials:
    if "category" in mat.keys() and mat['category'] == cat.name:
      materials.append(mat.name)
  cats.append([cat.name, materials])
with open("%s", "w") as f:
  f.write(json.dumps(cats, sort_keys=True, indent=4))
""" % catfile
        if pull: send_command(cmd)

        #leer el fichero
        with open(catfile, "r") as f:
            cats = json.loads(f.read())

        dd(cats)

        #    #refrescar categorias
        #    for cat in self.cats:
        #      self.cats.remove(0)
        #
        #    for cat in cats:
        #      item = self.cats.add()
        #      item.name = cat
        #
        return cats

    def view(self):
        for cat in self.cats:
            dd(cat.name)

    def add(self, name):
        if name and name not in [item.name for item in self.cats]:
            name = name.strip().capitalize()
            item = self.cats.add()
            item.name = name
            if self.save():
                dd(name, "added")
                return True
        else:
            dd("duplicated?")

    def remove(self, index):
        self.cats.remove(index)
        self.save()

class Library():

    def __init__(self, matlib_path, name):
        self.name = name
        self.path = os.path.join(matlib_path, name)
    #  @property
    #  def default(self):
    #    return self.name == default_library

    @property
    def shortname(self):
        #    if self.default:
        #      return "Default Library"
        return bpy.path.display_name(self.name).title()


    def __repr__(self):
        return str(type(self).__name__) + "('" + self.name + "')"

#bpy.utils.register_class(Library)

def get_libraries():
    libs = [Library(matlib_path, f) for f in os.listdir(matlib_path) if f[-5::] == "blend"]
    return sorted(libs, key=lambda x: bpy.path.display_name(x.name))

libraries = []
# get_libraries()

### MATLIB CLASS
class matlibProperties(PropertyGroup):

    #MATLIB PROPERTIES

    #libraries are read from the xml
    lib_index: IntProperty(min = -1, default = 2, update=update_lib_index)
    all_materials: CollectionProperty(type = matlibMaterials)
    materials: CollectionProperty(type = matlibMaterials)
    mat_index: IntProperty(min = -1, default = -1)
    categories: CollectionProperty(type = EmptyGroup)
    cat_index: IntProperty(min = -1, default = -1, update=update_cat_index)
    search: StringProperty(name="Search", description="Find By Name", update=update_search_index)

    #MATLIB OPTIONS
    #link: import material linked
    #force import:
    #   if disable it wont import a material if its present in the scene,(avoid duplicates)
    #   instead it will apply the scene material rather than importing the same one from the library
    #filter: enable or disable category filter
    #last selected: store the last selected object to regain focus when apply a material.
    #hide_search: Hides Search Field
    link: BoolProperty(name = "Linked", description="Link the material", default = False)
    force_import: BoolProperty(name = "Force Import", description="Use Scene Materials by default", default = False)
    filter: BoolProperty(name = "Filter",description="Filter Categories", default = True, update=update_filter)
    show_prefs: BoolProperty(name = "show_prefs", description="Preferences", default = False)
    last_selected: StringProperty(name="Last Selected")
    hide_search: BoolProperty(name="Hide Search", description="Use Blender Search Only")
    #import_file = StringProperty("Import File", subtype="FILE_PATH")
    #path = os.path.dirname(path)
    #Development only

    @property
    def libraries(self):
        global libraries
        return libraries

    @property
    def current_library(self):
        if check_index(libraries, self.lib_index):
            return libraries[self.lib_index]

    @property
    def active_material(self):
        if check_index(self.materials, self.mat_index):
            return self.materials[self.mat_index]

    def reload(self):
        dd("loading libraries")
        if self.current_library:
            self.load_library()
        elif self.lib_index == -1 and len(libraries):
            self.lib_index = 0


    def add_library(self, path, setEnabled = False):
        #sanitize path
        ext = os.path.extsep + "blend"
        if not path.endswith(ext):
            path += ext

        if check_path(path):
            #      if path == default_library:
            #        return 'ERROR', "Cannot add default library."
            #if path in [lib.path for lib in self.libraries]:
            return 'ERROR', "Library already exists."
        else:
            dd("Can't find " + path)
            #create file
            cmd = '''
import bpy
bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % winpath(path)
            if not (send_command(cmd, "createlib.py")):
                return 'ERROR', "There was an error creating the file. Make sure you run Blender with admin rights."

            #self.libraries = sorted(self.libraries, key=lambda lib: sortlibs(lib))
            dd("adding library", path)
            global libraries
            libraries = get_libraries()
            return "INFO", "Library added"

    def load_library(self):
        self.empty_list(True)
        if not self.current_library:
            return 'ERROR', "Library not found!."

        path = self.current_library.path

        dd("loading library", self.lib_index, path)

        if check_path(path):
            self.filter = False
            self.cat_index = -1

            categories = Categories(self.categories)
            self.cats = categories.read(True)
            self.load_categories()

            for mat in self.all_materials:
                self.all_materials.remove(0)

            for mat in list_materials(self.current_library.path, True):
                item = self.all_materials.add()
                item.name = mat
                for cat in self.cats:
                    if mat in cat[1]:
                        item.category = cat[0]
                        break

            self.update_list()
        else:
            return 'ERROR', "Library not found!."

    def update_list(self):
        ### THIS HAS TO SORT
        self.empty_list()
        if self.current_library:
            current_category = self.current_category
            #sorteditems = sorted(self.all_materials, key=lambda x: x.name)
            for mat in self.all_materials:
                #print(current_category, mat.category)
                if not self.filter or (self.filter and mat.category == current_category) or current_category == "":
                    item = self.materials.add()
                    item.name = mat.name
                    item.category = mat.category

    def empty_list(self, cats = False):
        #self.mat_index = -1
        for it in self.materials:
            self.materials.remove(0)

        if cats:
            for c in self.categories:
                self.categories.remove(0)

    ### CATEGORIES
    @property
    def current_category(self):
        #print(self.mat_index)
        if check_index(self.categories, self.cat_index):
            return self.categories[self.cat_index].name
        return ""

    def load_categories(self):

        for c in self.categories:
            self.categories.remove(0)

        for c in self.cats:
            cat = self.categories.add()
            cat.name = c[0]

    def add_category(self, name):
        if name:
            name = name.strip().title()
            dd("add category", name)
            categories = Categories(self.categories)

            categories.add(name)

    #      if lib:
    #        cat = xml.find("category", name, lib, create = True)
    #        self.load_categories()
    #      else:
    #        return 'ERROR', "Library not found"
    def remove_category(self):
        dd("removing category", self.current_category)
        categories = Categories(self.categories)
        categories.remove(self.cat_index)

    def set_category(self):
        mat = self.active_material
        #dd(lib, mat, self.current_category)
        if mat:
            #set mat to category
            if self.cat_index>-1:
                dd(self.current_category)
                cat = self.current_category
                if cat == self.all_materials[self.mat_index].category:
                    return
                cmd = """
import bpy
try:
  mat = bpy.data.materials['%s']
except:
  mat = None
if mat:
  mat['category'] = "%s"
  bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)
""" % (mat.name, cat, winpath(self.current_library.path))
                if send_command(cmd):
                    self.all_materials[self.mat_index].category = cat
                    mat.category = cat
                else:
                    return "WARNING", "There was an error."

            #        catnode = xml.find("category", self.current_category, lib, True)
            #        matnode = xml.find("material", mat.name, lib)
            #        if matnode:
            #          catnode.appendChild(matnode)
            #        else:
            #          matnode = xml.find("material", mat.name, catnode, True)
            #        xml.save()
            #        mat.category = cat
            #        self.current_library.materials[self.mat_index].category = cat
            #remove mat from any category
            else:
                mat.category = ""
                self.all_materials[self.mat_index].category = ""
        else:
            return "WARNING", "Select a material"

    def get_material(self, name, link=False):
        with bpy.data.libraries.load(self.current_library.path, link=link, relative=False) as (data_from, data_to):
            data_to.materials = [name]
        if link:
            print(name + " linked.")
        else:
            print(name + " appended.")

    def apply(self, context, preview=False):
        name = self.active_material.name
        if not name: return "WARNING", "Select a material from the list."

        linked = self.link or preview
        force =  self.force_import or linked

        objects = []
        active = context.object
        dummy = self.get_dummy(context)

        #setup objects
        if preview:
            if context.mode == "EDIT_MESH":
                return "WARNING", "Can't preview on EDIT MODE"
            if dummy!= active:
                self.last_selected = context.object.name
            context.view_layer.objects.active = dummy
            objects.append(dummy)
        #apply
        else:
            objects = [obj for obj in context.selected_objects if hasattr(obj.data, "materials")]

        if not objects:
            return "INFO", "Please select an object"

        if dummy == context.object and not preview:
            if (len(objects)==1 and dummy.select_get()):
                return "ERROR", "Apply is disabled for the Material Preview Object"
            try:
                last = context.scene.objects[self.last_selected]
                if last in context.selected_objects:
                    context.view_layer.objects.active = last
                else:
                    self.last_selected = ""
            except:
                context.view_layer.objects.active = None
        dummy.select_set(False)
        #objects = context.selected_objects

        material = None

        #mira si hay materiales linkados de la libreria actual
        for mat in bpy.data.materials:
            try:
                samelib = bpy.path.relpath(mat.library.filepath) == bpy.path.relpath(self.current_library.path)
            except:
                samelib = False

            if mat.name == name and mat.library and samelib:
                material = mat
                dd("encontre linked", name, "no importo nada")
                break

        if not force:
            #busca materiales no linkados
            for mat in bpy.data.materials:
                if mat.name == name and not mat.library:
                    material = mat
                    dd("encontre no linkado", name, "no importo nada")
                    break

        if not material:
            #go get it
            dd("voy a buscarlo")
            nmats = len(bpy.data.materials)

            self.get_material(name, linked)

            if not self.force_import:
                try:
                    material = bpy.data.materials[name]
                except:
                    pass

            if not material:
                if nmats == len(bpy.data.materials) and not linked:
                    return "ERROR", name + " doesn't exists at library " + str(linked)
                else:
                    for mat in reversed(bpy.data.materials):
                        if mat.name[0:len(name)] == name:
                            #careful on how blender writes library paths
                            try:
                                samelib = bpy.path.relpath(mat.library.filepath) == bpy.path.relpath(self.current_library.path)
                            except:
                                samelib = False

                            if linked and mat.library and samelib:
                                material = mat
                                dd(name, "importado con link")
                                break
                            else:
                                if not mat.library:
                                    dd(name, "importado sin link")
                                    material = mat
                                    break
                if material:
                    material.use_fake_user = False
                    material.user_clear()

        print ("Material", material, force)

        #if material:
        #maybe some test cases doesn't return a material, gotta take care of that
        #i cannot think of any case like that right now
        #maybe import linked when the database isn't sync
        if context.mode == "EDIT_MESH":
            obj = context.object
            dd(material)
            index = -1
            for i, mat in enumerate(obj.data.materials):
                if mat == material:
                    index = i
                    break

            if index == -1:
                obj.data.materials.append(material)
                index = len(obj.data.materials)-1
            dd(index)
            import bmesh
            bm  = bmesh.from_edit_mesh(obj.data)
            for f in bm.faces:
                if f.select:
                    f.material_index = index

        else:
            for obj in objects:
                index = obj.active_material_index
                if index < len(obj.material_slots):
                    obj.material_slots[index].material = None
                    obj.material_slots[index].material = material
                else:
                    obj.data.materials.append(material)

            if not linked:
                bpy.ops.object.make_local(type="SELECT_OBDATA_MATERIAL")

    def add_material(self, mat):

        if not mat:
            return 'WARNING', "Select a material from the scene."

        name = mat.name
        thispath = winpath(bpy.data.filepath)
        libpath = winpath(self.current_library.path)

        if not thispath:
            return 'WARNING', "Save this file before export."

        if not libpath:
            return 'WARNING', "Library not found!."

        elif bpy.data.is_dirty:
            bpy.ops.wm.save_mainfile(check_existing=True)

        if mat.library:
            return 'WARNING', 'Cannot export linked materials.'

        dd("adding material", name, libpath)

        overwrite = ""
        if name in list_materials(libpath):
            overwrite = '''
mat = bpy.data.materials["%s"]
mat.name = "tmp"
mat.use_fake_user = False
mat.user_clear()'''  % name

        cmd = '''
import bpy{0}
with bpy.data.libraries.load("{1}") as (data_from, data_to):
  data_to.materials = ["{2}"]
mat = bpy.data.materials["{2}"]
mat.use_fake_user=True
bpy.ops.file.pack_all()
bpy.ops.wm.save_mainfile(filepath="{3}", check_existing=False, compress=True)
'''.format(overwrite, thispath, name, libpath)

        if send_command(cmd):
            #self.load_library()
            if not overwrite:
                item = self.all_materials.add()
                item.name = name
                if "category" in mat.keys():
                    item.category = mat['category']
                #reorder all_materials
                items = sorted([[item.name, item.category] for item in self.all_materials], key = lambda x: x[0])

                self.all_materials.clear()
                for it in items:
                    item = self.all_materials.add()
                    item.name = it[0]
                    item.category = it[1]

                self.update_list()

            return 'INFO', "Material added."
        else:
            print("Save Material Error: Run Blender with administrative privileges.")
            return 'WARNING', "There was an error saving the material"

    def remove_material(self):
        name = self.active_material.name
        libpath = winpath(self.current_library.path)
        if name and libpath and name in list_materials(libpath):
            cmd = '''import bpy
mat = bpy.data.materials["%s"]
mat.use_fake_user = False
mat.user_clear()
bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % (name , libpath)
            if send_command(cmd, "removemat.py"):
                self.all_materials.remove(self.mat_index)
                self.update_list()
            else:
                return 'ERROR', "There was an error."
        return "INFO", name + " removed."

    def get_dummy(self, context):
        dummy_name = "Material_Preview_Dummy"
        dummy_mesh = "Material_Preview_Mesh"
        scn = context.scene
        try:
            dummy = scn.objects[dummy_name]
        except:
            #create dummy
            try:
                me = bpy.data.meshes(dummy_mesh)
            except:
                me = bpy.data.meshes.new(dummy_mesh)
            dummy = bpy.data.objects.new(dummy_name, me)
            context.collection.objects.link(dummy)

        dummy.hide_set(True)
        dummy.hide_render = True
        dummy.hide_select = True
        return dummy

# bpy.utils.register_class(matlibProperties)
# Scene.matlib = PointerProperty(type = matlibProperties)

### MENUS
class MATLIB_MT_LibsMenu(Menu):
    bl_label = "Libraries Menu"

    def draw(self, context):
        layout = self.layout
        libs = libraries
        #layout.operator("matlib.operator", text="Default Library").cmd="lib-1"
        for i, lib in enumerate(libs):
            layout.operator("matlib.operator", text=lib.shortname).cmd="lib"+str(i)

class MATLIB_MT_CatsMenu(Menu):
    bl_label = "Categories Menu"

    def draw(self, context):
        layout = self.layout
        cats = context.scene.matlib.categories
        layout.operator("matlib.operator", text="All").cmd="cat-1"
        for i, cat in enumerate(cats):
            layout.operator("matlib.operator", text=cat.name).cmd="cat"+str(i)

### OPERATORS
#class MATLIB_OT_add(Operator):
#  """Add Active Material"""
#  bl_label = "Add"
#  bl_idname = "matlib.add_material"
#
#  @classmethod
#  def poll(cls, context):
#    return context.active_object is not None
#
#  def execute(self, context):
#    print("executing")
#    return {"FINISHED"}



class MATLIB_OT_add(Operator):
    """Add active material to library"""
    bl_idname = "matlib.add"
    bl_label = "Add active material"

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return  obj is not None and obj.active_material is not None

    def execute(self, context):
        matlib = context.scene.matlib
        success = matlib.add_material(context.object.active_material)
        if type(success).__name__ == "tuple":
            print(success)
            self.report({success[0]}, success[1])
        return {'FINISHED'}

class MATLIB_OT_remove(Operator):
    """Remove material from library"""
    bl_idname = "matlib.remove"
    bl_label = "Remove material from library"

    @classmethod
    def poll(cls, context):
        matlib = context.scene.matlib
        return check_index(matlib.materials, matlib.mat_index)

    def execute(self, context):
        matlib = context.scene.matlib
        success = matlib.remove_material()
        if type(success).__name__ == "tuple":
            print(success)
            self.report({success[0]}, success[1])
        return {'FINISHED'}

class MATLIB_OT_remove(Operator):
    """Reload library"""
    bl_idname = "matlib.reload"
    bl_label = "Reload library"

    #  @classmethod
    #  def poll(cls, context):
    #    matlib = context.scene.matlib
    #    index = matlib.mat_index
    #    l = len(matlib.materials)
    #    return l>0 and index >=0 and index < l

    def execute(self, context):
        matlib = context.scene.matlib
        success = matlib.reload()
        if type(success).__name__ == "tuple":
            print(success)
            self.report({success[0]}, success[1])
        return {'FINISHED'}


class MATLIB_OT_apply(Operator):
    """Apply selected material"""
    bl_idname = "matlib.apply"
    bl_label = "Apply material"

    @classmethod
    def poll(cls, context):
        matlib = context.scene.matlib
        index = matlib.mat_index
        l = len(matlib.materials)
        obj = context.active_object
        return l>0 and index >=0 and index < l and obj is not None

    def execute(self, context):
        matlib = context.scene.matlib
        success = matlib.apply(context, False)
        if type(success).__name__ == "tuple":
            print(success)
            self.report({success[0]}, success[1])
        return {'FINISHED'}


class MATLIB_OT_preview(Operator):
    """Preview selected material"""
    bl_idname = "matlib.preview"
    bl_label = "Preview selected material"

    @classmethod
    def poll(cls, context):
        matlib = context.scene.matlib
        index = matlib.mat_index
        l = len(matlib.materials)
        obj = context.active_object
        return l>0 and index >=0 and index < l

    def execute(self, context):
        matlib = context.scene.matlib
        success = matlib.apply(context, True)
        if type(success).__name__ == "tuple":
            print(success)
            self.report({success[0]}, success[1])
        return {'FINISHED'}


class MATLIB_OT_flush(Operator):
    """Flush unused materials"""
    bl_idname = "matlib.flush"
    bl_label = "Flush unused materials"

    @classmethod
    def poll(cls, context):
        matlib = context.scene.matlib
        index = matlib.mat_index
        l = len(matlib.materials)
        obj = context.active_object
        return l>0 and index >=0 and index < l

    def execute(self, context):
        matlib = context.scene.matlib
        dummy = matlib.get_dummy(context)
        if dummy == context.object:
            try:
                context.view_layer.objects.active = context.scene.objects[matlib.last_selected]
            except:
                pass

        for slot in dummy.material_slots:
            slot.material = None
        i=0
        for mat in bpy.data.materials:
            if mat.users==0:
                i+=1
                print (mat.name, "removed.")
                bpy.data.materials.remove(mat)

        plural = "" if i == 1 else "s"
        self.report({'INFO'}, str(i) + " material"+plural+" removed.")

        return {'FINISHED'}


class MATLIB_OT_operator(Operator):
    """Add, Remove, Reload, Apply, Preview, Clean Material"""
    bl_label = "New"
    bl_idname = "matlib.operator"
    __doc__ = "Add, Remove, Reload, Apply, Preview, Clean Material"

    category: StringProperty(name="Category")
    filepath: StringProperty(options={'HIDDEN'})
    cmd: bpy.props.StringProperty(name="Command", options={'HIDDEN'})
    filter_glob: StringProperty(default="*.blend", options={'HIDDEN'})
    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def draw(self, context):
        layout = self.layout
        #cmd = LIBRARY_ADD
        if self.cmd == "LIBRARY_ADD":
            #layout.label(text="Select a blend file as library or")
            #layout.label(text="Type a name to create a new library.")
            layout.prop(self, "category", text="Library")
        elif self.cmd == "FILTER_ADD":
            layout.prop(self, "category")

    def invoke(self, context, event):

        cmd = self.cmd
        print("invoke", cmd)

        if cmd == "LIBRARY_ADD":
            self.filepath = matlib_path + os.path.sep
            dd("filepath", self.filepath, matlib_path)
            #context.window_manager.fileselect_add(self)
            context.window_manager.invoke_props_dialog(self)
            return {'RUNNING_MODAL'}
        elif cmd == "FILTER_ADD":
            context.window_manager.invoke_props_dialog(self)
            return {'RUNNING_MODAL'}
        return self.execute(context)

    ### TODO: execute doesn't trigger remove
    def execute(self, context):

        success = ""
        matlib = context.scene.matlib

        if self.cmd == "init":
            print("initialize")
            return {'FINISHED'}

        #Library Commands
        if self.cmd[0:3] == "lib":
            index = int(self.cmd[3::])
            matlib.lib_index = index
            #success = matlib.load_library()
        elif self.cmd == "LIBRARY_ADD":
            dd("execute lib add")
            libname = self.category
            if libname[-6::] != ".blend": libname+= ".blend"
            libname = os.path.join(matlib_path, libname)
            print(libname)

            success = matlib.add_library(libname, True)
            for i, l in enumerate(libraries):
                if l.name == self.category:
                    matlib.lib_index = i
                    break

        elif self.cmd == "RELOAD":
            success = matlib.reload()

        if not matlib.current_library:
            self.report({'ERROR'}, "Select a Library")
            return {'CANCELLED'}

        if self.cmd == "FILTER_ADD":
            success = matlib.add_category(self.category)
            for i, cat in enumerate(matlib.categories):
                if cat.name == self.category:
                    matlib.cat_index = i
                    break

        elif self.cmd == "FILTER_REMOVE":
            matlib.remove_category()

        elif self.cmd == "FILTER_SET":
            success = matlib.set_category()

        elif self.cmd[0:3] == "cat":
            index = int(self.cmd[3::])
            matlib.cat_index = index

        #Common Commands
        elif self.cmd == "ADD":
            success = matlib.add_material(context.object.active_material)

        elif self.cmd == "REMOVE":
            success = matlib.remove_material()


        elif self.cmd == "APPLY":
            success = matlib.apply(context)

        elif self.cmd == "PREVIEW":
            success = matlib.apply(context, True)

        elif self.cmd=="FLUSH":
            #release dummy materials
            dummy = matlib.get_dummy(context)
            if dummy == context.object:
                try:
                    context.view_layer.objects.active = context.scene.objects[matlib.last_selected]
                except:
                    pass

            for slot in dummy.material_slots:
                slot.material = None
            i=0
            for mat in bpy.data.materials:
                if mat.users==0:
                    i+=1
                    print (mat.name, "removed.")
                    bpy.data.materials.remove(mat)

            plural = "s"
            if i==1:
                plural = ""

            self.report({'INFO'}, str(i) + " material"+plural+" removed.")

        ### CONVERT
        elif self.cmd == "CONVERT":
            return {'FINISHED'}
            lib = matlib.current_library
            if lib:

                path = os.path.join(matlib_path, "www")
                if not os.path.exists(path):
                    os.mkdir(path)
                path = os.path.join(path, lib.shortname)
                if not os.path.exists(path):
                    os.mkdir(path)

                path = winpath(path)
                libpath = winpath(lib.name)

                print(path)
                print(libpath)

                #decirle a la libreria que cree un fichero blend por cada material que tenga.
                cmd = """
print(30*"+")
import bpy, os
def list_materials():
  list = []
  with bpy.data.libraries.load("{0}") as (data_from, data_to):
    for mat in data_from.materials:
      list.append(mat)
  return sorted(list)

def get_material(name, link=False):
  with bpy.data.libraries.load("{0}", link=link, relative=False) as (data_from, data_to):
    data_to.materials = [name]
  if link:
    print(name + " linked.")
  else:
    print(name + " appended.")

for scn in bpy.data.scenes:
  for obj in scn.objects:
    scn.objects.unlink(obj)
    obj.user_clear()
    bpy.data.objects.remove(obj)

def clean_materials():
  for mat in bpy.data.materials:
    mat.user_clear()
    bpy.data.materials.remove(mat)

bin = bpy.app.binary_path
mats = list_materials()
bpy.context.preferences.filepaths.save_version = 0
for mat in mats:
  clean_materials()
  matpath = os.path.join("{1}", mat + ".blend")
  print(matpath)
  get_material(mat)
  material = bpy.data.materials[0]
  material.use_fake_user = True
  bpy.ops.wm.save_mainfile(filepath = matpath, compress=True, check_existing=False)
""".format(libpath, path)
                print(cmd)
                send_command(cmd, "createlib.py")

        if type(success).__name__ == "tuple":
            print(success)
            self.report({success[0]}, success[1])

        return {'FINISHED'}


class MATLIB_PT_vxPanel(Panel):
    bl_label = "Material Library VX"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_context = "material"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(self, context):
        return context.active_object.active_material!=None

    def draw(self, context):
        layout = self.layout
        matlib = context.scene.matlib

        #hyper ugly trick but i dont know how to init classes at register time
        #    if matlibProperties.init:
        #      matlibProperties.init = False
        #      matlib.__init__()

        #libraries
        col = layout.column(align=True)
        if matlib.current_library:
            text = matlib.current_library.shortname
        else:
            text = "Select a Library"
        split = col.split(factor=0.55, align=True)
        split.menu("MATLIB_MT_LibsMenu",text=text)
        split.operator("matlib.operator", icon="ADD", text="New Library").cmd = "LIBRARY_ADD"

        #    #list
        row = layout.row()
        row.template_list("UI_UL_list", "  ", matlib, "materials", matlib, "mat_index", rows=6)
        col = row.column()
 #       row = layout.row()
        #operators
        col.operator("matlib.operator", icon="ADD", text="Add To Library").cmd="ADD"
        col.operator("matlib.operator", icon="MATERIAL", text="Apply To Selected").cmd="APPLY"
        col.operator("matlib.operator", icon="FILE_REFRESH", text="Reload Material").cmd="RELOAD"
        col.operator("matlib.operator", icon="COLOR", text="Preview Material").cmd="PREVIEW"
        col.operator("matlib.operator", icon="GHOST_DISABLED", text="Remove Preview").cmd="FLUSH"
        col.operator("matlib.operator", icon="REMOVE", text="Remove Material").cmd="REMOVE"
        col.prop(matlib, "show_prefs", icon="MODIFIER", text="Settings")

        # Search
        if not matlib.hide_search:
            row = layout.row(align=True)
            row.prop_search(matlib, "search", matlib, "materials", text="", icon="VIEWZOOM")

        #categories
        row = layout.row()
        if matlib.active_material:
            row.label(text="Category:")
            row.label(text = matlib.active_material.category)
        else:
            row.label(text="Category Tools:")
        row = layout.row(align=True)
        text = "All"
        if matlib.current_category: text = matlib.current_category
        row.menu("MATLIB_MT_CatsMenu",text=text)
        row = layout.row(align=True)
        row.prop(matlib, "filter", icon="FILTER", text="Filter")
        row.operator("matlib.operator", icon="FILE_PARENT", text="Set Type").cmd="FILTER_SET"
        row.operator("matlib.operator", icon="ADD", text="New").cmd="FILTER_ADD"
        row.operator("matlib.operator", icon="REMOVE", text="Remove").cmd="FILTER_REMOVE"

        #prefs
        if matlib.show_prefs:
            row = layout.row(align=True)
            row.prop(matlib, "force_import")
            row.prop(matlib, "link")
            row.prop(matlib, "hide_search")
#      row = layout.row(align=True)
#row = layout.row()
#row.operator("matlib.operator", icon="URL", text="Convert Library").cmd="CONVERT"

#      row = layout.row()
#      if (matlib.current_library):
#        row.label(matlib.current_library.name)
#      else:
#        row.label(text="Library not found!.")
"""
classes = [
matlibMaterials,
matlibProperties,
EmptyGroup,
MATLIB_PT_vxPanel,
MATLIB_OT_operator,
MATLIB_MT_LibsMenu,
MATLIB_MT_CatsMenu
]
#print(bpy.context.scene)
"""

@persistent
def refresh_libs(dummy=None):
    global libraries
    global matlib_path
    default_path = bpy.context.preferences.addons[__name__].preferences.matlib_path
    if default_path is not None and default_path != '':
        matlib_path = default_path

    libraries = get_libraries()


def reload_library(self, context):
    bpy.context.preferences.addons[__name__].preferences.matlib_path = bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.matlib_path)
    refresh_libs(self)


class matlibvxPref(AddonPreferences):
    bl_idname = __name__
    matlib_path: StringProperty(
        name="Additional Path",
        description="User defined path to .blend libraries files",
        default="",
        subtype="DIR_PATH",
        update=reload_library
    )

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "matlib_path")

classes = [
    matlibvxPref,
    matlibMaterials,
    EmptyGroup,
    MATLIB_PT_vxPanel,
    MATLIB_OT_operator,
    MATLIB_MT_LibsMenu,
    MATLIB_MT_CatsMenu,
    matlibProperties,
]

"""
classes = [
matlibProperties,
EmptyGroup,
matlibMaterials,
Categories,
Library,
MATLIB_MT_LibsMenu,
MATLIB_MT_CatsMenu,
MATLIB_OT_add,
MATLIB_OT_remove,
MATLIB_OT_remove,
MATLIB_OT_apply,
MATLIB_OT_preview,
MATLIB_OT_flush,
MATLIB_OT_operator,
MATLIB_PT_vxPanel,
matlibvxPref
]
"""
def register():
    global matlib_path

    for c in classes:
        bpy.utils.register_class(c)
    Scene.matlib_categories = CollectionProperty(type=EmptyGroup)
    Scene.matlib = PointerProperty(type = matlibProperties)
    bpy.app.handlers.load_post.append(refresh_libs)
    refresh_libs()


def unregister():
    global libraries

    try:
        # raise ValueError list.remove(x): x not in list
        del Scene.matlib_categories
    except:
        pass
    del Scene.matlib
    libraries.clear()
    bpy.app.handlers.load_post.remove(refresh_libs)
    for c in classes:
        bpy.utils.unregister_class(c)


if __name__ == "__main__":
    register()
