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

# ----------------------------------------------------------
# Automatic generation of books
# Author: Antonio Vazquez (antonioya)
#
# ----------------------------------------------------------
# noinspection PyUnresolvedReferences
import bpy
from math import cos, sin, radians
from random import randint
from copy import copy
from colorsys import rgb_to_hsv, hsv_to_rgb
from bpy.types import Operator
from bpy.props import BoolProperty, IntProperty, FloatProperty, FloatVectorProperty
from .achm_tools import *


# ------------------------------------------------------------------
# Define UI class
# Books
# ------------------------------------------------------------------
class ARCHIMESH_OT_Books(Operator):
    bl_idname = "mesh.archimesh_books"
    bl_label = "Books"
    bl_description = "Books Generator"
    bl_category = 'View'
    bl_options = {'REGISTER', 'UNDO'}

    width: FloatProperty(
            name='Width', min=0.001, max=1, default=0.045, precision=3,
            description='Bounding book width',
            )
    depth: FloatProperty(
            name='Depth', min=0.001, max=1, default=0.22, precision=3,
            description='Bounding book depth',
            )
    height: FloatProperty(
            name='Height', min=0.001, max=1, default=0.30, precision=3,
            description='Bounding book height',
            )
    num: IntProperty(
            name='Number of books', min=1, max=100, default=5,
            description='Number total of books',
            )

    rX: FloatProperty(
            name='X', min=0.000, max=0.999, default=0, precision=3,
            description='Randomness for X axis',
            )
    rY: FloatProperty(
            name='Y', min=0.000, max=0.999, default=0, precision=3,
            description='Randomness for Y axis',
            )
    rZ: FloatProperty(
            name='Z', min=0.000, max=0.999, default=0, precision=3,
            description='Randomness for Z axis',
            )

    rot: FloatProperty(
            name='Rotation', min=0.000, max=1, default=0, precision=3,
            description='Randomness for vertical position (0-> All straight)',
            )
    afn: IntProperty(
            name='Affinity', min=0, max=10, default=5,
            description='Number of books with same rotation angle',
            )

    # Materials
    crt_mat: BoolProperty(
            name="Create default Cycles materials",
            description="Create default materials for Cycles render",
            default=True,
            )
    objcol: FloatVectorProperty(
            name="Color",
            description="Color for material",
            default=(1.0, 1.0, 1.0, 1.0),
            min=0.1, max=1,
            subtype='COLOR',
            size=4,
            )
    rC: FloatProperty(
            name='Randomness',
            min=0.000, max=1, default=0, precision=3,
            description='Randomness for color ',
            )

    # -----------------------------------------------------
    # Draw (create UI interface)
    # -----------------------------------------------------
    # noinspection PyUnusedLocal
    def draw(self, context):
        layout = self.layout
        space = bpy.context.space_data
        if not space.local_view:
            # Imperial units warning
            if bpy.context.scene.unit_settings.system == "IMPERIAL":
                row = layout.row()
                row.label(text="Warning: Imperial units not supported", icon='COLOR_RED')

            box = layout.box()
            box.label(text="Book size")
            row = box.row()
            row.prop(self, 'width')
            row.prop(self, 'depth')
            row.prop(self, 'height')
            row = box.row()
            row.prop(self, 'num', slider=True)

            box = layout.box()
            box.label(text="Randomness")
            row = box.row()
            row.prop(self, 'rX', slider=True)
            row.prop(self, 'rY', slider=True)
            row.prop(self, 'rZ', slider=True)
            row = box.row()
            row.prop(self, 'rot', slider=True)
            row.prop(self, 'afn', slider=True)

            box = layout.box()
            if not context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
                box.enabled = False
            box.prop(self, 'crt_mat')
            if self.crt_mat:
                row = box.row()
                row.prop(self, 'objcol')
                row = box.row()
                row.prop(self, 'rC', slider=True)
        else:
            row = layout.row()
            row.label(text="Warning: Operator does not work in local view mode", icon='ERROR')

    # -----------------------------------------------------
    # Execute
    # -----------------------------------------------------
    # noinspection PyUnusedLocal
    def execute(self, context):
        if bpy.context.mode == "OBJECT":
            # Create shelves
            create_book_mesh(self)
            return {'FINISHED'}
        else:
            self.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
            return {'CANCELLED'}


# ------------------------------------------------------------------------------
# Generate mesh data
# All custom values are passed using self container (self.myvariable)
# ------------------------------------------------------------------------------
def create_book_mesh(self):
    # deactivate others
    for o in bpy.data.objects:
        if o.select_get() is True:
            o.select_set(False)
    bpy.ops.object.select_all(action='DESELECT')
    generate_books(self)

    return


# ------------------------------------------------------------------------------
# Generate books
# All custom values are passed using self container (self.myvariable)
# ------------------------------------------------------------------------------
def generate_books(self):
    boxes = []
    location = bpy.context.scene.cursor.location
    myloc = copy(location)  # copy location to keep 3D cursor position

    # Create
    lastx = myloc.x
    ox = 0
    oy = 0
    oz = 0
    ot = 0
    i = 0
    for x in range(self.num):
        # reset rotation
        if i >= self.afn:
            i = 0
            ot = -1

        mydata = create_book("Book" + str(x),
                             self.width, self.depth, self.height,
                             lastx, myloc.y, myloc.z,
                             self.crt_mat if bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'} else False,
                             self.rX, self.rY, self.rZ, self.rot, ox, oy, oz, ot,
                             self.objcol, self.rC)
        boxes.extend([mydata[0]])
        bookdata = mydata[1]

        # calculate rotation using previous book
        ot = bookdata[3]
        i += 1
        oz = 0

        # calculate x size after rotation
        if i < self.afn:
            size = 0.0002
        else:
            size = 0.0003 + cos(radians(90 - bookdata[3])) * bookdata[2]  # the height is the radius
            oz = bookdata[2]

        lastx = lastx + bookdata[0] + size

    # refine units
    for box in boxes:
        remove_doubles(box)
        set_normals(box)

    # deactivate others
    for o in bpy.data.objects:
        if o.select_get() is True:
            o.select_set(False)

    boxes[0].select_set(True)
    bpy.context.view_layer.objects.active = boxes[0]

    return


# ------------------------------------------------------------------------------
# Create books unit
#
# objName: Name for the new object
# thickness: wood thickness (sides)
# sX: Size in X axis
# sY: Size in Y axis
# sZ: Size in Z axis
# pX: position X axis
# pY: position Y axis
# pZ: position Z axis
# mat: Flag for creating materials
# frX: Random factor X
# frY: Random factor Y
# frZ: Random factor Z
# frR: Random factor Rotation
# oX: override x size
# oY: override y size
# oZ: override z size
# oR: override rotation
# objcol: color
# frC: color randomness factor
# ------------------------------------------------------------------------------
def create_book(objname, sx, sy, sz, px, py, pz, mat, frx,
                fry, frz, frr, ox, oy, oz, ot, objcol, frc):
    # gap Randomness
    ri = randint(10, 150)
    gap = ri / 100000
    # Randomness X
    if ox == 0:
        ri = randint(0, int(frx * 1000))
        factor = ri / 1000
        sx -= sx * factor
        if sx < (gap * 3):
            sx = gap * 3
    else:
        sx = ox

        # Randomness Y
    if oy == 0:
        ri = randint(0, int(fry * 1000))
        factor = ri / 1000
        sy -= sy * factor
        if sy < (gap * 3):
            sy = gap * 3
    else:
        sy = oy

        # Randomness Z
    if oz == 0:
        ri = randint(0, int(frz * 1000))
        factor = ri / 1000
        sz -= sz * factor
        if sz < (gap * 3):
            sz = gap * 3
    else:
        sz = oz

        # Randomness rotation
    rot = 0
    if frr > 0 and ot != -1:
        if ot == 0:
            ri = randint(0, int(frr * 1000))
            factor = ri / 1000
            rot = 30 * factor
        else:
            rot = ot

    # Randomness color (only hue)
    hsv = rgb_to_hsv(objcol[0], objcol[1], objcol[2])
    hue = hsv[0]
    if frc > 0:
        rc1 = randint(0, int(hue * 1000))  # 0 to hue
        rc2 = randint(int(hue * 1000), 1000)  # hue to maximum
        rc3 = randint(0, 1000)  # sign

        if rc3 >= hue * 1000:
            hue += (rc2 * frc) / 1000
        else:
            hue -= (rc1 * frc) / 1000
        # Convert random color
        objcol = hsv_to_rgb(hue, hsv[1], hsv[2])

    myvertex = []
    myfaces = []
    x = 0
    # Left side
    myvertex.extend([(x, -sy, 0), (0, 0, 0), (x, 0, sz), (x, -sy, sz)])
    myfaces.extend([(0, 1, 2, 3)])

    myvertex.extend([(x + gap, -sy + gap, 0), (x + gap, 0, 0), (x + gap, 0, sz),
                     (x + gap, -sy + gap, sz)])
    myfaces.extend([(4, 5, 6, 7)])

    # Right side
    x = sx - gap
    myvertex.extend([(x, -sy + gap, 0), (x, 0, 0), (x, 0, sz), (x, -sy + gap, sz)])
    myfaces.extend([(8, 9, 10, 11)])

    myvertex.extend([(x + gap, -sy, 0), (x + gap, 0, 0), (x + gap, 0, sz), (x + gap, -sy, sz)])
    myfaces.extend([(12, 13, 14, 15)])

    myfaces.extend(
        [(0, 12, 15, 3), (4, 8, 11, 7), (3, 15, 11, 7), (0, 12, 8, 4), (0, 1, 5, 4),
         (8, 9, 13, 12), (3, 2, 6, 7),
         (11, 10, 14, 15), (1, 2, 6, 5), (9, 10, 14, 13)])

    # Top inside
    myvertex.extend([(gap, -sy + gap, sz - gap), (gap, -gap, sz - gap), (sx - gap, -gap, sz - gap),
                     (sx - gap, -sy + gap, sz - gap)])
    myfaces.extend([(16, 17, 18, 19)])

    # bottom inside and front face
    myvertex.extend([(gap, -sy + gap, gap), (gap, -gap, gap), (sx - gap, -gap, gap), (sx - gap, -sy + gap, gap)])
    myfaces.extend([(20, 21, 22, 23), (17, 18, 22, 21)])

    mymesh = bpy.data.meshes.new(objname)
    mybook = bpy.data.objects.new(objname, mymesh)

    mybook.location[0] = px
    mybook.location[1] = py
    mybook.location[2] = pz + sin(radians(rot)) * sx
    bpy.context.collection.objects.link(mybook)

    mymesh.from_pydata(myvertex, [], myfaces)
    mymesh.update(calc_edges=True)

    # ---------------------------------
    # Materials and UV Maps
    # ---------------------------------
    if mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
        rgb = objcol
        # External
        mat = create_diffuse_material(objname + "_material", True,
                                      rgb[0], rgb[1], rgb[2], rgb[0], rgb[1], rgb[2], 0.05)
        set_material(mybook, mat)
        # UV unwrap external
        select_faces(mybook, 0, True)
        select_faces(mybook, 3, False)
        select_faces(mybook, 4, False)
        unwrap_mesh(mybook, False)
        # Add Internal
        mat = create_diffuse_material(objname + "_side_material", True, 0.5, 0.5, 0.5, 0.5, 0.5, 0.3, 0.03)
        mybook.data.materials.append(mat)
        select_faces(mybook, 14, True)
        select_faces(mybook, 15, False)
        select_faces(mybook, 16, False)
        set_material_faces(mybook, 1)
        # UV unwrap
        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.object.mode_set(mode='OBJECT')
        select_faces(mybook, 14, True)
        select_faces(mybook, 15, False)
        select_faces(mybook, 16, False)
        unwrap_mesh(mybook, False)

    # ---------------------------------
    # Rotation on Y axis
    # ---------------------------------
    mybook.rotation_euler = (0.0, radians(rot), 0.0)  # radians

    # add some gap to the size between books
    return mybook, (sx, sy, sz, rot)
