# -*- coding: utf-8 -*-
import bpy
import math

from bpy.types import PropertyGroup
from mathutils import Vector

from . import cfg
from . import at_panel
from . import at_operators
from . at_calc_func import(
    x_axis,
    y_axis,
    z_axis,
    xyz_axis,
    at_all_in_one,
    at_random,
    sum_serie,
    tsr
)

"""not used yet
if check on update, may really slow the addon """
def check_list(alist):
    """check all the objects"""
    for elem in alist:
        if elem in bpy.data.objects:
            pass
        else:
            cfg.display_error(str(elem)+" isn't valid.")
            print("Check_list : a name isn't valid ", elem)
            return False
    return True


def elem_in_row(column, row, indice):
    """Number of elements in a row"""
    elements = column + (row - 1) * indice
    # print("row elements =", elements)
    return elements


# ---------------------------- Properties ---------------------------
class ArrayTools_props(PropertyGroup):
    """Properties for array tools"""

    def add_in_column(self, row, nb_column=-1):
        """Add nb_column element(s) in each row"""
        column = cfg.at_count_values[0]
        if nb_column == -1:
            nb_column = cfg.at_count_values[1] - column

        ref_name = cfg.atools_objs[0][0]
        if ref_name in bpy.data.objects:
            ref_obj = bpy.data.objects[ref_name]
            # update the ref_mtx if object's transforms have changed
            cfg.ref_mtx = ref_obj.matrix_world.copy()
            # with offset no need to replace all elements, only the last
            if self.is_tr_off_last:
                for i in range(row):
                    col = column + i*self.alter
                    for j in range(col, col + nb_column):
                        objcp = ref_obj.copy()
                        array_col = bpy.data.collections.get(cfg.col_name)
                        array_col.objects.link(objcp)
                        if self.is_copy:
                            objcp.data = ref_obj.data.copy()
                        cfg.atools_objs[i].append(objcp.name)

                        self.transforms_lsr(j, i, cfg.ref_mtx, objcp.name)
                # update the global ui
                tr, sc, rot = self.calc_global()
                self.up_ui_tr_global(tr)
                self.up_ui_sc_global(sc)
                self.up_ui_rot_global(rot)

            else: # replace all elements
                for i in range(row):
                    col = column + i*self.alter
                    for j in range(col, col + nb_column):
                        objcp = ref_obj.copy()
                        array_col = bpy.data.collections.get(cfg.col_name)
                        array_col.objects.link(objcp)
                        if self.is_copy:
                            objcp.data = ref_obj.data.copy()
                        cfg.atools_objs[i].append(objcp.name)
                self.update_global(bpy.context)
            del objcp
            del ref_obj
        else:
            message = "Problem with reference object's name."
            cfg.display_error(message)
            print("Error in 'add_in_column' : ", message)


    def del_in_column(self, row, nb_column=-1):
        """Remove nb_column element(s) in each row"""
        if nb_column == -1:
            nb_column = cfg.at_count_values[0] - cfg.at_count_values[1]
        array_col = bpy.data.collections.get(cfg.col_name)
        for i in range(row-1, -1, -1):
            for j in range(nb_column):
                del_name = cfg.atools_objs[i].pop()
                if del_name in bpy.data.objects:
                    obj = bpy.data.objects[del_name]
                    array_col.objects.unlink(obj)
                    bpy.data.objects.remove(obj, do_unlink=True)
                else:
                    cfg.display_error(del_name + " doesn't exist anymore.")
                    print("Error in 'del_in_column' : ", del_name)

                # if no more element in list, remove the row
                if not cfg.atools_objs[i]:
                    cfg.atools_objs.pop()
                    self.up_ui_updateRow(row - 1)
                    continue
        if not self.is_tr_off_last:
            # if global is used last
            self.update_global(bpy.context)
        else:
            tr, sc, rot = self.calc_global()
            self.up_ui_tr_global(tr)
            self.up_ui_sc_global(sc)
            self.up_ui_rot_global(rot)


    def add_in_col_alter(self, row, nb_column):
        """Add elements in all rows except the first for variation"""
        array_col = bpy.data.collections.get(cfg.col_name)
        ref_name = cfg.atools_objs[0][0]
        column = self.count
        if ref_name in bpy.data.objects:
            ref_obj = bpy.data.objects[ref_name]
            cfg.ref_mtx = ref_obj.matrix_world.copy()
            if self.is_tr_off_last:
                for i in range(1, row):
                    for j in range(column, column + i * nb_column):
                        objcp = ref_obj.copy()
                        array_col = bpy.data.collections.get(cfg.col_name)
                        array_col.objects.link(objcp)
                        if self.is_copy:
                            objcp.data = ref_obj.data.copy()
                        cfg.atools_objs[i].append(objcp.name)
                        # print("objs=", cfg.atools_objs)

                self.update_offset(bpy.context)
            else: # replace all elements
                for i in range(1, row):
                    for j in range(column, column + i * nb_column):
                        objcp = ref_obj.copy()
                        array_col = bpy.data.collections.get(cfg.col_name)
                        array_col.objects.link(objcp)
                        if self.is_copy:
                            objcp.data = ref_obj.data.copy()
                        cfg.atools_objs[i].append(objcp.name)
                self.update_global(bpy.context)
            del objcp
            del ref_obj
        else:
            message = "Problem with reference object's name."
            cfg.display_error(message)
            print("Error in 'add_in_column' : ", message)


    def del_in_col_alter(self, row, nb_column):
        """Remove elements in all rows except the first"""
        array_col = bpy.data.collections.get(cfg.col_name)
        for i in range(row -1 , 0, -1):
            for j in range(nb_column * i):
                del_name = cfg.atools_objs[i].pop()
                # print("del name=", del_name)
                if del_name in bpy.data.objects:
                    obj = bpy.data.objects[del_name]
                    array_col.objects.unlink(obj)
                    bpy.data.objects.remove(obj, do_unlink=True)
                else:
                    cfg.display_error(del_name + " doesn't exist anymore.")
                    print("Error in 'del_in_column' : ", del_name)
        if self.is_tr_off_last:
            self.update_offset(bpy.context)
        else:
            self.update_global(bpy.context)

    def add_in_row(self, column, nb_row=-1):
        """Add column elements in nb_row new row(s)"""
        row = cfg.at_row_values[0]
        if nb_row == -1:
            nb_row = cfg.at_row_values[1] - row

        ref_name = cfg.atools_objs[0][0]
        if ref_name in bpy.data.objects:
            ref_obj = bpy.data.objects[ref_name]
            cfg.ref_mtx = ref_obj.matrix_world.copy()
            if self.is_tr_off_last:
                for i in range(row, row + nb_row):
                    cfg.atools_objs.append([])
                    for j in range(column + i*self.alter):
                        objcp = ref_obj.copy()
                        array_col = bpy.data.collections.get(cfg.col_name)
                        array_col.objects.link(objcp)
                        if self.is_copy:
                            objcp.data = ref_obj.data.copy()
                        cfg.atools_objs[i].append(objcp.name)
                        self.transforms_lsr(j, i, cfg.ref_mtx, objcp.name)
            else:
                for i in range(row, row + nb_row):
                    cfg.atools_objs.append([])
                    for j in range(column):
                        objcp = ref_obj.copy()
                        array_col = bpy.data.collections.get(cfg.col_name)
                        array_col.objects.link(objcp)
                        if self.is_copy:
                            objcp.data = ref_obj.data.copy()
                        cfg.atools_objs[i].append(objcp.name)
                self.update_global(bpy.context)
        else:
            message = "Problem with reference object's name."
            cfg.display_error(message)
            print("Error in 'add in row' : ", message)


    def del_in_row(self, nb_row=-1):
        """Remove nb_row row(s) : (column * nb_row) elements"""
        if nb_row == -1:
            nb_row = cfg.at_row_values[0] - cfg.at_row_values[1]
        array_col = bpy.data.collections.get(cfg.col_name)
        for i in range(nb_row):
            names = cfg.atools_objs.pop()
            for del_name in names:
                if del_name in bpy.data.objects:
                    obj = bpy.data.objects[del_name]
                    array_col.objects.unlink(obj)
                    bpy.data.objects.remove(obj, do_unlink=True)
                else:
                    cfg.display_error(del_name + " doesn't exist anymore.")
                    print("Error in 'del_in_column' : ", del_name)


    def at_del_all(self, del_rall):
        """Delete all copies and remove objects from lists
        del_rall : boolean, True to del reference object from list
        """
        array_col = bpy.data.collections.get(cfg.col_name)
        ref_name = cfg.atools_objs[0][0]
        for i in range(self.row):
            names = cfg.atools_objs.pop()
            for obj_name in reversed(names):
                if obj_name == ref_name:
                    continue
                # test if object exist
                if obj_name in bpy.data.objects:
                    obj = bpy.data.objects[obj_name]
                    array_col.objects.unlink(obj)
                    bpy.data.objects.remove(obj, do_unlink=True)
                else:
                    cfg.display_error(obj_name + " not exist!")
                    print("Error in 'del_all' : ", obj_name)

        if del_rall:
            cfg.atools_objs.clear()

            # removing the collection if empty
            if not array_col.objects:
                bpy.data.collections.remove(array_col)
        else:
            cfg.atools_objs.append([ref_name])
        # print("Del_all done!")

    # ----------------------- UI update -----------------------------
    # ---------------------------------------------------------------
    # ----------------------- count update --------------------------
    def updateCount(self, context):
        """update the number of element(s) in column"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            cfg.add_count(int(self.count))
            cfg.del_count()

            # cfg.count_values[0] always store old count value
            difference = self.count - cfg.at_count_values[0]

            self.update_infos()

            if difference > 0:
                self.add_in_column(self.row, difference)
            elif difference < 0:
                self.del_in_column(self.row, -difference)
        # print("objs =", cfg.atools_objs)


    def up_ui_updateCount(self, val):
        """Update the value of the property count in UI"""
        self.is_prog_change = True
        self.count = val

    # ----------------------- row update ----------------------------
    def update_row(self, context):
        """Update row property"""
        cfg.add_row(self.row)
        cfg.del_row()
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            if self.alter < 0 and cfg.maxrow < self.row:
                cfg.display_error("Maximun rows for these setting is : " + str(cfg.maxrow))
                self.up_ui_updateRow(cfg.maxrow)
                return

            # cfg.at_row_values[0] always store old row value
            difference = self.row - cfg.at_row_values[0]
            if difference > 0:
                self.add_in_row(self.count, difference)
            elif difference < 0:
                self.del_in_row(-difference)

            line = elem_in_row(self.count, self.row, self.alter)

            self.update_infos()

    def up_ui_updateRow(self, val):
        """Update the value of the property row in UI"""
        self.is_prog_change = True
        self.row = val

    def update_alter(self, context):
        """Update alter property"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            # alter must have at least 2 rows
            if self.row == 1 and self.alter != 0:
                cfg.display_error("Add more rows first.")
                self.up_ui_updateAlter(0)
                return
            if self.alter < 0:
                # (column + (row-1)* variation) is the number of elements
                # of the last row and must be at least >= 1
                alter = int((1 - self.count) / (self.row - 1))
                if self.alter < alter:
                    cfg.display_error("Min variation is '"+str(alter)+"' for these settings.")
                    self.up_ui_updateAlter(alter)
                    return

            cfg.add_alter(self.alter)
            cfg.del_alter()
            self.update_ralign()

            difference = self.alter - cfg.at_alter[0]
            if difference > 0:
                self.add_in_col_alter(self.row, difference)
            elif difference < 0:
                self.del_in_col_alter(self.row, -difference)
            # print(f"count={self.count}, row={self.row}, alter={self.alter}")
            line = elem_in_row(self.count, self.row, self.alter)
            # print("elems in row =", line)

            self.update_infos()


    def up_ui_updateAlter(self, val):
        """Update the value of the property alter in UI"""
        self.is_prog_change = True
        self.alter = val


    def update_ralign(self):
        """Update the value of ralign"""
        decal = -self.alter * self.tr_offset
        if self.align == 'LEFT':
            self.ralign = Vector((0.0, 0.0, 0.0))
        elif self.align == 'CENTER':
            self.ralign = decal / 2
        elif self.align == 'RIGHT':
            self.ralign = decal


    def update_align(self, context):
        """According to the value of align, calculate ralign"""
        self.update_ralign()
        
        if self.is_tr_off_last:
            self.update_offset(bpy.context)
        else:
            self.update_global(bpy.context)


    def update_infos(self):
        """Update properties total and erow"""
        sum = sum_serie(self.row, self.alter)
        square = self.count * self.row
        if self.alter >= 0:
            cfg.maxrow = self.row
        else:
            ca = self.count // -self.alter
            cfg.maxrow = ca if self.count % self.alter == 0 else ca + 1
        self.total = str(int(square + sum))
        self.erow = str(elem_in_row(self.count, self.row, self.alter))

    # ----------------------- translation update --------------------
    def up_ui_tr_offset(self, val):
        """Update the value of the property tr_offset in UI"""
        self.is_prog_change = True
        self.tr_offset = val

    def up_ui_tr_global(self, val):
        """Update the value of the property tr_global in UI"""
        self.is_prog_change = True
        self.tr_global = val

    # ----------------------- scale update --------------------------
    def up_ui_sc_offset(self, val):
        """Update the value of the property sc_offset in UI"""
        self.is_prog_change = True
        self.sc_offset = val

    def up_ui_sc_global(self, val):
        """Update the value of the property sc_global in UI"""
        self.is_prog_change = True
        self.sc_global = val

    # ----------------------- rotation update -----------------------
    def up_ui_rot_offset(self, val):
        """Update the value of the property rot_offset in UI"""
        self.is_prog_change = True
        self.rot_offset = val

    def up_ui_rot_global(self, val):
        """Update the value of the property rot_global in UI"""
        self.is_prog_change = True
        self.rot_global = val

    # ---------------------------------------------------------------
    def calc_global(self):
        """Calculate global for column"""
        tg = (self.count-1) * self.tr_offset
        sg = (xyz_axis() - (self.count-1) *
            (cfg.ref_mtx.to_scale() - (self.sc_offset/100))) * 100
        rg = self.count * Vector(self.rot_offset)
        return tg,sg,rg


    def transforms_lsr(self, column, row, mat, ename):
        """Calculate transforms according to the position of the element
        column : indice of the element's column
        row : indice of the element's row
        mat : matrix of the reference object
        ename : element's name to put in place
        """
        localxyz = (x_axis(), y_axis(), z_axis())

        translate, scaling, rotate = tsr(mat, column, row, self.tr_offset, self.tr_second,
            self.sc_offset, self.sc_second, self.rot_offset, self.rot_second, self.ralign)
        if ename in bpy.data.objects:
            obj = bpy.data.objects[ename]
        if self.at_pivot is not None:
            obj.matrix_world = at_all_in_one(mat, rotate, localxyz, translate,
                scaling, self.at_pivot.location)
        else:
            obj.matrix_world = at_all_in_one(mat, rotate, localxyz, translate,
                scaling, mat.translation)


    def apply_transforms(self, matx, nb_column, nb_row, tr, sc, rot):
        """Move, scale and rotate the selected elements
        tr : translation offset of the first row
        sc : scale offset of the first row
        rot : rotation offset of the first row
        return global transforms
        """
        # local axis always (1,0,0) (0,1,0) (0,0,1)
        localxyz = (x_axis(), y_axis(), z_axis())

        ref_scale = matx.to_scale()
        # duplicate code but avoid looping the test
        if self.at_pivot is not None:
            for i in range(nb_row):
                for j in range(nb_column + i*self.alter):
                    elem = cfg.atools_objs[i][j]
                    if elem in bpy.data.objects:
                        obj = bpy.data.objects[elem]
                    else:
                        cfg.display_error(elem + " no more exist !")
                        print("Error in 'apply_transforms', name no more exist : ", elem)
                        continue
                    t_off, s_off, r_off = tsr(matx, j, i, tr, self.tr_second, sc,
                        self.sc_second, rot, self.rot_second, self.ralign)

                    obj.matrix_world = at_all_in_one(matx, r_off,
                        localxyz, t_off, s_off, self.at_pivot.location)
        else:
            for i in range(nb_row):
                for j in range(nb_column + i*self.alter):
                    ref_loc = cfg.ref_mtx.translation
                    elem = cfg.atools_objs[i][j]
                    if elem in bpy.data.objects:
                        obj = bpy.data.objects[elem]
                    else:
                        cfg.display_error(elem + " no more exist !")
                        print("Error in 'apply_transforms', name no more exist : ", elem)
                        continue
                    t_off, s_off, r_off = tsr(matx, j, i, tr, self.tr_second, sc,
                        self.sc_second, rot, self.rot_second, self.ralign)

                    obj.matrix_world = at_all_in_one(matx, r_off,
                        localxyz, t_off, s_off, ref_loc)
        tr_col,sc_col,rot_col = self.calc_global()
        return(tr_col, sc_col, rot_col)

    def update_offset(self, context):
        """Update for all offsets"""
        if self.is_prog_change:
            self.is_prog_change = False
        else: # user change offset
            self.is_tr_off_last = True

            ref_name = cfg.atools_objs[0][0]
            if bpy.data.objects[ref_name]:
                cfg.ref_mtx = bpy.data.objects[ref_name].matrix_world.copy()
            aloc, asc, arot = self.apply_transforms(cfg.ref_mtx, self.count, self.row,
                self.tr_offset, self.sc_offset, Vector(self.rot_offset))

            # since offset changes, global too
            self.up_ui_tr_global(aloc)
            self.up_ui_sc_global(asc)
            self.up_ui_rot_global(arot)


    def update_global(self, context):
        """Update for all globals"""
        if self.is_prog_change:
            self.is_prog_change = False
        else: # user change global
            self.is_tr_off_last = False

            ref_name = cfg.atools_objs[0][0]
            if bpy.data.objects[ref_name]:
                cfg.ref_mtx = bpy.data.objects[ref_name].matrix_world.copy()
            ref_scale = cfg.ref_mtx.to_scale()

            translation_offset = Vector(self.tr_global) / (self.count - 1)
            scale_offset = ref_scale - ((ref_scale-(self.sc_global/100)) / (self.count - 1))
            rotation_offset = Vector(self.rot_global) / self.count

            self.apply_transforms(cfg.ref_mtx, self.count, self.row, translation_offset,
                Vector(scale_offset)*100, rotation_offset)

            # since global changes, offset too
            self.up_ui_tr_offset(translation_offset)
            self.up_ui_sc_offset(Vector(scale_offset*100))
            self.up_ui_rot_offset(rotation_offset)


    def update_second(self, context):
        """Update the secondary transforms"""
        ref_name = cfg.atools_objs[0][0]
        if bpy.data.objects[ref_name]:
            cfg.ref_mtx = bpy.data.objects[ref_name].matrix_world.copy()
        self.apply_transforms(cfg.ref_mtx, self.count, self.row, self.tr_offset,
            self.sc_offset, self.rot_offset)


    # ----------------------- is_copy update ------------------------
    def up_ui_is_copy(self):
        """Update the value of the property is_copy in UI"""
        self.is_prog_change = True
        self.is_copy = False


    def update_is_copy(self, context):
        """Allow a copy or duplicate(copy link by default)"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            if self.is_copy:  # no need to rebuild all
                for i in range(self.row):
                    for j in range(self.count):
                        if i == 0 and j == 0:
                            continue
                        ref_name = cfg.atools_objs[0][0]
                        elem_name = cfg.atools_objs[i][j]
                        bpy.data.objects[elem_name].data = bpy.data.objects[ref_name].data.copy()
            else:  # since the value change (now duplicate), need to rebuild
                count = self.count
                row = self.row
                ref_name = cfg.atools_objs[0][0]
                array_col = bpy.data.collections.get(cfg.col_name)

                # DO NOT USE BLENDER CRASH WITH IT
                # self.at_del_all(False)

                bpy.ops.object.delete({"selected_objects": array_col.objects})
                cfg.atools_objs.clear()
                cfg.atools_objs.append([ref_name])

                ref_obj = bpy.data.objects[ref_name]
                for i in range(row):
                    if i != 0:
                        cfg.atools_objs.append([])
                    for j in range(count + i*self.alter):
                        objcp = ref_obj.copy()
                        array_col.objects.link(objcp)
                        cfg.atools_objs[i].append(objcp.name)
                del objcp
                del ref_obj

                if self.is_tr_off_last:
                    self.update_offset(bpy.context)
                else:
                    self.update_global(bpy.context)

                print("Rebuild done!")

    # ----------------------- random part ---------------------------
    # ---------------------------------------------------------------
    def update_seed(self, context):
        if self.at_mode == 'ADV':
            sc_min = (self.sc_min_x, self.sc_min_y, self.sc_min_z)
            sc_max = (self.sc_max_x, self.sc_max_y, self.sc_max_z)
            at_random(self.at_seed, self.count, self.row, self.tr_min, self.tr_max, sc_min,
                sc_max, self.rot_min, self.rot_max, self.at_is_tr, self.at_is_sc, self.at_is_rot,
                self.sc_all, self.tr_offset, self.tr_second, self.sc_offset, self.sc_second,
                self.rot_offset, self.rot_second, self.at_pivot, self.alter, self.ralign)
        else: # simple mode
            vec = xyz_axis()
            tr = self.tr_rand * vec
            sc = self.sc_rand * vec
            rot = self.rot_rand * vec
            at_random(self.at_seed, self.count, self.row, -tr, tr, sc, 100*vec, -rot, rot,
                self.at_is_tr, self.at_is_sc, self.at_is_rot, False, self.tr_offset,
                self.tr_second, self.sc_offset, self.sc_second, self.rot_offset,
                self.rot_second, self.at_pivot, self.alter, self.ralign)


    def update_rtr(self, context):
        """rtr in simple mode update adv mode"""
        self.tr_max = self.tr_rand * Vector((1.0, 1.0, 1.0))
        self.tr_min = self.tr_rand * Vector((-1.0, -1.0, -1.0))


    def update_rsc(self, context):
        """rsc in simple mode update adv mode"""
        self.sc_max_x, self.sc_max_y, self.sc_max_z = (100.0, 100.0, 100.0)
        rand = self.sc_rand
        self.sc_min_x = rand
        self.sc_min_y = rand
        self.sc_min_z = rand


    def update_rrot(self, context):
        """rrot in simple mode update adv mode"""
        self.rot_max = self.rot_rand * Vector((1.0, 1.0, 1.0))
        self.rot_min = self.rot_rand * Vector((-1.0, -1.0, -1.0))


    def up_ui_sc_min_x(self, val):
        """Update the value of the property sc_min_x in UI"""
        self.is_prog_change = True
        self.sc_min_x = val


    def up_ui_sc_min_y(self, val):
        """Update the value of the property sc_min_y in UI"""
        self.is_prog_change = True
        self.sc_min_y = val


    def up_ui_sc_min_z(self, val):
        """Update the value of the property sc_min_z in UI"""
        self.is_prog_change = True
        self.sc_min_z = val


    def up_ui_sc_max_x(self, val):
        """Update the value of the property sc_max_x in UI"""
        self.is_prog_change = True
        self.sc_max_x = val


    def up_ui_sc_max_y(self, val):
        """Update the value of the property sc_max_y in UI"""
        self.is_prog_change = True
        self.sc_max_y = val


    def up_ui_sc_max_z(self, val):
        """Update the value of the property sc_max_z in UI"""
        self.is_prog_change = True
        self.sc_max_z = val

    # -------------- update min and max -----------------------------
    # if user enter a max value < min, change min and vice versa
    def up_tr_min(self, context):
        """Update tr_max if tr_min is higher"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            for i in range(3):
                if self.tr_min[i] > self.tr_max[i]:
                    self.is_prog_change = True
                    self.tr_max[i] = self.tr_min[i]


    def up_tr_max(self, context):
        """Update tr_min if tr_max is lower"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            for i in range(3):
                if self.tr_min[i] > self.tr_max[i]:
                    self.is_prog_change = True
                    self.tr_min[i] = self.tr_max[i]


    def up_sc_min_x(self, context):
        """Update sc_max_x if sc_min_x is higher"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            test = self.sc_min_x > self.sc_max_x
            if test and self.sc_all:
                # case : min > max and uniform = True
                self.up_ui_sc_max_x(self.sc_min_x)
                # with uniform : min_x = min_y = min_z same for max_
                self.up_ui_sc_min_y(self.sc_min_x)
                self.up_ui_sc_min_z(self.sc_min_x)
                self.up_ui_sc_max_y(self.sc_min_x)
                self.up_ui_sc_max_z(self.sc_min_x)
            elif self.sc_all:
                # case : min < max and uniform = True
                self.up_ui_sc_min_y(self.sc_min_x)
                self.up_ui_sc_min_z(self.sc_min_x)
                self.up_ui_sc_max_y(self.sc_max_x)
                self.up_ui_sc_max_z(self.sc_max_x)
            elif test:
                # case : min > max and uniform = False
                self.up_ui_sc_max_x(self.sc_min_x)

    def up_sc_min_y(self, context):
        """Update sc_max_y if sc_min_y is higher"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            test = self.sc_min_y > self.sc_max_y
            if test and self.sc_all:
                # case : min > max and uniform = True
                self.up_ui_sc_max_y(self.sc_min_y)
                # with uniform : min_x = min_y = min_z same for max_
                self.up_ui_sc_min_x(self.sc_min_y)
                self.up_ui_sc_min_z(self.sc_min_y)
                self.up_ui_sc_max_x(self.sc_min_y)
                self.up_ui_sc_max_y(self.sc_min_y)
            elif self.sc_all:
                # case : min < max and uniform = True
                self.up_ui_sc_min_x(self.sc_min_y)
                self.up_ui_sc_min_z(self.sc_min_y)
                self.up_ui_sc_max_x(self.sc_max_y)
                self.up_ui_sc_max_z(self.sc_max_y)
            elif test:
                # case : min > max and uniform = False
                self.up_ui_sc_max_y(self.sc_min_y)

    def up_sc_min_z(self, context):
        """Update sc_max_z if sc_min_z is higher"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            test = self.sc_min_z > self.sc_max_z
            if test and self.sc_all:
                # case : min > max and uniform = True
                self.up_ui_sc_max_z(self.sc_min_z)
                # with uniform : min_x = min_y = min_z same for max_
                self.up_ui_sc_min_x(self.sc_min_z)
                self.up_ui_sc_min_y(self.sc_min_z)
                self.up_ui_sc_max_x(self.sc_min_z)
                self.up_ui_sc_max_y(self.sc_min_z)
            elif self.sc_all:
                # case : min < max and uniform = True
                self.up_ui_sc_min_x(self.sc_min_z)
                self.up_ui_sc_min_y(self.sc_min_z)
                self.up_ui_sc_max_x(self.sc_max_z)
                self.up_ui_sc_max_y(self.sc_max_z)
            elif test:
                # case : min > max and uniform = False
                self.up_ui_sc_max_y(self.sc_min_z)

    def up_sc_max_x(self, context):
        """Update sc_min_x if sc_max_x is lower"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            test = self.sc_min_x > self.sc_max_x
            if test and self.sc_all:
                # case : min > max and uniform = True
                self.up_ui_sc_min_x(self.sc_max_x)
                # with uniform : min_x = min_y = min_z same for max_
                self.up_ui_sc_max_y(self.sc_max_x)
                self.up_ui_sc_max_z(self.sc_max_x)
                self.up_ui_sc_min_y(self.sc_max_x)
                self.up_ui_sc_min_z(self.sc_max_x)
            elif self.sc_all:
                # case : min < max and uniform = True
                self.up_ui_sc_max_y(self.sc_max_x)
                self.up_ui_sc_max_z(self.sc_max_x)
                self.up_ui_sc_min_y(self.sc_min_x)
                self.up_ui_sc_min_z(self.sc_min_x)
            elif test:
                # case : min > max and uniform = False
                self.up_ui_sc_min_x(self.sc_max_x)

    def up_sc_max_y(self, context):
        """Update sc_min_y if sc_max_y is lower"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            test = self.sc_min_y > self.sc_max_y
            if test and self.sc_all:
                # case : min > max and uniform = True
                self.up_ui_sc_min_y(self.sc_max_y)
                # with uniform : min_x = min_y = min_z same for max_
                self.up_ui_sc_max_x(self.sc_max_y)
                self.up_ui_sc_max_z(self.sc_max_y)
                self.up_ui_sc_min_x(self.sc_max_y)
                self.up_ui_sc_min_z(self.sc_max_y)
            elif self.sc_all:
                # case : min < max and uniform = True
                self.up_ui_sc_max_x(self.sc_max_y)
                self.up_ui_sc_max_z(self.sc_max_y)
                self.up_ui_sc_min_x(self.sc_min_y)
                self.up_ui_sc_min_z(self.sc_min_y)
            elif test:
                # case : min > max and uniform = False
                self.up_ui_sc_min_y(self.sc_max_y)

    def up_sc_max_z(self, context):
        """Update sc_min_z if sc_max_z is lower"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            test = self.sc_min_z > self.sc_max_z
            if test and self.sc_all:
                # case : min > max and uniform = True
                self.up_ui_sc_min_z(self.sc_max_z)
                # with uniform : min_x = min_y = min_z same for max_
                self.up_ui_sc_max_x(self.sc_max_z)
                self.up_ui_sc_max_y(self.sc_max_z)
                self.up_ui_sc_min_x(self.sc_max_z)
                self.up_ui_sc_min_y(self.sc_max_z)
            elif self.sc_all:
                # case : min < max and uniform = True
                self.up_ui_sc_max_x(self.sc_max_z)
                self.up_ui_sc_max_y(self.sc_max_z)
                self.up_ui_sc_min_x(self.sc_min_z)
                self.up_ui_sc_min_y(self.sc_min_z)
            elif test:
                # case : min > max and uniform = False
                self.up_ui_sc_min_z(self.sc_max_z)

    def up_rot_min(self, context):
        """Update rot_max if rot_min is higher"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            for i in range(3):
                if self.rot_min[i] > self.rot_max[i]:
                    self.is_prog_change = True
                    self.rot_max[i] = self.rot_min[i]

    def up_rot_max(self, context):
        """Update rot_min if rot_max is lower"""
        if self.is_prog_change:
            self.is_prog_change = False
        else:
            for i in range(3):
                if self.rot_min[i] > self.rot_max[i]:
                    self.is_prog_change = True
                    self.rot_min[i] = self.rot_max[i]

    # ----------------------- reset all properties ------------------
    def up_ui_reset(self):
        """Reset all UI properties"""
        self.up_ui_updateCount(2)
        self.up_ui_updateRow(1)
        self.up_ui_is_copy()
        self.up_ui_tr_offset(Vector((2.0, 0.0, 0.0)))
        self.up_ui_tr_global(Vector((2.0, 0.0, 0.0)))
        self.up_ui_sc_offset((100, 100, 100))
        self.up_ui_sc_global((100, 100, 100))
        self.up_ui_rot_offset(Vector((0.0, 0.0, 0.0)))
        self.up_ui_rot_global(Vector((0.0, 0.0, 0.0)))
        self.up_ui_updateAlter(0)
        self.total = "2"
        self.erow = "2"


    count: bpy.props.IntProperty(
        name='Count',
        description="Number of elements, original count as one",
        default=2,
        soft_min=2,
        update=updateCount
    )

    row: bpy.props.IntProperty(
        name="Row",
        description="Number of row(s)",
        default=1,
        soft_min=1,
        soft_max=100,
        update=update_row
    )

    """Allow a variation in the row :
    if row gets n elements, row +1 will get (n + variation) elements
    only if n + variation > 0
    """
    alter: bpy.props.IntProperty(
        name=" Row variation",
        description="""Variation in the number of elements in a row. (between -5 and 5).
            \n Be careful with it""",
        default=0,
        soft_min=-5,
        soft_max=5,
        update=update_alter
    )

    total: bpy.props.StringProperty(
        name="Total",
        description="Total of elements in array",
        default="2"
    )

    erow: bpy.props.StringProperty(
        description="Number of elements in the current row.",
        default="2"
    )

    # if alter <> 0, how align the rows
    align: bpy.props.EnumProperty(
        name='Align',
        description="Align of rows when variation is not zero",
        items=[
            ('LEFT', 'Left', "Align to the left", 'ALIGN_LEFT', 0),
            ('CENTER', 'Center', "Align to the center", 'ALIGN_CENTER', 1),
            ('RIGHT', 'Right', "Align to the right", 'ALIGN_RIGHT', 2)
        ],
        default='LEFT',
        update=update_align
    )

    # Vector alignment depends on align
    ralign: bpy.props.FloatVectorProperty(
        subtype='TRANSLATION',
        unit='LENGTH',
        default=(0.0, 0.0, 0.0)
    )

    # booleans use to know if user or prog change the value to avoid continuous loop
    is_prog_change: bpy.props.BoolProperty(default=False)  # True if prog change value

    # which one between offset and global user calls last, True is offset, False global
    is_tr_off_last: bpy.props.BoolProperty(default=True)

    # True if addon is initialised
    already_start: bpy.props.BoolProperty(default=False)

    # if the user need a single copy or a duplicate (link object)
    is_copy: bpy.props.BoolProperty(
        name="Copy only",
        description="Duplicate or copy, default is duplicate",
        default=False,
        update=update_is_copy
    )

    # translation vector offset
    tr_offset: bpy.props.FloatVectorProperty(
        name='Offset',
        description="Distance between elements",
        default=(2.0, 0.0, 0.0),
        subtype='TRANSLATION',
        unit='LENGTH',
        precision=2,
        step=50,
        options={'ANIMATABLE'},
        update=update_offset
    )

    # global translation distance
    tr_global: bpy.props.FloatVectorProperty(
        name='Global',
        description="Distance between the original and the last element",
        default=(2.0, 0.0, 0.0),
        subtype='TRANSLATION',
        unit='LENGTH',
        precision=2,
        step=50,
        options={'ANIMATABLE'},
        update=update_global
    )

    tr_second: bpy.props.FloatVectorProperty(
        name="Translation",
        description="Additional offset distance for rows",
        default=(0.0, 0.0, 0.0),
        subtype='TRANSLATION',
        unit='LENGTH',
        precision=2,
        step=50,
        update=update_second
    )

    at_pivot: bpy.props.PointerProperty(
        name='Pivot',
        description="Object you want as pivot point. If none, pivot point is the object's origine",
        type=bpy.types.Object
    )

    # scaling vector offset
    sc_offset: bpy.props.FloatVectorProperty(
        name='Offset',
        description="Incremental scale of the next elements",
        default=(100.0, 100.0, 100.0),
        subtype='XYZ',
        precision=1,
        step=100,
        options={'ANIMATABLE'},
        update=update_offset
    )

    # global scaling
    sc_global: bpy.props.FloatVectorProperty(
        name='Global',
        description="Scale of the last element",
        default=(100.0, 100.0, 100.0),
        subtype='XYZ',
        precision=1,
        step=100,
        options={'ANIMATABLE'},
        update=update_global
    )

    sc_second: bpy.props.FloatVectorProperty(
        name='Scale',
        description="Additionnal scale for rows",
        default=(100.0, 100.0, 100.0),
        subtype='XYZ',
        precision=1,
        step=100,
        options={'ANIMATABLE'},
        update=update_second
    )
    # rotation vector offset
    rot_offset: bpy.props.FloatVectorProperty(
        name='Offset',
        description="Angle between each element",
        default=(0.0, 0.0, 0.0),
        subtype='XYZ',
        unit='ROTATION',
        step=500,  # = 5
        options={'ANIMATABLE'},
        update=update_offset
    )

    # global rotation
    rot_global: bpy.props.FloatVectorProperty(
        name='Global',
        description="Maximum angle from the reference to the last element",
        default=(0.0, 0.0, 0.0),
        subtype='XYZ',
        unit='ROTATION',
        step=500,  # = 5
        options={'ANIMATABLE'},
        update=update_global
    )

    rot_second: bpy.props.FloatVectorProperty(
        name='Rotation',
        description="Additionnal rotation for rows",
        default=(0.0, 0.0, 0.0),
        subtype='XYZ',
        unit='ROTATION',
        step=500,
        options={'ANIMATABLE'},
        update=update_second
    )

    # ----------------------- random part ---------------------------
    at_seed: bpy.props.IntProperty(
        name='Seed',
        description="Seed value for random",
        soft_min=0,
        default=0,
        update=update_seed
    )

    at_mode: bpy.props.EnumProperty(
        name="Mode",
        description="Choose between simple mode or advanced",
        items=(('SIM', 'Simple', "Simple mode"),
            ('ADV', 'Advanced', "Advanced mode")),
        default='SIM'
    )

    at_is_tr: bpy.props.BoolProperty(
        name="Add translation",
        description="Add translation in random?",
        default=False
    )

    at_is_sc: bpy.props.BoolProperty(
        name="Add scale",
        description="Add scale in random?",
        default=False
    )

    at_is_rot: bpy.props.BoolProperty(
        name="Add rotation",
        description="Add rotation in random?",
        default=False
    )

    tr_min: bpy.props.FloatVectorProperty(
        name="min",
        description="Minimum random value for translation",
        unit='LENGTH',
        default=(0.0, 0.0, 0.0),
        update=up_tr_min
    )

    tr_max: bpy.props.FloatVectorProperty(
        name="max",
        description="Maximum random value for translation",
        unit='LENGTH',
        default=(0.0, 0.0, 0.0),
        update=up_tr_max
    )

    tr_rand: bpy.props.FloatProperty(
        name="Translation",
        description="Random values for all axis",
        unit='LENGTH',
        default=0.0,
        update=update_rtr
    )

    sc_all: bpy.props.BoolProperty(
        name="uniform scale",
        description="Uniform or non uniform scale, default is non uniform.",
        default=False
    )

    sc_min_x: bpy.props.IntProperty(
        name="min",
        description="Minimum random value for x scale",
        default=100,
        update=up_sc_min_x
    )

    sc_min_y: bpy.props.IntProperty(
        name="min",
        description="Minimum random value for y scale",
        default=100,
        update=up_sc_min_y
    )

    sc_min_z: bpy.props.IntProperty(
        name="min",
        description="Minimum random value for z scale",
        default=100,
        update=up_sc_min_z
    )

    sc_max_x: bpy.props.IntProperty(
        name="max",
        description="Maximum random value for x scale",
        default=100,
        update=up_sc_max_x
    )

    sc_max_y: bpy.props.IntProperty(
        name="max",
        description="Maximum random value for y scale",
        default=100,
        update=up_sc_max_y
    )

    sc_max_z: bpy.props.IntProperty(
        name="max",
        description="Maximum random value for z scale",
        default=100,
        update=up_sc_max_z
    )

    sc_rand: bpy.props.IntProperty(
        name="Scale",
        description="Random scale value for all axis",
        default=100,
        update=update_rsc
    )

    rot_min: bpy.props.FloatVectorProperty(
        name="min",
        description="Minimum random value for rotation",
        unit='ROTATION',
        default=(0.0, 0.0, 0.0),
        update=up_rot_min
    )

    rot_max: bpy.props.FloatVectorProperty(
        name="max",
        description="Maximum random value for rotation",
        unit='ROTATION',
        default=(0.0, 0.0, 0.0),
        update=up_rot_max
    )

    rot_rand: bpy.props.FloatProperty(
        name="Rotation",
        description="Random rotation for all axis",
        unit='ROTATION',
        default=0.0,
        update=update_rrot
    )