# SPDX-License-Identifier: GPL-2.0-or-later

# Author: Kayo Phoenix

import bpy
from bpy_extras import object_utils
from math import (
        pi, sin,
        cos,
        )
from bpy.props import (
        IntProperty,
        BoolProperty,
        BoolVectorProperty,
        FloatProperty,
        FloatVectorProperty,
        StringProperty,
        )


class honeycomb_geometry():
    def __init__(self, rows, cols, D, E):
        self.rows = rows
        self.cols = cols
        self.D = D
        self.E = E

        self.hE = 0.5 * self.E
        self.R = 0.5 * self.D

        self.a = sin(pi / 3)

        self.d = self.a * self.D
        self.hd = 0.5 * self.d
        self.e = self.hE / self.a
        self.he = 0.5 * self.e
        self.r = self.R - self.e
        self.hr = 0.5 * self.r

        self.H = self.R * (1.5 * self.rows + 0.5) + self.e
        if self.rows > 1:
            self.W = self.d * (self.cols + 0.5) + self.E
        else:
            self.W = self.d * self.cols + self.E

        self.hH = 0.5 * self.H
        self.hW = 0.5 * self.W

        self.sy = -self.hH + self.he + self.R
        self.sx = -self.hW + self.hE + self.hd

        self.gx = self.hd

        self.dy = 1.5 * self.R
        self.dx = self.d

    def vert(self, row, col):
        # full cell
        if row >= 0 and row < self.rows and col >= 0 and col < self.cols:
            return [0, 1, 2, 3, 4, 5]
        # right down corner
        if row == -1 and col == self.cols - 1:
            return [1, 2]
        if row == 0 and self.rows > 1 and col == self.cols:
            return [1, 2, 3]
        # left down corner
        if row == -1 and col == -1:
            return [0, 1]
        if self.rows % 2:
            # left up corner
            if row == self.rows and col == -1:
                return [4, 5]
            # right up corner
            if row == self.rows and col == self.cols - 1:
                return [3, 4]
            if row == self.rows - 1 and self.rows > 1 and col == self.cols:
                return [2, 3, 4]
        else:
            # left up corner
            if row == self.rows and col == 0:
                return [4, 5]
            if row == self.rows - 1 and self.rows > 1 and col == -1:
                return [0, 4, 5]
            # right up corner
            if row == self.rows and col == self.cols:
                return [3, 4]
        # horizontal lines
        if col >= 0 and col < self.cols:
            if row == -1:
                return [0, 1, 2]
            if row == self.rows:
                return [3, 4, 5]
        # vertical lines
        if row >= 0 and row < self.rows:
            if col == -1:
                if row % 2:
                    return [0, 1, 4, 5]
                else:
                    return [0, 5]
            if col == self.cols:
                if row % 2 or self.rows == 1:
                    return [2, 3]
                else:
                    return [1, 2, 3, 4]
        return []

    def cell(self, row, col, idx):
        cp = [self.sx + self.dx * col, self.sy + self.dy * row, 0]  # central point
        if row % 2:
            cp[0] += self.gx
        co = []  # vertices coords
        vi = self.vert(row, col)
        ap = {}

        for i in vi:
            a = pi / 6 + i * pi / 3  # angle
            ap[i] = idx + len(co)
            co.append((cp[0] + cos(a) * self.r, cp[1] + sin(a) * self.r, cp[2]))
        return co, ap

    def generate(self):
        ar = 1
        ac = 1

        cells = []
        verts = []
        faces = []

        for row in range(-ar, self.rows + ar):
            level = []
            for col in range(-ac, self.cols + ac):
                co, ap = self.cell(row, col, len(verts))
                verts += co
                level.append(ap)
            cells.append(level)

        # bottom row
        row = 0
        for col in range(1, len(cells[row]) - 1):
            s = cells[row][col]
            l = cells[row][col - 1]
            u = cells[row + 1][col]

            faces.append((s[1], u[5], u[4], s[2]))
            faces.append((s[2], u[4], l[0]))

        # top row
        row = len(cells) - 1
        cs = 0
        if row % 2:
            cs += 1
        for col in range(1 + cs, len(cells[row]) - 1):
            s = cells[row][col]
            l = cells[row][col - 1]
            d = cells[row - 1][col - cs]
            faces.append((s[3], l[5], d[1]))
            faces.append([s[3], d[1], d[0], s[4]])

        # middle rows
        for row in range(1, len(cells) - 1):
            cs = 0
            if row % 2:
                cs += 1
            for col in range(1, len(cells[row]) - 1):
                s = cells[row][col]
                l = cells[row][col - 1]
                u = cells[row + 1][col - cs]
                d = cells[row - 1][col - cs]

                faces.append((s[1], u[5], u[4], s[2]))
                faces.append((s[2], u[4], l[0]))
                faces.append([s[2], l[0], l[5], s[3]])
                faces.append((s[3], l[5], d[1]))
                faces.append([s[3], d[1], d[0], s[4]])

        # right column
        row = 0
        col = len(cells[row]) - 1
        for row in range(1, len(cells) - 1):
            cs = 0
            if row % 2:
                cs += 1

            s = cells[row][col]
            l = cells[row][col - 1]
            u = cells[row + 1][col - cs]
            d = cells[row - 1][col - cs]

            if row % 2 and row < len(cells) - 2:
                faces.append((s[1], u[5], u[4], s[2]))
            faces.append((s[2], u[4], l[0]))
            faces.append([s[2], l[0], l[5], s[3]])
            faces.append((s[3], l[5], d[1]))
            if row % 2 and row > 1:
                faces.append([s[3], d[1], d[0], s[4]])

        # final fix
        if not self.rows % 2:
            row = len(cells) - 1
            s = cells[row][col]
            l = cells[row][col - 1]
            d = cells[row - 1][col - 1]
            faces.append((s[3], l[5], d[1]))
            faces.append([s[3], d[1], d[0], s[4]])

        return verts, faces


def edge_max(diam):
    return diam * sin(pi / 3)


class add_mesh_honeycomb(bpy.types.Operator, object_utils.AddObjectHelper):
    bl_idname = "mesh.honeycomb_add"
    bl_label = "Add HoneyComb"
    bl_description = "Simple honeycomb mesh generator"
    bl_options = {'REGISTER', 'UNDO'}

    def fix_edge(self, context):
        m = edge_max(self.diam)
        if self.edge > m:
            self.edge = m

    HoneyComb : BoolProperty(name = "HoneyComb",
                default = True,
                description = "HoneyComb")
    change : BoolProperty(name = "Change",
                default = False,
                description = "change HoneyComb")

    rows: IntProperty(
            name="Num of rows",
            default=2,
            min=1, max=100,
            description='Number of the rows'
            )
    cols: IntProperty(
            name='Num of cols',
            default=2,
            min=1, max=100,
            description='Number of the columns'
            )
    diam: FloatProperty(
            name='Cell Diameter',
            default=1.0,
            min=0.0, update=fix_edge,
            description='Diameter of the cell'
            )
    edge: FloatProperty(
            name='Edge Width',
            default=0.1,
            min=0.0, update=fix_edge,
            description='Width of the edge'
            )

    def draw(self, context):
        layout = self.layout

        layout.prop(self, 'rows', expand=True)
        layout.prop(self, 'cols', expand=True)
        layout.prop(self, 'diam', expand=True)
        layout.prop(self, 'edge', expand=True)

        if self.change == False:
            col = layout.column(align=True)
            col.prop(self, 'align', expand=True)
            col = layout.column(align=True)
            col.prop(self, 'location', expand=True)
            col = layout.column(align=True)
            col.prop(self, 'rotation', expand=True)

    @classmethod
    def poll(cls, context):
        return context.scene is not None

    def execute(self, context):
        # turn off 'Enter Edit Mode'
        use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
        bpy.context.preferences.edit.use_enter_edit_mode = False

        if bpy.context.mode == "OBJECT":
            if context.selected_objects != [] and context.active_object and \
                (context.active_object.data is not None) and ('HoneyComb' in context.active_object.data.keys()) and \
                (self.change == True):
                obj = context.active_object
                oldmesh = obj.data
                oldmeshname = obj.data.name
                comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
                verts, faces = comb.generate()
                mesh = bpy.data.meshes.new('HoneyComb')
                mesh.from_pydata(verts, [], faces)
                obj.data = mesh
                for material in oldmesh.materials:
                    obj.data.materials.append(material)
                bpy.data.meshes.remove(oldmesh)
                obj.data.name = oldmeshname
            else:
                comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
                verts, faces = comb.generate()
                mesh = bpy.data.meshes.new('HoneyComb')
                mesh.from_pydata(verts, [], faces)
                obj = object_utils.object_data_add(context, mesh, operator=self)

            obj.data["HoneyComb"] = True
            obj.data["change"] = False
            for prm in HoneyCombParameters():
                obj.data[prm] = getattr(self, prm)

        if bpy.context.mode == "EDIT_MESH":
            active_object = context.active_object
            name_active_object = active_object.name
            bpy.ops.object.mode_set(mode='OBJECT')
            comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
            verts, faces = comb.generate()
            mesh = bpy.data.meshes.new('HoneyComb')
            mesh.from_pydata(verts, [], faces)
            obj = object_utils.object_data_add(context, mesh, operator=self)
            obj.select_set(True)
            active_object.select_set(True)
            bpy.context.view_layer.objects.active = active_object
            bpy.ops.object.join()
            context.active_object.name = name_active_object
            bpy.ops.object.mode_set(mode='EDIT')

        if use_enter_edit_mode:
            bpy.ops.object.mode_set(mode = 'EDIT')

        # restore pre operator state
        bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode

        return {'FINISHED'}

def HoneyCombParameters():
    HoneyCombParameters = [
        "rows",
        "cols",
        "diam",
        "edge",
        ]
    return HoneyCombParameters
