
import bpy
import os
import datetime

from .__meta__ import getVersion
from .utils_meta import writeProjectSettings
from .utils_debug import debug_err, debug_log, debug_warn
from .utils_web_project import getAssetsDirectory, runAutoCompress
from .settings_scene import tryFindMainScene, tryGetMainSceneSettings
from .blender_windows import get_context_window
from .utils import getWebProjectPath, isBlenderVersionSupported, showMessage
from .analytics import send_new_export




errors = []
def reportError(message: str):
    errors.append(message)

warnings = []
def reportWarning(message: str):
    warnings.append(message)

def lastExportHasErrors():
    return errors and len(errors) > 0

class BuildContext:
    targetPlatform: str | None
    """The target platform to build for. Options are \"folder\" or \"glitch\""""
    is_file_menu_export: bool = False
    def __init__(self):
        self.targetPlatform = None
        self.is_file_menu_export = False

class SceneExportContext:

    directory: str
    """the blend file directory"""
    projectDirectory: str
    """the project directory"""
    exportDirectory: str
    """the export directory"""
    exportExtension: str = "glb"
    # the scene that was active when export started
    startScene = bpy.types.Scene
    # the scene that is set to be the main scene (can be unassigned)
    mainScene = bpy.types.Scene or None
    # dependencies that have been collected during export
    sceneDependencies = []
    isExportingSubscene = False
    isFileMenuExport = False
    __exportedDependencies = []
    __exportedPaths = []
    __originalMode = 'OBJECT'

    __startTime = 0

    __lockFilePath = ""

    def __init__(self, directory: str, build:BuildContext | None, project_directory:str | None = None):
        global errors
        global warnings
        errors = []
        warnings = []
        
        if build is None:
            build = BuildContext()
        self.isFileMenuExport = build.is_file_menu_export or False
        self.startScene = get_context_window().scene
        self.projectDirectory = project_directory or getWebProjectPath()
        self.exportDirectory = self.projectDirectory # TODO: make this work with needle.config
        mainScene = tryFindMainScene()
        self.mainScene = mainScene
        if mainScene:
            window = get_context_window()
            window.scene = mainScene
            if bpy.context.scene is not mainScene:
                debug_err("Failed to set main scene as active scene")
                reportError("Failed to set main scene as active scene")
        if mainScene is None:
            self.mainScene = self.startScene
        self.directory = directory
        self.sceneDependencies = []
        self.__exportCount = 0
        self.__exportedDependencies = []
        self.__startTime = datetime.datetime.now()
        # enable object mode
        if  bpy.context.object: # this might be undefined when no object is selected
            self.__originalMode = bpy.context.object.mode
            if bpy.context.active_object is not None and bpy.context.active_object.mode != 'OBJECT':
                try:
                    bpy.ops.object.mode_set(mode='OBJECT')
                except Exception as e:
                    reportError("Failed to set object mode to OBJECT for object \"" + bpy.context.active_object.name + "\"")
                    debug_err(e)
        else:
            self.__originalMode = None
        # if mainScene is not None:
        #     get_context_window().scene = mainScene

        ## create a needle.lock file to indicate that an export is in progress
        self.__lockFilePath = os.path.join(self.projectDirectory, "needle.lock")

        # only write lock file and project settings if it's not a file menu export
        if not self.isFileMenuExport:
            if not os.path.exists(self.__lockFilePath):
                with open(self.__lockFilePath, "w") as file:
                    timestamp_millis = datetime.datetime.now().timestamp()
                    file.write(str(timestamp_millis))

            # write meta.json
            settings = tryGetMainSceneSettings()
            writeProjectSettings(self.projectDirectory, settings, build.targetPlatform)

        # clear assets folder
        # assetsDirectory = getAssetsDirectory(self.projectDirectory)
        # if os.path.exists(assetsDirectory):
            # TODO: delete the content here
            # pass

    def registerDependency(self, scene: bpy.types.Scene) -> str:
        if not isinstance(scene, bpy.types.Scene):
            raise TypeError(
                "SceneExportContext.__registerDependency: scene is not a scene")
        if scene not in self.sceneDependencies:
            print(" ↪ registering scene dependency", scene.name)
            self.sceneDependencies.append(scene)
        return self.getFileName(scene)

    def getFileName(self, scene: bpy.types.Scene) -> str:
        # if no main scene is assigned or this is the main scene
        # the scene should be exported as "scene.glb" (which is currently hardcoded in the template to be loaded)
        # we might want to add a little bit of js codegen to the template to load the scene with the name of the main scene 
        if self.mainScene is None or scene == self.mainScene:
            return "scene." + self.exportExtension
        return scene.name + "." + self.exportExtension

    def onBeforeExport(self, scene: bpy.types.Scene, path: str):
        self.__exportCount += 1
        if not scene in self.__exportedDependencies:
            self.__exportedDependencies.append(scene)
        if not path in self.__exportedPaths:
            self.__exportedPaths.append(path)

    def alreadyExported(self, scene: bpy.types.Scene) -> bool:
        return scene in self.__exportedDependencies

    def postProcess(self):
        # settings = tryGetMainSceneSettings()
        # for path in self.__exportedPaths:
            # if settings is not None and settings.useProgressive:
            #     makeProgressive(self.projectDirectory, path)
        if self.__originalMode and bpy.context.active_object is not None:
            try:
                if bpy.context.active_object.mode != self.__originalMode:
                    bpy.ops.object.mode_set(mode=self.__originalMode)
            except Exception as e:
                debug_err(e)
                reportError("Failed to set object mode back to " + self.__originalMode + " for object \"" + bpy.context.active_object.name + "\"")

    def onAfterExportFinished(self):
        export_duration = datetime.datetime.now() - self.__startTime
        send_new_export(self.directory, export_duration.total_seconds())
        # delete lock file if it exists
        if os.path.exists(self.__lockFilePath):
            os.remove(self.__lockFilePath)
        if self.mainScene and self.mainScene != self.startScene:
            get_context_window().scene = self.startScene
        pass

    def printReport(self):
        global errors
        global warnings
        debug_log("EXPORT REPORT FOR " + str(self.__exportCount) + " scenes to \"" + self.directory + "\"")
        # TODO might need to sort them by exported glb or export to a file
        if len(errors) == 0:
            debug_log("Exported successfully with " + str(len(errors)) + " errors, " + str(len(warnings)) + " warnings")
        elif len(errors) > 0:
            debug_log("!!! " + str(len(errors)) + " errors occured during export")
        if len(warnings) > 0:
            debug_warn("> Warnings:")
            for warning in warnings:
                debug_log("\t", warning)
        debug_log(f"Blender version: {bpy.app.version}, Needle Engine Addon: {getVersion()}")
        pass



__sceneExportContext: SceneExportContext | None = None

def getSceneExportContext():
    global __sceneExportContext
    return __sceneExportContext

# Returns true if an export is in process (e.g. when exporting sub-dependencies that don't have a main scene or a web project setup we WANT to export them)
def isCurrentlyExporting():
    return __sceneExportContext is not None



class ExportContext():
    """The export context"""

    exportSettings: any
    """the blender export settings"""

    exportOnlyComponentsInScene: bool = False
    """This is controlled by the file menu export mode option"""
    
    def __init__(self, ext):
        self.root = ext
        pass
    def getExtensionHandler(self, name:str):
        return self.root.extensionHandler[name]
    def isRootExport(self):
        context = getSceneExportContext()
        return context.isExportingSubscene == False
    def getExportDirectory(self):
        return getSceneExportContext().exportDirectory
    def getSceneExportContext(self):
        return getSceneExportContext()
    def isFileDialogueExport(self):
        """True if the export was triggered from the file menu"""
        return getSceneExportContext().isFileMenuExport
    

def onExportFromFileMenu(export_settings):
    global __sceneExportContext
    if __sceneExportContext is None:
        output_directory = export_settings["gltf_filedirectory"]
        buildContext = BuildContext()
        buildContext.is_file_menu_export = True
        debug_log("Export from file menu started")
        __sceneExportContext = SceneExportContext(output_directory, buildContext, output_directory)
    pass


def onExportFromFileMenuFinished(self):
    global __sceneExportContext
    if __sceneExportContext:
        debug_log("Export from file menu finished")
        __exportDependencies(self)
        if __sceneExportContext:
            __sceneExportContext.postProcess()
            __sceneExportContext.onAfterExportFinished()
            __sceneExportContext.printReport()
        __sceneExportContext = None
    pass

def exportScene(self, directory: str | None = None, fileName: str | None = None, context: BuildContext | None = None, project_directory:str = None, building_dist:bool = False):
    if not isBlenderVersionSupported():
        self.report({'ERROR'}, "Blender version not supported")
        return {"CANCELLED"}

    global __sceneExportContext

    if not directory:
        if __sceneExportContext:
            directory = __sceneExportContext.directory
        else:
            webProjectDirectory = getWebProjectPath()
            # check if web project exists
            if webProjectDirectory is None or not os.path.exists(webProjectDirectory):
                self.report({'INFO'}, "Web Project does not exist")
                return {"CANCELLED"}
            assetsDirectory = getAssetsDirectory(webProjectDirectory)
            if assetsDirectory:
                directory = assetsDirectory
            else:
                directory = os.path.join(webProjectDirectory, "assets")
            # clear assets dir
            if os.path.exists(directory):
                debug_log("Clearing assets directory", directory)
                for file in os.listdir(directory):
                    filePath = os.path.join(directory, file)
                    try:
                        if os.path.isfile(filePath):
                            os.unlink(filePath)
                    except Exception as e:
                        print(e)

    # make sure directory exists
    if not os.path.exists(directory):
        os.makedirs(directory)

    isRootExport = not __sceneExportContext
    if __sceneExportContext is None:
        debug_log("\n\n> BEGIN EXPORT")
        __sceneExportContext = SceneExportContext(directory, context, project_directory=project_directory)

    # blendNameWithoutExtension = bpy.path.basename(bpy.data.filepath)[:-6]
    fileName = fileName or __sceneExportContext.getFileName(__sceneExportContext.mainScene or __sceneExportContext.startScene)
    finalPath = os.path.join(directory, fileName)

    # run export
    try:
        __beforeExport()
        __runExport(finalPath)
    except RuntimeError as e:
        debug_err(e)
        reportError("Exporting GLB failed: " + str(e))
        self.report({'ERROR'}, "Export failed")
        showMessage(["Exporting GLB failed:", finalPath, "Please check the log file at Help/Needle/Open Log"], "Export failed")
        return {"CANCELLED"}
    finally:
        __postExport()
        if isRootExport:
            try:
                __exportDependencies(self)
                __sceneExportContext.postProcess()
                __sceneExportContext.onAfterExportFinished()
                __sceneExportContext.printReport()
                #if it's a local server export only apply compression
                if not building_dist:
                    runAutoCompress()
            except Exception as e:
                debug_err(e)
                reportError("Failed to export dependencies: " + str(e))
            finally:
                start = __sceneExportContext.startScene
                __sceneExportContext = None
                get_context_window().scene = start


def __runExport(finalPath: str):
    global __sceneExportContext
    currentScene = get_context_window().scene
    if __sceneExportContext.alreadyExported(currentScene):
        print("Scene already exported", currentScene.name)
        return
    __sceneExportContext.onBeforeExport(currentScene, finalPath)
    args = {
            "filepath":finalPath,
            "check_existing":False,
            "export_format":'GLB',
            "export_cameras":True,
            "export_lights":True,
            "use_active_scene":True,
    }
    if bpy.app.version >= (3, 6, 0):
        args["gltf_export_id"] = "Needle Engine"
        args["export_import_convert_lighting_mode"] = 'COMPAT'
        args["export_apply"] = True
        # extras is exporting ALL the custom types (e.g. the needle components) as well as empty objects
        # this is not what we want - if we enable this we need a way to remove those custom types
        # args["export_extras"] = True
        args["export_animations"] = True
        args["use_visible"] = False
        # HAS TO BE AUTO for lightmaps to work
        args["export_image_format"] = "AUTO"
        args["export_jpeg_quality"] = 100
    elif bpy.app.version >= (3, 4, 0):
        args["convert_lighting_mode"] = 'COMPAT'

    # TODO: this doesnt work yet - with SCENE the gather_animation hooks wont be invoked anymore
    # https://github.com/KhronosGroup/glTF-Blender-IO/issues/2293
    # if bpy.app.version >= (4, 2, 0):
    #     args["export_animation_mode"] = "SCENE" # NLA_TRACKS or SCENE
    #     args["export_pointer_animation"] = True
        # args["export_convert_animation_pointer"] = True
    # 
    if __canExportSceneDependencies() == False:
        del args["use_active_scene"]
        args["use_active_collection"] = True
    
    if currentScene is not bpy.context.scene:
        debug_err("ERROR: Exporting scene that is not the active scene \"" + bpy.context.scene.name + "\" but expected scene \"" + currentScene.name + "\"")
        reportError("Exporting scene that is not the active scene: " + bpy.context.scene.name + " - but expected " + currentScene.name)
    else:
        print("Exporting scene: " + bpy.context.scene.name)
    callMethodWithArgs(bpy.ops.export_scene.gltf, args)

def callMethodWithArgs(method, args):
    return method(**args)

def __canExportSceneDependencies():
    return bpy.app.version >= (3, 2, 0)


def __beforeExport():
    pass


def __postExport():
    pass


def __exportDependencies(self):
    if not __canExportSceneDependencies():
        print("Exporting dependencies is not supported in Blender 3.0")
        debug_warn("Exporting dependencies is not supported in Blender 3.0")
        return
    global __sceneExportContext
    if __sceneExportContext is None:
        raise RuntimeError(
            "No scene export context - registerSceneReference() can only be called during export")
    window = get_context_window()
    for dependency in __sceneExportContext.sceneDependencies:
        if dependency is None:
            continue
        if __sceneExportContext.alreadyExported(dependency):
            continue
        print("EXPORTING DEPENDENCY", dependency.name)
        debug_log("Exporting dependency: " + dependency.name)
        window.scene = dependency
        __sceneExportContext.isExportingSubscene = True
        exportScene(self, __sceneExportContext.directory, dependency.name + ".glb", project_directory=__sceneExportContext.projectDirectory)



# may ONLY be called during export
def registerDependency(scene: bpy.types.Scene) -> str or None:
    if not __canExportSceneDependencies():
        return None
    global __sceneExportContext
    if __sceneExportContext is None:
        raise RuntimeError(
            "No scene export context - registerSceneReference() can only be called during export")
    return __sceneExportContext.registerDependency(scene)
