import bpy
import sys
import subprocess
from . import fn
from pathlib import Path
from itertools import groupby
from pprint import pprint as pp
from time import time, strftime

def export_all_selected_frame_as_svg():
    '''Export All frames (only where there is a frame) of selected layer as svg'''
    ### Export operator parameters description
    # use_fill (boolean, (optional)) – Fill, Export strokes with fill enabled

    # selected_object_type (enum in ['ACTIVE', 'SELECTED', 'VISIBLE'], (optional)) –

    # Object, Which objects to include in the export
    #     ACTIVE Active, Include only the active object.
    #     SELECTED Selected, Include selected objects.
    #     VISIBLE Visible, Include all visible objects.
    #
    # stroke_sample (float in [0, 100], (optional)) – Sampling, Precision of stroke sampling. Low values mean a more precise result, and zero disables sampling
    # use_normalized_thickness (boolean, (optional)) – Normalize, Export strokes with constant thickness
    # use_clip_camera (boolean, (optional)) – Clip Camera, Clip drawings to camera size when export in camera view


    ## Write an ouput name (folder and image will use this name)
    ## if left empty, will use name of active object with 'svg_' prefix
    name = ''
    only_frames = 1 # put 0 to export whole frame range
    ## ----

    o = bpy.context.object
    assert o.type == 'GPENCIL', 'Active object should be GP'

    if only_frames:
        frames = []
        for ob in bpy.context.selected_objects:
            if ob.type != 'GPENCIL':
                continue
            frames += [f.frame_number for l in ob.data.layers if not l.hide for f in l.frames if len(f.strokes)]

        if frames:
            frames = sorted(list(set(frames)))
    else:
        frames = [f for f in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1)]

    print(len(frames), 'frames to export')
    pass_name = name if name else f'svg_{o.name}'


    blend = Path(bpy.data.filepath)

    for fnum in frames:
        out = f'{pass_name}_{fnum:04d}.svg'
        print(out)
        folder = blend.parent / 'render' / pass_name
        folder.mkdir(parents=True, exist_ok=True)

        fp =  folder / out
        if fp.exists():
            print(f'  already exists: {fp}')
            continue

        bpy.context.scene.frame_current = fnum
        bpy.ops.wm.gpencil_export_svg(filepath=str(fp),
        check_existing=True,
        use_fill=True, selected_object_type='SELECTED', # ACTIVE, VISIBLE
        stroke_sample=0.0,
        use_normalized_thickness=False,
        use_clip_camera=True) # False by defaut

    print('Done')


def pdf_render(fp, multi_page=False):
    '''Render a sequence of pdf and return path to folder
    or render a multipage pdf (in place of parent folder) and return path to file
    '''
    scn = bpy.context.scene

    if multi_page:
        ## Set folder name (when using sequence) as output with pdf extension
        fp = fp.parent
    
    fp.parent.mkdir(parents=True, exist_ok=True) # mode=0o777

    ## Single pdf
    if multi_page:
        ## In case of sequence path:
        ## Remove padding separator (or add scene range: "0100-0230" ?)
        # fp = fp.with_stem(fp.stem.rstrip('-_. ')) # strip typical padding separator
        
        fp = fp.with_suffix('.pdf')

        # print('Exporting multi-page pdf at: ', fp)
        bpy.ops.wm.gpencil_export_pdf(filepath=str(fp),
        check_existing=False, # True by default
        use_fill=True,
        selected_object_type='ACTIVE', # VISIBLE, SELECTED
        stroke_sample=0,
        use_normalized_thickness=False,
        frame_mode='SCENE')
        return fp

    ## Sequence
    for fnum in range(scn.frame_start, scn.frame_end + 1):
        # print('fnum: ', fnum)
        scn.frame_current = fnum
        # bpy.ops.wm.gpencil_export_svg(filepath=str(fp),
        # check_existing=True,
        # use_fill=True, selected_object_type='SELECTED', # ACTIVE, VISIBLE
        # stroke_sample=0.0,
        # use_normalized_thickness=False,
        # use_clip_camera=True) # False by defaut

        # bpy.ops.wm.gpencil_export_pdf(filepath=f'{bpy.path.abspath(str(fp)).rstrip("/")}{fnum:04d}.pdf',
        bpy.ops.wm.gpencil_export_pdf(filepath=f'{fp}{fnum:04d}.pdf',
        check_existing=False, # True by default
        use_fill=True,
        selected_object_type='ACTIVE', # VISIBLE, SELECTED
        stroke_sample=0,
        use_normalized_thickness=False,
        frame_mode='ACTIVE')
    return fp.parent

def swf_render(fp):
    '''
    Render as pdf then convert to swf using pdf2swf

    -h , --help                    Print short help message and exit
    -V , --version                 Print version info and exit
    -o , --output file.swf         Direct output to file.swf. If file.swf contains '%' (file%.swf), then each page goes to a separate file.
    -p , --pages range             Convert only pages in range with range e.g. 1-20 or 1,4,6,9-11 or
    -P , --password password       Use password for deciphering the pdf.
    -v , --verbose                 Be verbose. Use more than one -v for greater effect.
    -z , --zlib                    Use Flash 6 (MX) zlib compression.
    -i , --ignore                  Allows pdf2swf to change the draw order of the pdf. This may make the generated
    -j , --jpegquality quality     Set quality of embedded jpeg pictures to quality. 0 is worst (small), 100 is best (big). (default:85)
    -s , --set param=value         Set a SWF encoder specific parameter.  See pdf2swf -s help for more information.
    -w , --samewindow              When converting pdf hyperlinks, don't make the links open a new window. 
    -t , --stop                    Insert a stop() command in each page. 
    -T , --flashversion num        Set Flash Version in the SWF header to num.
    -F , --fontdir directory       Add directory to the font search path.
    -b , --defaultviewer           Link a standard viewer to the swf file. 
    -l , --defaultloader           Link a standard preloader to the swf file which will be displayed while the main swf is loading.
    -B , --viewer filename         Link viewer filename to the swf file. 
    -L , --preloader filename      Link preloader filename to the swf file. 
    -q , --quiet                   Suppress normal messages.  Use -qq to suppress warnings, also.
    -S , --shapes                  Don't use SWF Fonts, but store everything as shape.
    -f , --fonts                   Store full fonts in SWF. (Don't reduce to used characters).
    -G , --flatten                 Remove as many clip layers from file as possible. 
    -I , --info                    Don't do actual conversion, just display a list of all pages in the PDF.
    -Q , --maxtime n               Abort conversion after n seconds. Only available on Unix.

    ##with -s arg:
    PDF Parameters:

    PDF device global parameters:
    fontdir=<dir>     a directory with additional fonts
    font=<filename>   an additional font filename
    pages=<range>     the range of pages to convert (example: pages=1-100,210-)
    zoom=<dpi>        the resultion (default: 72)
    languagedir=<dir> Add an xpdf language directory
    multiply=<times>  Render everything at <times> the resolution
    poly2bitmap       Convert graphics to bitmaps
    bitmap            Convert everything to bitmaps
    SWF Parameters:

    SWF layer options:
    jpegsubpixels=<pixels>      resolution adjustment for jpeg images (same as jpegdpi, but in pixels)
    ppmsubpixels=<pixels        resolution adjustment for  lossless images (same as ppmdpi, but in pixels)
    subpixels=<pixels>          shortcut for setting both jpegsubpixels and ppmsubpixels
    drawonlyshapes              convert everything to shapes (currently broken)
    ignoredraworder             allow to perform a few optimizations for creating smaller SWFs
    linksopennewwindow          make links open a new browser window
    linktarget                  target window name of new links
    linkcolor=<color)           color of links (format: RRGGBBAA)
    linknameurl		    Link buttons will be named like the URL they refer to (handy for iterating through links with actionscript)
    storeallcharacters          don't reduce the fonts to used characters in the output file
    enablezlib                  switch on zlib compression (also done if flashversion>=6)
    bboxvars                    store the bounding box of the SWF file in actionscript variables
    dots                        Take care to handle dots correctly
    reordertags=0/1             (default: 1) perform some tag optimizations
    internallinkfunction=<name> when the user clicks a internal link (to a different page) in the converted file, this actionscript function is called
    externallinkfunction=<name> when the user clicks an external link (e.g. http://www.foo.bar/) on the converted file, this actionscript function is called
    disable_polygon_conversion  never convert strokes to polygons (will remove capstyles and joint styles)
    caplinewidth=<width>        the minimum thichness a line needs to have so that capstyles become visible (and are converted)
    insertstop                  put an ActionScript "STOP" tag in every frame
    protect                     add a "protect" tag to the file, to prevent loading in the Flash editor
    flashversion=<version>      the SWF fileversion (6)
    framerate=<fps>		    SWF framerate
    minlinewidth=<width>        convert horizontal/vertical boxes smaller than this width to lines (0.05) 
    simpleviewer                Add next/previous buttons to the SWF
    animate                     insert a showframe tag after each placeobject (animate draw order of PDF files)
    jpegquality=<quality>       set compression quality of jpeg images
    splinequality=<value>       Set the quality of spline convertion to value (0-100, default: 100).
    disablelinks                Disable links.
    '''

    # Convert pdf to swf
    if sys.platform == 'linux':
        binary = Path(__file__).parent / 'bin' / 'linux' / 'pdf2swf'
    elif sys.platform.startswith('win'):
        binary = Path(__file__).parent / 'bin' / 'windows' / 'pdf2swf.exe'
    else:
        print('Render to SWF is only supported on mat and linux')
        return

    pdf_fp = pdf_render(fp, multi_page=True)

    swf_fp = pdf_fp.with_suffix('.swf')
    cmd = [
        str(binary),
        '-s', f'framerate={bpy.context.scene.render.fps}',
        # -z, # use zlib
        str(pdf_fp),
        # '-o', # seems like there is no need to add output flag
        str(swf_fp)
    ]
    subprocess.call(cmd)
    
    # Remove single pdf file
    pdf_fp.unlink()
    return swf_fp

class GPEXP_OT_export_as_pdf(bpy.types.Operator):
    bl_idname = "gp.export_as_pdf"
    bl_label = "export_as_pdf"
    bl_description = "Export current layers as pdf"
    bl_options = {"REGISTER"}

    @classmethod
    def poll(cls, context):
        return True

    export_type : bpy.props.StringProperty(name='Export Type', default='pdf_sequence')

    @classmethod
    def description(cls, context, properties) -> str:
        if properties.export_type == 'pdf':
            return 'Export layers as individual pdf file (multi-page) using scene range'
        elif properties.export_type == 'pdf_sequence':
            return 'Export layers as sequence of single-paged pdf file, using scene range'
        return 'Export layers as individual SWF files, using scene range'

    def execute(self, context):
        ### store
        ## dict all visible objects as key with value : sub dict {layer : hide_bool}

        # obj_vis = [[o, o.hide_viewport, o.hide_render] for o in context.scene.objects if o.type == 'GPENCIL' and not (o.hide_get() or o.hide_viewport)]
        t0 = time()

        store = {o: {l: l.hide for l in o.data.layers} for o in context.scene.objects if o.type == 'GPENCIL' and not (o.hide_get() or o.hide_viewport)}
        # pp(store)
        act = context.object if context.object else None
        selection = [o for o in context.selected_objects]

        messages = []

        ## adaptative resampling on all concerned objects
        for ob in store.keys():
            mod = ob.grease_pencil_modifiers.get('resample')
            if not mod:
                mod = ob.grease_pencil_modifiers.new('resample', 'GP_SIMPLIFY')
            mod.mode = 'ADAPTIVE'
            mod.factor = 0.001

        # for ob in context.scene.objects:
        for ob in store.keys():
            if ob.type != 'GPENCIL':
                continue

            mess = f'--- {ob.name}:'
            print(mess)
            messages.append(mess)

            ## swap hide other GP object (or just swap select)
            # for so in store.keys():
            #     so.hide_viewport = True
            # ob.hide_viewport = False

            context.view_layer.objects.active = ob # render active only mode
            # for o in context.scene.objects:
            #     o.hide_viewport = True
            # ob.hide_viewport = False

            ## manage layers
            gpl = ob.data.layers
            vl_dicts = {vl_name: list(layer_grp) for vl_name, layer_grp in groupby(gpl, lambda x: x.viewlayer_render)}
            for vl_name, layer_list in vl_dicts.items():
                vl = context.scene.view_layers.get(vl_name)
                if not vl:
                    mess = f'/!\ {vl_name} viewlayer not exists : skipped {[l.info for l in layer_list]}'
                    print(mess)
                    messages.append(mess)
                    continue
                if vl_name in {'exclude', 'View Layer'}:
                    continue

                if not vl.use:
                    mess = f'{vl_name} viewlayer disabled'
                    print(mess)
                    messages.append(mess)
                    continue


                # Case of initially masked layer !
                hide_ct = 0
                total = len(layer_list)
                for l in layer_list:
                    if store[ob][l]: # get original hide bool
                        hide_ct += 1

                if hide_ct == total:
                    mess = f'/!\ Skip {vl_name}: {hide_ct}/{total} are hided'
                    print(mess)
                    messages.append(mess)
                    continue
                elif hide_ct > 0:
                    mess = f'Warning: {vl_name}: {hide_ct}/{total} are hided'
                    print(mess)
                    messages.append(mess)

                # check connections in compositor
                ng_name = f'NG_{ob.name}'
                ng = context.scene.node_tree.nodes.get(ng_name)
                if not ng:
                    mess = f'Skip {vl_name}: Not found nodegroup {ng_name}'
                    print(mess)
                    messages.append(mess)
                    continue

                ng_socket = ng.outputs.get(vl_name)

                if not ng_socket:
                    mess = f'Skip {vl_name}: Not found in nodegroup {ng_name} sockets'
                    print(mess)
                    messages.append(mess)
                    continue

                if not len(ng_socket.links):
                    mess = f' socket is disconnected in {ng_name} nodegroup'
                    print(mess)
                    messages.append(mess)
                    continue

                fo_node = ng_socket.links[0].to_node
                fo_socket = ng_socket.links[0].to_socket

                if fo_node.type != 'OUTPUT_FILE':
                    mess = f'Skip {vl_name}: node is not an output_file {fo_node.name}'
                    print(mess)
                    messages.append(mess)
                    continue

                if fo_node.mute:
                    mess = f'Skip {vl_name}: output is muted {fo_node.name}'
                    print(mess)
                    messages.append(mess)
                    continue

                # fo_socket.name isn't right

                idx = [i for i in fo_node.inputs].index(fo_socket)

                subpath = fo_node.file_slots[idx].path
                fp = Path(fo_node.base_path.rstrip('/')) / subpath
                fp = Path(bpy.path.abspath(str(fp)).rstrip("/"))

                print(f'render {total} layers at: {fp.parent}') #Dbg

                # hide all layer that are: not associated with VL (not in layer_list) or hided initially (store[ob][l])
                for l in gpl:
                    l.hide = l not in layer_list or store[ob][l]

                for l in gpl:
                    if not l.hide:
                        print(f'-> {l.info}') #Dbg

                if self.export_type == 'pdf_sequence':
                    pdf_render(fp, multi_page=False)
                elif self.export_type == 'pdf':
                    ## TODO: change fp to parent and adapt in pdf render ?
                    pdf_render(fp, multi_page=True)
                elif self.export_type == 'swf':
                    swf_render(fp)

                print()

        ### restore
        for ob, layer_dic in store.items():
            # ob.hide_viewport = False # no need
            for l, h in layer_dic.items():
                l.hide = h

        for o in selection:
            o.select_set(True)
        if act:
            context.view_layer.objects.active = act

        # for oviz in obj_vis:
        #     oviz[0].hide_viewport = oviz[1]

        print(f'Done ({time()-t0:.1f}s)')
        # self.report({'INFO'}, f'Done ({time()-t0:.1f}s)')
        fn.show_message_box(_message=messages, _title=f'PDF render report ({time()-t0:.1f}s)')
        return {"FINISHED"}


classes=(
GPEXP_OT_export_as_pdf,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
