
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on 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 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on 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 BlenderBIM Add-on.  If not, see <http://www.gnu.org/licenses/>.

import bpy
import os
from mathutils import Vector


class Annotator:
    @staticmethod
    def get_svg_text_size(size):
        sizes = {
            "1.8": "2.97",
            "2.5": "4.13",
            "3.5": "5.78",
            "5.0": "8.25",
            "7.0": "11.55",
        }
        return float(sizes[str(size)])

    @staticmethod
    def add_text(context, related_element=None):
        curve = bpy.data.curves.new(type="FONT", name="Text")
        curve.body = "TEXT"
        obj = bpy.data.objects.new("Text", curve)
        obj.matrix_world = context.scene.camera.matrix_world
        if related_element is None:
            location, _, _, _ = Annotator.get_placeholder_coords(context)
        else:
            obj.data.BIMTextProperties.related_element = related_element
            location = related_element.location
        obj.location = location
        obj.hide_render = True
        font = bpy.data.fonts.get("OpenGost TypeB TT")
        if not font:
            font = bpy.data.fonts.load(
                os.path.join(context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
            )
            font.name = "OpenGost Type B TT"
        obj.data.font = font
        obj.data.BIMTextProperties.font_size = "2.5"
        collection = context.scene.camera.users_collection[0]
        collection.objects.link(obj)
        Annotator.resize_text(obj)
        return obj

    @staticmethod
    def resize_text(text_obj):
        camera = None
        for obj in text_obj.users_collection[0].objects:
            if isinstance(obj.data, bpy.types.Camera):
                camera = obj
                break
        if not camera:
            return
        # This is a magic number for OpenGost
        font_size = 1.6 / 1000
        font_size *= float(text_obj.data.BIMTextProperties.font_size)

        if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
            human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
        else:
            human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
        numerator, denominator = fraction.split("/")
        font_size /= float(numerator) / float(denominator)

        text_obj.data.size = font_size

    @staticmethod
    def add_line_to_annotation(obj, context, co1=None, co2=None):
        if co1 is None:
            co1, co2, _, _ = Annotator.get_placeholder_coords(context)
        co1 = obj.matrix_world.inverted() @ co1
        co2 = obj.matrix_world.inverted() @ co2
        if isinstance(obj.data, bpy.types.Mesh):
            obj.data.vertices.add(2)
            obj.data.vertices[-2].co = co1
            obj.data.vertices[-1].co = co2
            obj.data.edges.add(1)
            obj.data.edges[-1].vertices = (obj.data.vertices[-2].index, obj.data.vertices[-1].index)
        if isinstance(obj.data, bpy.types.Curve):
            polyline = obj.data.splines.new("POLY")
            polyline.points.add(1)
            polyline.points[-2].co = list(co1) + [1]
            polyline.points[-1].co = list(co2) + [1]
        return obj

    @staticmethod
    def add_plane_to_annotation(obj, context):
        co1, co2, co3, co4 = Annotator.get_placeholder_coords(context)
        co1 = obj.matrix_world.inverted() @ co1  # bot left
        co2 = obj.matrix_world.inverted() @ co2  # top left
        co3 = obj.matrix_world.inverted() @ co3  # bot right
        co4 = obj.matrix_world.inverted() @ co4  # top right

        obj.data.vertices.add(4)
        v1 = obj.data.vertices[-4]
        v2 = obj.data.vertices[-3]
        v3 = obj.data.vertices[-2]
        v4 = obj.data.vertices[-1]
        v1.co = co4
        v2.co = co2
        v3.co = co1
        v4.co = co3

        obj.data.edges.add(4)
        e1 = obj.data.edges[-4]
        e2 = obj.data.edges[-3]
        e3 = obj.data.edges[-2]
        e4 = obj.data.edges[-1]
        e1.vertices = (v1.index, v2.index)
        e2.vertices = (v2.index, v3.index)
        e3.vertices = (v3.index, v4.index)
        e4.vertices = (v4.index, v1.index)

        obj.data.loops.add(4)
        l1 = obj.data.loops[-4]
        l2 = obj.data.loops[-3]
        l3 = obj.data.loops[-2]
        l4 = obj.data.loops[-1]
        l1.vertex_index = v1.index
        l1.edge_index = e1.index
        l2.vertex_index = v2.index
        l2.edge_index = e2.index
        l3.vertex_index = v3.index
        l3.edge_index = e3.index
        l4.vertex_index = v4.index
        l4.edge_index = e4.index

        obj.data.polygons.add(1)
        p1 = obj.data.polygons[-1]
        p1.vertices = (v1.index, v2.index, v3.index, v4.index)
        p1.loop_start = l1.index
        p1.loop_total = 4
        return obj

    @staticmethod
    def get_annotation_obj(name, data_type, context):
        collection = context.scene.camera.users_collection[0]
        for obj in collection.objects:
            if name in obj.name:
                return obj
        if data_type == "mesh":
            data = bpy.data.meshes.new(name)
        elif data_type == "curve":
            data = bpy.data.curves.new(name, type="CURVE")
            data.dimensions = "3D"
            data.resolution_u = 2
        obj = bpy.data.objects.new(name, data)
        collection.objects.link(obj)
        return obj

    @staticmethod
    def get_placeholder_coords(context):
        camera = context.scene.camera
        z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
        if context.scene.render.resolution_x > context.scene.render.resolution_y:
            y = (
                camera.data.ortho_scale
                * (context.scene.render.resolution_y / context.scene.render.resolution_x)
                / 4
            )
        else:
            y = camera.data.ortho_scale / 4
        y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
        x_offset = camera.matrix_world.to_quaternion() @ Vector((y/2, 0, 0))
        return (camera.location + z_offset,
                camera.location + z_offset + y_offset,
                camera.location + z_offset + x_offset,
                camera.location + z_offset + x_offset + y_offset)
