"""
This is an empty docstring.
"""

import numpy as np


class Constraint:
    def __init__(self, free):
        self.free = free # poner dtype boolean

    def is_free(self, c_component):
        return self.free[c_component]

    def print_constraint(self):
        print('{}'.format(self.free))


class Force:
    def __init__(self, force_components):
        self.components = np.array(force_components, dtype='float64')

    def get_component(self, c_component):
        return self.components[c_component]

    def print_force(self):
        print('{}'.format(self.components))



class Node:
    def __init__(self, position):
        self.position = np.array(position, dtype='float64')
        self.displacement = None
        self.constraint = None
        self.forces = None
        self.dof_numbers = np.zeros((3,1))

    def set_displacement(self, new_displacement):
        self.displacement = np.array(new_displacement, dtype='float64')

    def set_constraint(self, new_constraint):
        self.constraint = new_constraint

    def set_force(self, new_force):
        self.force = new_force

    def get_position(self):
        return self.position

    def get_displacement(self):
        return self.displacement

    def get_constraint(self):
        return self.constraint

    def get_force(self):
        return self.force

    def get_dof_numbers(self):
        return self.dof_numbers


    def enumerate_dofs(self, start):
        current_dof = start

        if self.constraint == None:
            self.dof_numbers = np.array([current_dof, current_dof + 1, current_dof + 2], dtype='int32').reshape(3,1)

        else:
            for i in range(3):
                if self.constraint.is_free(i) == False:
                    self.dof_numbers[i] = -1
                else:
                    self.dof_numbers[i] = current_dof
                    current_dof = current_dof + 1

        return current_dof

    def print_node(self):
        print('{}'.format(self.position))


class Element:
    def __init__(self, elastic_modulus, area, node_1, node_2):
        self.elastic_modulus = elastic_modulus
        self. area = area
        self.node_1 = node_1
        self.node_2 = node_2
        self.dof_numbers = None

    def get_elastic_modulus(self):
        return self.elastic_modulus

    def get_area(self):
        return self.area

    def get_node_1(self):
        return self.node_1
    
    def get_node_2(self):
        return self.node_2

    def get_dofs(self):
        return self.dof_numbers

    def get_length(self):
        return np.linalg.norm(self.node_1.get_position() -
                              self.node_2.get_position())


    def compute_stiffness_matrix(self):
        diff = self.node_1.get_position() - self.node_2.get_position()
        EAL = self.get_elastic_modulus() * self.get_area() / self.get_length() ** 3

        K_11 = EAL * np.outer(diff, diff)
        K_22 = EAL * np.outer(-diff, -diff)
        K_12 = EAL * np.outer(diff, -diff)
        K_21 = K_12
        
        K_11_12 = np.concatenate((K_11, K_12), axis=1)
        K_21_22 = np.concatenate((K_21, K_22), axis=1)

        K = np.concatenate((K_11_12, K_21_22), axis=0)

        return K
        

    def enumerate_dofs(self):
        self.dof_numbers = np.concatenate([self.node_1.get_dof_numbers(),
                                           self.node_2.get_dof_numbers()])

    def compute_force(self):
        position_node_1 = self.get_node_1().get_position()
        position_node_2 = self.get_node_2().get_position()
        displacement_node_1 = self.get_node_1().get_displacement()
        displacement_node_2 = self.get_node_2().get_displacement()

        T = (position_node_2 - position_node_1) / self.get_length()

        uN1 = np.dot(T, displacement_node_1)
        uN2 = np.dot(T, displacement_node_2)

        eStrain = (uN2-uN1) * (1/self.get_length())

        N = eStrain * self.get_elastic_modulus() * self.get_area()

        return N

    def print_element(self):
        print('Element:/n')
        print('E = {}\nArea = {}\nlength = {}'.format(self.get_elastic_modulus(),
                                                      self.get_area(),
                                                      self.get_length()))

class Visualizer:
    def __init__(self, struct):
        self.struct = struct
        self.num_elements = self.struct.get_number_elements()
        self.displacement_scale = None
        self.symbol_scale = None

class Structure:
    def __init__(self):
        self.nodes = list()
        self.elements = list()
        self.structure_displacement = None

    def get_node(self, node_id):
        self.nodes[node_id]

    def get_element(self, element_id):
        self.elements[element_id]

    def get_number_elements(self):
        return len(self.elements)

    def get_number_nodes(self):
        return len(self.nodes)

    def add_node(self, position_new_node):
        self.nodes.append(Node(position_new_node))
        return self.get_node[-1]

    def add_element(self, elastic_modulus, area, node_1_id, node_2_id):
        self.elements.append(Element(elastic_modulus, area,
                                     self.get_node[node_1_id],
                                     self.get_node[node_2_id]))
        return self.get_element[-1]

    def enumerate_dofs(self):
        NEQ = int(0)
        for i in range(self.get_number_nodes()):
            NEQ = self.get_node(i).enumerate_dofs(i)
        for j in range(self.get_number_elements()):
            self.get_element(j).enumerate_dofs();
        return NEQ

    def assemble_load_vector(self, rGlobal):
        for nod in range(self.get_number_nodes()):
            DOF = self.get_node(nod).get_dofs_numbers()
            if self.get_node(nod).get_force() is not None:
                force = self.get_node(nod).get_force()
            for i in range(3):
                if DOF[i] != -1:
                    rGlobal[DOF[i]] = rGlobal[DOF[i]] + force[i]
    
    def print_structure(self):
        print("ESCRIBIR ALGO ACA")