# ***** 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 LICENCE BLOCK *****

bl_info = {
    "name": "Intellisense for Text Editor",
    "author": "Mackraken",
    "version": (0, 2, 1),
    "blender": (2, 78, 5),
    "location": "Ctrl + Space at Text Editor",
    "description": "Adds intellense to the Text Editor",
    "warning": "Only works with 2.57 intellisense",
    "wiki_url": "",
    "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
    "category": "Development"}

import bpy
from bpy_extras import keyconfig_utils


def complete(context):
    from console import intellisense
    from console_python import get_console

    sc = context.space_data
    text = sc.text

    region = context.region
    for area in context.screen.areas:
        if area.type == "CONSOLE":
            region = area.regions[1]
            break

    console = get_console(hash(region))[0]

    line = text.current_line.body
    cursor = text.current_character

    result = intellisense.expand(line, cursor, console.locals, bpy.app.debug)

    return result


class Intellimenu(bpy.types.Menu):
    bl_label = ""
    bl_idname = "IntelliMenu"

    text = ""

    def draw(self, context):
        layout = self.layout
        # Very ugly see how can i fix this
        options = complete(context)

        options = options[2].split("  ")
        for op in options:
            layout.operator("text.intellioptions", text=op).text = op


# This operator executes when hits Ctrl+Space at the text editor

class Intellisense(bpy.types.Operator):
    bl_idname = "text.intellisense"
    bl_label = "Text Editor Intellisense"

    text = ""

    """
    @classmethod
    def poll(cls, context):
        return context.active_object is not None
    """

    def execute(self, context):
        sc = context.space_data
        text = sc.text

        if text.current_character > 0:
            result = complete(context)

            # print(result)

            if result[2] == "":
                text.current_line.body = result[0]
                bpy.ops.text.move(type='LINE_END')
            else:
                bpy.ops.wm.call_menu(name=Intellimenu.bl_idname)

        return {'FINISHED'}


# this operator completes the line with the options you choose from the menu
class Intellioptions(bpy.types.Operator):
    bl_idname = "text.intellioptions"
    bl_label = "Intellisense options"

    text = bpy.props.StringProperty()

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        sc = context.space_data
        text = sc.text

        if not text:
            self.report({'WARNING'},
                        "No Text-Block active. Operation Cancelled")

            return {"CANCELLED"}

        comp = self.text
        line = text.current_line.body

        lline = len(line)
        lcomp = len(comp)

        # intersect text
        intersect = [-1, -1]

        for i in range(lcomp):
            val1 = comp[0: i + 1]

            for j in range(lline):
                val2 = line[lline - j - 1::]
                # print("    ",j, val2)

                if val1 == val2:
                    intersect = [i, j]
                    break

        if intersect[0] > -1:
            newline = line[0: lline - intersect[1] - 1] + comp
        else:
            newline = line + comp

        # print(newline)
        text.current_line.body = newline

        bpy.ops.text.move(type='LINE_END')

        return {'FINISHED'}


def send_console(context, alls=0):

    sc = context.space_data
    text = sc.text

    console = None

    for area in bpy.context.screen.areas:
        if area.type == "CONSOLE":
            from console_python import get_console

            console = get_console(hash(area.regions[1]))[0]

    if console is None:
        return {'FINISHED'}

    if alls:
        for l in text.lines:
            console.push(l.body)
    else:
        # print(console.prompt)
        console.push(text.current_line.body)


class TestLine(bpy.types.Operator):
    bl_idname = "text.test_line"
    bl_label = "Test line"

    all = bpy.props.BoolProperty(default=False)

    """
    @classmethod
    def poll(cls, context):
        return context.active_object is not None
    """
    def execute(self, context):
        # print("test line")

        # send_console(context, self.all)
        sc = context.space_data
        text = sc.text

        if not text:
            self.report({'WARNING'},
                        "No Text-Block active. Operation Cancelled")

            return {"CANCELLED"}

        line = text.current_line.body
        console = None

        for area in bpy.context.screen.areas:
            if area.type == "CONSOLE":
                from console_python import get_console

                console = get_console(hash(area.regions[1]))[0]

        if console is None:
            return {'FINISHED'}

        command = ""

        forindex = line.find("for ")
        if forindex > -1:

            var = line[forindex + 4: -1]
            var = var[0: var.find(" ")]
            state = line[line.rindex(" ") + 1: -1]

            command = var + " = " + state + "[0]"

        else:
            command = line

        # print(command)
        try:
            console.push(command)
        except:
            pass

        bpy.ops.text.line_break()

        return {'FINISHED'}


class SetBreakPoint(bpy.types.Operator):
    bl_idname = "text.set_breakpoint"
    bl_label = "Set Breakpoint"

    def execute(self, context):

        sc = bpy.context.space_data
        text = sc.text

        if not text:
            self.report({'WARNING'},
                        "No Text-Block active. Operation Cancelled")

            return {"CANCELLED"}

        line = text.current_line
        br = " #breakpoint"
        # print(line.body.find(br))
        if line.body.find(br) > -1:

            line.body = line.body.replace(br, "")
        else:

            line.body += br

        return {'FINISHED'}


class Debug(bpy.types.Operator):
    bl_idname = "text.debug"
    bl_label = "Debug"

    def execute(self, context):
        success = False

        binpath = bpy.app.binary_path

        addonspath = binpath[
                0: binpath.rindex("\\") + 1] + \
                str(bpy.app.version[0]) + "." + \
                str(bpy.app.version[1]) + "\\scripts\\addons\\"

        print(addonspath)

        sc = context.space_data
        text = sc.text

        if not text:
            self.report({'WARNING'},
                        "No Text-Block active. Operation Cancelled")

            return {"CANCELLED"}

        br = " #breakpoint"

        filepath = addonspath + "debug.py"
        with open(filepath, "w") as files:
            files.write("import pdb\n")

            for line in text.lines:
                l = line.body

                if line.body.find(br) > -1:
                    indent = ""
                    for letter in line.body:

                        if not letter.isalpha():
                            indent += letter
                        else:
                            break
                    files.write(l[0: -len(br)] + "\n")

                    files.write(indent + "pdb.set_trace()\n")

                else:
                    files.write(line.body + "\n")

            files.close()
            success = True

        # not working as runcall expects a specific function to be called not a string
        # import pdb
        # import debug
        # pdb.runcall("debug")
        if success:
            self.report({'INFO'},
                        "Created a debug file: {}".format(filepath))

        return {'FINISHED'}


class DebugPanel(bpy.types.Panel):
    bl_label = "Debug"
    bl_space_type = "TEXT_EDITOR"
    bl_region_type = "UI"
    # bl_context = "object"

    text = bpy.props.StringProperty()

    def draw(self, context):
        layout = self.layout
        row = layout.row()

        row = layout.row()
        row.operator("text.debug", text="Debug")
        row = layout.row()
        row.operator("text.set_breakpoint")
        row = layout.row()
        row.operator("text.test_line").all = False
        row = layout.row()
        row.operator("text.test_line", text="Test All").all = True

        row = layout.row()
        row.label(text="Coming Soon ...")


# ASSIGN A KEY

# section = Input section. "Window, Text, ..."
# name = operator name or wm.call_menu
# type = key
# event = keyboard event (Press, release, ...)
# mods = array containing key modifiers (["ctrl", "alt", "shift"]
# propvalue = menu name, if name is set to "wm.call_menu"
# overwrite doesnt work at the moment

def assignKey(section, name, type, event, mods=[], propvalue="", overwrite=0):

    kconf = bpy.context.window_manager.keyconfigs.active

    # check section
    validsections = [item.name for item in kconf.keymaps]
    if section not in validsections:
        print(section + " is not a valid section.")
        # print(validsections)
        return False

    # check type
    type = type.upper()
    validkeys = [item.identifier for item in bpy.types.KeyMapItem.bl_rna.properties['type'].enum_items]

    if type not in validkeys:
        print(type + " is not a valid key.")
        # print(validkeys)
        return False

    # check event
    event = event.upper()
    validevents = [item.identifier for item in bpy.types.KeyMapItem.bl_rna.properties['value'].enum_items]

    if event not in validevents:
        print(event + " is not a valid event.")
        # print(validevents)

    kmap = kconf.keymaps[section]

    # get mods
    for i, mod in enumerate(mods):
        mods[i] = mod.lower()

    # any, shift, ctrl, alt, oskey
    kmod = [False, False, False, False, False]

    if "any" in mods:
        kmod[0] = True
    if "shift" in mods:
        kmod[1] = True
    if "ctrl" in mods:
        kmod[2] = True
    if "alt" in mods:
        kmod[3] = True
    if "oskey" in mods:
        kmod[4] = True

    # check if key exist
    kexists = False

    for key in kmap.keymap_items:
        keymods = [key.any, key.shift, key.ctrl, key.alt, key.oskey]
        if key.type == type and keymods == kmod:
            kexists = True
            print(key, "key exists")
            break

    if kexists:
        # overwrite?
        if overwrite:
            key.idname = name
            # key.type = type

    else:
        # create key
        key = kmap.keymap_items.new(name, type, event, False)
        key.any = kmod[0]
        key.shift = kmod[1]
        key.ctrl = kmod[2]
        key.alt = kmod[3]
        key.oskey = kmod[4]

        if propvalue != "":
            key.properties.name = propvalue


# ------------------- REGISTER ------------------------------------------------

# For explanation of the data structure look in modules\bpy_extras\keyconfig_utils
# (1) Identifier, (2) operator call/key assigment, (3) properties passed on call
KEYMAPS = (
    (("Text", "VIEW_3D", "WINDOW", False), (  # (1)
        ({"idname": Intellisense.bl_idname,
          "type": 'SPACE', "value": 'PRESS', "ctrl": True},  # (2)
         ()),  # (3)
        ({"idname": TestLine.bl_idname,
          "type": 'RET', "value": 'PRESS'},  # (2)
         ()),  # (3)
    )),
)


classes = (
    Intellisense,
    Intellioptions,
    Intellimenu,
    DebugPanel,
    TestLine,
    SetBreakPoint,
    Debug,
    )


def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    keyconfig_utils.addon_keymap_register(bpy.context.window_manager, KEYMAPS)
    # note: these keys were assigned and never removed
    # moved to the new system introduced in 2.78.x (the old function is still here)
    # assignKey("Text", "text.intellisense", "SPACE", "Press", ["ctrl"])
    # assignKey("Text", "text.test_line", "RET", "Press", [], "", 1)


def unregister():
    keyconfig_utils.addon_keymap_unregister(bpy.context.window_manager, KEYMAPS)

    for cls in classes:
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()
