# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import bpy
import sys, os
import http, http.client, http.server, urllib
import subprocess, shutil, time, hashlib

import netrender
import netrender.slave as slave
import netrender.master as master

from netrender.utils import *

VERSION = b"0.3"

PATH_PREFIX = "/tmp/"

QUEUED = 0
DISPATCHED = 1
DONE = 2
ERROR = 3

LAST_ADDRESS_TEST = 0
ADDRESS_TEST_TIMEOUT = 30

def base_poll(cls, context):
    rd = context.scene.render
    return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
    

def init_file():
    if netrender.init_file != bpy.data.filepath:
        netrender.init_file = bpy.data.filepath
        netrender.init_data = True
        netrender.valid_address = False

def init_data(netsettings):
    init_file()

    if netrender.init_data:
        netrender.init_data = False

        netsettings.active_slave_index = 0
        while(len(netsettings.slaves) > 0):
            netsettings.slaves.remove(0)

        netsettings.active_blacklisted_slave_index = 0
        while(len(netsettings.slaves_blacklist) > 0):
            netsettings.slaves_blacklist.remove(0)

        netsettings.active_job_index = 0
        while(len(netsettings.jobs) > 0):
            netsettings.jobs.remove(0)

def verify_address(netsettings, force=False):
    global LAST_ADDRESS_TEST
    init_file()

    if force or LAST_ADDRESS_TEST + ADDRESS_TEST_TIMEOUT < time.time():
        LAST_ADDRESS_TEST = time.time()

        try:
            conn = clientConnection(netsettings.server_address, netsettings.server_port, scan = False, timeout = 1)
        except:
            conn = None

        if conn:
            netrender.valid_address = True
            conn.close()
        else:
            netrender.valid_address = False
            
    return netrender.valid_address

class NeedValidAddress():
    @classmethod
    def poll(cls, context):
        return super().poll(context) and verify_address(context.scene.network_render)

class NetRenderButtonsPanel():
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_context = "render"
    # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
    
    @classmethod
    def poll(cls, context):
        rd = context.scene.render
        return rd.engine == 'NET_RENDER' and rd.use_game_engine == False 

# Setting panel, use in the scene for now.
class RENDER_PT_network_settings(NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "Network Settings"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        return super().poll(context)

    def draw(self, context):
        layout = self.layout

        netsettings = context.scene.network_render

        verify_address(netsettings)

        layout.prop(netsettings, "mode", expand=True)

        if netsettings.mode in ("RENDER_MASTER", "RENDER_SLAVE"):
            layout.operator("render.netclientstart", icon='PLAY')

        layout.prop(netsettings, "path")

        split = layout.split(percentage=0.7)

        col = split.column()
        col.label(text="Server Address:")
        col.prop(netsettings, "server_address", text="")

        col = split.column()
        col.label(text="Port:")
        col.prop(netsettings, "server_port", text="")

        if netsettings.mode != "RENDER_MASTER":
            layout.operator("render.netclientscan", icon='FILE_REFRESH', text="")
            
        if not netrender.valid_address:
            layout.label(text="No master at specified address")

        layout.operator("render.netclientweb", icon='QUESTION')

class RENDER_PT_network_slave_settings(NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "Slave Settings"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        scene = context.scene
        return super().poll(context) and scene.network_render.mode == "RENDER_SLAVE"

    def draw(self, context):
        layout = self.layout

        rd = context.scene.render
        netsettings = context.scene.network_render

        layout.prop(netsettings, "use_slave_clear")
        layout.prop(netsettings, "use_slave_thumb")
        layout.prop(netsettings, "use_slave_output_log")
        layout.label(text="Threads:")
        layout.prop(rd, "threads_mode", expand=True)
        
        col = layout.column()
        col.enabled = rd.threads_mode == 'FIXED'
        col.prop(rd, "threads")

class RENDER_PT_network_master_settings(NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "Master Settings"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        scene = context.scene
        return super().poll(context) and scene.network_render.mode == "RENDER_MASTER"

    def draw(self, context):
        layout = self.layout

        netsettings = context.scene.network_render

        layout.prop(netsettings, "use_master_broadcast")
        layout.prop(netsettings, "use_master_clear")

class RENDER_PT_network_job(NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "Job Settings"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        scene = context.scene
        return super().poll(context) and scene.network_render.mode == "RENDER_CLIENT"

    def draw(self, context):
        layout = self.layout

        netsettings = context.scene.network_render

        verify_address(netsettings)

        if netsettings.server_address != "[default]":
            layout.operator("render.netclientanim", icon='RENDER_ANIMATION')
            layout.operator("render.netclientsend", icon='FILE_BLEND')
            layout.operator("render.netclientsendframe", icon='RENDER_STILL')
            if netsettings.job_id:
                row = layout.row()
                row.operator("render.render", text="Get Image", icon='RENDER_STILL')
                row.operator("render.render", text="Get Animation", icon='RENDER_ANIMATION').animation = True

        split = layout.split(percentage=0.3)

        col = split.column()
        col.label(text="Type:")
        col.label(text="Name:")
        col.label(text="Category:")

        col = split.column()
        col.prop(netsettings, "job_type", text="")
        col.prop(netsettings, "job_name", text="")
        col.prop(netsettings, "job_category", text="")

        row = layout.row()
        row.prop(netsettings, "priority")
        row.prop(netsettings, "chunks")

class RENDER_PT_network_job_vcs(NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "VCS Job Settings"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        scene = context.scene
        return (super().poll(context)
            and scene.network_render.mode == "RENDER_CLIENT"
            and scene.network_render.job_type == "JOB_VCS")

    def draw(self, context):
        layout = self.layout

        netsettings = context.scene.network_render

        layout.operator("render.netclientvcsguess", icon='FILE_REFRESH', text="")

        layout.prop(netsettings, "vcs_system")
        layout.prop(netsettings, "vcs_revision")
        layout.prop(netsettings, "vcs_rpath")
        layout.prop(netsettings, "vcs_wpath")

class RENDER_PT_network_slaves(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "Slaves Status"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        netsettings = context.scene.network_render
        return super().poll(context) and netsettings.mode == "RENDER_CLIENT"

    def draw(self, context):
        layout = self.layout

        netsettings = context.scene.network_render

        row = layout.row()
        row.template_list(netsettings, "slaves", netsettings, "active_slave_index", rows=2)

        sub = row.column(align=True)
        sub.operator("render.netclientslaves", icon='FILE_REFRESH', text="")
        sub.operator("render.netclientblacklistslave", icon='ZOOMOUT', text="")

        if len(netrender.slaves) > netsettings.active_slave_index >= 0:
            layout.separator()

            slave = netrender.slaves[netsettings.active_slave_index]

            layout.label(text="Name: " + slave.name)
            layout.label(text="Address: " + slave.address[0])
            layout.label(text="Seen: " + time.ctime(slave.last_seen))
            layout.label(text="Stats: " + slave.stats)

class RENDER_PT_network_slaves_blacklist(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "Slaves Blacklist"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        netsettings = context.scene.network_render
        return super().poll(context) and netsettings.mode == "RENDER_CLIENT"

    def draw(self, context):
        layout = self.layout

        netsettings = context.scene.network_render

        row = layout.row()
        row.template_list(netsettings, "slaves_blacklist", netsettings, "active_blacklisted_slave_index", rows=2)

        sub = row.column(align=True)
        sub.operator("render.netclientwhitelistslave", icon='ZOOMOUT', text="")

        if len(netrender.blacklist) > netsettings.active_blacklisted_slave_index >= 0:
            layout.separator()

            slave = netrender.blacklist[netsettings.active_blacklisted_slave_index]

            layout.label(text="Name: " + slave.name)
            layout.label(text="Address: " + slave.address[0])
            layout.label(text="Seen: " + time.ctime(slave.last_seen))
            layout.label(text="Stats: " + slave.stats)

class RENDER_PT_network_jobs(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "Jobs"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        netsettings = context.scene.network_render
        return super().poll(context) and netsettings.mode == "RENDER_CLIENT"

    def draw(self, context):
        layout = self.layout

        netsettings = context.scene.network_render

        row = layout.row()
        row.template_list(netsettings, "jobs", netsettings, "active_job_index", rows=2)

        sub = row.column(align=True)
        sub.operator("render.netclientstatus", icon='FILE_REFRESH', text="")
        sub.operator("render.netclientcancel", icon='ZOOMOUT', text="")
        sub.operator("render.netclientcancelall", icon='PANEL_CLOSE', text="")
        sub.operator("render.netclientdownload", icon='RENDER_ANIMATION', text="")

        if len(netrender.jobs) > netsettings.active_job_index >= 0:
            layout.separator()

            job = netrender.jobs[netsettings.active_job_index]

            layout.label(text="Name: %s" % job.name)
            layout.label(text="Length: %04i" % len(job))
            layout.label(text="Done: %04i" % job.results[DONE])
            layout.label(text="Error: %04i" % job.results[ERROR])

import bl_ui.properties_render as properties_render
class RENDER_PT_network_output(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel):
    bl_label = "Output"
    COMPAT_ENGINES = {'NET_RENDER'}

    @classmethod
    def poll(cls, context):
        netsettings = context.scene.network_render
        return super().poll(context) and netsettings.mode == "RENDER_CLIENT"
    
    draw = properties_render.RENDER_PT_output.draw


class NetRenderSlave(bpy.types.PropertyGroup):
    @classmethod
    def register(NetRenderSlave):
        from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty

        NetRenderSlave.name = StringProperty(
                        name="Name of the slave",
                        description="",
                        maxlen = 64,
                        default = "")

class NetRenderJob(bpy.types.PropertyGroup):
    @classmethod
    def register(NetRenderJob):
        from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty

        NetRenderJob.name = StringProperty(
                        name="Name of the job",
                        description="",
                        maxlen = 128,
                        default = "")

class NetRenderSettings(bpy.types.PropertyGroup):
    @classmethod
    def register(NetRenderSettings):
        from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty

        def address_update_callback(self, context):
            netsettings = context.scene.network_render
            verify_address(netsettings, True)

        NetRenderSettings.server_address = StringProperty(
                        name="Server address",
                        description="IP or name of the master render server",
                        maxlen = 128,
                        default = "[default]",
                        update = address_update_callback)
        
        NetRenderSettings.server_port = IntProperty(
                        name="Server port",
                        description="port of the master render server",
                        default = 8000,
                        min=1,
                        max=65535)
        
        NetRenderSettings.use_master_broadcast = BoolProperty(
                        name="Broadcast",
                        description="broadcast master server address on local network",
                        default = True)
        
        NetRenderSettings.use_slave_clear = BoolProperty(
                        name="Clear on exit",
                        description="delete downloaded files on exit",
                        default = True)
        
        NetRenderSettings.use_slave_thumb = BoolProperty(
                        name="Generate thumbnails",
                        description="Generate thumbnails on slaves instead of master",
                        default = False)
        
        NetRenderSettings.use_slave_output_log = BoolProperty(
                        name="Output render log on console",
                        description="Output render text log to console as well as sending it to the master",
                        default = True)
        
        NetRenderSettings.use_master_clear = BoolProperty(
                        name="Clear on exit",
                        description="delete saved files on exit",
                        default = False)
        
        default_path = os.environ.get("TEMP")
        
        if not default_path:
            if os.name == 'nt':
                default_path = "c:/tmp/"
            else:
                default_path = "/tmp/"
        elif not default_path.endswith(os.sep):
            default_path += os.sep
        
        NetRenderSettings.path = StringProperty(
                        name="Path",
                        description="Path for temporary files",
                        maxlen = 128,
                        default = default_path,
                        subtype='FILE_PATH')
        
        NetRenderSettings.job_type = EnumProperty(
                                items=(
                                                ("JOB_BLENDER", "Blender", "Standard Blender Job"),
                                                ("JOB_PROCESS", "Process", "Custom Process Job"),
                                                ("JOB_VCS", "VCS", "Version Control System Managed Job"),
                                            ),
                                name="Job Type",
                                description="Type of render job",
                                default="JOB_BLENDER")
    
        NetRenderSettings.job_name = StringProperty(
                        name="Job name",
                        description="Name of the job",
                        maxlen = 128,
                        default = "[default]")
        
        NetRenderSettings.job_category = StringProperty(
                        name="Job category",
                        description="Category of the job",
                        maxlen = 128,
                        default = "")
        
        NetRenderSettings.chunks = IntProperty(
                        name="Chunks",
                        description="Number of frame to dispatch to each slave in one chunk",
                        default = 5,
                        min=1,
                        max=65535)
        
        NetRenderSettings.priority = IntProperty(
                        name="Priority",
                        description="Priority of the job",
                        default = 1,
                        min=1,
                        max=10)
        
        NetRenderSettings.vcs_wpath = StringProperty(
                        name="Working Copy",
                        description="Path of the local working copy",
                        maxlen = 1024,
                        default = "")
    
        NetRenderSettings.vcs_rpath = StringProperty(
                        name="Remote Path",
                        description="Path of the server copy (protocol specific)",
                        maxlen = 1024,
                        default = "")
    
        NetRenderSettings.vcs_revision = StringProperty(
                        name="Revision",
                        description="Revision for this job",
                        maxlen = 256,
                        default = "")
    
        NetRenderSettings.vcs_system = StringProperty(
                        name="VCS",
                        description="Version Control System",
                        maxlen = 64,
                        default = "Subversion")
    
        NetRenderSettings.job_id = StringProperty(
                        name="Network job id",
                        description="id of the last sent render job",
                        maxlen = 64,
                        default = "")
        
        NetRenderSettings.active_slave_index = IntProperty(
                        name="Index of the active slave",
                        description="",
                        default = -1,
                        min= -1,
                        max=65535)
        
        NetRenderSettings.active_blacklisted_slave_index = IntProperty(
                        name="Index of the active slave",
                        description="",
                        default = -1,
                        min= -1,
                        max=65535)
        
        NetRenderSettings.active_job_index = IntProperty(
                        name="Index of the active job",
                        description="",
                        default = -1,
                        min= -1,
                        max=65535)
        
        NetRenderSettings.mode = EnumProperty(
                                items=(
                                                ("RENDER_CLIENT", "Client", "Act as render client"),
                                                ("RENDER_MASTER", "Master", "Act as render master"),
                                                ("RENDER_SLAVE", "Slave", "Act as render slave"),
                                            ),
                                name="Network mode",
                                description="Mode of operation of this instance",
                                default="RENDER_CLIENT")
        
        NetRenderSettings.slaves = CollectionProperty(type=NetRenderSlave, name="Slaves", description="")
        NetRenderSettings.slaves_blacklist = CollectionProperty(type=NetRenderSlave, name="Slaves Blacklist", description="")
        NetRenderSettings.jobs = CollectionProperty(type=NetRenderJob, name="Job List", description="")
        
        bpy.types.Scene.network_render = PointerProperty(type=NetRenderSettings, name="Network Render", description="Network Render Settings")

    @classmethod
    def unregister(cls):
        del bpy.types.Scene.network_render
