"""
Miscellaneous stuff, used in many parts of wxGlade

@copyright: 2002-2007 Alberto Griggio
@copyright: 2014 Carsten Grohmann
@license: MIT (see license.txt) - THIS PROGRAM COMES WITH NO WARRANTY
"""

import common
import config

import StringIO
import logging
import os
import re
import types
import wx

accel_table = []
"""\
accelerator table to enable keyboard shortcuts for the popup menus of the
various widgets (remove, cut, copy, paste)
"""

focused_widget = None
"""\
If not None, this is the currently selected widget - This is different from
tree.WidgetTree.cur_widget because it takes into account also SizerSlot
objects

this is an implementation hack, used to handle keyboard shortcuts for
popup menus properly (for example, to ensure that the object to remove is
the currently highlighted one, ecc...)
"""

FileSelector = wx.FileSelector
DirSelector = wx.DirSelector

hidden_property_panel = None
"""\
wxPanel used to reparent the property-notebooks when they are hidden. This
has been added on 2003-06-22 to fix what seems to me a (wx)GTK2 bug
"""

use_menu_icons = None

_currently_under_mouse = None
"""\
If not None, this is the SizerSlot wich has the "mouse focus": this is used
to restore the mouse cursor if the user cancelled the addition of a widget
"""

_get_xpm_bitmap_re = re.compile(r'"(?:[^"]|\\")*"')

_item_bitmaps = {}


class wxMSWRadioButton(wx.RadioButton):
    """\
    Custom wxRadioButton class which tries to implement a better
    GetBestSize than the default one for WXMSW (mostly copied from
    wxCheckBox::DoGetBestSize in checkbox.cpp)
    """
    __radio_size = None

    def GetBestSize(self):
        if not self.__radio_size:
            dc = wx.ScreenDC()
            dc.SetFont(wx.SystemSettings_GetFont(
                wx.SYS_DEFAULT_GUI_FONT))
            self.__radio_size = (3*dc.GetCharHeight())/2
        label = self.GetLabel()
        if label:
            w, h = self.GetTextExtent(label)
            w += self.__radio_size + self.GetCharWidth()
            if h < self.__radio_size:
                h = self.__radio_size
        else:
            w = h = self.__radio_size
        return w, h

# end of class wxGladeRadioButton


class wxGTKGladePopupMenu(wx.Menu):
    """\
    Default wxMenu seems to have probles with SetTitle on GTK
    """

    def __init__(self, title):
        wx.Menu.__init__(self)
        self.TITLE_ID = wx.NewId()
        item = self.Append(self.TITLE_ID, title)
        self.AppendSeparator()
        font = item.GetFont()
        font.SetWeight(wx.BOLD)
        item.SetFont(wx.Font(font.GetPointSize(), font.GetFamily(),
                             font.GetStyle(), wx.BOLD))

    def SetTitle(self, title):
        self.SetLabel(self.TITLE_ID, title)

if wx.Platform == '__WXMSW__':
    wxGladeRadioButton = wxMSWRadioButton
else:
    wxGladeRadioButton = wx.RadioButton


if wx.Platform == '__WXGTK__':
    wxGladePopupMenu = wxGTKGladePopupMenu
else:
    wxGladePopupMenu = wx.Menu


class SelectionTag(wx.Window):
    """\
    This is one of the small black squares that appear at the corners of the
    active widgets
    """
    def __init__(self, parent, pos=None):
        kwds = {'size': (7, 7)}
        if pos:
            kwds['position'] = pos
        wx.Window.__init__(self, parent, -1, **kwds)
        self.SetBackgroundColour(wx.BLUE)
        self.Hide()

# end of class SelectionTag


class SelectionMarker(object):
    """\
    Collection of the 4 SelectionTagS for each widget
    """
    def __init__(self, owner, parent, visible=False):
        self.visible = visible
        self.owner = owner
        self.parent = parent
        if wx.Platform == '__WXMSW__':
            self.parent = owner
        self.tag_pos = None
        self.tags = None
        self.update()
        if visible:
            for t in self.tags:
                t.Show()

    def update(self, event=None):
        if self.owner is self.parent:
            x, y = 0, 0
        else:
            x, y = self.owner.GetPosition()
        w, h = self.owner.GetClientSize()

        def position(j):
            if not j:
                return x, y          # top-left
            elif j == 1:
                return x+w-7, y      # top-right
            elif j == 2:
                return x+w-7, y+h-7  # bottom-right
            else:
                return x, y+h-7      # bottom-left

        self.tag_pos = [position(i) for i in range(4)]
        if self.visible:
            if not self.tags:
                self.tags = [SelectionTag(self.parent) for i in range(4)]
            for i in range(4):
                self.tags[i].SetPosition(self.tag_pos[i])
        if event:
            event.Skip()

    def Show(self, visible):
        if self.visible != visible:
            self.visible = visible
            if self.visible:
                if not self.tags:
                    self.tags = [SelectionTag(self.parent) for i in range(4)]
                for i in range(4):
                    self.tags[i].SetPosition(self.tag_pos[i])
                    self.tags[i].Show()
            else:
                for tag in self.tags:
                    tag.Destroy()
                self.tags = None

    def Destroy(self):
        if self.tags:
            for tag in self.tags:
                tag.Destroy()
            self.tags = None

    def Reparent(self, parent):
        self.parent = parent
        if self.tags:
            for tag in self.tags:
                tag.Reparent(parent)

# end of class SelectionMarker


class UnicodeStringIO(object):
    """\
    Wrapper class to store data in Unicode
    """

    def __init__(self, encoding=None):
        self.out = StringIO.StringIO()
        self.encoding = encoding

    def write(self, data):
        if self.encoding is not None and type(data) == type(u''):
            data = data.encode(self.encoding)
        self.out.write(data)

    def getvalue(self):
        return self.out.getvalue()

# end of class UnicodeStringIO


class AsciiStringIO(StringIO.StringIO):
    """\
    Wrapper class to store data in ASCII

    @ivar isUnicode: True if the conversion to ASCII has failed at least
                     one time
    @type isUnicode: bool
    """

    def __init__(self, buf=''):
        StringIO.StringIO.__init__(self, buf)
        self.isUnicode = isinstance(buf, types.UnicodeType)

    def write(self, s):
        if not s:
            return

        if not isinstance(s, types.StringTypes):
            s = str(s)

        if isinstance(s, types.UnicodeType):
            try:
                s = s.encode('ascii')
            except UnicodeEncodeError:
                self.isUnicode = True
        StringIO.StringIO.write(self, s)

# end of class AsciiStringIO


def bound(number, lower, upper):
    return min(max(lower, number), upper)


def capitalize(string):
    """\
    Return string with first character capitalised. Some acronym like
    XML, XRC.

    @param string: String to convert
    @type string:  str

    @note: Be carefully it possibly breaks i18n.
    @rtype: str
    """
    # Don't capitalise those terms
    if string.upper() in ['XML', 'XRC', 'URL']:
        return string.upper()

    return string.capitalize()


def color_to_string(color):
    """\
    returns the hexadecimal string representation of the given colour

    Example::
        >>> color_to_string(wx.Colour(255, 255, 255))
       '#ffffff'

    @rtype: str
    """
    import operator
    return '#' + reduce(operator.add, ['%02x' % bound(c, 0, 255) for c in
                                       color.Get()])


def string_to_color(color):
    """\
    Returns the wxColour which corresponds to the given
    hexadecimal string representation

    Example::
        >>> string_to_color("#ffffff")
        wx.Colour(255, 255, 255)

    @rtype: wx.Colour
    """
    if len(color) != 7:
        raise ValueError
    return apply(wx.Colour, [int(color[i:i + 2], 16) for i in range(1, 7, 2)])


def format_for_version(version):
    """\
    Return the version information in C{for_version} in a string.

    Example::
        >>> format_for_version((2, 8))
        '2.8'

    @rtype: str
    @see: L{wxglade.codegen.BaseLangCodeWriter.for_version}
    """
    return '%s.%s' % version


def format_supported_by(version):
    """\
    Return formatted version string.

    Example::
        >>> format_supported_by('wx3')
        '3'
        >>> format_supported_by('wx28')
        '2.8'

    @param version: Version as specified in L{common.widget_config}
    @type version:  str

    @rtype: str

    @see: L{wcodegen.BaseWidgetWriter.is_supported()}
    @see: L{common.widget_config}
    """
    assert isinstance(version, str)

    if len(version) == 3:
        formatted = '%s' % version[2]
    elif len(version) == 4:
        formatted = '%s.%s' % (version[2], version[3])
    else:
        raise ValueError(_('Unknown version format for "%s"') %
                         repr(version))
    return formatted


def get_toplevel_parent(obj):
    if not isinstance(obj, wx.Window):
        window = obj.widget
    else:
        window = obj
    while window and not window.IsTopLevel():
        window = window.GetParent()
    return window


def get_toplevel_widget(widget):
    from edit_windows import EditBase, TopLevelBase
    from edit_sizers import Sizer
    if isinstance(widget, Sizer):
        widget = widget.window
    assert isinstance(widget, EditBase), \
        _("EditBase or SizerBase object needed")
    while widget and not isinstance(widget, TopLevelBase):
        widget = widget.parent
    return widget


def check_wx_version(major, minor=0, release=0, revision=0):
    """\
    returns True if the current wxPython version is at least
    major.minor.release
    """
    return wx.VERSION[:-1] >= (major, minor, release, revision)


def append_item(menu, id, text, xpm_file_or_artid=None):
    global use_menu_icons
    if use_menu_icons is None:
        use_menu_icons = config.preferences.use_menu_icons
    item = wx.MenuItem(menu, id, text)
    if wx.Platform == '__WXMSW__':
        path = 'msw/'
    else:
        path = 'gtk/'
    path = os.path.join(config.icons_path, path)
    if use_menu_icons and xpm_file_or_artid is not None:
        bmp = None
        if not xpm_file_or_artid.startswith('wxART_'):
            try:
                bmp = _item_bitmaps[xpm_file_or_artid]
            except KeyError:
                f = os.path.join(path, xpm_file_or_artid)
                if os.path.isfile(f):
                    bmp = _item_bitmaps[xpm_file_or_artid] = \
                        wx.Bitmap(f, wx.BITMAP_TYPE_XPM)
                else:
                    bmp = None
        else:
            # xpm_file_or_artid is an id for wx.ArtProvider
            bmp = wx.ArtProvider.GetBitmap(
                xpm_file_or_artid, wx.ART_MENU, (16, 16))
        if bmp is not None:
            try:
                item.SetBitmap(bmp)
            except AttributeError:
                pass
    menu.AppendItem(item)


def _remove():
    global focused_widget
    if focused_widget is not None:
        focused_widget.remove()
        focused_widget = None


def _cut():
    global focused_widget
    if focused_widget is not None:
        try:
            focused_widget.clipboard_cut()
        except AttributeError:
            pass
        else:
            focused_widget = None


def _copy():
    if focused_widget is not None:
        try:
            focused_widget.clipboard_copy()
        except AttributeError:
            pass


def _paste():
    if focused_widget is not None:
        try:
            focused_widget.clipboard_paste()
        except AttributeError:
            pass


accel_table = [
    (0, wx.WXK_DELETE, _remove),
    (wx.ACCEL_CTRL, ord('C'), _copy),
    (wx.ACCEL_CTRL, ord('X'), _cut),
    (wx.ACCEL_CTRL, ord('V'), _paste),
    ]


def _reverse_dict(src):
    """\
    Returns a dictionary whose keys are 'src' values and values 'src' keys.
    """
    ret = {}
    for key, val in src.iteritems():
        ret[val] = key
    return ret


def get_geometry(win):
    x, y = win.GetPosition()
    w, h = win.GetSize()
    if 0 <= x <= wx.SystemSettings_GetMetric(wx.SYS_SCREEN_X) and \
       0 <= y <= wx.SystemSettings_GetMetric(wx.SYS_SCREEN_Y):
        return x, y, w, h
    return None


def set_geometry(win, geometry):
    if geometry is None:
        return
    try:
        if len(geometry) == 4:
            win.SetDimensions(*[int(x) for x in geometry])
        else:
            win.SetPosition([int(x) for x in geometry])
    except Exception:
        logging.exception(_('Internal Error'))


# snagged out of the Python cookbook
def import_name(module_path, name):
    import imp
    path, mname = os.path.split(module_path)
    #logging.debug('path=%s, mname=%s', path, mname)
    mname = os.path.splitext(mname)[0]
    #logging.debug('mname: %s', mname)
    try:
        mfile, pathname, description = imp.find_module(mname, [path])
        try:
            module = imp.load_module(mname, mfile, pathname, description)
        finally:
            mfile.close()
    except ImportError:
        logging.exception(_('Internal Error'))
        return None
    return vars(module)[name]


#-----------------------------------------------------------------------------
# helper functions to work with a Unicode-enabled wxPython
#-----------------------------------------------------------------------------

def streq(s1, s2):
    """\
    Returns True if the strings or unicode objects s1 and s2 are equal, i.e.
    contain the same text. Appropriate encoding/decoding are performed to
    make the comparison

    @rtype: bool
    """
    try:
        return s1 == s2
    except UnicodeError:
        if type(s1) == type(u''):
            s1 = s1.encode(common.app_tree.app.encoding)
        else:
            s2 = s2.encode(common.app_tree.app.encoding)
        return s1 == s2


def wxstr(s, encoding=None):
    """\
    Converts the object s to str or unicode, according to what wxPython
    expects.

    @rtype: str
    """
    if encoding is None:
        if common.app_tree is None:
            return str(s)
        else:
            encoding = common.app_tree.app.encoding
    if wx.USE_UNICODE:
        if type(s) != type(u''):
            return unicode(str(s), encoding)
        else:
            return s
    else:
        if type(s) == type(u''):
            return s.encode(encoding)
        else:
            return str(s)


def design_title(title):
    return _('<Design> - ') + title


def get_xpm_bitmap(path):
    bmp = wx.NullBitmap
    if not os.path.exists(path):
        if '.zip' in path:
            import zipfile
            archive, name = path.split('.zip', 1)
            archive += '.zip'
            if name.startswith(os.sep):
                name = name.split(os.sep, 1)[1]
            if zipfile.is_zipfile(archive):
                # extract the XPM lines...
                try:
                    data = zipfile.ZipFile(archive).read(name)
                    data = [d[1:-1] for d in _get_xpm_bitmap_re.findall(data)]
##                     logging.debug("DATA:")
##                     for d in data:
##                         logging.debug(d)
                    bmp = wx.BitmapFromXPMData(data)
                except:
                    logging.exception(_('Internal Error'))
                    bmp = wx.NullBitmap
    else:
        bmp = wx.Bitmap(path, wx.BITMAP_TYPE_XPM)
    return bmp


def get_relative_path(path, for_preview=False):
    """\
    Get an absolute path relative to the current output directory (where the
    code is generated).
    """
    if os.path.isabs(path):
        return path
    p = common.app_tree.app.output_path
    if for_preview:
        p = getattr(common.app_tree.app, 'real_output_path', '')
        p = common.encode_from_xml(common.encode_to_xml(p))
    d = os.path.dirname(p)
    if d:
        path = os.path.join(d, path)
    else:
        path = os.path.abspath(path)
    return path
