# -*- coding: utf-8 -*-
# Copyright (c) 2015 sugiany
# This file is distributed under the MIT License. See the LICENSE.md for more details.

import bpy

from bpy.props import IntProperty, BoolProperty, FloatVectorProperty, FloatProperty

import bpy
import mathutils
import copy

class MengerSponge(object):
    FACE_INDICES = [
        [3, 7, 4, 0],
        [5, 6, 2, 1],
        [1, 2, 3, 0],
        [7, 6, 5, 4],
        [4, 5, 1, 0],
        [2, 6, 7, 3],
    ]

    def __init__(self, level):
        self.__level = level
        self.__max_point_number = 3 ** level
        self.__vertices_map = {}
        self.__indices = []
        self.__face_visibility = {}
        self.__faces = []

        for x in range(3):
            for y in range(3):
                for z in range(3):
                    self.__face_visibility[(x, y, z)] = [
                        x == 0 or x == 2 and (y == 1 or z == 1),
                        x == 2 or x == 0 and (y == 1 or z == 1),
                        y == 0 or y == 2 and (x == 1 or z == 1),
                        y == 2 or y == 0 and (x == 1 or z == 1),
                        z == 0 or z == 2 and (y == 1 or x == 1),
                        z == 2 or z == 0 and (y == 1 or x == 1),
                    ]

    def create(self, width, height):
        m = self.__max_point_number
        points = [
            (0, 0, 0),
            (m, 0, 0),
            (m, 0, m),
            (0, 0, m),
            (0, m, 0),
            (m, m, 0),
            (m, m, m),
            (0, m, m),
            ]
        self.__make_sub_sponge(points, None, self.__level)
        vertices = self.__make_vertices(width, height)
        return vertices, self.__faces

    def __get_vindex(self, p):
        if p in self.__vertices_map:
            return self.__vertices_map[p]
        index = len(self.__vertices_map)
        self.__vertices_map[p] = index
        return index

    def __make_vertices(self, width, height):
        vertices = [None] * len(self.__vertices_map)
        w2 = width / 2
        h2 = height / 2
        w_step = width / self.__max_point_number
        h_step = height / self.__max_point_number
        for p, i in sorted(self.__vertices_map.items(), key=lambda x: x[1]):
            vertices[i] = mathutils.Vector([
                    p[0] * w_step - w2,
                    p[1] * w_step - w2,
                    p[2] * h_step - h2,
                    ])
        return vertices

    def __make_sub_sponge(self, cur_points, face_vis, depth):
        if depth <= 0:
            if not face_vis:
                face_vis = True * 6
            cur_point_indices = []
            for p in cur_points:
                cur_point_indices.append(self.__get_vindex(p))
            for i, vis in enumerate(face_vis):
                if vis:
                    f = []
                    for vi in self.FACE_INDICES[i]:
                        f.append(cur_point_indices[vi])
                    self.__faces.append(f)
            return

        base = cur_points[0]
        width = (cur_points[1][0] - base[0]) / 3
        local_vert_map = {}
        for z in range(4):
            for y in range(4):
                for x in range(4):
                    local_vert_map[(x, y, z)] = (
                        width * x + base[0],
                        width * y + base[1],
                        width * z + base[2],
                        )

        for x in range(3):
            for y in range(3):
                for z in range(3):
                    if [x, y, z].count(1) > 1:
                        continue
                    next_points = [
                        local_vert_map[(x, y, z)],
                        local_vert_map[(x+1, y, z)],
                        local_vert_map[(x+1, y, z+1)],
                        local_vert_map[(x, y, z+1)],
                        local_vert_map[(x, y+1, z)],
                        local_vert_map[(x+1, y+1, z)],
                        local_vert_map[(x+1, y+1, z+1)],
                        local_vert_map[(x, y+1, z+1)],
                    ]
                    visibility = copy.copy(self.__face_visibility[(x, y, z)])
                    if face_vis:
                        visibility[0] = visibility[0] and (face_vis[0] or x != 0)
                        visibility[1] = visibility[1] and (face_vis[1] or x != 2)
                        visibility[2] = visibility[2] and (face_vis[2] or y != 0)
                        visibility[3] = visibility[3] and (face_vis[3] or y != 2)
                        visibility[4] = visibility[4] and (face_vis[4] or z != 0)
                        visibility[5] = visibility[5] and (face_vis[5] or z != 2)
                    self.__make_sub_sponge(
                        next_points,
                        visibility,
                        depth - 1)


class AddMengerSponge(bpy.types.Operator):
    """Add a menger sponge"""
    bl_idname = "mesh.menger_sponge_add"
    bl_label = "Menger Sponge"
    bl_options = {'REGISTER', 'UNDO'}

    level = IntProperty(
        name="Level",
        description="Sponge Level",
        min=0, max=4,
        default=1,
        )

    radius = FloatProperty(
        name="Width",
        description="Sponge Radius",
        min=0.01, max=100.0,
        default=1.0,
        )

    # generic transform props
    view_align = BoolProperty(
        name="Align to View",
        default=False,
        )
    location = FloatVectorProperty(
        name="Location",
        subtype='TRANSLATION',
        )
    rotation = FloatVectorProperty(
        name="Rotation",
        subtype='EULER',
        )

    def execute(self, context):
        sponger = MengerSponge(self.level)
        vertices, faces = sponger.create(self.radius * 2, self.radius * 2)
        del sponger

        mesh = bpy.data.meshes.new(name='Sponge')
        mesh.from_pydata(vertices, [], faces)
        uvs = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]
        mesh.uv_textures.new()
        for i, uvloop in enumerate(mesh.uv_layers.active.data):
            uvloop.uv = uvs[i%4]

        from bpy_extras import object_utils
        object_utils.object_data_add(context, mesh, operator=self)

        return {'FINISHED'}
