# SPDX-License-Identifier: GPL-2.0-or-later

"""Import, export and render to POV engines.

These engines can be POV-Ray, Uberpov, HgPovray but others too, since POV is a
Scene Description Language. The script has been split in as few files as possible and
metaphorically structured as a train with some boilerplate rendering locomotive,
followed by its cars each representing a thematic in the 3D field:

########################################### RENDERING ###########################################

__init__.py :
    Provide script's metadata, Initialize addon preferences, (re)load package modules

ui_core.py :
    Set up workspaces and load other ui files for the user to set up all variables

render_core.py :
    Define the POV render engines declinations from generic Blender RenderEngine class

render_properties.py :
    Initialize properties for render parameters (Blender generic and POV native)

render_gui.py :
    Display properties from render_properties.py for user to change them

render.py :
    Translate render properties (Blender and POV native) to POV, ini file and CLI

                                                                          __------------------Z__
                                                                    _--¨¨] |  __ __ _____________||
                                                                _-¨7____/  | | °|° | □□□ □□□ □□□ ||
                                                               (===========|=|  |  |=============||
                                                                `-,_(@)(@)----------------(@)(@)-'
############################################# LAYOUT ##############################################

scenography_properties.py
    Initialize properties for passing layout (camera/light/environment) parameters to pov

scenography_gui.py :
    Display camera/light/environment properties from scenography_properties.py for user to change

scenography.py
    Translate  camera/light/environment properties to corresponding pov features

                                                    __------------------Z__   ____________________
                                              _--¨¨] |  __ __ _____________|||____________________|
                                          _-¨7____/  | | °|° | □□□ □□□ □□□ ||| □□□ □□□ □□□ □□□ □□ |
                                         (===========|=|  |  |=============|||====================|
                                          `-,_(@)(@)----------------(@)(@)-'^-(@)(@)--------(@)(@)'
############################################### MODEL #############################################

model_properties.py :
    Initialize properties for translating Blender geometry objects parameters to pov

model_gui.py :
    Display properties from model_properties.py for the user to change them

model_all.py :
    Translate to POV the object level data

model_poly_topology.py :
    Translate to POV the mesh based geometries

model_curve_topology.py :
    Translate to POV the curve based geometries

model_meta_topology.py :
    Translate to POV the metaball based geometries

model_primitives.py :
    Display some simple POV native primitives in 3D view for input and output

model_primitives_topology.py :
    Display some POV native complex or compound primitives in 3D view for input and output

                             __------------------Z__   ____________________   ____________________
                       _--¨¨] |  __ __ _____________|||____________________|||____________________
                   _-¨7____/  | | °|° | □□□ □□□ □□□ ||| □□□ □□□ □□□ □□□ □□ ||| □□□ □□□ □□□ □□□ □□
                  (===========|=|  |  |=============|||====================|||====================
                   `-,_(@)(@)----------------(@)(@)-'^-(@)(@)--------(@)(@)-^-(@)(@)--------(@)(@)
############################################ SHADING #############################################

shading_properties.py :
    Initialize properties for translating Blender materials parameters to pov

shading_ray_properties.py :
    Initialize properties for translating Blender ray paths relevant material parameters to pov

shading_gui.py :
    Display variables from shading_properties.py and shading_ray_properties.py for user to change

shading.py
    Translate shading properties to declared textures at the top of a pov file

texturing_properties.py :
    Initialize properties for translating Blender materials/world... texture influences to pov

texturing_gui.py :
    Display properties from texturing_properties.py available for user to change

texturing.py :
    Translate blender pixel based bitmap texture influences into POV

texturing_procedural.py :
    Translate blender algorithmic procedural texture influences into POV

           __------------------Z__   ____________________   ____________________   ________________
     _--¨¨] |  __ __ _____________|||____________________|||____________________|||________________
 _-¨7____/  | | °|° | □□□ □□□ □□□ ||| □□□ □□□ □□□ □□□ □□ ||| □□□ □□□ □□□ □□□ □□ ||| □□□ □□□ □□□ □□□
(===========|=|  |  |=============|||====================|||====================|||================
 `-,_(@)(@)----------------(@)(@)-'^-(@)(@)--------(@)(@)-^-(@)(@)--------(@)(@)-^-(@)(@)----------
############################################ VFX/TECH #############################################

particles_properties.py :
    Initialize all strands, fx, or particles based objects properties to be translated to POV

particles.py :
    Translate to POV the particle based geometries

voxel_lib.py :
    Render smoke to *.df3 files using an updated version of Mike Kost's df3.py legacy library

nodes_properties.py :
    Define all node items and their respective variables or sockets available to POV node trees

nodes.py :
    Translate node trees to the pov file

nodes_fn.py :
    Functions toolbox used by nodes.py to translate node trees to the pov file

nodes_gui.py :
    Operators and menus to interact with POV specific node trees

scripting_properties.py :
    Initialize properties for hand written scene description language fragments (POV native)

scripting_gui.py :
    Display properties from scripting_properties.py for user to add his custom POV code

scripting.py :
    Insert POV native scene description elements into blender scene or to exported POV file

update_files.py :
    Update new variables to values from older API. This file needs an update

######################################## PRESETS/TEMPLATES ########################################

Along these essential files also coexist a few additional libraries to help make
Blender stand up to other POV enabled IDEs (povwin, POV for Mac, QTPOV, VSCode, Vim...)
    presets :
        Material (sss)
            apple.py ; chicken.py ; cream.py ; Ketchup.py ; marble.py ;
            potato.py ; skim_milk.py ; skin1.py ; skin2.py ; whole_milk.py
        Radiosity
            01_Debug.py ; 02_Fast.py ; 03_Normal.py ; 04_Two_Bounces.py ;
            05_Final.py ; 06_Outdoor_Low_Quality.py ; 07_Outdoor_High_Quality.py ;
            08_Outdoor(Sun)Light.py ; 09_Indoor_Low_Quality.py ;
            10_Indoor_High_Quality.py ;
        World
            01_Clear_Blue_Sky.py ; 02_Partly_Hazy_Sky.py ; 03_Overcast_Sky.py ;
            04_Cartoony_Sky.py ; 05_Under_Water.py ;
        Light
            01_(4800K)_Direct_Sun.py ;
            02_(5400K)_High_Noon_Sun.py ;
            03_(6000K)_Daylight_Window.py ;
            04_(6000K)_2500W_HMI_(Halogen_Metal_Iodide).py ;
            05_(4000K)_100W_Metal_Halide.py ;
            06_(3200K)_100W_Quartz_Halogen.py ;
            07_(2850K)_100w_Tungsten.py ;
            08_(2600K)_40w_Tungsten.py ;
            09_(5000K)_75W_Full_Spectrum_Fluorescent_T12.py ;
            10_(4300K)_40W_Vintage_Fluorescent_T12.py ;
            11_(5000K)_18W_Standard_Fluorescent_T8 ;
            12_(4200K)_18W_Cool_White_Fluorescent_T8.py ;
            13_(3000K)_18W_Warm_Fluorescent_T8.py ;
            14_(6500K)_54W_Grow_Light_Fluorescent_T5-HO.py ;
            15_(3200K)_40W_Induction_Fluorescent.py ;
            16_(2100K)_150W_High_Pressure_Sodium.py ;
            17_(1700K)_135W_Low_Pressure_Sodium.py ;
            18_(6800K)_175W_Mercury_Vapor.py ;
            19_(5200K)_700W_Carbon_Arc.py ;
            20_(6500K)_15W_LED_Spot.py ;
            21_(2700K)_7W_OLED_Panel.py ;
            22_(30000K)_40W_Black_Light_Fluorescent.py ;
            23_(30000K)_40W_Black_Light_Bulb.py;
            24_(1850K)_Candle.py
    templates:
        abyss.pov ; biscuit.pov ; bsp_Tango.pov ; chess2.pov ;
        cornell.pov ; diffract.pov ; diffuse_back.pov ; float5 ;
        gamma_showcase.pov ; grenadine.pov ; isocacti.pov ;
        mediasky.pov ; patio-radio.pov ; subsurface.pov ; wallstucco.pov
"""


bl_info = {
    'name': "POV@Ble",
    'author': "Campbell Barton, "
              "Maurice Raybaud, "
              "Leonid Desyatkov, "
              "Bastien Montagne, "
              "Constantin Rahn, "
              "Silvio Falcinelli,"
              "Paco García",
    'version': (0, 1, 3),
    'blender': (3, 2, 0),
    'location': "Render Properties > Render Engine > Persistence of Vision",
    'description': "Persistence of Vision addon for Blender",
    'doc_url': "{BLENDER_MANUAL_URL}/addons/render/povray.html",
    'category': "Render",
    'warning': "Co-maintainers welcome",
}

# Other occasional contributors, more or less in chronological order:
# Luca Bonavita ; Shane Ambler ; Brendon Murphy ; Doug Hammond ;
# Thomas Dinges ; Jonathan Smith ; Sebastian Nell ; Philipp Oeser ;
# Sybren A. Stüvel ; Dalai Felinto ; Sergey Sharybin ; Brecht Van Lommel ;
# Stephen Leger ; Rune Morling ; Aaron Carlisle ; Ankit Meel ;

if "bpy" in locals():
    import importlib

    importlib.reload(ui_core)
    importlib.reload(nodes_properties)
    importlib.reload(nodes_gui)
    importlib.reload(nodes_fn)
    importlib.reload(nodes)
    importlib.reload(scenography_properties)
    importlib.reload(scenography_gui)
    importlib.reload(scenography)
    importlib.reload(render_properties)
    importlib.reload(render_gui)
    importlib.reload(render_core)
    importlib.reload(render)
    importlib.reload(shading_properties)
    importlib.reload(shading_ray_properties)
    importlib.reload(shading_gui)
    importlib.reload(shading)
    importlib.reload(texturing_procedural)
    importlib.reload(texturing_properties)
    importlib.reload(texturing_gui)
    importlib.reload(texturing)
    importlib.reload(model_properties)
    importlib.reload(model_gui)
    importlib.reload(model_all)
    importlib.reload(model_poly_topology)
    importlib.reload(model_meta_topology)
    importlib.reload(model_curve_topology)
    importlib.reload(model_primitives)
    importlib.reload(model_primitives_topology)
    importlib.reload(particles_properties)
    importlib.reload(particles)
    importlib.reload(scripting_properties)
    importlib.reload(scripting_gui)
    importlib.reload(scripting)
    importlib.reload(update_files)

else:
    import bpy
    from bpy.utils import register_class, unregister_class

    from bpy.props import StringProperty, BoolProperty, EnumProperty

    from . import (
        ui_core,
        render_properties,
        scenography_properties,
        nodes_properties,
        nodes_gui,
        nodes,
        shading_properties,
        shading_ray_properties,
        texturing_properties,
        model_properties,
        scripting_properties,
        render,
        render_core,
        model_primitives,
        model_primitives_topology,
        particles_properties,
        particles,
    )

# ---------------------------------------------------------------- #
# Auto update.
# ---------------------------------------------------------------- #


class POV_OT_update_addon(bpy.types.Operator):
    """Update this addon to the latest version"""

    bl_idname = "pov.update_addon"
    bl_label = "Update POV addon"

    def execute(self, context):
        import os
        import shutil
        import tempfile
        import urllib.error
        import urllib.request
        import zipfile

    def recursive_overwrite(self, src, dest, ignore=None):
        """Update the script automatically (along with other addons).

        Arguments:
            src -- path where to update from
            dest -- storing temporary download here
        Keyword Arguments:
            ignore -- leave some directories alone (default: {None})

        Returns:
            finished flag for operator which is a set()
        """
        if os.path.isdir(src):
            if not os.path.isdir(dest):
                os.makedirs(dest)
            files = os.listdir(src)
            ignored = ignore(src, files) if ignore is not None else set()
            unignored_files = (fle for fle in files if fle not in ignored)
            for f in unignored_files:
                source = os.path.join(src, f)
                destination = os.path.join(dest, f)
                recursive_overwrite(source, destination, ignore)
        else:
            shutil.copyfile(src, dest)

        print("-" * 20)
        print("Updating POV addon...")

        with tempfile.TemporaryDirectory() as temp_dir_path:
            temp_zip_path = os.path.join(temp_dir_path, "master.zip")

            # Download zip archive of latest addons master branch commit
            # More work needed so we also get files from the shared addons presets /pov folder
            # switch this URL back to the BF hosted one as soon as gitweb snapshot gets fixed
            url = "https://github.com/blender/blender-addons/archive/refs/heads/master.zip"
            try:
                print("Downloading", url)

                with urllib.request.urlopen(url, timeout=60) as url_handle, open(
                    temp_zip_path, "wb"
                ) as file_handle:
                    file_handle.write(url_handle.read())
            except urllib.error.URLError as err:
                self.report({"ERROR"}, "Could not download: %s" % err)

            # Extract the zip
            print("Extracting ZIP archive")
            with zipfile.ZipFile(temp_zip_path) as zip_archive:
                pov_addon_pkg = (member for member in zip_archive.namelist() if
                                  "blender-addons-master/render_povray" in member)
                for member in pov_addon_pkg:
                    # Remove the first directory and the filename
                    # e.g. blender-addons-master/render_povray/nodes.py
                    # becomes render_povray/nodes.py
                    target_path = os.path.join(
                        temp_dir_path, os.path.join(*member.split("/")[1:-1])
                    )

                    filename = os.path.basename(member)
                    # Skip directories
                    if not filename:
                        continue

                    # Create the target directory if necessary
                    if not os.path.exists(target_path):
                        os.makedirs(target_path)

                    source = zip_archive.open(member)
                    target = open(os.path.join(target_path, filename), "wb")

                    with source, target:
                        shutil.copyfileobj(source, target)
                        print("copying", source, "to", target)

            extracted_render_povray_path = os.path.join(temp_dir_path, "render_povray")

            if not os.path.exists(extracted_render_povray_path):
                self.report({"ERROR"}, "Could not extract ZIP archive! Aborting.")
                return {"FINISHED"}

            # Find the old POV addon files
            render_povray_dir = os.path.abspath(os.path.dirname(__file__)) # Unnecessary abspath?
            print("POV addon addon folder:", render_povray_dir)

            # TODO: Create backup

            # Delete old POV addon files
            # (only directories and *.py files, user might have other stuff in there!)
            print("Deleting old POV addon files")
            # remove __init__.py
            os.remove(os.path.join(render_povray_dir, "__init__.py"))
            # remove all folders
            dir_names = 1
            for directory in next(os.walk(render_povray_dir))[dir_names]:
                shutil.rmtree(os.path.join(render_povray_dir, directory))

            print("Copying new POV addon files")
            # copy new POV addon files
            # copy __init__.py
            shutil.copy2(
                os.path.join(extracted_render_povray_path, "__init__.py"),
                render_povray_dir,
            )
            # copy all folders
            recursive_overwrite(extracted_render_povray_path, render_povray_dir)

        bpy.ops.preferences.addon_refresh()
        print("POV addon update finished, restart Blender for the changes to take effect.")
        print("-" * 20)
        self.report({"WARNING"}, "Restart Blender!")
        return {"FINISHED"}


# ---------------------------------------------------------------- #
# Povray Preferences.
# ---------------------------------------------------------------- #


class PovPreferences(bpy.types.AddonPreferences):
    """Declare preference variables to set up POV binary."""

    bl_idname = __name__

    branch_feature_set_povray: EnumProperty(
        name="Feature Set",
        description="Choose between official (POV-Ray) or (UberPOV) "
        "development branch features to write in the pov file",
        items=(
            ("povray", "Official POV-Ray", "", "PLUGIN", 0),
            ("uberpov", "Unofficial UberPOV", "", "PLUGIN", 1),
        ),
        default="povray",
    )

    filepath_povray: StringProperty(
        name="Binary Location", description="Path to renderer executable", subtype="FILE_PATH"
    )

    docpath_povray: StringProperty(
        name="Includes Location", description="Path to Insert Menu files", subtype="FILE_PATH",
        default="",
    )

    use_sounds: BoolProperty(
        name="Use Sound",
        description="Signaling end of the render process at various"
        "stages can help if you're away from monitor",
        default=False,
    )

    # TODO: Auto find POV sound directory as it does for binary
    # And implement the three cases, left uncommented for a dummy
    # interface in case some doc screenshots get made for that area
    filepath_complete_sound: StringProperty(
        name="Finish Render Sound",
        description="Path to finished render sound file",
        subtype="FILE_PATH",
    )

    filepath_parse_error_sound: StringProperty(
        name="Parse Error Sound",
        description="Path to parsing time error sound file",
        subtype="FILE_PATH",
    )

    filepath_cancel_sound: StringProperty(
        name="Cancel Render Sound",
        description="Path to cancelled or render time error sound file",
        subtype='FILE_PATH',
    )

    def draw(self, context):
        layout = self.layout
        layout.prop(self, 'branch_feature_set_povray')
        layout.prop(self, 'filepath_povray')
        layout.prop(self, 'docpath_povray')
        layout.prop(self, 'filepath_complete_sound')
        layout.prop(self, 'filepath_parse_error_sound')
        layout.prop(self, 'filepath_cancel_sound')
        layout.prop(self, 'use_sounds', icon='SOUND')
        layout.operator('pov.update_addon', icon='FILE_REFRESH')
        layout.operator("wm.url_open", text="Community",icon='EVENT_F').url = \
            "https://www.facebook.com/povable"


classes = (
    POV_OT_update_addon,
    PovPreferences,
)


def register():
    for cls in classes:
        register_class(cls)

    render_properties.register()
    scenography_properties.register()
    shading_properties.register()
    shading_ray_properties.register()
    texturing_properties.register()
    model_properties.register()
    particles_properties.register()
    scripting_properties.register()
    nodes_properties.register()
    nodes_gui.register()
    render.register()
    render_core.register()
    ui_core.register()
    model_primitives_topology.register()
    model_primitives.register()


def unregister():
    model_primitives.unregister()
    model_primitives_topology.unregister()
    ui_core.unregister()
    render_core.unregister()
    render.unregister()
    nodes_gui.unregister()
    nodes_properties.unregister()
    scripting_properties.unregister()
    particles_properties.unregister()
    model_properties.unregister()
    texturing_properties.unregister()
    shading_ray_properties.unregister()
    shading_properties.unregister()
    scenography_properties.unregister()
    render_properties.unregister()

    for cls in reversed(classes):
        unregister_class(cls)


if __name__ == '__main__':
    register()

# ------------8<---------[ BREAKPOINT ]--------------8<----------- #  Move this snippet around
# __import__('code').interact(local=dict(globals(), **locals()))   # < and uncomment this line
# ---------------------------------------------------------------- #  to inspect from Terminal