from traits.api import HasTraits, Property, cached_property, \
    Array, Int, \
    Str, Float, Dict, WeakRef

import bmcs_shell.folding.analysis.abaqus.abaqus_shell_manager as asm
import numpy as np

class AbaqusLink(HasTraits):
    # data source
    shell_analysis = WeakRef

    nodes = Property

    def _get_nodes(self):
        # return np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]])
        return self.shell_analysis.xdomain.mesh.X_Id

    facets = Property

    def _get_facets(self):
        # return np.array([[0, 1, 2]])
        return self.shell_analysis.xdomain.mesh.I_Fi

    bc_fixed = Property
    def _get_bc_fixed(self):
        """
        :return: [[node_idx, bc_x, bc_y, bc_z...]]
        """
        return self.shell_analysis.bcs.bc_fixed_array

    bc_loaded = Property
    def _get_bc_loaded(self):
        """
        :return: [[node_idx, f_x, f_y, f_z...]]
        """
        return self.shell_analysis.bcs.bc_loaded_array

    thickness = Property
    def _get_thickness(self):
        return self.shell_analysis.h

    nu = Property
    def _get_nu(self):
        return self.shell_analysis.tmodel.nu

    E = Property
    def _get_E(self):
        return self.shell_analysis.tmodel.E

    # -------------------------------------------------------------------------
    # Data for model building
    # -------------------------------------------------------------------------
    element_type = Str('S3R')
    model_name = Str('Model-1')
    material_name = Str('concrete')

    materials = Property()

    @cached_property
    def _get_materials(self):
        return {"concrete": [self.E, self.nu, 2500.0e-12],  # [Young's modulus in N/mm^2, poissions ratio, density in tonne/mm3 (SI-mm units)]
                      "steel": [2.1e5, 0.3, 7880.0e-12]}

    _inp_head = Property(Str, depends_on='model_name')

    @cached_property
    def _get__inp_head(self):
        """
        Head of the input file.
        """

        head = "*Heading\n\
** Job name: Job-1 Model name: " + self.model_name + "\n\
** Generated by: AbaqusLink\n\
*Preprint, echo=NO, model=Yes, history=NO, contact=NO\n\
**\n\
** Model definition\n\
**\n"
        return head

    _inp_nodes = Property(Str, depends_on='nodes')

    @cached_property
    def _get__inp_nodes(self):
        """
        Nodelist of the input file.
        """
        n = self.nodes
        nodes = "*Node,\t nset=node-1\n"
        for i in range(len(n)):
            temp_node = ' %i,\t %.4f,\t %.4f,\t %.4f\n' % (
                i + 1, n[i][0], n[i][1], n[i][2])
            nodes += temp_node
        return nodes

    _inp_elements = Property(Str, depends_on='facets, element_type')

    @cached_property
    def _get__inp_elements(self):
        """
        Elementlist of the input file.
        """
        f = self.facets
        facets = "*Element,\t elset=STRUC,\t type=" + self.element_type + "\n"
        for i in range(len(f)):
            temp_facet = ' %i,\t %i,\t %i,\t %i\t \n' % (
                i + 1, f[i][0] + 1, f[i][1] + 1, f[i][2] + 1)
            facets += temp_facet
        return facets

    _inp_sets = Property(Str, depends_on='nodes, facets, bounded_nodes')

    @cached_property
    def _get__inp_sets(self):
        """
        Sets of the input file.
        """
        set_str = '*Nset, nset=nodes, instance=Part-A, generate\n 1,\t {},\t 1\n'.format(len(self.nodes))
        set_str += '*Elset, elset=Struc, instance=Part-A, generate\n 1,\t {},\t 1\n'.format(len(self.facets))
        set_str += '*Elset, elset=_Surf-1_SNEG,  internal, instance=Part-A, generate\n 1,\t {},\t 1\n'.format(
            len(self.facets))

        bc_sets_names = self.bc_sets_names
        for i, node_bc in enumerate(self.bc_fixed):
            node_id = int(node_bc[0]) + 1
            set_str += '*Nset, nset={}, instance=Part-A\n'.format(bc_sets_names[i])
            set_str += '{}\n'.format(node_id)

        load_sets_names = self.load_sets_names
        for i, node_load in enumerate(self.bc_loaded):
            node_id = int(node_load[0]) + 1
            set_str += '*Nset, nset={}, instance=Part-A\n'.format(load_sets_names[i])
            set_str += '{}\n'.format(node_id)
        return set_str

    bc_sets_names = Property
    def _get_bc_sets_names(self):
        return ['BC-Node-{}'.format(int(node_id) + 1) for node_id in self.bc_fixed[:, 0]]

    load_sets_names = Property
    def _get_load_sets_names(self):
        return ['Load-Node-{}'.format(int(node_id) + 1) for node_id in self.bc_loaded[:, 0]]

    _inp_section = Property(Str, depends_on='thickness, material_name')

    @cached_property
    def _get__inp_section(self):
        """
        Sections of the input file.
        """
        section = '** Section: Section-1\n'
        section += '*Shell Section, elset=Struc, material='
        section += self.material_name + '\n'
        section += '%f \n' % self.thickness
        return section

    _inp_part = Property(
        Str, depends_on='_inp_nodes, _inp_elements, _inp_section')

    @cached_property
    def _get__inp_part(self):
        """
        Parts of the input file.
        """
        part = '*Part, NAME=Part-1\n'
        part += self._inp_nodes
        part += self._inp_elements
        part += self._inp_section
        part += '*End Part\n'
        return part

    _inp_instance = Property(Str)

    @cached_property
    def _get__inp_instance(self):
        """
        Instance of the input file.
        """
        instance = '*Instance, NAME=PART-A, PART=Part-1\n'
        instance += '*End Instance\n'
        return instance

    _inp_surface = Property(Str)

    @cached_property
    def _get__inp_surface(self):
        """
        Surfaces of the input file.
        """
        surf = '*Surface, type=ELEMENT, name=Surf-1\n'
        surf += '_Surf-1_SNEG, SNEG\n'
        return surf

    _inp_material = Property(Str, depends_on='material_name')

    @cached_property
    def _get__inp_material(self):
        """
        Materials of the input file.
        """
        material = '**\n'
        material += '** MATERIALS\n'
        material += '** \n'
        material += '*Material, name='
        material += self.material_name + '\n'
        material += '*Elastic\n'
        mat_values = self.materials[self.material_name]
        material += '{},\t {} \n'.format(mat_values[0], mat_values[1])
        material += '**\n *DENSITY\n {}\n'.format(mat_values[2])
        return material

    _inp_assembly = Property(Str, depends_on='nodes, facets')

    @cached_property
    def _get__inp_assembly(self):
        """
        Assembly of the input file.
        """
        assembly = '*Assembly, NAME = Assembly1\n'
        assembly += self._inp_instance
        assembly += self._inp_sets
        assembly += self._inp_surface
        assembly += '*End Assembly\n'
        return assembly

    _inp_boundary_conditions = Property(Str)

    @cached_property
    def _get__inp_boundary_conditions(self):
        bc_txt = '** BOUNDARY CONDITIONS\n'
        bc_txt += '** \n'

        bc_sets_names = self.bc_sets_names
        for i, node_bc in enumerate(self.bc_fixed):
            bc_txt += '** Name: BC-{} Type: Displacement/Rotation\n'.format(i + 1)
            bc_txt += '*Boundary\n'
            for bc_idx, bc_value in enumerate(node_bc[1:]):
                bc_id = bc_idx + 1
                if not np.isnan(bc_value):
                    bc_txt += '{}, {}, {}, {}\n'.format(bc_sets_names[i], bc_id, bc_id, bc_value)

        bc_txt += '** \n'
        return bc_txt

    _inp_loadcases = Property(Str)

    @cached_property
    def _get__inp_loadcases(self):
        """
        Loadcases of the input file.
        """
        load = '** LOADS\n'
        load += '** \n'

        # load += '** Name: Load-1   Type: Pressure\n'
        # load += '*DLOAD\n'
        # load += 'Struc, GRAV, 9810, 0.0, 0.0, -1.0\n**\n' # 9810 is Gravity in mm/s^2'

        # Concentrated forces
        load = self._add_loads(load, moment = False)

        # Moments
        load = self._add_loads(load, moment = True)

        load += '** \n'
        return load

    def _add_loads(self, load_str, moment):
        load_sets_names = self.load_sets_names
        for i, node_load in enumerate(self.bc_loaded):
            load_str += '** Name: Load-{} Type: {}\n'.format(i + 1, 'Moment' if moment else 'Concentrated force')
            load_str += '*Cload\n'
            node_load_range = node_load[4:] if moment else node_load[1:4]
            for load_idx, load_value in enumerate(node_load_range):
                load_id = load_idx + 4 if moment else load_idx + 1
                if not np.isnan(load_value):
                    load_str += '{}, {}, {}\n'.format(load_sets_names[i], load_id, load_value)
        return load_str

    _inp_output_requests = Property(Str)

    @cached_property
    def _get__inp_output_requests(self):
        """
        Output of the input file.
        """
        out = '**\n'
        out += '** OUTPUT REQUESTS\n'
        out += '** \n'
        out += '*Restart, write, frequency=0\n'
        out += '** \n'
        out += '** FIELD OUTPUT: F-Output-1\n'
        out += '** \n'
        out += '*Output, field, variable=PRESELECT\n'
        out += '**Output, field\n'
        out += '**Node Output\n'
        out += '**CF, RF, U\n'
        out += '**Element Output\n'
        out += '**ALPHA, ALPHAN, CS11, CTSHR, MISES, MISESMAX, MISESONLY, PRESSONLY, PS, S, SF, SM, SSAVG, TRIAX, TSHR, VS\n'
        out += '*Output, field, frequency=0\n'
        out += '**\n'
        out += '** HISTORY OUTPUT: H-Output-1\n'
        out += '** \n'
        out += '*Output, history, variable=PRESELECT\n'
        out += '**NODE PRINT\n'
        out += '**U,\n'
        out += '**RF,\n'
        out += '**EL PRINT\n'
        out += '**S,\n'
        return out

    _inp_step_header = Property(Str)
    @cached_property
    def _get__inp_step_header(self):
        step_header = '** ----------------------------------------------------------------\n'
        step_header += '**\n'
        step_header += '** STEP: Step-1\n'
        step_header += '** \n'
        step_header += '*Step, name=Step-1\n'
        step_header += '*Static\n'
        step_header += '0.1, 1., 1e-05, 0.1\n'
        step_header += '**\n'
        return step_header

    _inp_step_footer = Property(Str)
    @cached_property
    def _get__inp_step_footer(self):
        return '*End Step\n'

    def build_inp(self):
        fname = self.model_name + '.inp'
        inp_file = open(fname, 'w')
        inp_file.write(self._inp_head)
        inp_file.write(self._inp_part)
        inp_file.write(self._inp_assembly)
        inp_file.write(self._inp_material)
        inp_file.write(self._inp_step_header)
        inp_file.write(self._inp_boundary_conditions)
        inp_file.write(self._inp_loadcases)
        inp_file.write(self._inp_output_requests)
        inp_file.write(self._inp_step_footer)
        inp_file.close()
        print('inp file %s written' % fname)

    # =======================================================================
    # Solving of the problem in Abaqus
    # =======================================================================

    tail = '\n'

    def abaqus_solve(self):
        p = asm.open_shell()
        asm.solve_abaqus(p, self.model_name, self.tail)
        # results file is 'dat'
        # asm.download_file(
        #     p, self.login, self.model_name + '.dat', self.tail, self.model_name + '.dat')
        p.kill()

    def abaqus_cae(self):
        pass
        # asm.open_abaqus(p, self.tail)


if __name__ == '__main__':
    from bmcs_shell.folding.analysis.fem.vmats_shell_elastic import MATSShellElastic
    from bmcs_shell.folding.analysis.wb_shell_analysis import WBShellAnalysis

    tmodel = MATSShellElastic(E=28000, nu=0.2)
    n_phi_plus = 2
    n_x_plus = 1
    wba = WBShellAnalysis(h=10, tmodel=tmodel)
    data = dict(alpha=1.24, a=500, a_high=3000, b=900, b_high=3000, c=400, c_high=2000,
                n_phi_plus=n_phi_plus, n_x_plus=n_x_plus, show_nodes=False)
    wba.geo.trait_set(**data)

    al = AbaqusLink(shell_analysis=wba)
    al.model_name = 'test_name'
    al.build_inp()
    # al.abaqus_solve()
    # al.abaqus_cae()

    # import subprocess
    #
    # # cmd = ['/run/myscript', '--arg', 'value']
    # cmd = ['abaqus cae', '--arg', 'value']
    # process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
    # process.wait()
    # print(process.returncode)
    #
    # # p = asm.open_shell()
    # # asm.open_abaqus(p, '\n')
