import bpy
import os
from os import listdir, scandir
from os.path import join, dirname, basename, exists, isfile, isdir, splitext
import re, fnmatch, glob
from pathlib import Path
from time import strftime
C = bpy.context
D = bpy.data

from .utils import open_file, open_folder, get_addon_prefs

exclude = (
### add lines here to exclude specific attribute
'bl_rna', 'identifier','name_property','rna_type','properties', 'compare', 'to_string',#basic
)

""" 
        rd_keep = [
            "resolution_percentage",
            "resolution_x",
            "resolution_y",
            "filepath",
            "use_stamp",
            "stamp_font_size",
            ]
        im_keep = [
            'file_format',
            'color_mode',
            'quality',
            'compression',
            ]
        ff_keep = [
            'codec',
            'format',
            'constant_rate_factor',
            'ffmpeg_preset',
            'gopsize',
            'audio_codec',
            'audio_bitrate',
            ]
 """

'''
def render_with_restore():
    class RenderFileRestorer:
        rd = bpy.context.scene.render
        im = rd.image_settings
        ff = rd.ffmpeg
        # ffmpeg (ff) need to be before image_settings(im) in list
        # otherwise __exit__ may try to restore settings of image mode in video mode !
        # ex : "RGBA" not found in ('BW', 'RGB') (will still not stop thx to try block)

        zones = [rd, ff, im]
        
        val_dic = {}
        cam = bpy.context.scene.camera
        def __enter__(self):
            ## store attribute of data_path in self.zones list.
            for data_path in self.zones:
                self.val_dic[data_path] = {}
                for attr in dir(data_path):#iterate in attribute of given datapath
                    if attr not in exclude and not attr.startswith('__') and not callable(getattr(data_path, attr)) and not data_path.is_property_readonly(attr):
                        self.val_dic[data_path][attr] = getattr(data_path, attr)
            
            if self.cam and self.cam.name == 'draw_cam':
                if self.cam.parent:
                    bpy.context.scene.camera = self.cam.parent

        def __exit__(self, type, value, traceback):
            ## restore attribute from self.zones list
            for data_path, prop_dic in self.val_dic.items():
                for attr, val in prop_dic.items():
                    try:
                        setattr(data_path, attr, val)
                    except Exception as e:
                        print(f"/!\ Impossible to re-assign: {attr} = {val}")
                        print(e)

            if self.cam:
                bpy.context.scene.camera = self.cam


    return RenderFileRestorer()
'''


class render_with_restore:
    def __init__(self):
        rd = bpy.context.scene.render
        im = rd.image_settings
        ff = rd.ffmpeg
        # ffmpeg (ff) need to be before image_settings(im) in list
        # otherwise __exit__ may try to restore settings of image mode in video mode !
        # ex : "RGBA" not found in ('BW', 'RGB') (will still not stop thx to try block)
        self.zones = [rd, ff, im]
        
        self.val_dic = {}
        self.cam = bpy.context.scene.camera

    def __enter__(self):
        ## store attribute of data_path in self.zones list.
        for data_path in self.zones:
            self.val_dic[data_path] = {}
            for attr in dir(data_path):#iterate in attribute of given datapath
                if attr not in exclude and not attr.startswith('__') and not callable(getattr(data_path, attr)) and not data_path.is_property_readonly(attr):
                    self.val_dic[data_path][attr] = getattr(data_path, attr)
        
        if self.cam and self.cam.name == 'draw_cam':
            if self.cam.parent:
                bpy.context.scene.camera = self.cam.parent

    def __exit__(self, type, value, traceback):
        ## restore attribute from self.zones list
        for data_path, prop_dic in self.val_dic.items():
            for attr, val in prop_dic.items():
                try:
                    setattr(data_path, attr, val)
                except Exception as e:
                    print(f"/!\ Impossible to re-assign: {attr} = {val}")
                    print(e)

        if self.cam:
            bpy.context.scene.camera = self.cam

def playblast(viewport = False, stamping = True):
    scn = bpy.context.scene
    res_factor = scn.gptoolprops.resolution_percentage
    playblast_path = get_addon_prefs().playblast_path
    rd = scn.render
    ff = rd.ffmpeg
    with render_with_restore():
        ### can add propeties for personalisation as toolsetting props

        rd.resolution_percentage = res_factor
        while ( rd.resolution_x * res_factor / 100 ) % 2 != 0: # rd.resolution_percentage
            rd.resolution_x = rd.resolution_x + 1
        while ( rd.resolution_y * res_factor / 100 ) % 2 != 0: # rd.resolution_percentage
            rd.resolution_y = rd.resolution_y + 1 

        rd.image_settings.file_format = 'FFMPEG'
        ff.format = 'MPEG4'
        ff.codec = 'H264'
        ff.constant_rate_factor = 'HIGH' # MEDIUM
        ff.ffmpeg_preset = 'REALTIME'
        ff.gopsize = 10
        ff.audio_codec = 'AAC'
        ff.audio_bitrate = 128
        rd.use_sequencer = False
        rd.stamp_background = (0.0, 0.0, 0.0, 0.75)# blacker notes BG (default 0.25)
        # rd.use_compositing

        # rd.filepath = join(dirname(bpy.data.filepath), basename(bpy.data.filepath))
        # rd.frame_path(frame=0, preview=0, view="_sauce")## give absolute render filepath with some suffix
        # rd.is_movie_format# check if its movie mode

        ## set filepath
        # mode incermental or just use fulldate (cannot create conflict and filter OK but long name)
        blend = Path(bpy.data.filepath)
        date_format = "%Y-%m-%d_%H-%M-%S"

        ## old direct place
        # fp = join(blend.parent, "playblast", f'playblast_{blend.stem}_{strftime(date_format)}.mp4')

        fp = Path(bpy.path.abspath(playblast_path)).resolve() / f'playblast_{blend.stem}_{strftime(date_format)}.mp4'
        fp = str(fp)

        #may need a properties for choosing location : bpy.types.Scene.qrd_savepath = bpy.props.StringProperty(subtype='DIR_PATH', description="Export location, if not specify, create a 'quick_render' directory aside blend location")#(change defaut name in user_prefernece)
        rd.filepath = fp
        rd.use_stamp = stamping# toolsetting.use_stamp# True for playblast
        #stamp options
        rd.stamp_font_size = int(rd.stamp_font_size * res_factor / 100) # rd.resolution_percentage
        
        # bpy.ops.render.render_wrap(use_view=viewport)
        ### render
        if viewport:## openGL
            bpy.ops.render.opengl(animation=True, view_context=True)# 'INVOKE_DEFAULT', 

        else:## normal render
            bpy.ops.render.render(animation=True)# 'INVOKE_DEFAULT', 

    # print("Playblast Done :", fp)#Dbg
    return fp


class PBLAST_OT_playblast_anim(bpy.types.Operator):
    bl_idname = "render.playblast_anim"
    bl_label = "Playblast anim"
    bl_description = "Launch animation playblast, use resolution percentage (Lock blender during process)"
    bl_options = {"REGISTER"}

    use_view : bpy.props.BoolProperty(name='use_view', default=False)

    def execute(self, context):
        if not bpy.data.is_saved:
            self.report({'ERROR'}, 'File is not saved, Playblast cancelled')
            return {"CANCELLED"}
        

        fp = playblast(viewport = self.use_view, stamping = True)
        if fp:
            self.report({'INFO'}, f'File saved at: {fp}')
            addon_prefs = get_addon_prefs()
            if addon_prefs:
                if addon_prefs.playblast_auto_play:
                    open_file(fp)
                if addon_prefs.playblast_auto_open_folder:
                    open_folder(dirname(fp))

        return {"FINISHED"}

def register():
    bpy.utils.register_class(PBLAST_OT_playblast_anim)

def unregister():
    bpy.utils.unregister_class(PBLAST_OT_playblast_anim)

'''
## Potential cancelling method for image sequence rendering.
for cfra in range(start, end+1):
    print("Baking frame " + str(cfra))

    # update scene to new frame and bake to template image
    scene.frame_set(cfra)
    ret = bpy.ops.object.bake_image()
    if 'CANCELLED' in ret:
        return {'CANCELLED'}
'''

""" 
class PBLAST_OT_render_wrap(bpy.types.Operator):
    bl_idname = "render.render_wrap"
    bl_label = "Render wraped"
    bl_description = "render"
    bl_options = {"REGISTER"}## need hide

    use_view : bpy.props.BoolProperty(name='use_view', default=False)

    def execute(self, context):
        if self.use_view:## openGL
            ret = bpy.ops.render.opengl('INVOKE_DEFAULT', animation=True, view_context=True)
        else:## normal render
            ret = bpy.ops.render.render('INVOKE_DEFAULT', animation=True)
        return {"FINISHED"}
 """

""" if __name__ == "__main__":
    register() """