# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# Original Author = Jacob Morris
# URL = blendingjacob.blogspot.com

# Note: scene properties are moved into __init__ together with the 3 update functions
#       for properties search for the name patterns adv_obj and advanced_objects

bl_info = {
    "name": "CubeSter",
    "author": "Jacob Morris",
    "version": (0, 7, 2),
    "blender": (2, 78, 0),
    "location": "View 3D > Toolbar > CubeSter",
    "description": "Takes image, image sequence, or audio file and converts it "
                   "into a height map based on pixel color and alpha values",
    "category": "Add Mesh"
}

import bpy
import bmesh
from bpy.types import (
    Operator,
    Panel,
)

import timeit
from random import uniform
from math import radians
from os import (
    path,
    listdir,
)


# create block at center position x, y with block width 2 * hx and 2 * hy and height of h
def create_block(x, y, hw, h, verts: list, faces: list):
    if bpy.context.scene.advanced_objects.cubester_block_style == "size":
        z = 0.0
    else:
        z = h
        h = 2 * hw

    p = len(verts)
    verts += [(x - hw, y - hw, z), (x + hw, y - hw, z), (x + hw, y + hw, z), (x - hw, y + hw, z)]
    verts += [(x - hw, y - hw, z + h), (x + hw, y - hw, z + h),
              (x + hw, y + hw, z + h), (x - hw, y + hw, z + h)]

    faces += [(p, p + 1, p + 5, p + 4), (p + 1, p + 2, p + 6, p + 5),
              (p + 2, p + 3, p + 7, p + 6), (p, p + 4, p + 7, p + 3),
              (p + 4, p + 5, p + 6, p + 7), (p, p + 3, p + 2, p + 1)]


# go through all frames in len(frames), adjusting values at frames[x][y]
def create_f_curves(mesh, frames, frame_step_size, style):
    # use data to animate mesh
    action = bpy.data.actions.new("CubeSterAnimation")

    mesh.animation_data_create()
    mesh.animation_data.action = action

    data_path = "vertices[%d].co"

    vert_index = 4 if style == "blocks" else 0  # index of first vertex

    # loop for every face height value
    for frame_start_vert in range(len(frames[0])):
        # only go once if plane, otherwise do all four vertices that are in top plane if blocks
        end_point = frame_start_vert + 4 if style == "blocks" else frame_start_vert + 1

        # loop through to get the four vertices that compose the face
        for frame_vert in range(frame_start_vert, end_point):
            # fcurves for x, y, z
            fcurves = [action.fcurves.new(data_path % vert_index, i) for i in range(3)]
            frame_counter = 0  # go through each frame and add position
            temp_v = mesh.vertices[vert_index].co

            # loop through frames
            for frame in frames:
                # new x, y, z positions
                vals = [temp_v[0], temp_v[1], frame[frame_start_vert]]
                for i in range(3):  # for each x, y, z set each corresponding fcurve
                    fcurves[i].keyframe_points.insert(frame_counter, vals[i], {'FAST'})

                frame_counter += frame_step_size  # skip frames for smoother animation

            vert_index += 1

        # only skip vertices if made of blocks
        if style == "blocks":
            vert_index += 4


# create material with given name, apply to object
def create_material(scene, ob, name):
    mat = bpy.data.materials.new("CubeSter_" + name)
    adv_obj = scene.advanced_objects
    image = None

    # image
    if not adv_obj.cubester_use_image_color and adv_obj.cubester_color_image in bpy.data.images:
        try:
            image = bpy.data.images[adv_obj.cubester_color_image]
        except:
            pass
    else:
        try:
            image = bpy.data.images[adv_obj.cubester_image]
        except:
            pass

    if scene.render.engine == "CYCLES":
        mat.use_nodes = True
        nodes = mat.node_tree.nodes

        att = nodes.new("ShaderNodeAttribute")
        att.attribute_name = "Col"
        att.location = (-200, 300)

        att = nodes.new("ShaderNodeTexImage")
        if image:
            att.image = image

        if adv_obj.cubester_load_type == "multiple":
            att.image.source = "SEQUENCE"
        att.location = (-200, 700)

        att = nodes.new("ShaderNodeTexCoord")
        att.location = (-450, 600)

        if adv_obj.cubester_materials == "image":
            mat.node_tree.links.new(
                nodes["Image Texture"].outputs[0],
                nodes["Diffuse BSDF"].inputs[0]
            )
            mat.node_tree.links.new(
                nodes["Texture Coordinate"].outputs[2],
                nodes["Image Texture"].inputs[0]
            )
        else:
            mat.node_tree.links.new(
                nodes["Attribute"].outputs[0],
                nodes["Diffuse BSDF"].inputs[0]
            )
    else:
        if adv_obj.cubester_materials == "image" or scene.render.engine != "BLENDER_RENDER":
            tex = bpy.data.textures.new("CubeSter_" + name, "IMAGE")
            if image:
                tex.image = image
            slot = mat.texture_slots.add()
            slot.texture = tex
        else:
            mat.use_vertex_color_paint = True

    ob.data.materials.append(mat)


# generate mesh from audio
def create_mesh_from_audio(self, context, verts, faces):
    scene = context.scene
    view_layer = context.view_layer
    adv_obj = scene.advanced_objects
    audio_filepath = adv_obj.cubester_audio_path
    width = adv_obj.cubester_audio_width_blocks
    length = adv_obj.cubester_audio_length_blocks

    size_per_hundred = adv_obj.cubester_size_per_hundred_pixels
    size = size_per_hundred / 100
    # Note: used for compatibility with vertex colors changes
    bl_version = bool(bpy.app.version >= (2, 79, 1))

    # create all blocks
    y = -(width / 2) * size + (size / 2)
    for r in range(width):
        x = -(length / 2) * size + (size / 2)
        for c in range(length):
            create_block(x, y, size / 2, 1, verts, faces)

            x += size
        y += size

    # create object
    mesh = bpy.data.meshes.new("cubed")
    mesh.from_pydata(verts, [], faces)
    ob = bpy.data.objects.new("cubed", mesh)
    bpy.context.collection.objects.link(ob)
    bpy.context.view_layer.objects.active = ob
    ob.select_set(True)

    # initial vertex colors
    if adv_obj.cubester_materials == "image" and adv_obj.cubester_color_image != "":
        picture = bpy.data.images[adv_obj.cubester_color_image]
        pixels = list(picture.pixels)
        vert_colors = []

        skip_y = int(picture.size[1] / width)
        skip_x = int(picture.size[0] / length)

        for row in range(0, picture.size[1], skip_y + 1):
            # go through each column, step by appropriate amount
            for column in range(0, picture.size[0] * 4, 4 + skip_x * 4):
                r, g, b, a = get_pixel_values(picture, pixels, row, column)
                get_colors = (r, g, b, a) if bl_version else (r, g, b)
                vert_colors += [get_colors for i in range(24)]

        bpy.ops.mesh.vertex_color_add()

        i = 0
        vert_colors_size = len(vert_colors)
        for c in ob.data.vertex_colors[0].data:
            if i < vert_colors_size:
                c.color = vert_colors[i]
                i += 1

        # image sequence handling
        if adv_obj.cubester_load_type == "multiple":
            images = find_sequence_images(self, bpy.context)

            frames_vert_colors = []

            max_images = adv_obj.cubester_max_images + 1 if \
                        len(images[0]) > adv_obj.cubester_max_images else len(images[0])

            # goes through and for each image for each block finds new height
            for image_index in range(0, max_images, adv_obj.cubester_skip_images):
                filepath = images[0][image_index]
                name = images[1][image_index]
                picture = fetch_image(self, name, filepath)
                pixels = list(picture.pixels)

                frame_colors = []

                for row in range(0, picture.size[1], skip_y + 1):
                    for column in range(0, picture.size[0] * 4, 4 + skip_x * 4):
                        r, g, b, a = get_pixel_values(picture, pixels, row, column)
                        get_colors = (r, g, b, a) if bl_version else (r, g, b)
                        frame_colors += [get_colors for i in range(24)]

                frames_vert_colors.append(frame_colors)

            adv_obj.cubester_vertex_colors[ob.name] = \
                                    {"type": "vertex", "frames": frames_vert_colors,
                                    "frame_skip": adv_obj.cubester_frame_step,
                                    "total_images": max_images}

        # either add material or create
        if ("CubeSter_" + "Vertex") in bpy.data.materials:
            ob.data.materials.append(bpy.data.materials["CubeSter_" + "Vertex"])
        else:
            create_material(scene, ob, "Vertex")

    # set keyframe for each object as initial point
    frame = [1 for i in range(int(len(verts) / 8))]
    frames = [frame]

    area = bpy.context.area
    old_type = area.type
    area.type = "GRAPH_EDITOR"

    scene.frame_current = 0

    create_f_curves(mesh, frames, 1, "blocks")

    # deselect all fcurves
    fcurves = ob.data.animation_data.action.fcurves.data.fcurves
    for i in fcurves:
        i.select = False

    max_images = adv_obj.cubester_audio_max_freq
    min_freq = adv_obj.cubester_audio_min_freq
    freq_frame = adv_obj.cubester_audio_offset_type

    freq_step = (max_images - min_freq) / length
    freq_sub_step = freq_step / width

    frame_step = adv_obj.cubester_audio_frame_offset

    # animate each block with a portion of the frequency
    for c in range(length):
        frame_off = 0
        for r in range(width):
            if freq_frame == "frame":
                scene.frame_current = frame_off
                l = c * freq_step
                h = (c + 1) * freq_step
                frame_off += frame_step
            else:
                l = c * freq_step + (r * freq_sub_step)
                h = c * freq_step + ((r + 1) * freq_sub_step)

            pos = c + (r * length)  # block number
            index = pos * 4  # first index for vertex

            # select curves
            for i in range(index, index + 4):
                curve = i * 3 + 2  # fcurve location
                fcurves[curve].select = True
            try:
                bpy.ops.graph.sound_bake(filepath=bpy.path.abspath(audio_filepath), low=l, high=h)
            except:
                pass

            # deselect curves
            for i in range(index, index + 4):
                curve = i * 3 + 2  # fcurve location
                fcurves[curve].select = False

    area.type = old_type

    # UV unwrap
    create_uv_map(bpy.context, width, length)

    # if radial apply needed modifiers
    if adv_obj.cubester_audio_block_layout == "radial":
        # add bezier curve of correct width
        bpy.ops.curve.primitive_bezier_circle_add()
        curve = bpy.context.object
        # slope determined off of collected data
        curve_size = (0.319 * (width * (size * 100)) - 0.0169) / 100
        curve.dimensions = (curve_size, curve_size, 0.0)
        # correct for z height
        curve.scale = (curve.scale[0], curve.scale[0], curve.scale[0])

        ob.select_set(True)
        curve.select_set(False)
        view_layer.objects.active = ob

        # data was collected and then multi-variable regression was done in Excel
        # influence of width and length
        width_infl, length_infl, intercept = -0.159125, 0.49996, 0.007637
        x_offset = ((width * (size * 100) * width_infl) +
                   (length * (size * 100) * length_infl) + intercept) / 100
        ob.location = (ob.location[0] + x_offset, ob.location[1], ob.location[2])

        ob.rotation_euler = (radians(-90), 0.0, 0.0)
        bpy.ops.object.modifier_add(type="CURVE")
        ob.modifiers["Curve"].object = curve
        ob.modifiers["Curve"].deform_axis = "POS_Z"


# generate mesh from image(s)
def create_mesh_from_image(self, scene, verts, faces):
    context = bpy.context
    adv_obj = scene.advanced_objects
    picture = bpy.data.images[adv_obj.cubester_image]
    pixels = list(picture.pixels)
    # Note: used for compatibility with vertex colors changes
    bl_version = bool(bpy.app.version >= (2, 79, 1))

    x_pixels = picture.size[0] / (adv_obj.cubester_skip_pixels + 1)
    y_pixels = picture.size[1] / (adv_obj.cubester_skip_pixels + 1)

    width = x_pixels / 100 * adv_obj.cubester_size_per_hundred_pixels
    height = y_pixels / 100 * adv_obj.cubester_size_per_hundred_pixels

    step = width / x_pixels
    half_width = step / 2

    y = -height / 2 + half_width

    vert_colors = []
    rows = 0

    # go through each row of pixels stepping by adv_obj.cubester_skip_pixels + 1
    for row in range(0, picture.size[1], adv_obj.cubester_skip_pixels + 1):
        rows += 1
        x = -width / 2 + half_width  # reset to left edge of mesh
        # go through each column, step by appropriate amount
        for column in range(0, picture.size[0] * 4, 4 + adv_obj.cubester_skip_pixels * 4):
            r, g, b, a = get_pixel_values(picture, pixels, row, column)
            get_colors = (r, g, b, a) if bl_version else (r, g, b)
            h = find_point_height(r, g, b, a, scene)

            # if not transparent
            if h != -1:
                if adv_obj.cubester_mesh_style == "blocks":
                    create_block(x, y, half_width, h, verts, faces)
                    vert_colors += [get_colors for i in range(24)]
                else:
                    verts += [(x, y, h)]
                    vert_colors += [get_colors for i in range(4)]

            x += step
        y += step

        # if plane not blocks, then remove last 4 items from vertex_colors
        # as the faces have already wrapped around
        if adv_obj.cubester_mesh_style == "plane":
            del vert_colors[len(vert_colors) - 4:len(vert_colors)]

    # create faces if plane based and not block based
    if adv_obj.cubester_mesh_style == "plane":
        off = int(len(verts) / rows)
        for r in range(rows - 1):
            for c in range(off - 1):
                faces += [(r * off + c, r * off + c + 1, (r + 1) * off + c + 1, (r + 1) * off + c)]

    mesh = bpy.data.meshes.new("cubed")
    mesh.from_pydata(verts, [], faces)
    ob = bpy.data.objects.new("cubed", mesh)
    context.collection.objects.link(ob)
    context.view_layer.objects.active = ob
    ob.select_set(True)

    # uv unwrap
    if adv_obj.cubester_mesh_style == "blocks":
        create_uv_map(context, rows, int(len(faces) / 6 / rows))
    else:
        create_uv_map(context, rows - 1, int(len(faces) / (rows - 1)))

    # material
    # determine name and if already created
    if adv_obj.cubester_materials == "vertex":  # vertex color
        image_name = "Vertex"
    elif not adv_obj.cubester_use_image_color and \
       adv_obj.cubester_color_image in bpy.data.images and \
       adv_obj.cubester_materials == "image":  # replaced image
        image_name = adv_obj.cubester_color_image
    else:  # normal image
        image_name = adv_obj.cubester_image

    # either add material or create
    if ("CubeSter_" + image_name) in bpy.data.materials:
        ob.data.materials.append(bpy.data.materials["CubeSter_" + image_name])

    # create material
    else:
        create_material(scene, ob, image_name)

    # vertex colors
    bpy.ops.mesh.vertex_color_add()
    i = 0
    for c in ob.data.vertex_colors[0].data:
        c.color = vert_colors[i]
        i += 1

    frames = []
    # image sequence handling
    if adv_obj.cubester_load_type == "multiple":
        images = find_sequence_images(self, context)
        frames_vert_colors = []

        max_images = adv_obj.cubester_max_images + 1 if \
                    len(images[0]) > adv_obj.cubester_max_images else len(images[0])

        # goes through and for each image for each block finds new height
        for image_index in range(0, max_images, adv_obj.cubester_skip_images):
            filepath = images[0][image_index]
            name = images[1][image_index]
            picture = fetch_image(self, name, filepath)
            pixels = list(picture.pixels)

            frame_heights = []
            frame_colors = []

            for row in range(0, picture.size[1], adv_obj.cubester_skip_pixels + 1):
                for column in range(0, picture.size[0] * 4, 4 + adv_obj.cubester_skip_pixels * 4):
                    r, g, b, a = get_pixel_values(picture, pixels, row, column)
                    get_colors = (r, g, b, a) if bl_version else (r, g, b)
                    h = find_point_height(r, g, b, a, scene)

                    if h != -1:
                        frame_heights.append(h)
                        if adv_obj.cubester_mesh_style == "blocks":
                            frame_colors += [get_colors for i in range(24)]
                        else:
                            frame_colors += [get_colors for i in range(4)]

            if adv_obj.cubester_mesh_style == "plane":
                del vert_colors[len(vert_colors) - 4:len(vert_colors)]

            frames.append(frame_heights)
            frames_vert_colors.append(frame_colors)

        # determine what data to use
        if adv_obj.cubester_materials == "vertex" or scene.render.engine == "BLENDER_ENGINE":
            adv_obj.cubester_vertex_colors[ob.name] = {
                "type": "vertex", "frames": frames_vert_colors,
                "frame_skip": adv_obj.cubester_frame_step,
                "total_images": max_images
            }
        else:
            adv_obj.cubester_vertex_colors[ob.name] = {
                "type": "image", "frame_skip": adv_obj.cubester_frame_step,
                "total_images": max_images
            }
            att = get_image_node(ob.data.materials[0])
            att.image_user.frame_duration = len(frames) * adv_obj.cubester_frame_step

        # animate mesh
        create_f_curves(
            mesh, frames,
            adv_obj.cubester_frame_step,
            adv_obj.cubester_mesh_style
        )


# generate uv map for object
def create_uv_map(context, rows, columns):
    adv_obj = context.scene.advanced_objects
    mesh = context.object.data
    mesh.uv_textures.new("cubester")
    bm = bmesh.new()
    bm.from_mesh(mesh)

    uv_layer = bm.loops.layers.uv[0]
    bm.faces.ensure_lookup_table()

    x_scale = 1 / columns
    y_scale = 1 / rows

    y_pos = 0.0
    x_pos = 0.0
    count = columns - 1  # hold current count to compare to if need to go to next row

    # if blocks
    if adv_obj.cubester_mesh_style == "blocks":
        for fa in range(int(len(bm.faces) / 6)):
            for i in range(6):
                pos = (fa * 6) + i
                bm.faces[pos].loops[0][uv_layer].uv = (x_pos, y_pos)
                bm.faces[pos].loops[1][uv_layer].uv = (x_pos + x_scale, y_pos)
                bm.faces[pos].loops[2][uv_layer].uv = (x_pos + x_scale, y_pos + y_scale)
                bm.faces[pos].loops[3][uv_layer].uv = (x_pos, y_pos + y_scale)

            x_pos += x_scale

            if fa >= count:
                y_pos += y_scale
                x_pos = 0.0
                count += columns

    # if planes
    else:
        for fa in range(len(bm.faces)):
            bm.faces[fa].loops[0][uv_layer].uv = (x_pos, y_pos)
            bm.faces[fa].loops[1][uv_layer].uv = (x_pos + x_scale, y_pos)
            bm.faces[fa].loops[2][uv_layer].uv = (x_pos + x_scale, y_pos + y_scale)
            bm.faces[fa].loops[3][uv_layer].uv = (x_pos, y_pos + y_scale)

            x_pos += x_scale

            if fa >= count:
                y_pos += y_scale
                x_pos = 0.0
                count += columns

    bm.to_mesh(mesh)


# if already loaded return image, else load and return
def fetch_image(self, name, load_path):
    if name in bpy.data.images:
        return bpy.data.images[name]
    else:
        try:
            image = bpy.data.images.load(load_path)
            return image
        except RuntimeError:
            self.report({"ERROR"}, "CubeSter: '{}' could not be loaded".format(load_path))
            return None


# find height for point
def find_point_height(r, g, b, a, scene):
    adv_obj = scene.advanced_objects
    if a:  # if not completely transparent
        normalize = 1

        # channel weighting
        if not adv_obj.cubester_advanced:
            composed = 0.25 * r + 0.25 * g + 0.25 * b + 0.25 * a
        else:
            # user defined weighting
            if not adv_obj.cubester_random_weights:
                composed = adv_obj.cubester_weight_r * r + adv_obj.cubester_weight_g * g + \
                        adv_obj.cubester_weight_b * b + adv_obj.cubester_weight_a * a
                total = adv_obj.cubester_weight_r + adv_obj.cubester_weight_g + adv_obj.cubester_weight_b + \
                        adv_obj.cubester_weight_a

                normalize = 1 / total
            # random weighting
            else:
                weights = [uniform(0.0, 1.0) for i in range(4)]
                composed = weights[0] * r + weights[1] * g + weights[2] * b + weights[3] * a
                total = weights[0] + weights[1] + weights[2] + weights[3]
                normalize = 1 / total

        if adv_obj.cubester_invert:
            h = (1 - composed) * adv_obj.cubester_height_scale * normalize
        else:
            h = composed * adv_obj.cubester_height_scale * normalize

        return h
    else:
        return -1


# find all images that would belong to sequence
def find_sequence_images(self, context):
    scene = context.scene
    images = [[], []]

    if scene.advanced_objects.cubester_image in bpy.data.images:
        image = bpy.data.images[scene.advanced_objects.cubester_image]
        main = image.name.split(".")[0]

        # first part of name to check against other files
        length = len(main)
        keep_going = True
        for i in range(length - 1, -1, -1):
            if main[i].isdigit() and keep_going:
                length -= 1
            else:
                keep_going = not keep_going
        name = main[0:length]

        dir_name = path.dirname(bpy.path.abspath(image.filepath))

        try:
            for file in listdir(dir_name):
                if path.isfile(path.join(dir_name, file)) and file.startswith(name):
                    images[0].append(path.join(dir_name, file))
                    images[1].append(file)
        except FileNotFoundError:
            self.report({"ERROR"}, "CubeSter: '{}' directory not found".format(dir_name))

    return images


# get image node
def get_image_node(mat):
    nodes = mat.node_tree.nodes
    att = nodes["Image Texture"]

    return att


# get the RGBA values from pixel
def get_pixel_values(picture, pixels, row, column):
    # determine i position to start at based on row and column position
    i = (row * picture.size[0] * 4) + column
    pixs = pixels[i: i + 4]
    r = pixs[0]
    g = pixs[1]
    b = pixs[2]
    a = pixs[3]

    return r, g, b, a


# frame change handler for materials
def material_frame_handler(scene):
    frame = scene.frame_current
    adv_obj = scene.advanced_objects

    keys = list(adv_obj.cubester_vertex_colors.keys())

    # get keys and see if object is still in scene
    for i in keys:
        # if object is in scene then update information
        if i in bpy.data.objects:
            ob = bpy.data.objects[i]
            data = adv_obj.advanced_objects.cubester_vertex_colors[ob.name]
            skip_frames = data["frame_skip"]

            # update materials using vertex colors
            if data['type'] == "vertex":
                colors = data["frames"]

                if frame % skip_frames == 0 and 0 <= frame < (data['total_images'] - 1) * skip_frames:
                    use_frame = int(frame / skip_frames)
                    color = colors[use_frame]

                    i = 0
                    for c in ob.data.vertex_colors[0].data:
                        c.color = color[i]
                        i += 1

            else:
                att = get_image_node(ob.data.materials[0])
                offset = frame - int(frame / skip_frames)
                att.image_user.frame_offset = -offset

        # if the object is no longer in the scene then delete then entry
        else:
            del adv_obj.advanced_objects.cubester_vertex_colors[i]


class CubeSterPanel(Panel):
    bl_idname = "OBJECT_PT_cubester"
    bl_label = "CubeSter"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_category = "Create"
    bl_options = {"DEFAULT_CLOSED"}
    bl_context = "objectmode"

    def draw(self, context):
        layout = self.layout.box()
        scene = bpy.context.scene
        adv_obj = scene.advanced_objects
        images_found = 0
        rows = 0
        columns = 0

        layout.prop(adv_obj, "cubester_audio_image")

        if adv_obj.cubester_audio_image == "image":
            box = layout.box()
            box.prop(adv_obj, "cubester_load_type")
            box.label(text="Image To Convert:")
            box.prop_search(adv_obj, "cubester_image", bpy.data, "images")
            box.prop(adv_obj, "cubester_load_image")

            # find number of appropriate images if sequence
            if adv_obj.cubester_load_type == "multiple":
                box = layout.box()
                # display number of images found there
                images = find_sequence_images(self, context)
                images_found = len(images[0]) if len(images[0]) <= adv_obj.cubester_max_images \
                               else adv_obj.cubester_max_images

                if len(images[0]):
                    box.label(str(len(images[0])) + " Images Found", icon="PACKAGE")

                box.prop(adv_obj, "cubester_max_images")
                box.prop(adv_obj, "cubester_skip_images")
                box.prop(adv_obj, "cubester_frame_step")

            box = layout.box()
            col = box.column(align=True)
            col.prop(adv_obj, "cubester_skip_pixels")
            col.prop(adv_obj, "cubester_size_per_hundred_pixels")
            col.prop(adv_obj, "cubester_height_scale")
            box.prop(adv_obj, "cubester_invert", icon="FILE_REFRESH")

            box = layout.box()
            box.prop(adv_obj, "cubester_mesh_style", icon="MESH_GRID")

            if adv_obj.cubester_mesh_style == "blocks":
                box.prop(adv_obj, "cubester_block_style")
        else:
            # audio file
            layout.prop(adv_obj, "cubester_audio_path")

            box = layout.box()
            col = box.column(align=True)
            col.prop(adv_obj, "cubester_audio_min_freq")
            col.prop(adv_obj, "cubester_audio_max_freq")

            box.separator()
            box.prop(adv_obj, "cubester_audio_offset_type")

            if adv_obj.cubester_audio_offset_type == "frame":
                box.prop(adv_obj, "cubester_audio_frame_offset")
            box.prop(adv_obj, "cubester_audio_block_layout")
            box.separator()

            col = box.column(align=True)
            col.prop(adv_obj, "cubester_audio_width_blocks")
            col.prop(adv_obj, "cubester_audio_length_blocks")

            rows = adv_obj.cubester_audio_width_blocks
            columns = adv_obj.cubester_audio_length_blocks

            col.prop(adv_obj, "cubester_size_per_hundred_pixels")

        # materials
        box = layout.box()
        box.prop(adv_obj, "cubester_materials", icon="MATERIAL")

        if adv_obj.cubester_materials == "image":
            box.prop(adv_obj, "cubester_load_type")

            # find number of appropriate images if sequence
            if adv_obj.cubester_load_type == "multiple":
                # display number of images found there
                images = find_sequence_images(self, context)
                images_found = len(images[0]) if len(images[0]) <= adv_obj.cubester_max_images \
                    else adv_obj.cubester_max_images

                if len(images[0]):
                    box.label(str(len(images[0])) + " Images Found", icon="PACKAGE")
                box.prop(adv_obj, "cubester_max_images")
                box.prop(adv_obj, "cubester_skip_images")
                box.prop(adv_obj, "cubester_frame_step")

            box.separator()

            if adv_obj.cubester_audio_image == "image":
                box.prop(adv_obj, "cubester_use_image_color", icon="COLOR")

            if not adv_obj.cubester_use_image_color or adv_obj.cubester_audio_image == "audio":
                box.label(text="Image To Use For Colors:")
                box.prop_search(adv_obj, "cubester_color_image", bpy.data, "images")
                box.prop(adv_obj, "cubester_load_color_image")

            if adv_obj.cubester_image in bpy.data.images:
                rows = int(bpy.data.images[adv_obj.cubester_image].size[1] /
                          (adv_obj.cubester_skip_pixels + 1))
                columns = int(bpy.data.images[adv_obj.cubester_image].size[0] /
                             (adv_obj.cubester_skip_pixels + 1))

        box = layout.box()

        if adv_obj.cubester_mesh_style == "blocks":
            box.label(text="Approximate Cube Count: " + str(rows * columns))
            box.label(text="Expected Verts/Faces: " + str(rows * columns * 8) + " / " + str(rows * columns * 6))
        else:
            box.label(text="Approximate Point Count: " + str(rows * columns))
            box.label(text="Expected Verts/Faces: " + str(rows * columns) + " / " + str(rows * (columns - 1)))

        # blocks and plane generation time values
        if adv_obj.cubester_mesh_style == "blocks":
            slope = 0.0000876958
            intercept = 0.02501
            block_infl, frame_infl, intercept2 = 0.0025934, 0.38507, -0.5840189
        else:
            slope = 0.000017753
            intercept = 0.04201
            block_infl, frame_infl, intercept2 = 0.000619, 0.344636, -0.272759

        # if creating image based mesh
        points = rows * columns
        if adv_obj.cubester_audio_image == "image":
            if adv_obj.cubester_load_type == "single":
                time = rows * columns * slope + intercept  # approximate time count for mesh
            else:
                time = (points * slope) + intercept + (points * block_infl) + \
                       (images_found / adv_obj.cubester_skip_images * frame_infl) + intercept2

                box.label(text="Images To Be Used: " + str(int(images_found / adv_obj.cubester_skip_images)))
        else:
            # audio based mesh
            box.label(text="Audio Track Length: " + str(adv_obj.cubester_audio_file_length) + " frames")

            block_infl, frame_infl, intercept = 0.0948, 0.0687566, -25.85985
            time = (points * block_infl) + (adv_obj.cubester_audio_file_length * frame_infl) + intercept
            if time < 0.0:  # usually no audio loaded
                time = 0.0

        time_mod = "s"
        if time > 60:  # convert to minutes if needed
            time /= 60
            time_mod = "min"
        time = round(time, 3)

        box.label(text="Expected Time: " + str(time) + " " + time_mod)

        # advanced
        if adv_obj.cubester_audio_image == "image":
            icon_1 = "TRIA_DOWN" if adv_obj.cubester_advanced else "TRIA_RIGHT"
            # layout.separator()
            box = layout.box()
            box.prop(adv_obj, "cubester_advanced", icon=icon_1)

            if adv_obj.cubester_advanced:
                box.prop(adv_obj, "cubester_random_weights", icon="RNDCURVE")

                if not adv_obj.cubester_random_weights:
                    box.label(text="RGBA Channel Weights", icon="COLOR")
                    col = box.column(align=True)
                    col.prop(adv_obj, "cubester_weight_r")
                    col.prop(adv_obj, "cubester_weight_g")
                    col.prop(adv_obj, "cubester_weight_b")
                    col.prop(adv_obj, "cubester_weight_a")

        # generate mesh
        layout.operator("mesh.cubester", icon="OBJECT_DATA")


class CubeSter(Operator):
    bl_idname = "mesh.cubester"
    bl_label = "Generate CubeSter Mesh"
    bl_description = "Generate a mesh from an Image or Sound File"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):

        verts, faces = [], []

        start = timeit.default_timer()
        scene = bpy.context.scene
        adv_obj = scene.advanced_objects

        if adv_obj.cubester_audio_image == "image":
            if adv_obj.cubester_image != "":
                create_mesh_from_image(self, scene, verts, faces)
                frames = find_sequence_images(self, context)
                created = len(frames[0])
            else:
                self.report({'WARNING'},
                            "Please add an Image for Object generation. Operation Cancelled")
                return {"CANCELLED"}
        else:
            if (adv_obj.cubester_audio_path != "" and
                    path.isfile(adv_obj.cubester_audio_path) and
                    adv_obj.cubester_check_audio is True):

                create_mesh_from_audio(self, context, verts, faces)
                created = adv_obj.cubester_audio_file_length
            else:
                self.report({'WARNING'},
                            "Please add an Sound File for Object generation. Operation Cancelled")
                return {"CANCELLED"}

        stop = timeit.default_timer()

        if adv_obj.cubester_mesh_style == "blocks" or adv_obj.cubester_audio_image == "audio":
            self.report(
                {"INFO"},
                "CubeSter: {} blocks and {} frame(s) "
                "in {}s".format(str(int(len(verts) / 8)),
                                str(created),
                                str(round(stop - start, 4)))
            )
        else:
            self.report(
                {"INFO"},
                "CubeSter: {} points and {} frame(s) "
                "in {}s" .format(str(len(verts)),
                                 str(created),
                                 str(round(stop - start, 4)))
            )

        return {"FINISHED"}


def register():
    bpy.utils.register_module(__name__)
    bpy.app.handlers.frame_change_pre.append(material_frame_handler)


def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.app.handlers.frame_change_pre.remove(material_frame_handler)


if __name__ == "__main__":
    register()
