from __future__ import print_function
from __future__ import absolute_import
from __future__ import division

try:
    basestring
except NameError:
    basestring = str

import os
import sys
import ast

from compas_rhino.forms import TextForm
from compas_rhino.forms import ImageForm

import System

import rhinoscriptsyntax as rs
import Rhino
import clr

clr.AddReference('Rhino.UI')
import Rhino.UI  # noqa: E402
from Rhino.UI.Dialogs import ShowMessageBox  # noqa: E402

try:
    from compas_rhino.forms import PropertyListForm
except ImportError:
    from Rhino.UI.Dialogs import ShowPropertyListBox


__all__ = [
    'wait',
    'get_tolerance',
    'toggle_toolbargroup',
    'pick_point',
    'browse_for_folder',
    'browse_for_file',
    'print_display_on',
    'display_message',
    'display_text',
    'display_image',
    'display_html',
    'update_settings',
    'update_named_values',
    'screenshot_current_view',
    'select_folder',
    'select_file',
    'unload_modules',
]


# ==============================================================================
# Truly miscellaneous :)
# ==============================================================================


def screenshot_current_view(path,
                            width=1920,
                            height=1080,
                            scale=1,
                            draw_grid=False,
                            draw_world_axes=False,
                            draw_cplane_axes=False,
                            background=False):
    """Take a screenshot of the current view.

    Parameters
    ----------
    path : str
        The filepath for saving the screenshot.

    Other Parameters
    ----------------
    width : int, optional
    height : int, optional
    scale : float, optional
    draw_grid : bool, optional
    draw_world_axes : bool, optional
    draw_cplane_axes : bool, optional
    background : bool, optional

    Returns
    -------
    bool
        True if the command was successful.
        False otherwise.

    """
    properties = [draw_grid, draw_world_axes, draw_cplane_axes, background]
    properties = ["Yes" if item else "No" for item in properties]
    scale = max(1, scale)  # the rhino command requires a scale > 1
    rs.EnableRedraw(True)
    rs.Sleep(0)
    result = rs.Command("-_ViewCaptureToFile \"" + os.path.abspath(path) + "\""
                        " Width=" + str(width) +
                        " Height=" + str(height) +
                        " Scale=" + str(scale) +
                        " DrawGrid=" + properties[0] +
                        " DrawWorldAxes=" + properties[1] +
                        " DrawCPlaneAxes=" + properties[2] +
                        " TransparentBackground=" + properties[3] +
                        " _enter", False)
    rs.EnableRedraw(False)
    return result


def wait():
    """Make Rhino wait to prevent the spinning wheel from appearing.

    """
    return Rhino.RhinoApp.Wait()


def get_tolerance():
    """Get the absolute tolerance.

    Returns
    -------
    float
        The tolerance.

    """
    return rs.UnitAbsoluteTolerance()


def toggle_toolbargroup(rui, group):
    if not os.path.exists(rui) or not os.path.isfile(rui):
        return
    collection = rs.IsToolbarCollection(rui)
    if not collection:
        collection = rs.OpenToolbarCollection(rui)
        if rs.IsToolbar(collection, group, True):
            rs.ShowToolbar(collection, group)
    else:
        if rs.IsToolbar(collection, group, True):
            if rs.IsToolbarVisible(collection, group):
                rs.HideToolbar(collection, group)
            else:
                rs.ShowToolbar(collection, group)


def pick_point(message='Pick a point.'):
    point = rs.GetPoint(message)
    if point:
        return list(point)
    return None


# ==============================================================================
# File system
# ==============================================================================


def browse_for_folder(message=None, default=None):
    return rs.BrowseForFolder(folder=default, message=message, title='compas')


select_folder = browse_for_folder


def browse_for_file(title=None, folder=None, filter=None):
    if filter == 'json':
        filter = 'JSON files (*.json)|*.json||'
    elif filter == 'obj':
        filter = 'OBJ files (*.obj)|*.obj||'
    elif filter == 'fofin':
        filter = 'FOFIN session files (*.fofin)|*.fofin||'
    else:
        pass
    return rs.OpenFileName(title, filter=filter, folder=folder)


select_file = browse_for_file


# ==============================================================================
# Display
# ==============================================================================


def print_display_on(on=True):
    if on:
        rs.Command('_PrintDisplay State On Color Display Thickness 1 _Enter')
    else:
        rs.Command('_PrintDisplay State Off _Enter')


def display_message(message):
    return ShowMessageBox(message, 'Message')


def display_text(text, title='Text', width=800, height=600):
    if isinstance(text, (list, tuple)):
        text = '{0}'.format(System.Environment.NewLine).join(text)
    form = TextForm(text, title, width, height)
    return form.show()


def display_image(image, title='Image', width=800, height=600):
    form = ImageForm(image, title, width, height)
    return form.show()


def display_html():
    raise NotImplementedError


# ==============================================================================
# Settings and attributes
# ==============================================================================


def update_named_values(names, values, message='', title='Update named values', evaluate=False):
    try:
        dialog = PropertyListForm(names, values)
    except Exception:
        values = ShowPropertyListBox(message, title, names, values)
    else:
        if dialog.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow):
            values = dialog.values
        else:
            values = None
    if evaluate:
        if values:
            values = list(values)
            for i in range(len(values)):
                value = values[i]
                try:
                    value = ast.literal_eval(value)
                except (TypeError, ValueError, SyntaxError):
                    pass
                values[i] = value
    return values


def update_settings(settings, message='', title='Update settings'):
    names = sorted(settings.keys())
    values = [str(settings[name]) for name in names]
    values = update_named_values(names, values, message=message, title=title)
    if values:
        values = list(values)
        for name, value in zip(names, values):
            try:
                settings[name] = ast.literal_eval(value)
            except (TypeError, ValueError, SyntaxError):
                settings[name] = value
        return True
    return False


def unload_modules(top_level_module_name):
    """Unloads all modules named starting with the specified string.

    This function eases the development workflow when editing a library that is
    used from Rhino/Grasshopper.

    Parameters
    ----------
    top_level_module_name : :obj:`str`
        Name of the top-level module to unload.

    Returns
    -------
    list
        List of unloaded module names.
    """
    modules = filter(lambda m: m.startswith(top_level_module_name), sys.modules)

    for module in modules:
        sys.modules.pop(module)

    return modules
