bl_info = {
    "name": "Unfolder Sync",
    "author": "Unfolder",
    "version": (2, 5),
    "blender": (2, 80, 0),
    "location": "View3D > Sidebar > Unfolder Tab",
    "description": "Sync Objects with Unfolder",
    "category": "Import-Export"}

import bpy
import socket
import os
import json

class Request:
    def __init__(self, method, document=None):
        self.id = 4
        self.build = 10
        self.method = method
        self.document = document

class Response:
    def __init__(self, code, message, documents):
        self.code = code
        self.message = message
        self.documents = documents

def send(request, socketPath):
    if not os.path.exists(socketPath): 
        return None
    try:
        client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        client.connect(socketPath)
        client.sendall(json.dumps(vars(request)).encode("utf-8"))
        client.settimeout(2.0)
        response = client.recv(4096).decode("utf-8")
        client.close()
        dict = json.loads(response)
        return Response(dict["code"], dict["message"], dict["documents"])
    except:
        return None

def exportObjFile(path):
    if not os.path.exists(path): return False
    filePath = os.path.join(path, "sync.obj")
    settings = bpy.context.window_manager.unfolder_sync_settings
    if bpy.app.version >= (3, 3, 0):
        bpy.ops.wm.obj_export(
            filepath=filePath,
            check_existing=False,
            forward_axis='NEGATIVE_Z',
            up_axis='Y',
            global_scale=1.0,
            apply_modifiers=settings.apply_modifiers,
            export_selected_objects=settings.selection_only,
            export_uv=True,
            export_normals=True,
            export_materials=settings.use_material,
            export_triangulated_mesh=False,
            export_object_groups=False,
            export_material_groups=False,
            export_vertex_groups=False,
            export_smooth_groups=False,
        )
    else:
        bpy.ops.export_scene.obj(
            filepath=filePath,
            check_existing=0,
            axis_up="Y",
            axis_forward="-Z",
            filter_glob="*.obj;*.mtl",
            use_selection=settings.selection_only,
            use_animation=0,
            use_mesh_modifiers=settings.apply_modifiers,
            use_edges=1,
            use_normals=1,
            use_uvs=1,
            use_materials=settings.use_material,
            use_triangles=0,
            use_nurbs=1,
            use_vertex_groups=0,
            use_blen_objects=0,
            group_by_object=1,
            group_by_material=0,
            keep_vertex_order=1,
            global_scale=1,
            path_mode="AUTO"
        )
    return True

unfolderDocuments = []

def drawDocumentMenu(self, context):
    for d in unfolderDocuments:
        self.layout.operator("unfoldersync.sync", text=d).syncDocument = d

class UNFOLDER_PT_syncPanel(bpy.types.Panel):
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Unfolder"
    bl_label = "Sync"

    def draw(self, context):
        settings = bpy.context.window_manager.unfolder_sync_settings
        layout = self.layout
        layout.prop(settings, "selection_only")
        layout.prop(settings, "apply_modifiers")
        layout.prop(settings, "use_material")
        layout.separator()
        layout.operator("unfoldersync.sync", text="Sync", icon="FILE_REFRESH").syncDocument = ""

class UNFOLDER_OT_sync(bpy.types.Operator):
    """Sync objects with Unfolder"""
    bl_idname = "unfoldersync.sync"
    bl_label = "Sync"
    bl_options = {"REGISTER"}

    syncDocument: bpy.props.StringProperty()

    def reportError(self, code, message="Sync Failed"):
        self.report({"ERROR"}, message + "\nError Code: " + str(code))

    def execute(self, context):
        code = 100
        cachePath  = os.path.expanduser("~/Library/Containers/app.unfolder.Unfolder/Data/Library/Caches/")
        socketPath = os.path.join(cachePath, "sync.socket")
        if os.path.exists(cachePath):
            code += 1
        if not os.path.exists(socketPath):
            cachePath  = os.path.expanduser("~/Library/Caches/app.unfolder.UnfolderL/")
            socketPath = os.path.join(cachePath, "sync.socket")
            if os.path.exists(cachePath):
                code += 2
            if not os.path.exists(socketPath):
                self.reportError(code, "Cannot Connect to Unfolder\nPlease Ensure Unfolder is Running.")
                return {"CANCELLED"}
        
        if not self.syncDocument:
            res = send(Request(0), socketPath)
            if res is None:
                self.reportError(201)
                return {"CANCELLED"}

            if res.code != 0:
                self.reportError(res.code, res.message)
                return {"CANCELLED"}

            if len(res.documents) == 0:
                self.reportError(220)
                return {"CANCELLED"}

            if len(res.documents) == 1:
                self.syncDocument = res.documents[0]
            else:
                global unfolderDocuments
                unfolderDocuments = res.documents
                bpy.context.window_manager.popup_menu(
                    drawDocumentMenu, 
                    title="Which document do you want to sync?", 
                    icon="QUESTION"
                )
                return {"CANCELLED"}

        success = exportObjFile(cachePath)
        if not success:
            self.reportError(301)
            return {"CANCELLED"}

        res = send(Request(1, self.syncDocument), socketPath)
        if res is None:
            self.reportError(202)
            return {"CANCELLED"}

        if res.code != 1:
            self.reportError(res.code, res.message)
            return {"CANCELLED"}
        
        return {"FINISHED"}

class UnfolderSyncSettings(bpy.types.PropertyGroup):
    selection_only: bpy.props.BoolProperty(
        name = "Selection Only",
        description = "Sync selected objects only",
        default = False
    )
    apply_modifiers: bpy.props.BoolProperty(
        name = "Apply Modifiers",
        description = "Apply modifiers",
        default = True
    )
    use_material: bpy.props.BoolProperty(
        name = "Use Materials",
        description = "Sync materials",
        default = True
    )

def register():
    bpy.utils.register_class(UnfolderSyncSettings)
    bpy.types.WindowManager.unfolder_sync_settings = bpy.props.PointerProperty(type=UnfolderSyncSettings)
    bpy.utils.register_class(UNFOLDER_OT_sync)
    bpy.utils.register_class(UNFOLDER_PT_syncPanel)
  
def unregister():
    bpy.utils.unregister_class(UNFOLDER_PT_syncPanel)
    bpy.utils.unregister_class(UNFOLDER_OT_sync)
    del bpy.types.WindowManager.unfolder_sync_settings
    bpy.utils.unregister_class(UnfolderSyncSettings)
