import math
import os
import random
import re

import bpy
from mathutils import Color, Euler, Vector
import numpy as np
from math import radians,tan,cos,sin

class Blender_helper:

    def __init__(self) -> None:
        pass

    def clear_scene(self):
        # Clear existing objects
        # Delete all objects
        bpy.ops.object.select_all(action='DESELECT')
        bpy.ops.object.select_by_type(type='MESH')
        bpy.ops.object.delete()

        # Delete all materials
        for material in bpy.data.materials:
            bpy.data.materials.remove(material)

        # Delete all cameras
        for camera in bpy.data.cameras:
            bpy.data.cameras.remove(camera)

        # Delete all lights
        for light in bpy.data.lights:
            bpy.data.lights.remove(light)

        # Delete all curves (bezier circles, etc.)
        for curve in bpy.data.curves:
            bpy.data.curves.remove(curve)

        # Delete all images
        for image in bpy.data.images:
            bpy.data.images.remove(image)

        # Delete all textures
        for texture in bpy.data.textures:
            bpy.data.textures.remove(texture)

        # Delete all nodes in node trees
        for material in bpy.data.materials:
            if material.use_nodes:
                material.node_tree.nodes.clear()

        # Delete all node groups
        for node_group in bpy.data.node_groups:
            bpy.data.node_groups.remove(node_group)

        return None

    def add_basic_scene(self):
        """
        This function adds  basic setup like, camera, light and floor for the objects

        use the functions below
        """

        # add background plane
        background_plane = self.add_background_plane(
            plane_size=5, name='Background_plane')

        # add light source and set it to desired place
        light_source = self.add_light_source(
            type='sun', obj_name='Sun', shadow=True)

        # add camera and set it to desired place
        camera = self.add_camera(camera_name='Camera')

        # add curves for following the path
        camera_track = self.add_beizer_curve(
            name='camera_track', location=(0, 0, 2.5), scale=(0, 0, 0))
        light_track = self.add_beizer_curve(
            name='light_track', location=(0, 0, 3), scale=(0, 0, 0))

        # Set the follow path constraints
        camera.constraints.new(type='FOLLOW_PATH')
        camera.constraints['Follow Path'].target = camera_track

        light_source.constraints.new(type='FOLLOW_PATH')
        light_source.constraints['Follow Path'].target = light_track

        return background_plane, light_source, camera, camera_track, light_track

    def add_regression_scene(self):
        # add background plane
        background_plane = self.add_background_plane(
            plane_size=5, name='Background_plane')

        # add light source and set it to desired place
        light_source = self.add_light_source(
            type='sun', obj_name='Sun', shadow=True)

        # add camera and set it to desired place
        camera = self.add_camera(camera_name='Camera')

        # add curves for following the path
        light_track = self.add_beizer_curve(
            name='light_track', location=(0, 0, 3), scale=(0, 0, 0))
        light_source.constraints.new(type='FOLLOW_PATH')
        light_source.constraints['Follow Path'].target = light_track

        camera_track = self.add_nurbs_path(name='camera_track')
        camera.constraints.new(type='FOLLOW_PATH')
        camera.constraints['Follow Path'].target = camera_track

        return background_plane, light_source, camera, camera_track, light_track

    def add_nurbs_path(self, name):
        bpy.ops.curve.primitive_nurbs_path_add(enter_editmode=False,align='WORLD',location=(0, 0, 1),rotation=(0,1.5707,0),scale=(1, 1, 1))

        bpy.data.objects['NurbsPath'].name = str(name)
        return bpy.data.objects[str(name)]

    def add_beizer_curve(self, name, location, scale):
        bpy.ops.curve.primitive_bezier_circle_add(enter_editmode=False,
                                                  align='WORLD',
                                                  location=location,
                                                  scale=scale
                                                  )
        bpy.data.objects['BezierCircle'].name = str(name)
        return bpy.data.objects[str(name)]

    def set_random_curve_height(curve_object_name, min_value, max_value):
        """ This function sets the height of the path curve for randomness/variation
        """
        z_value = round(random.uniform(min_value, max_value), 2)
        bpy.data.objects[str(curve_object_name)].location[2] = z_value

    def track_object(self, tracking_object_name, object_to_track):
        """ Function to make camera track the objects
        """
        tracking_object = bpy.data.objects[str(tracking_object_name)]
        if not str('Track To') in tracking_object.constraints.keys():
            tracking_object.constraints.new(type="TRACK_TO")
            tracking_object.constraints['Track To'].target = object_to_track
        else:
            tracking_object.constraints['Track To'].target = object_to_track

    def add_background_plane(self, plane_size, name):
        """
        This function adds the background plane to the scene along with a default material.
        """
        bpy.ops.mesh.primitive_plane_add(
            size=plane_size, enter_editmode=False, align='WORLD', location=(0, 0, 0))
        bpy.data.objects['Plane'].name = name
        return bpy.data.objects[str(name)]

    def add_camera(self, camera_name: str):
        """
        Adds a camera to the origin of the scene

        Keyword arguments:
        camera_name -- Name for the camera: str
        """
        bpy.ops.object.camera_add(
            location=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0))
        bpy.context.object.data.lens = 50
        bpy.context.object.data.name = str(camera_name)
        bpy.context.scene.camera = bpy.context.object
        return bpy.data.objects[str(camera_name)]

    def add_light_source(self, type: str, obj_name: str, shadow: bool):
        """
        Adds a light source with respective type to the origin of the scene.

        Keyword arguments:
        type -- The type of light source, default SUN, other: POINT,AREA,SPOT
        obj_name -- Name for the object: str
        shadow -- Enable or Disable shadows: bool

        returns: Light object
        """
        bpy.ops.object.light_add(
            type=str(type).upper(), radius=1, location=(0, 0, 0))
        bpy.context.object.data.name = str(obj_name)
        bpy.context.object.data.energy = 5
        bpy.context.object.data.use_shadow = shadow
        bpy.context.object.data.use_contact_shadow = shadow
        return bpy.data.objects[str(obj_name)]

    def set_camera(self, obj_name: str, position: tuple, rotation: tuple):
        """
        This function sets the camera to the desired location and orientation.
        """
        bpy.data.cameras[str(obj_name)].location = position
        bpy.data.cameras[str(obj_name)].Rotation = rotation

    def set_light_source(self, obj_name: str, position: tuple, rotation: tuple):
        """
        Sets the light source to the desired loacation,orientation and energy values.

        Keyword arguments:
        obj_name -- Name of the light object: str
        position -- Position values of the light object: (float,float,float)
        rotation -- Rotation values of the light object: (float,float,float)
        """
        bpy.data.lights[str(obj_name)].location = position
        bpy.data.lights[str(obj_name)].Rotation = rotation

    def get_object_names(self, background_plane_name, camera_track, light_track):
        """
        returns list of object names present in the scene after removing unnecessary things.

        Keyword arguments:
        background_plane_name--name of the background plane object: str
        """

        obj_names = bpy.context.scene.objects.keys()
        for name in bpy.data.cameras.keys():
            obj_names.remove(name)
        for name in bpy.data.lights.keys():
            obj_names.remove(name)

        obj_names.remove(str(background_plane_name))
        obj_names.remove(str(camera_track))
        obj_names.remove(str(light_track))
        return sorted(obj_names)

    def hide_objects(self, obj_names, hide: bool):
        """
        hides the objects present in the scene during rendering.
        """
        for name in obj_names:
            bpy.context.scene.objects[name].hide_render = hide

    def random_placement(self,obj, camera, min_distance_factor):
        scene = bpy.context.scene
        # Get the current camera field of view in degrees if not provided

        fov_radians = camera.data.angle
        print(f"\nFov radians is : {fov_radians} ")
        # Calculate camera height and width for the current FOV
        aspect_ratio = camera.data.sensor_width / camera.data.sensor_height
        aspect_ratio = scene.render.resolution_x/scene.render.resolution_y
        camera_z_loc = camera.matrix_world.decompose()[0][2]
        print(f"Camera Z location is : {camera_z_loc}")
        camera_height = 2 * camera_z_loc * tan(fov_radians / 2)
        camera_width = camera_height * aspect_ratio

        # Calculate the size of the object within the camera frame (e.g., 20% of camera width)
        object_size_percentage = 0.1
        object_width = object_size_percentage * camera_width

        print(f"Object width is {object_width}")
        # Calculate the maximum distance for the object based on its size and desired size within the frame
        max_distance = object_width / (2 * tan(fov_radians / 2))

        # Calculate the minimum distance based on a factor of the maximum distance
        min_distance = min_distance_factor * max_distance

        # Calculate random distance within the range
        print(f'Maximum distance is {max_distance}, Minimum distance is : {min_distance}')
        random_distance = random.uniform(min_distance, max_distance)

        # Calculate random angle (azimuth) around the camera
        random_angle = random.uniform(0, 2 * np.pi)

        # Calculate the object position relative to the camera
        random_x = random_distance * cos(random_angle)
        random_y = random_distance * sin(random_angle)
        random_z = obj.dimensions.z / 2

        print(f'Original x is {obj.location.x} : Random x is {random_x} Original y is : {obj.location.y} Random y is {random_y}')
        # Set the object location
        obj.location.x = random_x
        obj.location.y = random_y

    def import_objects_obj_format(self, models_dir, target_object_size, dataset_name):
        """
        adds objects from cad format(.obj format) along with their materials.

        models_dir: path for the models
        target_size: controls the size of the objects
        dataset_name: Name of the dataset, Robocup or YCB
        """

        object_names = []
        obj_files = []
        # Iterate through all subdirectories in the base path
        for root, dirs, files in os.walk(models_dir):
            # Iterate through all files in the current directory
            for file in files:
                # Check if the file is an OBJ file
                if file.endswith(".obj"):
                    # Construct the full path to the OBJ file
                    object_file = os.path.join(root, file)
                    obj_files.append(object_file)

        # Clear the scene
        bpy.ops.wm.read_factory_settings()
        self.clear_scene()

        for idx, object_file in enumerate(obj_files):

            if dataset_name == 'robocup':
                obj_name = object_file.split('/')[-1].split('.')[0]
            elif dataset_name == 'ycb':
                obj_name = "".join(object_file.split('/')[-3].split('_')[1::])

            # Import the OBJ file
            bpy.ops.import_scene.obj(filepath=object_file)

            # Set the active object to the imported object
            imported_object = bpy.context.selected_objects[-1]
            # Rename the new object
            imported_object.name = str(obj_name)
            object_names.append(imported_object.name)

            if imported_object is not None:
                # Get the maximum dimension of the object
                max_dimension = max(imported_object.dimensions)

                # Calculate the scale factor to achieve the target size
                scale_factor = target_object_size / max_dimension

                # Apply scale transformation to the object
                imported_object.scale = (
                    scale_factor, scale_factor, scale_factor)

                # Set the origin of the object to its center
                imported_object.select_set(True)
                bpy.context.view_layer.objects.active = imported_object
                bpy.ops.object.origin_set(
                    type='ORIGIN_CENTER_OF_VOLUME', center='BOUNDS')
                imported_object.location = (0.0, 0.0, 0.0)
                imported_object.rotation_euler = (0.0, 0.0, 0.0)
            else:
                print("Error: Object is None")

        return None

    def get_object(self, obj_name):
        """
        Returns the object present in the scene.
        """
        return bpy.context.scene.objects[obj_name]

    def set_random_rotation(self, obj_to_change):
        """
        Applies a random rotation to the given object.

        Keyword arguments:
        obj_to_change: blender object
        """

        random_rotat_values = [
            random.random()*2*math.pi, random.random()*2*math.pi, random.random()*2*math.pi]
        obj_to_change.rotation_euler = Euler(random_rotat_values, 'XYZ')

    def set_random_lighting(self, light_source_name, min_value, max_value):
        """
        Applies random light intensities to the scene.

        Keyword arguments:
        light_source_name:str
        min_value: float
        max_value: float
        """
        # bpy.data.lights[str(light_source_name)].energy = round(random.uniform(min_value,max_value),2)
        bpy.data.lights[str(light_source_name)].energy = random.uniform(
            min_value, max_value)

    def set_random_focal_length(self, camera_name, min_value, max_value):
        value = random.randint(min_value, max_value)
        bpy.data.cameras[str(camera_name)].lens = float(value)

    def set_random_background_color(self, obj_name):
        """Applies Materials randomly to the object, Changes specially the Principled BSDf Base color values for the given object's material.

        Keyword arguments:
        obj_name: str
        """
        material_to_change = bpy.data.objects[str(obj_name)].active_material
        # bpy.data.materials[material_name]
        color = Color()
        hue = random.random()  # Random hue between 0 and 1
        color.hsv = (hue, 0.85, 0.85)
        rgba = [color.r, color.g, color.b, 1]
        material_to_change.node_tree.nodes['Principled BSDF'].inputs[0].default_value = rgba

    def set_render_parameters(self, device, render_engine, res_x, res_y, num_samples):
        """Sets the active scene with the specified render parameters.

        Keyword arguments:
        device-- Type of the device CPU or GPU : str
        res_x-- resolution width of the image to render: int
        res_y-- resolution height of the image to render: int
        num_samples-- number of render samples
        """
        scene = bpy.context.scene
        scene.render.engine = str(render_engine)
        scene.cycles.device = str(device).upper()
        scene.render.resolution_x = int(res_x)
        scene.render.resolution_y = int(res_y)
        scene.render.resolution_percentage = 100
        scene.cycles.samples = num_samples
        scene.render.image_settings.file_format = "PNG"

    def get_texture_map_paths(self, texture_folder):
        """Returns paths for the images which can be used for image textures.

        Keyword arguments:
        texture_folder-- Path for the folder containing the images.
        """
        texture_dict = {
            "normal_map": None,
            "base_color": None,
            "disp_map": None,
            "metal_map": None,
            "roughness_map": None
        }

        files = os.listdir(path=texture_folder)
        for file in files:
            # Match the files and seperate
            nrm_match = re.search(r'\wnormal', file) or re.search(
                r'\wNRM', file) or re.search(r'\wnor', file)
            base_match = re.search(r'\wbasecolor', file) or re.search(
                r'\wCOL_VAR1', file) or re.search(r'\wdiff', file) or re.search(r'\wcol', file)
            disp_match = re.search(r'\wDISP_4K', file) or re.search(
                r'\wheight', file) or re.search(r'\wdisplacement', file) or re.search(r'\wdisp', file)
            metal_match = re.search(r'\wmetallic', file) or re.search(
                r'\wREFL', file) or re.search(r'\wmetal', file)
            rough_match = re.search(r'\wroughness', file) or re.search(
                r'\wGLOSS', file) or re.search(r'\wrough', file)

            if nrm_match:
                normal_map = os.path.join(texture_folder, file)
                texture_dict['normal_map'] = normal_map

            if base_match:
                base_color = os.path.join(texture_folder, file)
                texture_dict['base_color'] = base_color
            if disp_match:
                disp_map = os.path.join(texture_folder, file)
                texture_dict['disp_map'] = disp_map
            if metal_match:
                metal_map = os.path.join(texture_folder, file)
                texture_dict['metal_map'] = metal_map
            if rough_match:
                roughness_map = os.path.join(texture_folder, file)
                texture_dict['roughness_map'] = roughness_map
        return texture_dict

    def get_texture_paths(self, texture_dir):
        """Gets the paths for all the texture_folders from the main textures folder and returns them as a list

        Keyword arguments:
        texture_dir-- path for the directory containig the textures.
        """
        bnames = os.listdir(texture_dir)
        for i, cs in enumerate(zip(*bnames)):
            if len(set(cs)) != 1:
                break
        for _i, cs in enumerate(zip(*[b[::-1] for b in bnames])):
            if len(set(cs)) != 1:
                break
        texture_paths = [os.path.join(texture_dir, bname+'/')
                         for bname in bnames if not bname.endswith('.md')]
        return texture_paths

    def set_random_pbr_img_textures(self, textures_path, obj_name, scale):
        """Applies image textures randomly from the available images to the specified object.

        Keyword arguments:
        texture_paths-- list of paths for the texture folders.
        obj_name-- name of the object to change the materail/texture
        """

        texture_paths = self.get_texture_paths(textures_path)
        # print("Texture paths : ",texture_paths)
        texture_path = random.choice(texture_paths)

        material_name = str(texture_path.split('/')[-2])
        texture_dict = self.get_texture_map_paths(texture_folder=texture_path)

        # create a new material with the name.
        material = bpy.data.materials.new(
            name=material_name)  # Change name everytime
        material.use_nodes = True

        # Create the nodes for the material

        # Nodes for controlling the texture
        texture_coordinate = material.node_tree.nodes.new(
            type="ShaderNodeTexCoord")
        mapping_node = material.node_tree.nodes.new(type="ShaderNodeMapping")
        mapping_node.inputs['Scale'].default_value = (
            scale, scale, scale)  # Control the scale of the texture

        # Create vector nodes
        normal_map = material.node_tree.nodes.new(type="ShaderNodeNormalMap")
        displacement_map = material.node_tree.nodes.new(
            type="ShaderNodeDisplacement")
        bump_map = material.node_tree.nodes.new(type="ShaderNodeBump")
        invert_node = material.node_tree.nodes.new(type="ShaderNodeInvert")

        # Nodes for principled bsdf and output
        principled_bsdf = material.node_tree.nodes['Principled BSDF']
        material_output = material.node_tree.nodes['Material Output']

        # Connect the nodes
        material.node_tree.links.new(
            texture_coordinate.outputs['UV'], mapping_node.inputs['Vector'])
        # Nodes for image textures
        # Base color
        if texture_dict['base_color'] != None:
            base_color_img = material.node_tree.nodes.new(
                type="ShaderNodeTexImage")
            base_color_img.image = bpy.data.images.load(
                texture_dict['base_color'])
            material.node_tree.links.new(
                mapping_node.outputs['Vector'], base_color_img.inputs['Vector'])
            material.node_tree.links.new(
                base_color_img.outputs['Color'], principled_bsdf.inputs['Base Color'])

        # Normal map
        if texture_dict['normal_map'] != None:
            normal_img = material.node_tree.nodes.new(
                type="ShaderNodeTexImage")
            normal_img.image = bpy.data.images.load(texture_dict['normal_map'])
            material.node_tree.links.new(
                mapping_node.outputs['Vector'], normal_img.inputs['Vector'])
            material.node_tree.links.new(
                normal_img.outputs['Color'], normal_map.inputs['Color'])
            material.node_tree.links.new(
                normal_map.outputs['Normal'], principled_bsdf.inputs['Normal'])

        # Displacement map
        if texture_dict['disp_map'] != None:
            displacement_img = material.node_tree.nodes.new(
                type="ShaderNodeTexImage")
            displacement_img.image = bpy.data.images.load(
                texture_dict['disp_map'])
            material.node_tree.links.new(
                mapping_node.outputs['Vector'], displacement_img.inputs['Vector'])
            material.node_tree.links.new(
                displacement_img.outputs['Color'], displacement_map.inputs['Height'])
            material.node_tree.links.new(
                displacement_map.outputs['Displacement'], material_output.inputs['Displacement'])

        # Roughness map
        if texture_dict['roughness_map'] != None:
            roughness_img = material.node_tree.nodes.new(
                type="ShaderNodeTexImage")
            roughness_img.image = bpy.data.images.load(
                texture_dict['roughness_map'])
            material.node_tree.links.new(
                mapping_node.outputs['Vector'], roughness_img.inputs['Vector'])
            material.node_tree.links.new(
                roughness_img.outputs['Color'], invert_node.inputs['Color'])
            material.node_tree.links.new(
                invert_node.outputs['Color'], principled_bsdf.inputs['Roughness'])

        # Metal map
        if texture_dict['metal_map'] != None:
            metallic_img = material.node_tree.nodes.new(
                type="ShaderNodeTexImage")
            metallic_img.image = bpy.data.images.load(
                texture_dict['metal_map'])
            material.node_tree.links.new(
                mapping_node.outputs['Vector'], metallic_img.inputs['Vector'])
            material.node_tree.links.new(
                metallic_img.outputs['Color'], principled_bsdf.inputs['Metallic'])

        # Set material final output
        material.node_tree.links.new(
            principled_bsdf.outputs['BSDF'], material_output.inputs['Surface'])

        # Set the material to the object
        obj = bpy.data.objects[obj_name]

        if obj.data.materials:
            obj.data.materials.append(material)
            obj.active_material = bpy.data.materials[material_name]
        else:
            obj.data.materials.append(material)
            obj.active_material = material

        # print("List of materials : ", bpy.data.materials.keys())

        return material

    def add_blur_dof(self, blur_value,focus_background_name):
        """Adds blur effect to the images using depth of field parameter of the camera.

        Keyword arguments:
        focus_background_name: name of the backgrond plane
        """

        camera = bpy.data.objects['Camera']
        camera.data.dof.use_dof = True
        # camera.data.dof.focus_object = bpy.data.objects[str(focus_background_name)]
        camera.data.dof.focus_distance = blur_value

    def deform_objects(self, obj_to_deform):
        """ Deforms the object using simple deform modifer properties

        Keyword arguments:
            obj_to_deform: blender object to deform
        """

        if not 'simple_deform' in obj_to_deform.modifiers.keys():
            obj_to_deform.modifiers.new(
                name="simple_deform", type='SIMPLE_DEFORM')
            deform_modifier = obj_to_deform.modifiers['simple_deform']
        else:
            deform_modifier = obj_to_deform.modifiers['simple_deform']
        deform_modifier.deform_method = 'BEND'

        deform_modifier.deform_axis = 'Z'
        deform_modifier.angle = random.uniform(-0.523, -3.14)

    def adjust_object_position(self, obj, target_size, background_plane):
        """
        moves the objects above the background plane.

        Keyword arguments:
        obj: blender mesh object
        target_size: gap between the object and the background plane.
        """
        max_dim = np.max(obj.dimensions)
        lift_distance = (max_dim + target_size) / 2
        obj.location.z = lift_distance

    def track_camera_object(self, object_to_track):
        camera = bpy.data.objects['Camera']
        if not str('Track To') in camera.constraints.keys():
            camera.constraints.new(type="TRACK_TO")
            camera.constraints['Track To'].target = object_to_track

    def track_light_object(self, object_to_track):
        light = bpy.data.objects['Sun']
        if not str('Track To') in light.constraints.keys():
            light.constraints.new(type="TRACK_TO")
            light.constraints['Track To'].target = object_to_track

    def random_camera_position(self, camera_name):
        camera = bpy.data.objects[str(camera_name)]
        camera.constraints['Follow Path'].offset = random.randint(0, 100)

    def random_light_position(self, light_name):
        light = bpy.data.objects[str(light_name)]
        light.constraints['Follow Path'].offset = random.randint(0, 100)

    def reset_obj_location_rotation(self, obj):
        obj.location = (0.0, 0.0, 0.0)
        obj.rotation_euler = (0.0, 0.0, 0.0)

    def set_camera_postion_on_path(self,camera_name,distance_value):
        camera = bpy.data.objects[str(camera_name)]
        camera.constraints['Follow Path'].offset = distance_value


from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.utils import to_categorical
from keras.optimizers import SGD
import pandas as pd
import numpy as np
from utilfunction import *
from imblearn.over_sampling import SMOTE
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.linear_model import LogisticRegression
from sklearn.dummy import DummyClassifier
import matplotlib.pyplot as plt
from keras.layers import Dense, Dropout, BatchNormalization, Activation

df = pd.read_pickle('sedentarismdata.pkl')
df = delete_user(df,52)
df = METcalculation(df)
#df = delete_sleep_hours(df)
df = makeSedentaryClasses(df)
df = makeDummies(df)
df = shift_hours(df,1, 'classification')
df.drop(['hourofday'], axis=1, inplace=True)
df = get_user_data(df,10)

def get_model():
    estimator = Sequential([
        Dense(256,input_dim=28,kernel_initializer='uniform', kernel_regularizer='l2',use_bias=False),
        BatchNormalization(),
        Activation('relu'),
        Dense(128, kernel_initializer='uniform', kernel_regularizer='l2',use_bias=False),
        BatchNormalization(),
        Activation('relu'),
        Dense(64, kernel_initializer='uniform',kernel_regularizer='l2',use_bias=False),
        BatchNormalization(),
        Activation('relu'),
        Dense(32, kernel_initializer='uniform', kernel_regularizer='l2',use_bias=False),
        BatchNormalization(),
        Activation('relu'),
        Dense(1, activation='sigmoid')
        ])
    estimator.compile(loss='binary_crossentropy',
                  optimizer='adam',
                  metrics=['binary_accuracy'])
    return estimator

X, y = get_X_y_classification(df, True)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

'''
#codigo para usar oversampling
columns = X.columns
sm = SMOTE(random_state=12, ratio='all')
X_train, y_train = sm.fit_sample(X_train, y_train)
X_train = pd.DataFrame(X_train, columns=columns)
y_train = pd.Series(y_train)

clf = LogisticRegression(random_state=0, solver='lbfgs', max_iter=350)
clf.fit(X_train, y_train)

print(clf.score(X_test, y_test))
print(classification_report(y_test, clf.predict(X_test)))
'''

clf = LogisticRegression(solver='liblinear', max_iter=400, class_weight='balanced')
model = create_model(clf)
print('LOGREG\n')
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))

print('DNN\n')
clf = KerasClassifier(get_model,epochs=20, batch_size=32, verbose=2, validation_data=(X_test,y_test))
modelnn = create_model(clf)
modelnn.fit(X_train, y_train)
y_pred = modelnn.predict(X_test)
print(classification_report(y_test, y_pred))

print(classification_report(y_test, DummyClassifier(strategy='most_frequent', random_state=7)
                           .fit(X_train,y_train).predict(X_test)))
#model.summary()
plt.close()
cols = X.columns
nums = np.arange(1,33)
a = clf.coef_
plt.plot(nums,a.reshape(-1,1))
plt.xticks(nums, cols, rotation='vertical')
plt.grid(True)
plt.show()


import datetime
import uuid

import six
import sqlalchemy as sa
from sqlalchemy.sql import func as sfunc

from zaqar import storage
from zaqar.storage import pooling
from zaqar.storage import sqlalchemy
from zaqar.storage.sqlalchemy import controllers
from zaqar.storage.sqlalchemy import tables
from zaqar.storage.sqlalchemy import utils
from zaqar import tests as testing
from zaqar.tests.queues.storage import base

class SqlalchemyTableTests(testing.TestBase):

    def setUp(self):
        super(SqlalchemyTableTests, self).setUp()
        self.engine = sa.create_engine('sqlite:///:memory:')
        tables.metadata.create_all(self.engine, checkfirst=True)

    def test_table_queries(self):
        self.engine.execute(tables.Queues.insert(), id=1, project='test',
                            name='zaqar', metadata=utils.json_encode('aaaa'))
        self.engine.execute(tables.Messages.insert(), id=1, qid=1, ttl=10,
                            body=utils.json_encode('bbbb'), client='a',
                            created=datetime.datetime.now())
        self.engine.execute(tables.Claims.insert(), id=1, qid=1, ttl=10,
                            created=datetime.datetime.now())

        rs = self.engine.execute(tables.Claims.select())
        row = rs.fetchone()

        self.assertEqual(row.id, 1)
        self.assertEqual(row.qid, 1)
        self.assertEqual(row.ttl, 10)

        self.engine.execute(tables.Claims.delete(tables.Claims.c.id == 1))
        rs = self.engine.execute(tables.Claims.select())
        row = rs.fetchone()

        self.assertIsNone(row)

class SqlalchemyQueueTests(base.QueueControllerTest):
    driver_class = sqlalchemy.DataDriver
    controller_class = controllers.QueueController

class SqlalchemyMessageTests(base.MessageControllerTest):
    driver_class = sqlalchemy.DataDriver
    controller_class = controllers.MessageController

    def test_expired_messages_be_deleted(self):
        messages = [{'body': 3.14, 'ttl': 0}, {'body': 0.618, 'ttl': 600}]
        client_uuid = uuid.uuid4()

        [msgid_expired, msgid] = self.controller.post(self.queue_name,
                                                      messages,
                                                      project=self.project,
                                                      client_uuid=client_uuid)
        mid = utils.msgid_decode(msgid_expired)

        def _get(count=False):
            j = sa.join(tables.Messages, tables.Queues,
                        tables.Messages.c.qid == tables.Queues.c.id)

            sel = sa.sql.select([tables.Messages.c.body,
                                tables.Messages.c.ttl,
                                tables.Messages.c.created])

            if count:
                sel = sa.sql.select([sfunc.count(tables.Messages.c.id)])

            sel = sel.select_from(j)
            and_stmt = [tables.Messages.c.id == mid,
                        tables.Queues.c.name == self.queue_name,
                        tables.Queues.c.project == self.project]

            sel = sel.where(sa.and_(*and_stmt))

            return self.driver.get(sel)

        [count] = _get(count=True)
        self.assertEqual(count, 1)

        # Expired messages will be removed from db until next Post
        message = [{'body': 3.14, 'ttl': 300}]
        self.controller.post(self.queue_name,
                             message,
                             project=self.project,
                             client_uuid=client_uuid)

        with testing.expect(utils.NoResult):
            _get()

class SqlalchemyClaimTests(base.ClaimControllerTest):
    driver_class = sqlalchemy.DataDriver
    controller_class = controllers.ClaimController

    def test_delete_message_expired_claim(self):
        # NOTE(flaper87): Several reasons to do this:
        # The sqla driver is deprecated
        # It's not optimized
        # mocking utcnow mocks the driver too, which
        # requires to put sleeps in the test
        self.skip("Fix sqlalchemy driver")

class SqlalchemyPoolsTest(base.PoolsControllerTest):
    driver_class = sqlalchemy.ControlDriver
    controller_class = controllers.PoolsController

    def setUp(self):
        super(SqlalchemyPoolsTest, self).setUp()
        self.load_conf('wsgi_sqlalchemy.conf')

    def tearDown(self):
        super(SqlalchemyPoolsTest, self).tearDown()

class SqlalchemyCatalogueTest(base.CatalogueControllerTest):
    driver_class = sqlalchemy.ControlDriver
    controller_class = controllers.CatalogueController

    def setUp(self):
        super(SqlalchemyCatalogueTest, self).setUp()
        self.load_conf('wsgi_sqlalchemy.conf')

    def tearDown(self):
        super(SqlalchemyCatalogueTest, self).tearDown()

class PooledMessageTests(base.MessageControllerTest):
    config_file = 'wsgi_sqlalchemy_pooled.conf'
    controller_class = pooling.MessageController
    driver_class = pooling.DataDriver
    control_driver_class = sqlalchemy.ControlDriver
    controller_base_class = storage.Message

class PooledClaimsTests(base.ClaimControllerTest):
    config_file = 'wsgi_sqlalchemy_pooled.conf'
    controller_class = pooling.ClaimController
    driver_class = pooling.DataDriver
    control_driver_class = sqlalchemy.ControlDriver
    controller_base_class = storage.Claim

    def test_delete_message_expired_claim(self):
        # NOTE(flaper87): Several reasons to do this:
        # The sqla driver is deprecated
        # It's not optimized
        # mocking utcnow mocks the driver too, which
        # requires to put sleeps in the test
        self.skip("Fix sqlalchemy driver")

class PooledQueueTests(base.QueueControllerTest):
    config_file = 'wsgi_sqlalchemy_pooled.conf'
    controller_class = pooling.QueueController
    driver_class = pooling.DataDriver
    control_driver_class = sqlalchemy.ControlDriver
    controller_base_class = storage.Queue

class MsgidTests(testing.TestBase):

    def test_encode(self):
        if six.PY2:
            ids = [3, long(1), 0]
        elif six.PY3:
            ids = [3, 1, 0]
        msgids = ['5c693a50', '5c693a52', '5c693a53']
        for msgid, id in zip(msgids, ids):
            self.assertEqual(msgid, utils.msgid_encode(id))

    def test_decode(self):
        msgids = ['5c693a50', '5c693a52', '5c693a53', '']
        ids = [3, 1, 0, None]
        for msgid, id in zip(msgids, ids):
            self.assertEqual(id, utils.msgid_decode(msgid))


import numpy as np
from libraries import transformations as transf
from skeleton.Skeleton import get_xyz_data_for_bone_by_name
from tools.QuaternionTools import qv_mult
import Skeleton

xaxis = np.array([1, 0, 0])

def get_quat_data_for_bone_by_name(data, name, quat_suffixes):
    colW = name + quat_suffixes[0]
    colI = name + quat_suffixes[1]
    colJ = name + quat_suffixes[2]
    colK = name + quat_suffixes[3]
    colD = name + quat_suffixes[4]
    data_vecs = data[[colW, colI, colJ, colK, colD]]
    return data_vecs

class Bone:
    def __init__(self, name='n/a', length=1.0, quat=None):
        self.name = name
        self.length = length
        self.quat = quat

        self.parent = None
        self.children = []

        self.axis = None
        self.pos3D = np.zeros(3)  # used for plotting

    def add_child(self, child):
        self.children.append(child)
        child.parent = self

class SkeletonQuat(Skeleton.Skeleton):
    def __init__(self, columns, features,
                 node_origin, links,
                 node_suffixes_xyz, node_suffixes_quat):
        Skeleton.Skeleton.__init__(self, columns, features)
        self.pos3D = np.zeros(3)
        self.bone_map = {}
        self.root = None
        # self.metadata = metadata
        self.node_origin = node_origin
        self.links = links
        self.node_suffixes_xyz = node_suffixes_xyz
        self.node_suffixes_quat = node_suffixes_quat
        self.setup_bone_graph()

    def add_bone(self, name):
        bone = Bone(name=name, length=0, quat=None)
        self.bone_map[name] = bone
        return bone

    def get_quat_data_for_bone_by_name(self, data, name, quat_suffixes):
        colW = name + quat_suffixes[0]
        colI = name + quat_suffixes[1]
        colJ = name + quat_suffixes[2]
        colK = name + quat_suffixes[3]
        colD = name + quat_suffixes[4]
        data_vecs = data[[self.label_map[colW.lower()],
                          self.label_map[colI.lower()],
                          self.label_map[colJ.lower()],
                          self.label_map[colK.lower()],
                          self.label_map[colD.lower()]]]
        return data_vecs

    def create_bone_graph(self, links):

        for link in links:

            # create the bones if they do not already exist:
            for i in xrange(2):
                bone_name = link[i]
                if not self.bone_map.has_key(bone_name):
                    self.add_bone(name=bone_name)

            # link the bones as parent-child:
            bone = self.bone_map[link[0]]
            bone2 = self.bone_map[link[1]]
            bone.add_child(bone2)

    def setup_bone_graph(self):
        self.bone_map = {}
        self.root = self.add_bone(self.node_origin)
        self.create_bone_graph(self.links)

    def set_data(self, data_row, recreate_bone_graph=True):

        # set origin position:
        self.pos3D = self.get_xyz_data_for_bone_by_name(data_row,
                                                        self.node_origin,
                                                        self.node_suffixes_xyz)

        # set up the bone graph if necessary:
        if recreate_bone_graph:
            self.setup_bone_graph()

        # set the bone quat data:
        for bone in self.bone_map.itervalues():
            if bone.name == self.node_origin:  # TEST EXTRA QUAT BONE:
                continue
            data_quat = self.get_quat_data_for_bone_by_name(data_row,
                                                            bone.name,
                                                            self.node_suffixes_quat)
            #print bone.name, data_quat
            if bone.name == self.node_origin:
                data_quat[4] = 1
            bone.quat = data_quat[0:4]#transf.unit_vector(data_quat[0:4])# unit...
            bone.length = data_quat[4]

        # update the layout (i.e. the 3D positions) for plotting
        self.layout(self.root)

    def layout_offset(self, root, offset):
        root.pos3D += offset
        for child in root.children:
            self.layout_offset(child, offset)

    def layout(self, root):
        if root.parent is None:
            # 04.04.2017 Daniel: set root of skeleton to zero location, then shift everything in the end
            # this way, we properly separate 3d location of the whole hand from the relative locations
            root.pos3D = np.array([0., 0., 0.])  # self.pos3D
            root.axis = transf.unit_vector(root.pos3D - xaxis)
        else:
            root.pos3D = root.parent.pos3D + root.length * qv_mult(root.quat, root.parent.axis)
            root.axis = transf.unit_vector(root.pos3D - root.parent.pos3D)

        for child in root.children:
            self.layout(child)

        if root.parent is None:
            self.layout_offset(root, self.pos3D)

    def plot_rec(self, ax, root, showJoints, c, cmap, lw, alpha, show_bone_names=False):
        if show_bone_names:
            ax.text(root.pos3D[0], root.pos3D[1], root.pos3D[2], root.name)
        if showJoints:
            ax.scatter(root.pos3D[0], root.pos3D[1], root.pos3D[2], c=c, lw=lw, alpha=alpha)
        for child in root.children:
            ax.plot([root.pos3D[0], child.pos3D[0]],
                    [root.pos3D[1], child.pos3D[1]],
                    [root.pos3D[2], child.pos3D[2]],
                    c=c, lw=lw, alpha=alpha)
            self.plot_rec(ax, child, showJoints, c, cmap, lw, alpha, show_bone_names=show_bone_names)

    def plot(self, ax, data, showJoints=False, c='k', cmap=None, lw=1, alpha=1):
        rows, cols = data.shape
        for row in xrange(rows):
            cs = None
            if cmap != None:
                cs = [cmap(v)[0:3] for v in np.linspace(0, 1, rows)]
            self.set_data(data[row, :], recreate_bone_graph=row == 0)
            self.plot_rec(ax, self.root, showJoints, c, cs, lw, alpha)

    def plot2D_rec(self, ax, root, showJoints, c, lw, alpha,
                   show_bone_names=False, scale=1, offset=[0, 0]):
        if show_bone_names:
            ax.text(root.pos3D[0] * scale + offset[0], root.pos3D[1] * scale + offset[1], root.name)
        if showJoints:
            ax.scatter(root.pos3D[0] * scale + offset[0], root.pos3D[1] * scale + offset[1],
                       c=c, lw=lw, alpha=alpha)
        for child in root.children:
            ax.plot([root.pos3D[0] * scale + offset[0], child.pos3D[0] * scale + offset[0]],
                    [root.pos3D[1] * scale + offset[1], child.pos3D[1] * scale + offset[1]],
                    c=c, lw=lw, alpha=alpha)
            self.plot2D_rec(ax, child, showJoints, c, lw, alpha,
                            show_bone_names=show_bone_names, scale=scale, offset=offset)

    def plot2D(self, ax, data, showJoints=False, c='k', lw=1,
               alpha=1, scale=1, offsets=None):
        rows, cols = data.shape
        for row in xrange(rows):
            if not isinstance(c, basestring):
                c_here = c[row]
            else:
                c_here = c

            self.set_data(data[row, :], recreate_bone_graph=row == 0)
            offset_row = [0, 0] if offsets is None else offsets[row, :]
            self.plot2D_rec(ax, self.root, showJoints, c_here, lw, alpha,
                            scale=scale, offset=offset_row)

    def convert_data_quat_to_xyz(self, data, recreate_bone_graph=False):
        rows, cols = data.shape

        data_xyz = np.zeros((rows, len(self.bone_map) * 3))
        for row in xrange(rows):

            self.set_data(data[row, :], recreate_bone_graph=(recreate_bone_graph and row == 0))
            col_i = 0
            for bone_name in self.features:
                bone = self.bone_map[bone_name]
                # print bone_name,  bone.pos3D
                data_xyz[row, col_i:col_i + 3] = bone.pos3D
                col_i += 3

        return data_xyz