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

# <pep8 compliant>
"""Get some Blender particle objects translated to POV."""

import bpy
import random


def pixel_relative_guess(ob):
    """Convert some object x dimension to a rough pixel relative order of magnitude"""
    from bpy_extras import object_utils
    scene = bpy.context.scene
    cam = scene.camera
    render = scene.render
    # Get rendered image resolution
    output_x_res = render.resolution_x
    focal_length = cam.data.lens
    # Get object bounding box size
    object_location = ob.location
    object_dimension_x = ob.dimensions[0]
    world_to_camera = object_utils.world_to_camera_view(scene, cam, object_location)

    apparent_size = (object_dimension_x * focal_length) / world_to_camera[2]
    sensor_width = cam.data.sensor_width
    pixel_pitch_x = sensor_width / output_x_res
    return apparent_size / pixel_pitch_x


def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
    """Get Blender path particles (hair strands) objects translated to POV sphere_sweep unions."""
    # tstart = time.time()
    textured_hair = 0
    if ob.material_slots[p_sys.settings.material - 1].material and ob.active_material is not None:
        pmaterial = ob.material_slots[p_sys.settings.material - 1].material
        # XXX Todo: replace by pov_(Particles?)_texture_slot
        for th in pmaterial.pov_texture_slots:
            povtex = th.texture  # slot.name
            tex = bpy.data.textures[povtex]

            if (
                th
                and th.use
                and (
                    (tex.type == 'IMAGE' and tex.image) or tex.type != 'IMAGE'
                )
                and th.use_map_color_diffuse
            ):
                textured_hair = 1
        if pmaterial.strand.use_blender_units:
            strand_start = pmaterial.strand.root_size
            strand_end = pmaterial.strand.tip_size
        else:
            try:
                # inexact pixel size, just to make radius relative to screen and object size.
                pixel_fac = pixel_relative_guess(ob)
            except ZeroDivisionError:
                # Fallback to hardwired constant value
                pixel_fac = 4500
                print("no pixel size found for stand radius, falling back to  %i" % pixel_fac)

            strand_start = pmaterial.strand.root_size / pixel_fac
            strand_end = pmaterial.strand.tip_size / pixel_fac
        strand_shape = pmaterial.strand.shape
    else:
        pmaterial = "default"  # No material assigned in blender, use default one
        strand_start = 0.01
        strand_end = 0.01
        strand_shape = 0.0
    # Set the number of particles to render count rather than 3d view display
    # p_sys.set_resolution(scene, ob, 'RENDER') # DEPRECATED
    # When you render, the entire dependency graph will be
    # evaluated at render resolution, including the particles.
    # In the viewport it will be at viewport resolution.
    # So there is no need fo render engines to use this function anymore,
    # it's automatic now.
    steps = p_sys.settings.display_step
    steps = 2 ** steps  # or + 1 # Formerly : len(particle.hair_keys)

    total_number_of_strands = p_sys.settings.count + p_sys.settings.rendered_child_count
    # hairCounter = 0
    file.write('#declare HairArray = array[%i] {\n' % total_number_of_strands)
    for pindex in range(total_number_of_strands):

        # if particle.is_exist and particle.is_visible:
        # hairCounter += 1
        # controlPointCounter = 0
        # Each hair is represented as a separate sphere_sweep in POV-Ray.

        file.write('sphere_sweep{')
        if p_sys.settings.use_hair_bspline:
            file.write('b_spline ')
            file.write(
                '%i,\n' % (steps + 2)
            )  # +2 because the first point needs tripling to be more than a handle in POV
        else:
            file.write('linear_spline ')
            file.write('%i,\n' % steps)
        # changing world coordinates to object local coordinates by
        # multiplying with inverted matrix
        init_coord = ob.matrix_world.inverted() @ (p_sys.co_hair(ob, particle_no=pindex, step=0))
        if (
            ob.material_slots[p_sys.settings.material - 1].material
            and ob.active_material is not None
        ):
            pmaterial = ob.material_slots[p_sys.settings.material - 1].material
            for th in pmaterial.pov_texture_slots:
                if th and th.use and th.use_map_color_diffuse:
                    povtex = th.texture  # slot.name
                    tex = bpy.data.textures[povtex]
                    # treat POV textures as bitmaps
                    if (
                        tex.type == 'IMAGE'
                        and tex.image
                        and th.texture_coords == 'UV'
                        and ob.data.uv_textures is not None
                    ):
                        # or (
                        # tex.pov.tex_pattern_type != 'emulator'
                        # and th.texture_coords == 'UV'
                        # and ob.data.uv_textures is not None
                        # ):
                        image = tex.image
                        image_width = image.size[0]
                        image_height = image.size[1]
                        image_pixels = image.pixels[:]
                        uv_co = p_sys.uv_on_emitter(mod, p_sys.particles[pindex], pindex, 0)
                        x_co = round(uv_co[0] * (image_width - 1))
                        y_co = round(uv_co[1] * (image_height - 1))
                        pixelnumber = (image_width * y_co) + x_co
                        r = image_pixels[pixelnumber * 4]
                        g = image_pixels[pixelnumber * 4 + 1]
                        b = image_pixels[pixelnumber * 4 + 2]
                        a = image_pixels[pixelnumber * 4 + 3]
                        init_color = (r, g, b, a)
                    else:
                        # only overwrite variable for each competing texture for now
                        init_color = tex.evaluate((init_coord[0], init_coord[1], init_coord[2]))
        for step in range(steps):
            coord = ob.matrix_world.inverted() @ (p_sys.co_hair(ob, particle_no=pindex, step=step))
            # for controlPoint in particle.hair_keys:
            if p_sys.settings.clump_factor != 0:
                hair_strand_diameter = p_sys.settings.clump_factor / 200.0 * random.uniform(0.5, 1)
            elif step == 0:
                hair_strand_diameter = strand_start
            else:
                # still initialize variable
                hair_strand_diameter = strand_start
                if strand_shape != 0.0:
                    if strand_shape < 0.0:
                        fac = pow(step, (1.0 + strand_shape))
                    else:
                        fac = pow(step, (1.0 / (1.0 - strand_shape)))
                else:
                    fac = step
                hair_strand_diameter += fac * (strand_end - strand_start) / (
                    p_sys.settings.display_step + 1
                )  # XXX +1 or -1 or nothing ?
            if step == 0 and p_sys.settings.use_hair_bspline:
                # Write three times the first point to compensate pov Bezier handling
                file.write(
                    '<%.6g,%.6g,%.6g>,%.7g,\n'
                    % (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
                )
                file.write(
                    '<%.6g,%.6g,%.6g>,%.7g,\n'
                    % (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
                )
                # Useless because particle location is the tip, not the root:
                # file.write(
                # '<%.6g,%.6g,%.6g>,%.7g'
                # % (
                # particle.location[0],
                # particle.location[1],
                # particle.location[2],
                # abs(hair_strand_diameter)
                # )
                # )
                # file.write(',\n')
            # controlPointCounter += 1
            # total_number_of_strands += len(p_sys.particles)# len(particle.hair_keys)

            # Each control point is written out, along with the radius of the
            # hair at that point.
            file.write(
                '<%.6g,%.6g,%.6g>,%.7g' % (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
            )

            # All coordinates except the last need a following comma.

            if step == steps - 1:
                if textured_hair:
                    # Write pigment and alpha (between Pov and Blender,
                    # alpha 0 and 1 are reversed)
                    file.write(
                        '\npigment{ color srgbf < %.3g, %.3g, %.3g, %.3g> }\n'
                        % (init_color[0], init_color[1], init_color[2], 1.0 - init_color[3])
                    )
                # End the sphere_sweep declaration for this hair
                file.write('}\n')

            else:
                file.write(',\n')
        # All but the final sphere_sweep (each array element) needs a terminating comma.
        if pindex != total_number_of_strands:
            file.write(',\n')
        else:
            file.write('\n')

    # End the array declaration.

    file.write('}\n')
    file.write('\n')

    if not textured_hair:
        # Pick up the hair material diffuse color and create a default POV-Ray hair texture.

        file.write('#ifndef (HairTexture)\n')
        file.write('  #declare HairTexture = texture {\n')
        file.write(
            '    pigment {srgbt <%s,%s,%s,%s>}\n'
            % (
                pmaterial.diffuse_color[0],
                pmaterial.diffuse_color[1],
                pmaterial.diffuse_color[2],
                (pmaterial.strand.width_fade + 0.05),
            )
        )
        file.write('  }\n')
        file.write('#end\n')
        file.write('\n')

    # Dynamically create a union of the hairstrands (or a subset of them).
    # By default use every hairstrand, commented line is for hand tweaking test renders.
    file.write('//Increasing HairStep divides the amount of hair for test renders.\n')
    file.write('#ifndef(HairStep) #declare HairStep = 1; #end\n')
    file.write('union{\n')
    file.write('  #local I = 0;\n')
    file.write('  #while (I < %i)\n' % total_number_of_strands)
    file.write('    object {HairArray[I]')
    if textured_hair:
        file.write('\n')
    else:
        file.write(' texture{HairTexture}\n')
    # Translucency of the hair:
    file.write('        hollow\n')
    file.write('        double_illuminate\n')
    file.write('        interior {\n')
    file.write('            ior 1.45\n')
    file.write('            media {\n')
    file.write('                scattering { 1, 10*<0.73, 0.35, 0.15> /*extinction 0*/ }\n')
    file.write('                absorption 10/<0.83, 0.75, 0.15>\n')
    file.write('                samples 1\n')
    file.write('                method 2\n')
    file.write('                density {cylindrical\n')
    file.write('                    color_map {\n')
    file.write('                        [0.0 rgb <0.83, 0.45, 0.35>]\n')
    file.write('                        [0.5 rgb <0.8, 0.8, 0.4>]\n')
    file.write('                        [1.0 rgb <1,1,1>]\n')
    file.write('                    }\n')
    file.write('                }\n')
    file.write('            }\n')
    file.write('        }\n')
    file.write('    }\n')

    file.write('    #local I = I + HairStep;\n')
    file.write('  #end\n')

    write_matrix(global_matrix @ ob.matrix_world)

    file.write('}')
    print("Totals hairstrands written: %i" % total_number_of_strands)
    print("Number of tufts (particle systems)", len(ob.particle_systems))

    # Set back the displayed number of particles to preview count
    # p_sys.set_resolution(scene, ob, 'PREVIEW') #DEPRECATED
    # When you render, the entire dependency graph will be
    # evaluated at render resolution, including the particles.
    # In the viewport it will be at viewport resolution.
    # So there is no need fo render engines to use this function anymore,
    # it's automatic now.