import bpy
from bpy.props import *
import os
import copy
import math
from collections import OrderedDict

from .asset import Asset
from .channels import Channels
from .utils import *
from .error import *
from .fileutils import MultiFile
from mathutils import Vector, Matrix

WHITE = Vector((1.0,1.0,1.0))
GREY = Vector((0.5,0.5,0.5))
BLACK = Vector((0.0,0.0,0.0))

#-------------------------------------------------------------
#   Materials
#-------------------------------------------------------------

def getMatKey(id):
    from .asset import normalizePath
    id = normalizePath(id)
    key = id.split("#")[-1]
    words = key.rsplit("-",1)
    if (len(words) == 2 and
        words[1].isdigit()):
        return words[0]
    else:
        return key

class Material(Asset, Channels):

    def __init__(self, fileref):
        Asset.__init__(self, fileref)
        Channels.__init__(self)
        self.scene = None
        self.shader = 'DAZ'
        self.channels = OrderedDict()
        self.textures = OrderedDict()
        self.groups = []
        self.ignore = False
        self.shells = []
        self.geometry = None
        self.geosockets = []
        self.uv_set = None
        self.uv_sets = {}
        self.udim = 0
        self.basemix = 0
        self.thinWalled = False
        self.refractive = False
        self.thinGlass = False
        self.shareGlossy = False
        self.metallic = False
        self.dualLobeWeight = 0
        self.translucent = False

    def __repr__(self):
        return ("<Material %s %s %s>" % (self.id, self.rna, self.geometry))

    def parse(self, struct):
        Asset.parse(self, struct)
        Channels.parse(self, struct)

    def update(self, struct):
        Asset.update(self, struct)
        Channels.update(self, struct)
        if "uv_set" in struct.keys():
            from .geometry import Uvset
            self.uv_set = self.getTypedAsset(struct["uv_set"], Uvset)
            if self.uv_set:
                self.uv_set.material = self
        if "geometry" in struct.keys():
            geo = self.getAsset(struct["geometry"], True)
            key = getMatKey(self.id)
            if geo is not None:
                if key not in geo.materials.keys():
                    geo.materials[key] = []
                geo.materials[key].append(self)
                self.geometry = geo

        self.basemix = self.getValue(["Base Mixing"], 0)
        if self.basemix == 2:
            self.basemix = 0
        elif self.basemix not in [0,1]:
            raise DazError("Unknown Base Mixing: %s             " % self.material.basemix)

        self.thinWalled = self.getValue(["Thin Walled"], False)
        self.refractive = (self.getValue("getChannelRefractionStrength", 0) > 0.01 or
                           self.getValue("getChannelOpacity", 1) < 0.99)
        self.thinGlass = (self.thinWalled and self.refractive)
        self.shareGlossy = self.getValue(["Share Glossy Inputs"], False)
        self.metallic = (self.getValue(["Metallic Weight"], 0) > 0.5)
        self.dualLobeWeight = self.getValue(["Dual Lobe Specular Weight"], 0)
        self.translucent = (self.getValue("getChannelTranslucencyWeight", 0) > 0.01)

    def setExtra(self, struct):
        if struct["type"] == "studio/material/uber_iray":
            self.shader = 'IRAY'
        elif struct["type"] == "studio/material/daz_brick":
            self.shader = '3DELIGHT'
        elif struct["type"] == "studio/material/daz_shader":
            self.shader = 'DAZ'

    def build(self, context):
        from .asset import normalizePath
        from .geometry import Geometry
        if self.ignore:
            return
        if self.rna is None:
            self.rna = bpy.data.materials.new(self.name)
        scn = self.scene = context.scene
        mat = self.rna
        self.storeRna(mat)
        mat.DazRenderEngine = scn.render.engine
        mat.DazShader = self.shader
        if bpy.app.version < (2,80,0):
            mat.game_settings.alpha_blend = 'CLIP'
        if self.uv_set:
            self.uv_sets[self.uv_set.name] = self.uv_set
        geo = self.geometry
        if geo and isinstance(geo, Geometry):
            for uv,uvset in geo.uv_sets.items():
                if uvset:
                    self.uv_sets[uv] = self.uv_sets[uvset.name] = uvset
        for shname,shmat,uv in self.shells:
            shmat.shader = self.shader
        if self.thinGlass:
            mat.DazThinGlass = True

    def postbuild(self, context):
        pass

    def getUvKey(self, key, struct):
        if key in struct.keys():
            return key
        if False and key[0:7].lower() == "default":
            for key1 in struct.keys():
                if key1[0:7].lower() == "default":
                    print("Alt key: '%s' = '%s'" % (key, key1))
                    return key1
        print("Missing UV for '%s', '%s' not in %s" % (self.getLabel(), key, list(struct.keys())))
        return key

    def getUvSet(self, uv):
        key = self.getUvKey(uv, self.uv_sets)
        if key is None:
            return self.uv_set
        elif key not in self.uv_sets.keys():
            uvset = Asset(None)
            uvset.name = key
            self.uv_sets[key] = uvset
        return self.uv_sets[key]

    def fixUdim(self, context, udim):
        mat = self.getRna(context)
        if mat is None:
            return
        try:
            mat.DazUDim = udim
        except ValueError:
            print("UDIM out of range: %d" % udim)
        mat.DazVDim = 0
        addUdim(mat, udim, 0)

    def getGamma(self, channel):
        global theGammas
        url = self.getImageFile(channel)
        gamma = 0
        if url in theGammas.keys():
            gamma = theGammas[url]
        elif "default_image_gamma" in channel.keys():
            gamma = channel["default_image_gamma"]
        return gamma

#-------------------------------------------------------------
#   Get channels
#-------------------------------------------------------------

    def getChannelDiffuse(self):
        channel = self.getChannel(["diffuse", "Diffuse Color"])
        if (GS.brightenEyes != 1.0 and
            self.name[0:6].lower() in ["irises", "sclera"]):
            if bpy.app.version < (2,80,0):
                factor = GS.brightenEyes
            else:
                factor = math.sqrt(GS.brightenEyes)
            if "value" in channel.keys():
                channel["value"] = factor*Vector(channel["value"])
            if "current_value" in channel.keys():
                channel["current_value"] = factor*Vector(channel["current_value"])
        return channel

    def getChannelDiffuseStrength(self):
        return self.getChannel(["diffuse_strength", "Diffuse Strength"])

    def getChannelDiffuseRoughness(self):
        return self.getChannel(["Diffuse Roughness"])

    def getChannelGlossyColor(self):
        return self.getTexChannel(["Glossy Color", "specular", "Specular Color"])

    def getChannelGlossyLayeredWeight(self):
        return self.getTexChannel(["Glossy Layered Weight", "Glossy Weight", "specular_strength", "Specular Strength"])

    def getChannelGlossyReflectivity(self):
        return self.getChannel(["Glossy Reflectivity"])

    def getChannelGlossyRoughness(self):
        return self.getChannel(["Glossy Roughness"])

    def getChannelGlossySpecular(self):
        return self.getChannel(["Glossy Specular"])

    def getChannelGlossiness(self):
        channel = self.getChannel(["glossiness", "Glossiness"])
        if channel:
            return channel, False
        else:
            return self.getChannel(["Glossy Roughness"]), True

    def getChannelOpacity(self):
        return self.getChannel(["opacity", "Opacity Strength"])

    def getChannelCutoutOpacity(self):
        return self.getChannel(["Cutout Opacity", "transparency"])

    def getChannelAmbientColor(self):
        return self.getChannel(["ambient", "Ambient Color"])

    def getChannelAmbientStrength(self):
        return self.getChannel(["ambient_strength", "Ambient Strength"])

    def getChannelEmissionColor(self):
        return self.getChannel(["emission", "Emission Color"])

    def getChannelReflectionColor(self):
        return self.getChannel(["reflection", "Reflection Color"])

    def getChannelReflectionStrength(self):
        return self.getChannel(["reflection_strength", "Reflection Strength"])

    def getChannelRefractionColor(self):
        return self.getChannel(["refraction", "Refraction Color"])

    def getChannelRefractionStrength(self):
        return self.getChannel(["refraction_strength", "Refraction Weight"])

    def getChannelIOR(self):
        return self.getChannel(["ior", "Refraction Index"])

    def getChannelTranslucencyColor(self):
        return self.getChannel(["Translucency Color"])

    def getChannelTranslucencyWeight(self):
        return self.getChannel(["translucency", "Translucency Weight"])

    def getChannelSSSColor(self):
        return self.getChannel(["SSS Color", "Subsurface Color"])

    def getChannelSSSAmount(self):
        return self.getChannel(["SSS Amount", "Subsurface Strength"])

    def getChannelSSSScale(self):
        return self.getChannel(["SSS Scale", "Subsurface Scale"])

    def getChannelScatterDist(self):
        return self.getChannel(["Scattering Measurement Distance"])

    def getChannelSSSIOR(self):
        return self.getChannel(["Subsurface Refraction"])

    def getChannelTopCoatRoughness(self):
        return self.getChannel(["Top Coat Roughness"])

    def getChannelNormal(self):
        return self.getChannel(["normal", "Normal Map"])

    def getChannelBump(self):
        return self.getChannel(["bump", "Bump Strength"])

    def getChannelBumpMin(self):
        return self.getChannel(["bump_min", "Bump Minimum", "Negative Bump"])

    def getChannelBumpMax(self):
        return self.getChannel(["bump_max", "Bump Maximum", "Positive Bump"])

    def getChannelDisplacement(self):
        return self.getChannel(["displacement", "Displacement Strength"])

    def getChannelDispMin(self):
        return self.getChannel(["displacement_min", "Displacement Minimum", "Minimum Displacement"])

    def getChannelDispMax(self):
        return self.getChannel(["displacement_max", "Displacement Maximum", "Maximum Displacement"])

    def getChannelHorizontalTiles(self):
        return self.getChannel(["u_scale", "Horizontal Tiles"])

    def getChannelHorizontalOffset(self):
        return self.getChannel(["u_offset", "Horizontal Offset"])

    def getChannelVerticalTiles(self):
        return self.getChannel(["v_scale", "Vertical Tiles"])

    def getChannelVerticalOffset(self):
        return self.getChannel(["v_offset", "Vertical Offset"])

    def isActive(self, name):
        cname = "%s Active" % name
        if cname in self.channels.keys():
            channel = self.channels[cname]
            if "current_value" in channel.keys():
                return channel["current_value"]
            elif "value" in channel.keys():
                return channel["value"]
        return True

    def getColor(self, attr, default):
        return self.getChannelColor(self.getChannel(attr), default)

    def getTexChannel(self, channels):
        for key in channels:
            channel = self.getChannel([key])
            if channel and self.hasTextures(channel):
                return channel
        return self.getChannel(channels)

    def hasTexChannel(self, channels):
        for key in channels:
            channel = self.getChannel([key])
            if channel and self.hasTextures(channel):
                return True
        return False

    def getChannelColor(self, channel, default, warn=True):
        color = self.getChannelValue(channel, default, warn)
        if isinstance(color, int) or isinstance(color, float):
            color = (color, color, color)
        return self.srgbToLinear(color)

    def srgbToLinear(self, srgb):
        lin = []
        for s in srgb:
            #   this is the correct linear function used by cycles
            # if s < 0.04045:
            #     l = s/12.92
            # else:
            #     l = ((s+0.055)/1.055)**2.4
            #   this is the gamma 2.2 approximation used by iray
            if s < 0:
                l = 0
            else:
                l = round(s**2.2, 6)
            lin.append(l)
        return Vector(lin)

    def getTextures(self, channel):
        if isinstance(channel, tuple):
            channel = channel[0]
        if channel is None:
            return [],[]
        elif "image" in channel.keys():
            if channel["image"] is None:
                return [],[]
            else:
                maps = self.getAsset(channel["image"]).maps
                if maps is None:
                    maps = []
        elif "image_file" in channel.keys():
            map = Map({}, False)
            map.url = channel["image_file"]
            maps = [map]
        elif "map" in channel.keys():
            maps = Maps(self.fileref)
            maps.parse(channel["map"])
            halt
        elif "literal_image" in channel.keys():
            map = Map(channel, False)
            map.image = channel["literal_image"]
            maps = [map]
        elif "literal_maps" in channel.keys():
            maps = []
            for struct in channel["literal_maps"]["map"]:
                if "mask" in struct.keys():
                    mask = Map(struct["mask"], True)
                    maps.append(mask)
                map = Map(struct, False)
                maps.append(map)
        else:
            return [],[]

        texs = []
        nmaps = []
        for map in maps:
            if map.url:
                tex = map.getTexture()
            elif map.literal_image:
                tex = Texture(map)
                tex.image = map.literal_image
            else:
                tex = None
            if tex:
                texs.append(tex)
                nmaps.append(map)
        return texs,nmaps

    def hasTextures(self, channel):
        return (self.getTextures(channel)[0] != [])

    def hasAnyTexture(self):
        for key in self.channels:
            channel = self.getChannel([key])
            if self.getTextures(channel)[0]:
                return True
        return False

    def sssActive(self, scn):
        if not self.isActive("Subsurface"):
            return False
        if self.refractive or self.thinWalled:
            return False
        return True

#-------------------------------------------------------------
#   UDims
#-------------------------------------------------------------

def addUdim(mat, udim, vdim):
    if mat.node_tree:
        addUdimTree(mat.node_tree, udim, vdim)
    else:
        for mtex in mat.texture_slots:
            if mtex and mtex.texture and mtex.texture.extension == 'CLIP':
                mtex.offset[0] += udim
                mtex.offset[1] += vdim

def addUdimTree(tree, udim, vdim):
    if tree is None:
        return
    for node in tree.nodes:
        if node.type == 'MAPPING':
            if hasattr(node, "translation"):
                slot = node.translation
            else:
                slot = node.inputs["Location"].default_value
            slot[0] += udim
            slot[1] += vdim
        elif node.type == 'GROUP':
            addUdimTree(node.node_tree, udim, vdim)

#-------------------------------------------------------------
#   Textures
#-------------------------------------------------------------

def getRenderMaterial(struct, base):
    from .cycles import CyclesMaterial
    from .internal import InternalMaterial

    if isinstance(base, CyclesMaterial):
        return CyclesMaterial
    elif isinstance(base, InternalMaterial):
        return InternalMaterial

    if LS.materialMethod == 'INTERNAL':
        return InternalMaterial
    else:
        return CyclesMaterial

#-------------------------------------------------------------
#   Textures
#-------------------------------------------------------------

class Map:
    def __init__(self, map, ismask):
        self.url = None
        self.label = None
        self.operation = "alpha_blend"
        self.color = (1,1,1)
        self.ismask = ismask
        self.image = None
        self.size = None
        for key,default in [
            ("url", None),
            ("color", BLACK),
            ("label", None),
            ("operation", "alpha_blend"),
            ("literal_image", None),
            ("invert", False),
            ("transparency", 1),
            ("rotation", 0),
            ("xmirror", False),
            ("ymirror", False),
            ("xscale", 1),
            ("yscale", 1),
            ("xoffset", 0),
            ("yoffset", 0)]:
            if key in map.keys():
                setattr(self, key, map[key])
            else:
                setattr(self, key, default)

    def __repr__(self):
        return ("<Map %s %s %s (%s %s)>" % (self.image, self.ismask, self.size, self.xoffset, self.yoffset))

    def getTexture(self):
        global theTextures
        if self.url in theTextures.keys():
            return theTextures[self.url]
        else:
            tex = Texture(self)
        if self.url:
            theTextures[self.url] = tex
        return tex

    def build(self):
        if self.image:
            return self.image
        elif self.url:
            self.image = getImage(self.url)
            return self.image
        else:
            return self

def getImage(url):
    global theImages
    if url in theImages.keys():
        return theImages[url]
    else:
        return loadImage(url)

def loadImage(url):
    from .asset import getDazPath
    global theImages
    filepath = getDazPath(url)
    if filepath is None:
        reportError('Image not found:  \n"%s"' % filepath, trigger=(3,4))
        img = None
    else:
        img = bpy.data.images.load(filepath)
        img.name = os.path.splitext(os.path.basename(filepath))[0]
        theImages[url] = img
    return img

class Images(Asset):
    def __init__(self, fileref):
        Asset.__init__(self, fileref)
        self.maps = []

    def __repr__(self):
        return ("<Images %s r: %s>" % (self.id, self.maps))

    def parse(self, struct):
        Asset.parse(self, struct)
        mapSize = None
        for key in struct.keys():
            if key == "map":
                for mstruct in struct["map"]:
                    if "mask" in mstruct.keys():
                        self.maps.append(Map(mstruct["mask"], True))
                    self.maps.append(Map(mstruct, False))
            elif key == "map_size":
                mapSize = struct[key]
        if mapSize is not None:
            for map in self.maps:
                map.size = mapSize
        self.parseGamma(struct)

    def update(self, struct):
        self.parseGamma(struct)

    def parseGamma(self, struct):
        global theGammas
        if "map_gamma" in struct.keys():
            gamma = struct["map_gamma"]
            for map in self.maps:
                theGammas[map.url] = gamma

    def build(self):
        images = []
        for map in self.maps:
            img = map.build()
            images.append(img)
        return images

def setImageColorSpace(img, colorspace):
    try:
        img.colorspace_settings.name = colorspace
        return
    except TypeError:
        pass
    alternatives = {
        "sRGB" : ["sRGB OETF"],
        "Non-Color" : ["Non-Colour Data"],
    }
    for alt in alternatives[colorspace]:
        try:
            img.colorspace_settings.name = alt
            return
        except TypeError:
            pass

class Texture:

    def __init__(self, map):
        self.rna = None
        self.map = map
        self.built = {"COLOR":False, "NONE":False}
        self.images = {"COLOR":None, "NONE":None}

    def __repr__(self):
        return ("<Texture %s %s %s>" % (self.map.url, self.map.image, self.image))

    def buildInternal(self):
        global theTextures
        if self.built["COLOR"]:
            return self

        if self.map.url:
            key = self.map.url
        elif self.map.image:
            key = self.map.image.name
        else:
            key = None

        if key is not None:
            img = self.images["COLOR"] = self.map.build()
            if img:
                tex = self.rna = bpy.data.textures.new(img.name, 'IMAGE')
                tex.image = img
            else:
                tex = None
            theTextures[key] = self
        else:
            tex = self.rna = bpy.data.textures.new(self.map.label, 'BLEND')
            tex.use_color_ramp = True
            r,g,b = self.map.color
            for elt in tex.color_ramp.elements:
                elt.color = (r,g,b,1)
        self.built["COLOR"] = True
        return self

    def buildCycles(self, colorSpace):
        if self.built[colorSpace]:
            return self.images[colorSpace]
        elif colorSpace == "COLOR" and self.images["NONE"]:
            img = self.images["NONE"].copy()
        elif colorSpace == "NONE" and self.images["COLOR"]:
            img = self.images["COLOR"].copy()
        elif self.map.url:
            img = self.map.build()
        elif self.map.image:
            img = self.map.image
        else:
            img = None
        if hasattr(img, "colorspace_settings"):
            if colorSpace == "COLOR":
                img.colorspace_settings.name = "sRGB"
            elif colorSpace == "NONE":
                img.colorspace_settings.name = "Non-Color"
            else:
                img.colorspace_settings.name = colorSpace
        if img:
            self.images[colorSpace] = img
        self.built[colorSpace] = True
        return img

    def hasMapping(self, map):
        if map:
            return (map.size is not None)
        else:
            return (self.map and self.map.size is not None)

    def getMapping(self, mat, map):
        # mapping scale x = texture width / lie document size x * (lie x scale / 100)
        # mapping scale y = texture height / lie document size y * (lie y scale / 100)
        # mapping location x = udim place + lie x position * (lie y scale / 100) / lie document size x
        # mapping location y = (lie document size y - texture height * (lie y scale / 100) - lie y position) / lie document size y

        if self.images["COLOR"]:
            img = self.images["COLOR"]
        elif self.images["NONE"]:
            img = self.images["NONE"]
        else:
            raise RuntimeError("BUG: getMapping finds no image")

        tx,ty = img.size
        mx,my = map.size
        kx,ky = tx/mx,ty/my
        ox,oy = map.xoffset/mx, map.yoffset/my
        rz = map.rotation

        ox += mat.getChannelValue(mat.getChannelHorizontalOffset(), 0)
        oy += mat.getChannelValue(mat.getChannelVerticalOffset(), 0)
        kx *= mat.getChannelValue(mat.getChannelHorizontalTiles(), 1)
        ky *= mat.getChannelValue(mat.getChannelVerticalTiles(), 1)

        sx = map.xscale*kx
        sy = map.yscale*ky

        if rz == 0:
            dx = ox
            dy = 1 - sy - oy
            if map.xmirror:
                dx = sx + ox
                sx = -sx
            if map.ymirror:
                dy = 1 - oy
                sy = -sy
        elif rz == 90:
            dx = ox
            dy = 1 - oy
            if map.xmirror:
                dy = 1 - sy - oy
                sy = -sy
            if map.ymirror:
                dx = sx + ox
                sx = -sx
            tmp = sx
            sx = sy
            sy = tmp
            rz = 270*D
        elif rz == 180:
            dx = sx + ox
            dy = 1 - oy
            if map.xmirror:
                dx = ox
                sx = -sx
            if map.ymirror:
                dy = 1 - sy - oy
                sy = -sy
            rz = 180*D
        elif rz == 270:
            dx = sx + ox
            dy = 1 - sy - oy
            if map.xmirror:
                dy = 1 - oy
                sy = -sy
            if map.ymirror:
                dx = ox
                sx = -sx
            tmp = sx
            sx = sy
            sy = tmp
            rz = 90*D

        return (dx,dy,sx,sy,rz)

#-------------------------------------------------------------z
#
#-------------------------------------------------------------

def clearMaterials():
    global theImages, theTextures, theGammas, theShellGroups
    theImages = {}
    theTextures = {}
    theGammas = {}
    theShellGroups = []

clearMaterials()

def isWhite(color):
    return (tuple(color[0:3]) == (1.0,1.0,1.0))

def isBlack(color):
    return (tuple(color[0:3]) == (0.0,0.0,0.0))

#-------------------------------------------------------------
#   Save local textures
#-------------------------------------------------------------

class DAZ_OT_SaveLocalTextures(DazPropsOperator, B.KeepDirsBool):
    bl_idname = "daz.save_local_textures"
    bl_label = "Save Local Textures"
    bl_description = "Copy textures to the textures subfolder in the blend file's directory"
    bl_options = {'UNDO'}

    @classmethod
    def poll(self, context):
        return bpy.data.filepath

    def draw(self, context):
        self.layout.prop(self, "keepdirs")

    def run(self, context):
        from shutil import copyfile
        texpath = os.path.join(os.path.dirname(bpy.data.filepath), "textures")
        print("Save textures to '%s'" % texpath)
        if not os.path.exists(texpath):
            os.makedirs(texpath)

        images = []
        for ob in getSceneObjects(context):
            if ob.type == 'MESH':
                for mat in ob.data.materials:
                    if mat and mat.use_nodes:
                        self.saveNodesInTree(mat.node_tree, images)
                    elif mat:
                        for mtex in mat.texture_slots:
                            if mtex:
                                tex = mtex.texture
                                if hasattr(tex, "image") and tex.image:
                                    images.append(tex.image)
                ob.DazLocalTextures = True

        for img in images:
            src = bpy.path.abspath(img.filepath)
            src = bpy.path.reduce_dirs([src])[0]
            file = bpy.path.basename(src)
            srclower = src.lower().replace("\\", "/")
            if self.keepdirs and "/textures/" in srclower:
                subpath = os.path.dirname(srclower.rsplit("/textures/",1)[1])
                folder = os.path.join(texpath, subpath)
                if not os.path.exists(folder):
                    print("Make %s" % folder)
                    os.makedirs(folder)
                trg = os.path.join(folder, file)
            else:
                trg = os.path.join(texpath, file)
            if src != trg and not os.path.exists(trg):
                print("Copy %s\n => %s" % (src, trg))
                copyfile(src, trg)
            img.filepath = bpy.path.relpath(trg)

    def saveNodesInTree(self, tree, images):
        for node in tree.nodes.values():
            if node.type == 'TEX_IMAGE':
                images.append(node.image)
            elif node.type == 'GROUP':
                self.saveNodesInTree(node.node_tree, images)

#-------------------------------------------------------------
#   Merge identical materials
#-------------------------------------------------------------

class MaterialMerger:

    def mergeMaterials(self, ob):
        if ob.type != 'MESH':
            return

        self.matlist = []
        self.assoc = {}
        self.reindex = {}
        m = 0
        reduced = False
        for n,mat in enumerate(ob.data.materials):
            if self.keepMaterial(n, mat, ob):
                self.matlist.append(mat)
                self.reindex[n] = self.assoc[mat.name] = m
                m += 1
            else:
                reduced = True
        if reduced:
            for f in ob.data.polygons:
                f.material_index = self.reindex[f.material_index]
            for n,mat in enumerate(self.matlist):
                ob.data.materials[n] = mat
            for n in range(len(self.matlist), len(ob.data.materials)):
                ob.data.materials.pop()

class DAZ_OT_MergeMaterials(DazOperator, MaterialMerger, IsMesh):
    bl_idname = "daz.merge_materials"
    bl_label = "Merge Materials"
    bl_description = "Merge identical materials"
    bl_options = {'UNDO'}

    def run(self, context):
        for ob in getSceneObjects(context):
           if getSelected(ob):
               self.mergeMaterials(ob)

    def keepMaterial(self, mn, mat, ob):
        for mat2 in self.matlist:
            if self.areSameMaterial(mat, mat2):
                self.reindex[mn] = self.assoc[mat2.name]
                return False
        return True

    def areSameMaterial(self, mat1, mat2):
        deadMatProps = [
            "texture_slots", "node_tree",
            "name", "name_full", "active_texture",
        ]
        matProps = self.getRelevantProps(mat1, deadMatProps)
        if not self.haveSameAttrs(mat1, mat2, matProps):
            return False
        if mat1.use_nodes and mat2.use_nodes:
            if self.areSameCycles(mat1.node_tree, mat2.node_tree):
                print(mat1.name, "=", mat2.name)
                return True
        elif mat1.use_nodes or mat2.use_nodes:
            return False
        elif self.areSameInternal(mat1.texture_slots, mat2.texture_slots):
            print(mat1.name, "=", mat2.name)
            return True
        else:
            return False

    def getRelevantProps(self, rna, deadProps):
        props = []
        for prop in dir(rna):
            if (prop[0] != "_" and
                prop not in deadProps):
                props.append(prop)
        return props

    def haveSameAttrs(self, rna1, rna2, props):
        for prop in props:
            attr1 = attr2 = None
            if hasattr(rna1, prop) and hasattr(rna2, prop):
                attr1 = getattr(rna1, prop)
                attr2 = getattr(rna2, prop)
                if not self.checkEqual(attr1, attr2):
                    return False
            elif hasattr(rna1, prop) or hasattr(rna2, prop):
                return False
        return True

    def checkEqual(self, attr1, attr2):
        if (isinstance(attr1, int) or
            isinstance(attr1, float) or
            isinstance(attr1, str)):
            return (attr1 == attr2)
        if isinstance(attr1, bpy.types.Image):
            return (isinstance(attr2, bpy.types.Image) and (attr1.name == attr2.name))
        if (isinstance(attr1, set) and isinstance(attr2, set)):
            return True
        if hasattr(attr1, "__len__") and hasattr(attr2, "__len__"):
            if (len(attr1) != len(attr2)):
                return False
            for n in range(len(attr1)):
                if not self.checkEqual(attr1[n], attr2[n]):
                    return False
        return True

    def areSameCycles(self, tree1, tree2):
        if not self.haveSameKeys(tree1.nodes, tree2.nodes):
            return False
        if not self.haveSameKeys(tree1.links, tree2.links):
            return False
        for key,node1 in tree1.nodes.items():
            node2 = tree2.nodes[key]
            if not self.areSameNode(node1, node2):
                return False
        for link1 in tree1.links:
            hit = False
            for link2 in tree2.links:
                if self.areSameLink(link1, link2):
                    hit = True
                    break
            if not hit:
                return False
        for link2 in tree2.links:
            hit = False
            for link1 in tree1.links:
                if self.areSameLink(link1, link2):
                    hit = True
                    break
            if not hit:
                return False
        return True

    def areSameNode(self, node1, node2):
        if not self.haveSameKeys(node1, node2):
            return False
        deadNodeProps = ["dimensions", "location"]
        nodeProps = self.getRelevantProps(node1, deadNodeProps)
        if not self.haveSameAttrs(node1, node2, nodeProps):
            return False
        if not self.haveSameInputs(node1, node2):
            return False
        return True

    def areSameLink(self, link1, link2):
        return (
            (link1.from_node.name == link2.from_node.name) and
            (link1.to_node.name == link2.to_node.name) and
            (link1.from_socket.name == link2.from_socket.name) and
            (link1.to_socket.name == link2.to_socket.name)
        )

    def haveSameInputs(self, nodes1, nodes2):
        if len(nodes1.inputs) != len(nodes2.inputs):
            return False
        for n,socket1 in enumerate(nodes1.inputs):
            socket2 = nodes2.inputs[n]
            if hasattr(socket1, "default_value"):
                if not hasattr(socket2, "default_value"):
                    return False
                val1 = socket1.default_value
                val2 = socket2.default_value
                if (hasattr(val1, "__len__") and
                    hasattr(val2, "__len__")):
                    for m in range(len(val1)):
                        if val1[m] != val2[m]:
                            return False
                elif val1 != val2:
                    return False
            elif hasattr(socket2, "default_value"):
                return False
        return True

    def haveSameKeys(self, struct1, struct2):
        for key in struct1.keys():
            if key not in struct2.keys():
                return False
        return True

    def areSameInternal(self, mtexs1, mtexs2):
        if len(mtexs1) != len(mtexs2):
            return False
        if len(mtexs1) == 0:
            return True

        deadMtexProps = [
            "name", "output_node",
        ]
        mtexProps = self.getRelevantProps(mtexs1[0], deadMtexProps)

        for n,mtex1 in enumerate(mtexs1):
            mtex2 = mtexs2[n]
            if mtex1 is None and mtex2 is None:
                continue
            if mtex1 is None or mtex2 is None:
                return False
            if not self.haveSameAttrs(mtex1, mtex2, mtexProps):
                return False
            if hasattr(mtex1.texture, "image"):
                img1 = mtex1.texture.image
            else:
                img1 = None
            if hasattr(mtex2.texture, "image"):
                img2 = mtex2.texture.image
            else:
                img2 = None
            if img1 is None and img2 is None:
                continue
            if img1 is None or img2 is None:
                return False
            if img1.filepath != img2.filepath:
                return False

        return True

# ---------------------------------------------------------------------
#   Copy materials
# ---------------------------------------------------------------------

class DAZ_OT_CopyMaterials(DazPropsOperator, IsMesh, B.CopyMaterials):
    bl_idname = "daz.copy_materials"
    bl_label = "Copy Materials"
    bl_description = "Copy materials from active mesh to selected meshes"
    bl_options = {'UNDO'}

    def draw(self, context):
        self.layout.prop(self, "useMatchNames")
        self.layout.prop(self, "errorMismatch")

    def run(self, context):
        src = context.object
        self.mismatch = ""
        found = False
        for trg in getSceneObjects(context):
           if getSelected(trg) and trg != src and trg.type == 'MESH':
               self.copyMaterials(src, trg)
               found = True
        if not found:
            raise DazError("No target mesh selected")
        if self.mismatch:
            msg = "Material number mismatch.\n" + self.mismatch
            raise DazError(msg, warning=True)

    def copyMaterials(self, src, trg):
        ntrgmats = len(trg.data.materials)
        nsrcmats = len(src.data.materials)
        if ntrgmats != nsrcmats:
            self.mismatch += ("\n%s (%d materials) != %s (%d materials)"
                          % (src.name, nsrcmats, trg.name, ntrgmats))
            if self.errorMismatch:
                msg = "Material number mismatch.\n" + self.mismatch
                raise DazError(msg)
        mnums = [(f,f.material_index) for f in trg.data.polygons]
        srclist = [(mat.name, mn, mat) for mn,mat in enumerate(src.data.materials)]
        trglist = [(mat.name, mn, mat) for mn,mat in enumerate(trg.data.materials)]

        trgrest = trglist[nsrcmats:ntrgmats]
        trglist = trglist[:nsrcmats]
        srcrest = srclist[ntrgmats:nsrcmats]
        srclist = srclist[:ntrgmats]
        if self.useMatchNames:
            srclist.sort()
            trglist.sort()
            trgmats = {}
            for n,data in enumerate(srclist):
                mat = data[2]
                tname,mn,_tmat = trglist[n]
                trgmats[mn] = mat
                mat.name = tname
            trgmats = list(trgmats.items())
            trgmats.sort()
        else:
            trgmats = [data[1:3] for data in srclist]

        trg.data.materials.clear()
        for _mn,mat in trgmats:
            trg.data.materials.append(mat)
        for _,_,mat in trgrest:
            trg.data.materials.append(mat)
        for f,mn in mnums:
            f.material_index = mn

# ---------------------------------------------------------------------
#   Resize textures
# ---------------------------------------------------------------------

class ChangeResolution(B.ResizeOptions):
    def __init__(self):
        self.filenames = []
        self.images = {}

    def getFileNames(self, paths):
        for path in paths:
            fname = bpy.path.basename(self.getBasePath(path))
            self.filenames.append(fname)

    def getAllTextures(self, context):
        paths = {}
        for ob in getSceneObjects(context):
            if ob.type == 'MESH' and getSelected(ob):
                for mat in ob.data.materials:
                    if mat.node_tree:
                        self.getTreeTextures(mat.node_tree, paths)
                    else:
                        for mtex in mat.texture_slots:
                            if mtex and mtex.texture.type == 'IMAGE':
                                paths[mtex.texture.image.filepath] = True
        return paths

    def getTreeTextures(self, tree, paths):
        for node in tree.nodes.values():
            if node.type == 'TEX_IMAGE':
                paths[node.image.filepath] = True
            elif node.type == 'GROUP':
                self.getTreeTextures(node.node_tree, paths)

    def replaceTextures(self, context):
        for ob in getSceneObjects(context):
            if ob.type == 'MESH' and getSelected(ob):
                for mat in ob.data.materials:
                    if mat.node_tree:
                        self.resizeTree(mat.node_tree)
                    else:
                        for mtex in mat.texture_slots:
                            if mtex and mtex.texture.type == 'IMAGE':
                                mtex.texture.image = self.replaceImage(mtex.texture.image)

    def resizeTree(self, tree):
        for node in tree.nodes.values():
            if node.type == 'TEX_IMAGE':
                newimg = self.replaceImage(node.image)
                node.image = newimg
            elif node.type == 'GROUP':
                self.resizeTree(node.node_tree)

    def getBasePath(self, path):
        fname,ext = os.path.splitext(path)
        if fname[-5:] == "-res0":
            return fname[:-5] + ext
        elif fname[-5:-1] == "-res" and fname[-1].isdigit():
            return fname[:-5] + ext
        else:
            return path

    def replaceImage(self, img):
        if img is None:
            return None
        if hasattr(img, "colorspace_settings"):
            colorSpace = img.colorspace_settings.name
            if colorSpace not in self.images.keys():
                self.images[colorSpace] = {}
            images = self.images[colorSpace]
        else:
            colorSpace = None
            images = self.images

        path = self.getBasePath(img.filepath)
        filename = bpy.path.basename(path)
        if filename not in self.filenames:
            return img

        if self.overwrite:
            if img.filepath in images.keys():
                return images[img.filepath][1]
            try:
                print("Reload", img.filepath)
                img.reload()
                newimg = img
                if colorSpace:
                    newimg.colorspace_settings.name = colorSpace
            except RuntimeError:
                newimg = None
            if newimg:
                images[img.filepath] = (img, newimg)
                return newimg
            else:
                print("Cannot reload '%s'" % img.filepath)
                return img

        newname,newpath = self.getNewPath(path)
        if newpath == img.filepath:
            return img
        elif newpath in images.keys():
            return images[newpath][1]
        elif newname in bpy.data.images.keys():
            return bpy.data.images[newname]
        elif newpath in bpy.data.images.keys():
            return bpy.data.images[newpath]
        else:
            try:
                print("Replace '%s'\n   with '%s'" % (img.filepath, newpath))
                newimg = bpy.data.images.load(newpath)
                if colorSpace:
                    newimg.colorspace_settings.name = colorSpace
            except RuntimeError:
                newimg = None
        if newimg:
            images[newpath] = (img, newimg)
            return newimg
        else:
            print('"%s" does not exist' % newpath)
            return img

    def getNewPath(self, path):
        base,ext = os.path.splitext(path)
        if self.steps == 0:
            newbase = base
        else:
            newbase = ("%s-res%d" % (base, self.steps))
        newname = bpy.path.basename(newbase)
        newpath = newbase + ext
        return newname, newpath

class DAZ_OT_ChangeResolution(DazOperator, ChangeResolution):
    bl_idname = "daz.change_resolution"
    bl_label = "Change Resolution"
    bl_description = (
        "Change all textures of selected meshes with resized versions.\n" +
        "The resized textures must already exist.")
    bl_options = {'UNDO'}

    @classmethod
    def poll(self, context):
        return (context.object and context.object.DazLocalTextures)

    def draw(self, context):
        self.layout.prop(self, "steps")

    def invoke(self, context, event):
        context.window_manager.invoke_props_dialog(self)
        return {'RUNNING_MODAL'}

    def run(self, context):
        self.overwrite = False
        paths = self.getAllTextures(context)
        self.getFileNames(paths.keys())
        self.replaceTextures(context)

class DAZ_OT_ResizeTextures(DazOperator, B.ImageFile, MultiFile, ChangeResolution):
    bl_idname = "daz.resize_textures"
    bl_label = "Resize Textures"
    bl_description = (
        "Replace all textures of selected meshes with resized versions.\n" +
        "Python and OpenCV must be installed on your system.")
    bl_options = {'UNDO'}

    @classmethod
    def poll(self, context):
        return (context.object and context.object.DazLocalTextures)

    def draw(self, context):
        self.layout.prop(self, "steps")
        self.layout.prop(self, "resizeAll")

    def invoke(self, context, event):
        texpath = os.path.join(os.path.dirname(bpy.data.filepath), "textures/")
        self.properties.filepath = texpath
        return MultiFile.invoke(self, context, event)

    def run(self, context):
        from .fileutils import getMultiFiles
        from .globvars import theImageExtensions

        if self.resizeAll:
            paths = self.getAllTextures(context)
        else:
            paths = getMultiFiles(self, theImageExtensions)
        self.getFileNames(paths)

        program = os.path.join(os.path.dirname(__file__), "standalone/resize.py")
        if self.overwrite:
            overwrite = "-o"
        else:
            overwrite = ""
        folder = os.path.dirname(bpy.data.filepath)
        for path in paths:
            if path[0:2] == "//":
                path = os.path.join(folder, path[2:])
            _,newpath = self.getNewPath(self.getBasePath(path))
            if not os.path.exists(newpath):
                cmd = ('python "%s" "%s" %d %s' % (program, path, self.steps, overwrite))
                os.system(cmd)
            else:
                print("Skip", os.path.basename(newpath))

        self.replaceTextures(context)

#----------------------------------------------------------
#   Render settings
#----------------------------------------------------------

def checkRenderSettings(context, force):
    from .light import getMinLightSettings

    renderSettingsCycles = {
        "Bounces" : [("max_bounces", ">", 8)],
        "Diffuse" : [("diffuse_bounces", ">", 1)],
        "Glossy" : [("glossy_bounces", ">", 4)],
        "Transparent" : [("transparent_max_bounces", ">", 16),
                         ("transmission_bounces", ">", 8),
                         ("caustics_refractive", "=", True)],
        "Volume" : [("volume_bounces", ">", 4)],
    }

    renderSettingsEevee = {
        "Transparent" : [("use_ssr", "=", True),
                         ("use_ssr_refraction", "=", True)],
        "Bounces" : [("shadow_cube_size", ">", "1024"),
                 ("shadow_cascade_size", ">", "2048"),
                 ("use_shadow_high_bitdepth", "=", True),
                 ("use_soft_shadows", "=", True),
                 ("light_threshold", "<", 0.001),
                 ("sss_samples", ">", 16),
                 ("sss_jitter_threshold", ">", 0.5),
                ],
    }

    lightSettings = {
        "Bounces" : getMinLightSettings(),
    }

    scn = context.scene
    if scn.render.engine in ["BLENDER_RENDER", "BLENDER_GAME"]:
        return

    handle = GS.handleRenderSettings
    if force:
        handle = "UPDATE"
    msg = ""
    msg += checkSettings(scn.cycles, renderSettingsCycles, handle, "Cycles Settings")
    if bpy.app.version >= (2,80,0):
        msg += checkSettings(scn.eevee, renderSettingsEevee, handle, "Eevee Settings")

    if bpy.app.version < (2,80,0):
        bpydatalamps = bpy.data.lamps
        lamptype = "Lamp"
    else:
        bpydatalamps = bpy.data.lights
        lamptype = "Light"
    handle = GS.handleLightSettings
    if force:
        handle = "UPDATE"
    for lamp in bpydatalamps:
        header = '%s "%s" settings' % (lamptype, lamp.name)
        msg += checkSettings(lamp, lightSettings, handle, header)

    if msg:
        msg += "See http://diffeomorphic.blogspot.com/2020/04/render-settings.html for details."
        print(msg)
        return msg
    else:
        return ""

def checkSettings(engine, settings, handle, header):
    msg = ""
    if handle == "IGNORE":
        return msg
    ok = True
    for key,used in LS.usedFeatures.items():
        if used and key in settings.keys():
            for attr,op,minval in settings[key]:
                if not hasattr(engine, attr):
                    continue
                val = getattr(engine, attr)
                fix,minval = checkSetting(attr, op, val, minval, ok, header)
                if fix:
                    ok = False
                    if handle == "UPDATE":
                        setattr(engine, attr, minval)
    if not ok:
        if handle == "WARN":
            msg = ("%s are insufficient to render this scene correctly.\n" % header)
        else:
            msg = ("%s have been updated to minimal requirements for this scene.\n" % header)
    return msg

def checkSetting(attr, op, val, minval, first, header):
    negop = None
    eps = 1e-4
    if op == "=":
        if val != minval:
            negop = "!="
    elif op == ">":
        if isinstance(val, str):
            if int(val) < int(minval):
                negop = "<"
        elif val < minval-eps:
            negop = "<"
    elif op == "<":
        if isinstance(val, str):
            if int(val) > int(minval):
                negop = ">"
        elif val > minval+eps:
            negop = ">"

    if negop:
        msg = ("  %s: %s %s %s" % (attr, val, negop, minval))
        if first:
            print("%s:" % header)
        print(msg)
        return True,minval
    else:
        return False,minval

class DAZ_OT_UpdateSettings(DazOperator):
    bl_idname = "daz.update_settings"
    bl_label = "Update Render Settings"
    bl_description = "Update render and lamp settings if they are inadequate"
    bl_options = {'UNDO'}

    def run(self, context):
        checkRenderSettings(context, True)

#----------------------------------------------------------
#   Initialize
#----------------------------------------------------------

classes = [
    DAZ_OT_SaveLocalTextures,
    DAZ_OT_MergeMaterials,
    DAZ_OT_CopyMaterials,
    DAZ_OT_ChangeResolution,
    DAZ_OT_ResizeTextures,
    DAZ_OT_UpdateSettings,
]

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

    bpy.types.Object.DazLocalTextures = BoolProperty(default = False)

    bpy.types.Scene.DazHandleRenderSettings = EnumProperty(
        items = [("IGNORE", "Ignore", "Ignore insufficient render settings"),
                 ("WARN", "Warn", "Warn about insufficient render settings"),
                 ("UPDATE", "Update", "Update insufficient render settings")],
        name = "Render Settings",
        default = "UPDATE"
    )

    bpy.types.Scene.DazHandleLightSettings = EnumProperty(
        items = [("IGNORE", "Ignore", "Ignore insufficient light settings"),
                 ("WARN", "Warn", "Warn about insufficient light settings"),
                 ("UPDATE", "Update", "Update insufficient light settings")],
        name = "Light Settings",
        default = "UPDATE"
    )

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


import numpy as np
import pyvista as pv
from matplotlib import cm
import pickle

from utils.create_from_ply import create_from_ply
from utils.mesh_tools import Mesh

def farthest_point_sampling(mesh_vertices, n, gdist_mat):
    gdist_mat = gdist_mat.toarray()
    vertices = mesh_vertices
    vertices_num = vertices.shape[0]
    s1 = np.random.randint(vertices_num)
    S_inds = [s1] # Initialize compact set for vertices S inds
    while not len(S_inds) == n:
        v_S_dist = gdist_mat[:, S_inds]
        v_f = np.argmax(np.min(v_S_dist, axis=1),axis=0)
        S_inds.append(int(v_f))

    return S_inds, mesh_vertices[S_inds]

def plot_mesh_sampling_pointcloud(mesh, n, gdist=None, plotter=None):
        if gdist == None:
            mesh_gdist = gdist.local_gdist_matrix(mesh.v.astype(np.float64), mesh.f.astype(np.int32))
        else:
            mesh_gdist = gdist
        fps_inds, fps_vals = farthest_point_sampling(mesh.v, n, mesh_gdist)
        if plotter == None:
            plotter = pv.Plotter()
        mesh.render_surface(plotter=plotter, scalar_func=mesh.v)
        fps_pointcloud = pv.PolyData(fps_vals)
        return fps_pointcloud

# Create from ply and read gdist from file
ply_obj, obj_gdist = create_from_ply(ply_name='tr_reg_000', write=False, read=True)

## Create obj mesh
mesh_vertices = ply_obj.points
mesh_faces = ply_obj.cells_dict['triangle']
obj_mesh = Mesh(mesh_vertices, np.c_[3*np.ones(np.shape(mesh_faces)[0]), mesh_faces])

# Visualize for different n the farthest_sampling

n_list = [300, 700, 1300, 2000, 4000]
num_n = len(n_list)

plotter = pv.Plotter(shape=(1, num_n))

for i, n in enumerate(n_list):
    plotter.subplot(0, i)
    plotter.add_text("n = " + str(n))
    obj_mesh.render_surface(scalar_func=obj_mesh.v, plotter=plotter)
    fps_pointcloud = plot_mesh_sampling_pointcloud(obj_mesh, n=n, gdist=obj_gdist)
    plotter.add_mesh(fps_pointcloud, render_points_as_spheres=True, cmap=cm.get_cmap('winter'), point_size=8)

plotter.show()


import torch
import numpy as np

class ConfusionMatrix:
    """ Implementation of Confusion Matrix.g

    Arguments:
        num_classes (int): number of evaluated classes.
        normalized (bool): if normalized is True then confusion matrix will be normalized.
    """
    def __init__(self, num_classes, normalized=False):
        self.num_classes = num_classes
        self.normalized = normalized
        self.conf = np.ndarray((num_classes, num_classes), np.int32)
        self.reset()

    def reset(self):
        """ Reset of the Confusion Matrix.
        """
        self.conf.fill(0)

    def add(self, pred, target):
        """ Add sample to the Confusion Matrix.

        Arguments:
            pred (torch.Tensor() or numpy.ndarray): predicted mask.
            target (torch.Tensor() or numpy.ndarray): ground-truth mask.
        """
        if torch.is_tensor(pred):
            pred = pred.detach().cpu().numpy()
        if torch.is_tensor(target):
            target = target.detach().cpu().numpy()

        valid_indices = np.where((target >= 0) & (target < self.num_classes))
        pred = pred[valid_indices]
        target = target[valid_indices]

        replace_indices = np.vstack((target.flatten(), pred.flatten())).T

        conf, _ = np.histogramdd(
            replace_indices,
            bins=(self.num_classes, self.num_classes),
            range=[(0, self.num_classes), (0, self.num_classes)]
        )

        self.conf += conf.astype(np.int32)

    def value(self):
        """ Return of the Confusion Matrix.

        Returns:
            numpy.ndarray(num_classes, num_classes): confusion matrix.
        """
        if self.normalized:
            conf = self.conf.astype(np.float32)
            return conf / conf.sum(1).clip(min=1e-12)[:, None]
        return self.conf


r"""Fetch TXP data.

Steps:
  1. Open a new RFmx Session.
  2. Configure Frequency Reference.
  3. Configure Selected Ports.
  4. Configure basic signal properties(Center Frequency, Reference Level and External Attenuation).
  5. Configure Trigger Parameters for Digital Edge Trigger.
  6. Configure Link Direction, Frequency Range, Carrier Bandwidthand Subcarrier Spacing.
  7. Select TXP measurement and enable Traces.
  8. Configure Measurement Offset & Measurement Length Parameters and Averaging Parameters for TXP
     measurement.
  9. Initiate the Measurement.
  10. Fetch TXP Measurements and Traces.
  11. Close RFmx Session.

The gRPC API is built from the C API. RFmx NR documentation is installed with the driver at:
  C:\Program Files (x86)\National Instruments\RFmx\NR\Documentation\rfmxnrcvi.chm

Getting Started:

To run this example, install "RFmx NR" on the server machine:
  https://www.ni.com/en-us/support/downloads/software-products/download.rfmx-nr.html

For instructions on how to use protoc to generate gRPC client interfaces, see our "Creating a gRPC
Client" wiki page:
  https://github.com/ni/grpc-device/wiki/Creating-a-gRPC-Client

Refer to the NI-RFmxNR gRPC Wiki for the latest C Function Reference:
  https://github.com/ni/grpc-device/wiki/NI-RFmxNR-C-Function-Reference

Running from command line:

Server machine's IP address, port number, and physical channel name can be passed as separate
command line arguments.
  > python txp-single-carrier.py <server_address> <port_number> <resource_name>
If they are not passed in as command line arguments, then by default the server address will be
"localhost:31763", with "SimulatedDevice" as the resource name.
"""

import sys

import grpc
import nirfmxnr_pb2 as nirfmxnr_types
import nirfmxnr_pb2_grpc as grpc_nirfmxnr

SERVER_ADDRESS = "localhost"
SERVER_PORT = "31763"
SESSION_NAME = "RFmxNRSession"

# Resource name and options for a simulated 5663 client.
RESOURCE = "SimulatedDevice"
OPTIONS = "Simulate=1,DriverSetup=Model:5663"

# Read in cmd args
if len(sys.argv) >= 2:
    SERVER_ADDRESS = sys.argv[1]
if len(sys.argv) >= 3:
    SERVER_PORT = sys.argv[2]
if len(sys.argv) >= 4:
    RESOURCE = sys.argv[3]
    OPTIONS = ""

# Create a gRPC channel + client.
channel = grpc.insecure_channel(f"{SERVER_ADDRESS}:{SERVER_PORT}")
client = grpc_nirfmxnr.NiRFmxNRStub(channel)
instr = None

def check_for_warning(response, instrument):
    """Print to console if the status indicates a warning."""
    if response.status > 0:
        warning_message = client.GetErrorString(
            nirfmxnr_types.GetErrorStringRequest(
                instrument=instrument,
                error_code=response.status,
            )
        )
        sys.stderr.write(
            f"{warning_message.error_description}\nWarning status: {response.status}\n"
        )

try:
    initialize_response = client.Initialize(
        nirfmxnr_types.InitializeRequest(
            session_name=SESSION_NAME,
            resource_name=RESOURCE,
            option_string=OPTIONS,
        )
    )
    instr = initialize_response.instrument

    client.CfgFrequencyReference(
        nirfmxnr_types.CfgFrequencyReferenceRequest(
            instrument=instr,
            channel_name="",
            frequency_reference_source_mapped=nirfmxnr_types.FREQUENCY_REFERENCE_SOURCE_ONBOARD_CLOCK,
            frequency_reference_frequency=10e6,
        )
    )

    client.SetAttributeString(
        nirfmxnr_types.SetAttributeStringRequest(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_SELECTED_PORTS,
            attr_val_raw="",
        )
    )

    client.CfgRF(
        nirfmxnr_types.CfgRFRequest(
            instrument=instr,
            selector_string="",
            center_frequency=3.5e9,
            reference_level=0.0,
            external_attenuation=0.0,
        )
    )

    client.CfgDigitalEdgeTrigger(
        nirfmxnr_types.CfgDigitalEdgeTriggerRequest(
            instrument=instr,
            selector_string="",
            digital_edge_source_mapped=nirfmxnr_types.DIGITAL_EDGE_TRIGGER_SOURCE_PXI_TRIG0,
            digital_edge=nirfmxnr_types.DIGITAL_EDGE_TRIGGER_EDGE_RISING,
            trigger_delay=0.0,
            enable_trigger=False,
        )
    )

    client.SetAttributeI32(
        nirfmxnr_types.SetAttributeI32Request(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_LINK_DIRECTION,
            attr_val_raw=nirfmxnr_types.NIRFMXNR_INT32_LINK_DIRECTION_UPLINK,
        )
    )

    client.SetAttributeI32(
        nirfmxnr_types.SetAttributeI32Request(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_FREQUENCY_RANGE,
            attr_val_raw=nirfmxnr_types.NIRFMXNR_INT32_FREQUENCY_RANGE_RANGE1,
        )
    )

    client.SetAttributeF64(
        nirfmxnr_types.SetAttributeF64Request(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_COMPONENT_CARRIER_BANDWIDTH,
            attr_val=20e6,
        )
    )

    client.SetAttributeF64(
        nirfmxnr_types.SetAttributeF64Request(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_BANDWIDTH_PART_SUBCARRIER_SPACING,
            attr_val=30e3,
        )
    )

    client.SelectMeasurements(
        nirfmxnr_types.SelectMeasurementsRequest(
            instrument=instr,
            selector_string="",
            measurements=nirfmxnr_types.MEASUREMENT_TYPES_TXP,
            enable_all_traces=True,
        )
    )

    client.SetAttributeF64(
        nirfmxnr_types.SetAttributeF64Request(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_TXP_MEASUREMENT_INTERVAL,
            attr_val=1.0e-3,
        )
    )

    client.SetAttributeF64(
        nirfmxnr_types.SetAttributeF64Request(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_TXP_MEASUREMENT_OFFSET,
            attr_val=0.0,
        )
    )

    client.SetAttributeI32(
        nirfmxnr_types.SetAttributeI32Request(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_TXP_AVERAGING_ENABLED,
            attr_val_raw=nirfmxnr_types.NIRFMXNR_INT32_TXP_AVERAGING_ENABLED_TRUE,
        )
    )

    client.SetAttributeI32(
        nirfmxnr_types.SetAttributeI32Request(
            instrument=instr,
            selector_string="",
            attribute_id=nirfmxnr_types.NIRFMXNR_ATTRIBUTE_TXP_AVERAGING_COUNT,
            attr_val_raw=10,
        )
    )

    initiate_response = client.Initiate(
        nirfmxnr_types.InitiateRequest(
            instrument=instr,
            selector_string="",
            result_name="",
        )
    )
    check_for_warning(initiate_response, instr)
    ### Fetch results ###

    txp_fetch_measurement_response = client.TXPFetchMeasurement(
        nirfmxnr_types.TXPFetchMeasurementRequest(
            instrument=instr,
            selector_string="",
            timeout=10.0,
        )
    )
    check_for_warning(txp_fetch_measurement_response, instr)
    avearge_power_mean = txp_fetch_measurement_response.average_power_mean
    peak_power_maximum = txp_fetch_measurement_response.peak_power_maximum

    txp_fetch_power_trace_response = client.TXPFetchPowerTrace(
        nirfmxnr_types.TXPFetchPowerTraceRequest(
            instrument=instr,
            selector_string="",
            timeout=10.0,
        )
    )
    check_for_warning(txp_fetch_power_trace_response, instr)
    x0 = txp_fetch_power_trace_response.x0
    dx = txp_fetch_power_trace_response.dx
    power = txp_fetch_power_trace_response.power

    print("\n------------Measurement------------\n")
    print(f"Average Power Mean (dBm) : {avearge_power_mean}")
    print(f"Peak Power Maximum (dBm) : {peak_power_maximum}")
except grpc.RpcError as rpc_error:
    error_message = str(rpc_error.details() or "")
    for entry in rpc_error.trailing_metadata() or []:
        if entry.key == "ni-error":
            value = entry.value if isinstance(entry.value, str) else entry.value.decode("utf-8")
            error_message += f"\nError status: {value}"
    if rpc_error.code() == grpc.StatusCode.UNAVAILABLE:
        error_message = f"Failed to connect to server on {SERVER_ADDRESS}:{SERVER_PORT}"
    elif rpc_error.code() == grpc.StatusCode.UNIMPLEMENTED:
        error_message = (
            "The operation is not implemented or is not supported/enabled in this service"
        )
    sys.stderr.write(f"{error_message}\n")
finally:
    if instr:
        client.Close(nirfmxnr_types.CloseRequest(instrument=instr, force_destroy=False))


import numpy as np
import pickle
import torch
from itertools import permutations

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

class Graph:

    def __init__(self, info_filename):
        '''
        Read the pickle info file
        Structure of the loaded file:
            [[vertices_b0],
            [edges_b0, map_b0, faces_b0, pool_indices_b0],
            [edges_b1, map_b1, faces_b1, pool_indices_b1],
            [edges_b2, map_b2, faces_b2]]
        '''
        info_file = open(info_filename,"rb")
        info = pickle.load(info_file)
        # [[v0], [e0, m0, f0, idx[0]], [e1, m1, f1, idx[1]], [e2, m2, f2]]

        self.vertices = torch.Tensor(info[0][0]).float().to(device)
        self.edges = info[1][0]
        self.faces = info[1][2]
        self.idx = info[1][3]
        self.block_id = 1
        self.info = info
        adj_matrices = []
        maps = []
        len_maps = []
        for block_id in range(1, len(info)):
            # Build vertex-vertex map (useful for laplacian loss)
            map = info[block_id][1]
            len_map = torch.Tensor([len(l) for l in map]).view(-1, 1).to(device)
            map = [torch.Tensor(l).to(device) for l in map]
            # Pad to make tensor as vertices don't have same nb of neighbors
            map = torch.nn.utils.rnn.pad_sequence(map, True, -1).long().to(device)
            maps.append(map)
            len_maps.append(len_map)

            # Adjacency mat
            edges = info[block_id][0]
            mat = list(zip(*edges))
            adj_matrices.append(torch.tensor([list(mat[0]), list(mat[1])],
                                             dtype=torch.long).to(device))
        self.adjacency_mat = adj_matrices
        self.maps = maps
        self.len_maps = len_maps

    def reset(self):
        '''
        Reset graph to initial ellipsoid
        '''
        self.vertices = torch.Tensor(self.info[0][0]).float().to(device)
        self.edges = self.info[1][0]
        self.faces = self.info[1][2]
        self.idx = self.info[1][3]
        self.block_id = 1

    def to_obj(self, obj_file):
        '''
        Export the current graph to obj file
        '''
        # Make a copy out of the torch comput graph
        vertices_copy = self.vertices.cpu().clone().detach().numpy()

        # Writing vertices
        file = open(obj_file,"w")
        for id_vertex in range(vertices_copy.shape[0]):
            xcoord = str(vertices_copy[id_vertex, 0]).replace('.', ',')
            ycoord = str(vertices_copy[id_vertex, 1]).replace('.', ',')
            zcoord = str(vertices_copy[id_vertex, 2]).replace('.', ',')
            file.write("v " + xcoord + " " + ycoord + " " + zcoord + "\n")

        # Writing faces
        for face in self.faces:
            face0 = str(face[0])
            face1 = str(face[1])
            face2 = str(face[2])
            file.write("f " + face0 + " " + face1 + " " + face2 + "\n")

    def unpool(self, x=None):
        '''
        Graph unpooling layer
        Args:
            x: Vertex features
        Return:
            x: Vertex features with augmented vertices
        '''
        if self.block_id >= 3:
            raise RuntimeError("Maximum unpooling level already achieved (b3)")

        # Increase number of vertices
        new_v = torch.sum(self.vertices[self.idx], dim=1)/2
        self.vertices = torch.cat((self.vertices, new_v), dim = 0)

        # Interpol features for the extra vertices
        if x is not None:
            new_feat = torch.sum(x[self.idx], dim=1)/2
            x = torch.cat((x, new_feat), dim = 0)

        # Adapt edges and faces
        self.edges = self.info[self.block_id+1][0]
        self.faces = self.info[self.block_id+1][2]

        # Update pooling idx
        if self.block_id < 2:
            self.idx = self.info[self.block_id+1][3]
        self.block_id += 1

        return x

if __name__ == "__main__":
    print("Testing Graph")
    graph = Graph("./ellipsoid/init_info.pickle")
    print("Vertices dim (block 1):", graph.vertices.size())
    graph.unpool()
    print("Vertices dim (block 2):", graph.vertices.size())
    graph.unpool()
    print("Vertices dim (block 3):", graph.vertices.size())
    graph.reset()
    print("Vertices dim (block 1):", graph.vertices.size())


import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
from keras.models import Sequential
from keras.layers import Dense, LSTM, Dropout
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error
import warnings
warnings.filterwarnings('ignore')

# Tabelas com as ações
df_acoes = pd.read_csv('base_dados.csv')

# Ajustar data
date_start = datetime.today() - timedelta(days=1865)
date_end = datetime.today()

intervals = ['5m', '15m', '30m', '60m', '90m', '1h', '1d', '5d', '1wk', '1mo', '3mo']

def format_date(dt, format='%Y-%m-%d'):
    return dt.strftime(format)

df = yf.download(tickers = 'ITSA4.SA', start=date_start, end=date_end, interval='1d').reset_index()
print(df)

#Filtrando a Base de Dados
df1 = df[['Date', 'Close']].set_index('Date')
print(df1)

#Gráfico Fechamento
plt.style.use('seaborn-darkgrid')
ax = plt.figure(figsize=(18, 6))
plt.title('Preço de Fechamento', fontsize=15, loc='left', pad=10)
plt.plot(df1.index, df1['Close'])
plt.ylabel('Preço (R$)')
plt.tight_layout()
plt.show()

# Separação dos dados Treino e Teste
close_price = df1['Close']
step = 15

train_size = int(len(close_price) * 0.8)
test_size = len(close_price) - train_size
train_data, input_data = np.array(close_price[0:train_size]), np.array(close_price[train_size - step:])
test_data = np.array(close_price[train_size:])

# Normalizar os dados
scaler = MinMaxScaler(feature_range=(0,1))
train_data_norm = scaler.fit_transform(np.array(train_data).reshape(-1,1))
test_data_norm = scaler.transform(np.array(input_data).reshape(-1,1))

# Pré-processamento
X_train, y_train = [], []
for i in range(step, len(train_data)):
    X_train.append(train_data_norm[i-step:i])
    y_train.append(train_data_norm[i])

X_test, y_test = [], []
for i in range(step, step + len(test_data)):
    X_test.append(test_data_norm[i-step:i])
    y_test.append(test_data_norm[i])

# Trasnformando em array
X_train = np.array(X_train)
y_train = np.array(y_train)
X_test = np.array(X_test)
y_test = np.array(y_test)

# Montando a Rede Neural
model = Sequential()
model.add(LSTM(35, return_sequences=True, activation='relu', input_shape=(step, 1)))
model.add(Dropout(0.2))
model.add(LSTM(35, return_sequences=False))
model.add(Dropout(0.2))

#Adicionando camadas na rede neural
model.add(Dense(25))
model.add(Dense(1))
model.compile(optimizer ='adam', loss= 'mse')
model.summary()

#Treinamento do modelo
results=model.fit(X_train, y_train, validation_data = (X_test, y_test), epochs=270, batch_size=180, verbose=2)

#model.save('model_app.h5')

plt.plot(results.history["loss"], label='Training loss')
plt.plot(results.history["val_loss"], label='Validation loss')
plt.legend()
plt.show()

# Fazendo a previsão
predict = model.predict(X_test)
predict = scaler.inverse_transform(predict)

rsme = np.sqrt(np.mean(predict - y_test) ** 2)
print('Erro quadrático médio:', rsme)

mse = mean_squared_error(test_data, predict)
print(mse)

df_predict = df1.filter(['Close'])[train_size:]
df_predict['Predict'] = predict
df_predict[['Close', 'Predict']]
print(df_predict)

# Gráfico Realizado vs Modelo
plt.figure(figsize = (18,9))
plt.plot(df_predict.index, df_predict['Close'], color = 'green', label = 'Real')
plt.plot(df_predict.index, df_predict['Predict'], color = 'red', label = 'Previsão')
plt.title('Realizado vs Modelo', fontsize=14, loc='left', pad=10)
plt.ylabel('Preço (R$)')
plt.tight_layout()
plt.legend()
plt.show()

# Prevendo o preço para os próximos 10 dias
lenght_test = len(test_data_norm)

days_steps = lenght_test - step

input_steps = test_data_norm[days_steps:]
input_steps = np.array(input_steps).reshape(1, -1)

list_output_steps = list(input_steps)
list_output_steps = list_output_steps[0].tolist()

pred_output=[]
i=0
n_future=10
while(i<n_future):

    if(len(list_output_steps) > step):

        input_steps = np.array(list_output_steps[1:])
        input_steps = input_steps.reshape(1, -1)
        input_steps = input_steps.reshape((1, step, 1))

        pred = model.predict(input_steps, verbose=0)
        list_output_steps.extend(pred[0].tolist())
        list_output_steps=list_output_steps[1:]

        pred_output.extend(pred.tolist())
        i=i+1
    else:
        input_steps = input_steps.reshape((1, step,1))
        pred = model.predict(input_steps, verbose=0)
        list_output_steps.extend(pred[0].tolist())
        i=i+1
print(pred_output)

prev = scaler.inverse_transform(pred_output)
prev = np.array(prev).reshape(1,-1)
list_output_prev = list(prev)
list_output_prev = prev[0].tolist()

dates = df1.index
predict_dates = pd.date_range(list(dates)[-1] + pd.DateOffset(1), periods=9, freq='b').tolist()

forecast_dates = []
for i in predict_dates:
    forecast_dates.append(i.date())

df_forecast = pd.DataFrame({'Date': np.array(forecast_dates), 'Predict': list_output_prev})

df_forecast=df_forecast.set_index(pd.DatetimeIndex(df_forecast['Date'].values))
df_forecast.drop('Date', axis=1, inplace=True)
print(df_forecast)


import datetime

import pytest
from sqlalchemy import create_engine, Engine

import student_repo

@pytest.fixture
def engine():
    engine = create_engine("sqlite://", isolation_level="AUTOCOMMIT")
    con = engine.raw_connection()

    with open('../sql/create_tables.sql') as f:
        sql = f.read()
        # hack to make sqlite accept the MySql DDL
        sqlite_compliant_sql = sql.replace('AUTO_INCREMENT', 'AUTOINCREMENT')
        con.executescript(sqlite_compliant_sql)

    return engine

def test_student_repo_add_student(engine):
    student_repo.init(engine)
    student = {
        'lastName': 'Melville',
        'firstName': 'Herman',
        'grade': 10,
        'dob': datetime.datetime(2001, 6, 30),
        'gpa': None,
        'studentId': 'asdf1234'
    }
    student_repo.add_student(student)

    assert student_repo.get_student('asdf1234') == student


import hashlib

# import smtplib
import uuid
from collections import UserDict
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from ipaddress import IPv4Address, IPv6Address, ip_address
from pprint import pformat
from random import randint
from typing import Union
from urllib.parse import urlencode

class Event(UserDict):
    # HTTP PageView
    # Fields under __request__:
    # user_agent, user_ip, host, path, referer

    def __getattr__(self, name):
        return self["__request__"][name] or ""

    def _cid_v1(self):
        # Author: Cyrus Afrasiabi
        # Reference: https://github.com/kra3/py-ga-mob
        # More at: pyga.utils.generate_hash

        hash_val = 1

        if self.user_agent:
            hash_val = 0
            for ordinal in map(ord, self.user_agent[::-1]):
                hash_val = ((hash_val << 6) & 0xFFFFFFF) + ordinal + (ordinal << 14)
                left_most_7 = hash_val & 0xFE00000
                if left_most_7 != 0:
                    hash_val ^= left_most_7 >> 21

        return (randint(0, 0x7FFFFFFF) ^ hash_val) & 0x7FFFFFFF

    def _cid_v2(self):
        # Author: Zhongchao Qian
        # More at: https://github.com/biothings/biothings.api/pull/124
        """
        Generates a unique user ID

        Using the remote IP and client user agent, to produce a somewhat
        unique identifier for users. A UUID version 4 conforming to RFC 4122
        is produced which is generally acceptable. It is not entirely random,
        but looks random enough to the outside. Using a hash func. the user
        info is completely anonymized.

        Args:
            remote_ip: Client IP address as a string, IPv4 or IPv6
            user_agent: User agent string of the client
        """

        try:
            rip: Union[IPv4Address, IPv6Address] = ip_address(self.user_ip)
            ip_packed = rip.packed
        except ValueError:  # in the weird case I don't get an IP
            ip_packed = randint(0, 0xFFFFFFFF).to_bytes(4, "big")  # nosec

        h = hashlib.blake2b(digest_size=16, salt=b"biothings")
        h.update(ip_packed)
        h.update(self.user_agent.encode("utf-8", errors="replace"))

        d = bytearray(h.digest())
        # truncating hash is not that bad, fixing some bits should be okay, too
        d[6] = 0x40 | (d[6] & 0x0F)  # set version
        d[8] = 0x80 | (d[8] & 0x3F)  # set variant
        u = str(uuid.UUID(bytes=bytes(d)))
        return u

    def _cid(self, version):
        if version == 1:
            return self._cid_v1()
        elif version == 2:
            return self._cid_v2()

        # this is a required GA field
        raise ValueError("CID Version.")

    def to_GA_payload(self, tracking_id, cid_version=1):
        # by default implements
        # a GA PageView hit-type

        # In the future, consider adding additional
        # keys as cutomized dimensions or metrics.

        payload = {
            "v": 1,  # protocol version
            "t": "pageview",
            "tid": tracking_id,
            "cid": self._cid(cid_version),
            "uip": self.user_ip,
            "dh": self.host,
            "dp": self.path,
        }

        # add document referer
        if isinstance(self.referer, str):
            if len(self.referer) <= 2048:  # GA Limit
                payload["dr"] = self.referer

        # add user_agent
        if self.user_agent:
            payload["ua"] = self.user_agent

        # this also escapes payload vals
        return [urlencode(payload)]

    def to_GA4_payload(self, measurement_id, cid_version=1):
        # Document about page_view event: https://support.google.com/analytics/answer/9964640#pageviews&zippy=%2Cin-this-article
        # GA4 does not support [Document path as UA](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#dp)
        # so we use location instead of host and path

        # TODO consider to use request.uri as page_location
        payload = {
            "name": "page_view",
            "params": _clean(
                {
                    "page_location": f"{self.host}{self.path}",
                    "page_title": self.path.strip("/").replace("/", "-"),
                }
            ),
        }

        # add document referer
        if isinstance(self.referer, str):
            # Parameter values (including item parameter values) must be 100 character or fewer.
            if len(self.referer) <= 100:
                payload["params"]["page_referrer"] = self.referer

        # add user_agent
        if self.user_agent:
            payload["params"]["user_agent"] = self.user_agent[:100]

        return [payload]

    def __str__(self):  # to facilitate logging
        return f"{type(self).__name__}({pformat(self)})"

def _clean(dict):
    return {k: v for k, v in dict.items() if v}

class GAEvent(Event):
    # GA Event
    # {
    #   "category": "video",
    #   "action": "play",
    #   "label": "sample.mpg",
    #   "value": "60"
    # }

    def to_GA_payload(self, tracking_id, cid_version=1):
        payloads = super().to_GA_payload(tracking_id, cid_version)
        if self.get("category") and self.get("action"):
            payloads.append(
                urlencode(
                    _clean(
                        {
                            "v": 1,  # protocol version
                            "t": "event",
                            "tid": tracking_id,
                            "cid": self._cid(cid_version),
                            "ec": self["category"],
                            "ea": self["action"],
                            "el": self.get("label", ""),
                            "ev": self.get("value", ""),
                        }
                    )
                )
            )
        for event in self.get("__secondary__", []):
            event["__request__"] = self["__request__"]
            payloads.extend(event.to_GA_payload(tracking_id, cid_version)[1:])
            # ignore the first event (pageview)
            # which is already generated once
        return payloads

    def to_GA4_payload(self, measurement_id, cid_version=1):
        payloads = super().to_GA4_payload(measurement_id, cid_version)
        if self.get("category") and self.get("action"):
            payloads.append(
                {
                    # Following this article https://support.google.com/analytics/answer/11091026?hl=en&ref_topic=11091421#zippy=%2Cin-this-article
                    # <action> in the Universal Analytics property maps to <event_name> in the Google Analytics 4
                    # and 'event_category', 'event_label', and 'value' — along with their respective values — map to parameters with values.
                    "name": self["action"],
                    "params": _clean(
                        {
                            "event_category": self["category"],
                            "event_label": self.get("label", ""),
                            "value": self.get("value", ""),
                        }
                    ),
                }
            )
        for event in self.get("__secondary__", []):
            event["__request__"] = self["__request__"]
            payloads.extend(event.to_GA4_payload(measurement_id, cid_version)[1:])
            # ignore the first event (pageview)
            # which is already generated once
        return payloads

class Message(Event):
    """
    Logical document that can be sent through services.
    Processable fields: title, body, url, url_text, image, image_altext
    Optionally define default field values below.
    """

    DEFAULTS = {
        "title": "Notification Message",
        "url_text": "View Details",
        "image_altext": "<IMAGE>",
    }

    def __getattr__(self, attr):
        # virtual attributes
        if attr in ("title", "body", "url", "url_text", "image", "image_altext"):
            if attr in self:
                return self[attr]
            if attr in self.DEFAULTS:
                return self.DEFAULTS[attr]
            return ""
        raise AttributeError()

    def to_ADF(self):
        """
        Generate ADF for Atlassian Jira payload. Overwrite this to build differently.
        https://developer.atlassian.com/cloud/jira/platform/apis/document/playground/
        """
        adf = {"version": 1, "type": "doc", "content": []}
        if self.body:
            adf["content"].append({"type": "paragraph", "content": [{"type": "text", "text": self.body}]})
        if self.url:
            adf["content"].append(
                {
                    "type": "paragraph",
                    "content": [
                        {
                            "type": "text",
                            "text": self.url_text,
                            "marks": [{"type": "link", "attrs": {"href": self.url}}],
                        }
                    ],
                }
            )
        return adf

    def to_slack_payload(self):
        """
        Generate slack webhook notification payload.
        https://api.slack.com/messaging/composing/layouts
        """
        blocks = []
        if self.title:
            blocks.append(
                {
                    "type": "header",
                    "text": {
                        "type": "plain_text",
                        "text": ":sparkles: " + self.title,
                        "emoji": True,
                    },
                }
            )
            blocks.append({"type": "divider"})
        if self.body:
            body = {
                "type": "section",
                "text": {"type": "mrkdwn", "text": self.body},
            }
            if self.image:
                body["accessory"] = {
                    "type": "image",
                    "image_url": self.image,
                    "alt_text": self.image_altext,
                }
            blocks.append(body)
        if self.url:
            blocks.append(
                {
                    "type": "section",
                    "text": {"type": "mrkdwn", "text": f"*<{self.url}|{self.url_text}>*"},
                }
            )
        return {"attachments": [{"blocks": blocks}]}

    def to_jira_payload(self, profile):
        """
        Combine notification message with project profile to
        genereate jira issue tracking ticket request payload.
        """
        return {
            "fields": {
                "project": {"id": profile.project_id},
                "summary": self.title,
                "issuetype": {"id": profile.issuetype_id},
                "assignee": {"id": profile.assignee_id},
                "reporter": {"id": profile.reporter_id},
                "priority": {"id": "3"},
                "labels": [profile.label],
                "description": self.to_ADF(),
            }
        }

    def to_email_payload(self, sendfrom, sendto):
        """
        Build a MIMEMultipart message that can be sent as an email.
        https://docs.aws.amazon.com/ses/latest/DeveloperGuide/examples-send-using-smtp.html
        """
        # Create message container - the correct MIME type is multipart/alternative.
        msg = MIMEMultipart("alternative")
        msg["Subject"] = self.title
        # msg['From'] = email.utils.formataddr((SENDERNAME, SENDER))
        msg["From"] = sendfrom
        msg["To"] = sendto

        # Comment or delete the next line if you are not using a configuration set
        # msg.add_header('X-SES-CONFIGURATION-SET',CONFIGURATION_SET)

        # Record the MIME types of both parts - text/plain and text/html.
        part1 = MIMEText(self.body, "plain")
        part2 = MIMEText(
            f"""
        <!DOCTYPE html>
        <html>
            <head>
                <title>{self.title}</title>
            </head>
            <body>
                <p>{self.body}</p>
            </body>
        </html>""",
            "html",
        )

        # Attach parts into message container.
        # According to RFC 2046, the last part of a multipart message, in this case
        # the HTML message, is best and preferred.
        msg.attach(part1)
        msg.attach(part2)

        return msg


# Generic Python imports
from pathlib import Path
import logging
import tempfile

#Pipelining
import luigi

# Science Python imports
import pandas as pd

from run_hotspots_job import ParalleliselRunner
from compute_plifs import lcompute_plip

tempfile.tempdir =  "/home/jin76872/Desktop/Mih/Data/tmp_superstar_ghecom"

class Pipeline:
    def __init__(self, ensemble_name, ensemble_dir, aligned_protein_paths, hotspot_rotations=3000, hotspot_spheres=False, protonated=False, charged=False):
        """

        :param ensemble_name str: Name of the ensemble (eg 'p38a', 'p38a_out, etc')
        :param ensemble_dir: str or Path to where pipeline output will be stored (apart from the PLIP result files, which are saved with the pdbs).
        :param aligned_protein_paths: str or Paths to where the aligned input pdbs live
        :param hotspot_rotations: probe rotations for the hotspots algorithm
        :param hotspot_spheres: whether to use the hotspot spheres or not
        :param protonated: bool, whether the input structures have been previously protonated (True) or if protonation is to be done through the ccdc API
        """
        self.name = ensemble_name
        self.ens_dir = ensemble_dir
        self.protein_paths = aligned_protein_paths
        self.hs_rotations = hotspot_rotations
        self.hs_spheres = hotspot_spheres
        self.hs_protonated = protonated
        self.hs_charged=charged
        self.out_file = Path(self.ens_dir, f'{self.name}.csv')

        log_msg = f" Running ensemble with parameters: \n " \
                  f"Name: {self.name} \n" \
                  f"Ensemble directory: {self.ens_dir} \n" \
                  f"Hotspot rotations: {self.hs_rotations} \n" \
                  f"Hotspot use_spheres: {self.hs_spheres} \n" \
                  f"Structures pre-protonated: {self.hs_protonated} \n" \
                  f"Ensemble output file: {str(self.out_file.resolve())}"
        # create a log file for the run
        logging.basicConfig(filename=Path(self.ens_dir, f'{self.name}.log'), filemode='w', level=logging.INFO)
        logging.info(log_msg)

    def check_path(self, ppath):
        if not Path(ppath).exists():
            logging.warning(f"Expected output file {ppath} does not exist")
            return

        if type(ppath) is str:
            return ppath
        else:
            return str(ppath.resolve())

    def run_pipeline(self):

        str_paths = [str(x.resolve()) for x in self.protein_paths]

        plip_paths = [lcompute_plip(p) for p in str_paths]

        luigi.build([ParalleliselRunner(str_paths, self.hs_rotations, self.hs_charged, self.hs_protonated, self.hs_spheres)],
                               local_scheduler=True,
                               workers=17)
        if self.hs_spheres:
            hs_paths = [Path(p.parent, "fullsize_hotspots_{}_spheres".format(self.hs_rotations), "out.zip") for p in self.protein_paths]
        else:
            hs_paths = [Path(p.parent, "fullsize_hotspots_{}".format(self.hs_rotations), "out.zip") for p in self.protein_paths]

        plip_paths = [self.check_path(Path(pp.parent, 'report.xml')) for pp in self.protein_paths]
        hs_paths = [self.check_path(hp) for hp in hs_paths]

        # Now put them into the output file.
        df = pd.DataFrame()
        df['protein_path'] = str_paths
        df['hotspot_result_path'] = hs_paths
        df['plip_path'] = plip_paths

        df.to_csv(self.out_file)

if __name__ == "__main__":
    from reduce_KLIFS_structures import get_protein_paths

    # ens_root_path = Path("/home/jin76872/Desktop/Mih/Data/ensemble_maps_validation/p38a/p38a/reduced_ensemble")
    # ens_paths = [x for x in ens_root_path.glob("*/*.pdb")]

    ens_root_path = Path("/home/jin76872/Desktop/Mih/Data/ensemble_maps_validation/CDK2/CDK2")
    root_df = pd.read_csv(Path(ens_root_path.parent, "removed_duplicates.csv"))
    ens_paths = get_protein_paths(root_df, ens_root_path, mode="complex")

    p = Pipeline(ensemble_name="CK2_no_duplicates",
                 ensemble_dir=ens_root_path,
                 aligned_protein_paths=ens_paths,
                 hotspot_rotations=3000,
                 hotspot_spheres=False,
                 protonated=True)

    p.run_pipeline()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Alien Invasion Game

In this game the player controls a ship that appears at the bottom center of the screen.
The player can move the ship right and left using the arrow keys and shoot bullets using the spacebar.
When the game begins, a fleet of aliens fills the sky and moves across and down the screen.
The player shoots and destroys the aliens. If the player shoots all aliens, a new fleet appears
that moves faster than the previous one. If any alien hits the player's ship or reaches the
bottom of the screen, the player loses a ship. If the player loses three ships, the game ends.

Created on Wed Jun  5 10:26:42 2019

@author: Elisabeth

using Pygame modules
"""

# creating a Pygame window

import sys
import pygame
import game_functions as gf
from settings import Settings
from ship import Ship
from pygame.sprite import Group
from game_stats import GameStats
from button import Button
from scoreboard import Scoreboard

def run_game():
    # Initialize game and create a screen object
    pygame.init()

    ai_settings=Settings()
    #create display window called screen
    screen=pygame.display.set_mode((
            ai_settings.screen_width,ai_settings.screen_height))
    pygame.display.set_caption("Alien Invasion")

    # Make play button.
    play_button=Button(ai_settings, screen, "Play")

    # Create an instance to store game statistics and create a scoreboard
    stats= GameStats(ai_settings)
    sb=Scoreboard(ai_settings,screen, stats)

    # Add ship
    ship=Ship(ai_settings,screen)

    # Make a group to store bullets
    bullets=Group()

    # Maka group of aliens
    aliens=Group()
    gf.create_fleet(ai_settings,screen,ship,aliens)

    # Start the main loop for a game
    while True:
        #Respond to keypresses and mouse events.
        gf.check_events(ai_settings, screen, stats,sb, play_button, ship, aliens, bullets)

        if stats.game_active:
            # update position of game elements
            ship.update()
            gf.update_bullets(ai_settings, screen,stats, sb, ship, aliens,bullets)
            gf.update_aliens(ai_settings,screen,stats, sb, ship,aliens,bullets)

        # update screen
        gf.update_screen(ai_settings, screen,stats,sb, ship,aliens, bullets, play_button)

run_game()


import functools
import pathlib

def parse(text: str):
    pairs = text.split('\n\n')
    result = []
    for pair in pairs:
        a, b = pair.split()
        a = eval(a)
        b = eval(b)
        result.append((a, b))
    return result

def is_less(a, b):
    if isinstance(a, int) and isinstance(b, int):
        return a < b
    if isinstance(a, int):
        a = [a]
    if isinstance(b, int):
        b = [b]
    i = 0
    while i < len(a) and i < len(b) and a[i] == b[i]:
        i += 1
    if i < len(a) and i < len(b):
        if is_less(a[i], b[i]):
            return True
        if is_less(b[i], a[i]):
            return False
        i += 1
    return len(a) < len(b)

def part1(text: str):
    pairs = parse(text)
    result = 0
    for i, (a, b) in enumerate(pairs, 1):
        if is_less(a, b):
            result += i
    return result

def compare(a, b):
    if is_less(a, b):
        return -1
    return 1

def part2(text: str):
    pairs = parse(text)
    packets = [[[2]], [[6]]]
    for pair in pairs:
        packets.extend(pair)
    packets.sort(reverse=False, key=functools.cmp_to_key(compare))
    a = 1 + packets.index([[2]])
    b = 1 + packets.index([[6]])
    return a * b

def test():
    day = int(__file__[-5:-3])
    test_text = pathlib.Path(f"data/input-{day:02}-test.txt").read_text()
    real_text = pathlib.Path(f"data/input-{day:02}-real.txt").read_text()
    assert part1(test_text) == 13
    assert part1(real_text) == 5555
    assert part2(test_text) == 140
    assert part2(real_text) == 22852


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = [
        ('confrence', '0020_auto_20160426_1958'),
    ]

    operations = [
        migrations.AddField(
            model_name='submission',
            name='type',
            field=models.CharField(max_length=30, null=True),
        ),
    ]


import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('TkAgg')

plt.close("all")
ts = pd.Series(np.random.rand(1000),
               index=pd.date_range("1/1/2000", periods=1000))
ts = ts.cumsum()

df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index,
                  columns=["JavaScript", "Python", "Java", "Dart"])
df = df.cumsum()
plt.figure()
df.plot()
plt.legend(loc='best')

plt.show()


import pygame
import random
from config import WIDTH, HEIGHT, INIT, GAME, QUIT
from init_screen import init_screen
from Jogo import game_screen

pygame.init()
pygame.mixer.init()

window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('BOMBERFOX')

state = INIT
while state != QUIT:
    if state == INIT:
        state = init_screen(window)
    elif state == GAME:
        state = game_screen(window)
    else:
        state = QUIT

pygame.quit()


from uuid import UUID
import server.repository.tags_repository
import server.repository.tagging_repository
import server.service.tags_service
import pytest

@pytest.fixture()
def sut(monkeypatch):
    def return_mock_tags_repository_get_tag_id_by_name(rep, tagname):
        vals = dict()
        return vals[tagname]

    def return_mock_tags_repository_contains(rep, tagname):
        vals = dict()
        vals['scandinavian'] = False
        vals['underwear'] = False
        vals['books'] = False
        return vals[tagname]

    monkeypatch.setattr(server.repository.tags_repository.TagsRepository, 'get_tag_id_by_name',
                        return_mock_tags_repository_get_tag_id_by_name)
    monkeypatch.setattr(server.repository.tags_repository.TagsRepository, 'contains',
                        return_mock_tags_repository_contains)

    mock_tags_repository = server.repository.tags_repository.TagsRepository('')

    def return_mock_tagging_repository_get_shop_ids_list_by_tag_id(rep, tag_id):
        vals = dict()
        return vals[tag_id]

    monkeypatch.setattr(server.repository.tagging_repository.TaggingRepository, 'get_shop_ids_list_by_tag_id',
                        return_mock_tagging_repository_get_shop_ids_list_by_tag_id)
    mock_tagging_repository = server.repository.tagging_repository.TaggingRepository('')

    return server.service.tags_service.TagsService(mock_tags_repository, mock_tagging_repository)

def test_result_should_be_empty(sut):
    result = sut.get_shop_ids_list_by_tag_names(['scandinavian', 'underwear', 'books'])
    assert len(result) == 0


import uuid

from flask import Flask, Blueprint, jsonify, session, render_template, redirect, url_for

from forms import LinkForm
from settings import DB_URI
from models import db, UserLink

bp = Blueprint('links', __name__)

@bp.route('/', methods=['GET', 'POST'])
def index():
    form = LinkForm()

    if 'user_id' not in session:
        session['user_id'] = str(uuid.uuid4())

    short_link = None
    redirect_count = 0

    if form.validate_on_submit():
        existing_link = UserLink.query.filter_by(
            user_id=session['user_id'],
            link=form.link.data,
        ).first()

        if existing_link:
            short_link_id = existing_link.short_link
            redirect_count = existing_link.redirect_count
        else:
            link = UserLink(link=form.link.data,
                            short_link=abs(hash(form.link.data + session['user_id'])),
                            user_id=session['user_id'])
            db.session.add(link)
            db.session.commit()
            short_link_id = link.short_link
            redirect_count = link.redirect_count

        short_link = url_for('links.redirect_on_short_link', short_link=short_link_id, _external=True)

    return render_template('index.html',
                           title='Shorten your link',
                           form=form,
                           short_link=short_link,
                           redirect_count=redirect_count)

@bp.route('/<short_link>')
def redirect_on_short_link(short_link):
    link = UserLink.query.filter_by(short_link=short_link).first()
    print(link)
    if link:
        link.redirect_count += 1
        db.session.commit()
        return redirect(link.link)
    else:
        return render_template('404.html'), 404

@bp.route('/healthcheck')
def healthcheck():
    return jsonify({'status': 'ok'})

def create_app():
    app = Flask(__name__)
    app.register_blueprint(bp)
    app.config["SECRET_KEY"] = "secret"
    app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI

    db.init_app(app)
    return app