#!/usr/bin/env python3

from typing import Any

from com.sun.star.view.SelectionType import SINGLE, MULTI, RANGE

from .easyevents import *
from .easydocs import LODocuments
from .easymain import (log, TITLE,
    BaseObject, Color,
    create_instance, set_properties)
from .easytools import _, LOInspect, Paths, Services


__all__ = [
    'LODialog',
    'inputbox',
]


COLOR_ON_FOCUS = Color()('LightYellow')
SEPARATION = 5
MODELS = {
    'label': 'com.sun.star.awt.UnoControlFixedTextModel',
    'text': 'com.sun.star.awt.UnoControlEditModel',
    'button': 'com.sun.star.awt.UnoControlButtonModel',
    'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel',
    'radio': 'com.sun.star.awt.UnoControlRadioButtonModel',
    'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel',
    'image': 'com.sun.star.awt.UnoControlImageControlModel',
    'listbox': 'com.sun.star.awt.UnoControlListBoxModel',
    'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel',
    'tree': 'com.sun.star.awt.tree.TreeControlModel',
    'grid': 'com.sun.star.awt.grid.UnoControlGridModel',
    'pages': 'com.sun.star.awt.UnoMultiPageModel',
    'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel',
    'combobox': 'com.sun.star.awt.UnoControlComboBoxModel',
    'spinbutton': 'com.sun.star.awt.UnoControlSpinButtonModel',
    'numeric': 'com.sun.star.awt.UnoControlNumericFieldModel',
}

TYPE_CONTROL = {
    'stardiv.Toolkit.UnoFixedTextControl': 'label',
    'stardiv.Toolkit.UnoEditControl': 'text',
    'stardiv.Toolkit.UnoButtonControl': 'button',
}

IMPLEMENTATIONS = {
    'grid': 'stardiv.Toolkit.GridControl',
    'link': 'stardiv.Toolkit.UnoFixedHyperlinkControl',
    'roadmap': 'stardiv.Toolkit.UnoRoadmapControl',
    'pages': 'stardiv.Toolkit.UnoMultiPageControl',
}


def add_listeners(events, control):
    name = control.Model.Name
    listeners = {
        'addActionListener': EventsButton,
        # ~ 'addMouseListener': EventsMouse,
        # ~ 'addFocusListener': EventsFocus,
        # ~ 'addItemListener': EventsItem,
        # ~ 'addKeyListener': EventsKey,
        # ~ 'addTabListener': EventsTab,
        # ~ 'addSpinListener': EventsSpin,
    }
    if hasattr(control, 'obj'):
        control = control.obj
    # ~ log.debug(control.ImplementationName)
    is_grid = control.ImplementationName == IMPLEMENTATIONS['grid']
    is_link = control.ImplementationName == IMPLEMENTATIONS['link']
    is_roadmap = control.ImplementationName == IMPLEMENTATIONS['roadmap']
    is_pages = control.ImplementationName == IMPLEMENTATIONS['pages']

    for key, value in listeners.items():
        if hasattr(control, key):
            if is_grid and key == 'addMouseListener':
                control.addMouseListener(EventsMouseGrid(events, name))
                continue
            if is_link and key == 'addMouseListener':
                control.addMouseListener(EventsMouseLink(events, name))
                continue
            if is_roadmap and key == 'addItemListener':
                control.addItemListener(EventsItemRoadmap(events, name))
                continue

            getattr(control, key)(listeners[key](events, name))

    if is_grid:
        controllers = EventsGrid(events, name)
        control.addSelectionListener(controllers)
        control.Model.GridDataModel.addGridDataListener(controllers)
    return


class UnoBaseObject(object):

    def __init__(self, obj: Any):
        self._obj = obj
        self._model = obj.Model

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    @property
    def obj(self):
        return self._obj

    @property
    def model(self):
        return self._model
    @property
    def m(self):
        return self._model

    @property
    def properties(self):
        data = LOInspect(self.obj).properties
        return data
    @properties.setter
    def properties(self, properties: dict):
        set_properties(self.model, properties)

    @property
    def name(self):
        return self.model.Name

    @property
    def parent(self):
        return self.obj.Context

    @property
    def tag(self):
        return self.model.Tag
    @tag.setter
    def tag(self, value):
        self.model.Tag = value

    @property
    def visible(self):
        return self.obj.Visible
    @visible.setter
    def visible(self, value):
        self.obj.setVisible(value)

    @property
    def enabled(self):
        return self.model.Enabled
    @enabled.setter
    def enabled(self, value):
        self.model.Enabled = value

    @property
    def step(self):
        return self.model.Step
    @step.setter
    def step(self, value):
        self.model.Step = value

    @property
    def align(self):
        return self.model.Align
    @align.setter
    def align(self, value):
        self.model.Align = value

    @property
    def valign(self):
        return self.model.VerticalAlign
    @valign.setter
    def valign(self, value):
        self.model.VerticalAlign = value

    @property
    def font_weight(self):
        return self.model.FontWeight
    @font_weight.setter
    def font_weight(self, value):
        self.model.FontWeight = value

    @property
    def font_height(self):
        return self.model.FontHeight
    @font_height.setter
    def font_height(self, value):
        self.model.FontHeight = value

    @property
    def font_name(self):
        return self.model.FontName
    @font_name.setter
    def font_name(self, value):
        self.model.FontName = value

    @property
    def font_underline(self):
        return self.model.FontUnderline
    @font_underline.setter
    def font_underline(self, value):
        self.model.FontUnderline = value

    @property
    def text_color(self):
        return self.model.TextColor
    @text_color.setter
    def text_color(self, value):
        self.model.TextColor = value

    @property
    def back_color(self):
        return self.model.BackgroundColor
    @back_color.setter
    def back_color(self, value):
        self.model.BackgroundColor = value

    @property
    def multi_line(self):
        return self.model.MultiLine
    @multi_line.setter
    def multi_line(self, value):
        self.model.MultiLine = value

    @property
    def help_text(self):
        return self.model.HelpText
    @help_text.setter
    def help_text(self, value):
        self.model.HelpText = value

    @property
    def border(self):
        return self.model.Border
    @border.setter
    def border(self, value):
        # ~ Bug for report
        self.model.Border = value

    @property
    def width(self):
        return self._model.Width
    @width.setter
    def width(self, value):
        self.model.Width = value

    @property
    def height(self):
        return self.model.Height
    @height.setter
    def height(self, value):
        self.model.Height = value

    def _get_possize(self, name):
        ps = self.obj.getPosSize()
        return getattr(ps, name)

    def _set_possize(self, name, value):
        ps = self.obj.getPosSize()
        setattr(ps, name, value)
        self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE)
        return

    @property
    def x(self):
        if hasattr(self.model, 'PositionX'):
            return self.model.PositionX
        return self._get_possize('X')
    @x.setter
    def x(self, value):
        if hasattr(self.model, 'PositionX'):
            self.model.PositionX = value
        else:
            self._set_possize('X', value)

    @property
    def y(self):
        if hasattr(self.model, 'PositionY'):
            return self.model.PositionY
        return self._get_possize('Y')
    @y.setter
    def y(self, value):
        if hasattr(self.model, 'PositionY'):
            self.model.PositionY = value
        else:
            self._set_possize('Y', value)

    @property
    def tab_index(self):
        return self._model.TabIndex
    @tab_index.setter
    def tab_index(self, value):
        self.model.TabIndex = value

    @property
    def tab_stop(self):
        return self._model.Tabstop
    @tab_stop.setter
    def tab_stop(self, value):
        self.model.Tabstop = value

    @property
    def ps(self):
        ps = self.obj.getPosSize()
        return ps
    @ps.setter
    def ps(self, ps):
        self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE)

    def set_focus(self):
        self.obj.setFocus()
        return

    def ps_from(self, source):
        self.ps = source.ps
        return

    def center(self, horizontal=True, vertical=False):
        p = self.parent.Model
        w = p.Width
        h = p.Height
        if horizontal:
            x = w / 2 - self.width / 2
            self.x = x
        if vertical:
            y = h / 2 - self.height / 2
            self.y = y
        return

    def move(self, origin, x=0, y=5, center=False):
        if x:
            self.x = origin.x + origin.width + x
        else:
            self.x = origin.x
        if y:
            h = origin.height
            if y < 0:
                h = 0
            self.y = origin.y + h + y
        else:
            self.y = origin.y

        if center:
            self.center()
        return


class UnoLabel(UnoBaseObject):

    def __init__(self, obj):
        super().__init__(obj)

    @property
    def type(self):
        return 'label'

    @property
    def value(self):
        return self.model.Label
    @value.setter
    def value(self, value):
        self.model.Label = value


# ~ https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1awt_1_1UnoControlEditModel.html
class UnoText(UnoBaseObject):

    def __init__(self, obj):
        super().__init__(obj)

    @property
    def type(self):
        return 'text'

    @property
    def value(self):
        return self.model.Text
    @value.setter
    def value(self, value):
        self.model.Text = value

    @property
    def echochar(self):
        return chr(self.model.EchoChar)
    @echochar.setter
    def echochar(self, value):
        if value:
            self.model.EchoChar = ord(value[0])
        else:
            self.model.EchoChar = 0

    def validate(self):
        return


class UnoButton(UnoBaseObject):

    def __init__(self, obj):
        super().__init__(obj)

    @property
    def type(self):
        return 'button'

    @property
    def value(self):
        return self.model.Label
    @value.setter
    def value(self, value):
        self.model.Label = value

    @property
    def image(self):
        return self.model.ImageURL
    @image.setter
    def image(self, value):
        self.model.ImageURL = Paths.to_url(value)


UNO_CLASSES = {
    'label': UnoLabel,
    'text': UnoText,
    'button': UnoButton,
    # ~ 'link': UnoLabelLink,
    # ~ 'radio': UnoRadio,
    # ~ 'checkbox': UnoCheckBox,
    # ~ 'image': UnoImage,
    # ~ 'listbox': UnoListBox,
    # ~ 'roadmap': UnoRoadmap,
    # ~ 'tree': UnoTree,
    # ~ 'grid': UnoGrid,
    # ~ 'pages': UnoPages,
    # ~ 'spinbutton': UnoSpinButton,
    # ~ 'numeric': UnoNumericField,
}


class DialogBox(BaseObject):
    SERVICE = 'com.sun.star.awt.DialogProvider'
    SERVICE_DIALOG = 'com.sun.star.awt.UnoControlDialog'

    def __init__(self, properties: dict={}):
        self._controls = {}
        obj = self._create(properties)
        super().__init__(obj)
        self._init_controls()

    def _create_from_path(self, path: str):
        dp = create_instance(self.SERVICE, True)
        dialog = dp.createDialog(Paths.to_url(path))
        return dialog

    def _create_from_location(self, properties: dict):
        # ~ uid = docs.active.uid
        # ~ url = f'vnd.sun.star.tdoc:/{uid}/Dialogs/{library}/{name}.xml'

        name = properties['Name']
        library = properties.get('Library', 'Standard')
        location = properties.get('Location', 'application').lower()
        if location == 'user':
            location = 'application'

        url = f'vnd.sun.star.script:{library}.{name}?location={location}'

        if location == 'document':
            doc = LODocuments().active.obj
            dp = create_instance(self.SERVICE, arguments=doc)
        else:
            dp = create_instance(self.SERVICE, True)

        dialog = dp.createDialog(url)
        return dialog

    def _create_from_properties(self, properties: dict):
        dialog = create_instance(self.SERVICE_DIALOG, True)
        model = create_instance(f'{self.SERVICE_DIALOG}Model', True)

        properties['Width'] = properties.get('Width', 200)
        properties['Height'] = properties.get('Height', 150)

        set_properties(model, properties)
        dialog.setModel(model)
        dialog.setVisible(False)
        dialog.createPeer(Services.toolkit, None)
        return dialog

    def _create(self, properties: dict):
        path = properties.pop('Path', '')
        if path:
            dialog = self._create_from_path(path)
        elif 'Location' in properties:
            dialog = self._create_from_location(properties)
        else:
            dialog = self._create_from_properties(properties)
        return dialog

    def _init_controls(self):
        for control in self.obj.Controls:
            tipo = control.ImplementationName
            name = control.Model.Name
            if not tipo in TYPE_CONTROL:
                log.debug(f'Type control: {tipo}')
                raise AttributeError(f"Has no class '{tipo}'")
            control = UNO_CLASSES[TYPE_CONTROL[tipo]](control)
            setattr(self, name, control)
            self._controls[name] = control
        return

    def execute(self):
        return self.obj.execute()

    def open(self):
        return self.execute()

    def close(self, value=0):
        value = self.obj.endDialog(value)
        return value

    # ~ def close(self, value=0):
        # ~ if self._modal:
            # ~ value = self.obj.endDialog(value)
        # ~ else:
            # ~ self.visible = False
            # ~ self.obj.dispose()
        # ~ return value

    # ~ def show(self, modal=True):
        # ~ self._modal = modal
        # ~ if modal:
            # ~ return self.obj.execute()
        # ~ else:
            # ~ self.visible = True
        # ~ return

    @property
    def model(self):
        return self.obj.Model

    @property
    def name(self):
        return self.model.Name

    @property
    def height(self):
        return self.model.Height
    @height.setter
    def height(self, value):
        self.model.Height = value

    @property
    def width(self):
        return self.model.Width
    @width.setter
    def width(self, value):
        self.model.Width = value

    @property
    def visible(self):
        return self.obj.Visible
    @visible.setter
    def visible(self, value):
        self.obj.Visible = value

    @property
    def step(self):
        return self.model.Step
    @step.setter
    def step(self, value):
        self.model.Step = value

    @property
    def color_on_focus(self):
        return self._color_on_focus
    @color_on_focus.setter
    def color_on_focus(self, value):
        self._color_on_focus = get_color(value)

    @property
    def events(self):
        return self._events
    @events.setter
    def events(self, controllers):
        self._events = controllers(self)
        self._connect_listeners()

    def _connect_listeners(self):
        for control in self.obj.Controls:
            add_listeners(self.events, control)
        return

    def _set_image_url(self, image: str):
        if Paths.exists(image):
            return Paths.to_url(image)

        path = Paths.join(self._path, DIR['images'], image)
        return Paths.to_url(path)

    def _special_properties(self, tipo, properties):
        if tipo == 'link' and not 'Label' in properties:
            properties['Label'] = properties['URL']
            return properties

        if tipo == 'button':
            if 'ImageURL' in properties:
                properties['ImageURL'] = self._set_image_url(properties['ImageURL'])
            properties['FocusOnClick'] = properties.get('FocusOnClick', False)
            return properties

        if tipo == 'roadmap':
            properties['Height'] = properties.get('Height', self.height)
            if 'Title' in properties:
                properties['Text'] = properties.pop('Title')
            return properties

        if tipo == 'tree':
            properties['SelectionType'] = properties.get('SelectionType', SINGLE)
            return properties

        if tipo == 'grid':
            properties['X'] = properties.get('X', SEPARATION)
            properties['Y'] = properties.get('Y', SEPARATION)
            properties['Width'] = properties.get('Width', self.width - SEPARATION * 2)
            properties['Height'] = properties.get('Height', self.height - SEPARATION * 2)
            properties['ShowRowHeader'] = properties.get('ShowRowHeader', True)
            return properties

        if tipo == 'pages':
            properties['Width'] = properties.get('Width', self.width)
            properties['Height'] = properties.get('Height', self.height)

        return properties

    def add_control(self, properties):
        tipo = properties.pop('Type').lower()
        root = properties.pop('Root', '')
        sheets = properties.pop('Sheets', ())
        columns = properties.pop('Columns', ())

        properties = self._special_properties(tipo, properties)
        model = self.model.createInstance(MODELS[tipo])

        set_properties(model, properties)
        name = properties['Name']
        self.model.insertByName(name, model)
        control = self.obj.getControl(name)
        add_listeners(self.events, control)
        control = UNO_CLASSES[tipo](control)

        if tipo in ('listbox',):
            control.path = self.path

        if tipo == 'tree' and root:
            control.root = root
        elif tipo == 'grid' and columns:
            control.columns = columns
        elif tipo == 'pages' and sheets:
            control.sheets = sheets
            control.events = self.events

        setattr(self, name, control)
        self._controls[name] = control
        return control


class LODialog():
    @classmethod
    def create(cls, properties: dict={}):
        return DialogBox(properties)


def inputbox(message, default='', title=TITLE, echochar=''):

    class ControllersInput(object):

        def __init__(self, dialog):
            self.d = dialog

        def cmd_ok_action(self, event):
            self.d.close(1)
            return

    properties = {
        'Title': title,
        'Width': 200,
        'Height': 80,
    }
    dlg = DialogBox(properties)
    dlg.events = ControllersInput

    properties = {
        'Type': 'Label',
        'Name': 'lbl_msg',
        'Label': message,
        'Width': 140,
        'Height': 50,
        'X': 5,
        'Y': 5,
        'MultiLine': True,
        'Border': 1,
    }
    dlg.add_control(properties)

    properties = {
        'Type': 'Text',
        'Name': 'txt_value',
        'Text': default,
        'Width': 190,
        'Height': 15,
    }
    if echochar:
        properties['EchoChar'] = ord(echochar[0])
    dlg.add_control(properties)
    dlg.txt_value.move(dlg.lbl_msg)

    properties = {
        'Type': 'button',
        'Name': 'cmd_ok',
        'Label': _('OK'),
        'Width': 40,
        'Height': 15,
        'DefaultButton': True,
        'PushButtonType': 1,
    }
    dlg.add_control(properties)
    dlg.cmd_ok.move(dlg.lbl_msg, 10, 0)

    properties = {
        'Type': 'button',
        'Name': 'cmd_cancel',
        'Label': _('Cancel'),
        'Width': 40,
        'Height': 15,
        'PushButtonType': 2,
    }
    dlg.add_control(properties)
    dlg.cmd_cancel.move(dlg.cmd_ok)

    value = ''
    if dlg.open():
        value = dlg.txt_value.value

    return value
