# This file is part of Gajim.
#
# Gajim 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; version 3 only.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.

import datetime

from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import GObject

from gajim.common.i18n import _
from gajim.common.i18n import Q_
from gajim.common.const import URIType
from gajim.common.helpers import open_uri
from gajim.common.helpers import parse_uri
from gajim.common.structs import URI
from gajim.gui.util import gtk_month
from gajim.gui.util import python_month


LABEL_DICT = {
    'fn': _('Full Name'),
    'n': _('Name'),
    'bday': _('Birthday'),
    'gender': _('Gender'),
    'adr': Q_('?profile:Address'),
    'tel': _('Phone No.'),
    'email': _('Email'),
    'impp': _('IM Address'),
    'title': Q_('?profile:Title'),
    'role': Q_('?profile:Role'),
    'org': _('Organisation'),
    'note': Q_('?profile:Note'),
    'url': _('URL'),
    'key': Q_('?profile:Public Encryption Key'),
}


FIELD_TOOLTIPS = {
    'key': _('Your public key or authentication certificate')
}


ADR_FIELDS = ['street', 'ext', 'pobox', 'code', 'locality', 'region', 'country']


ADR_PLACEHOLDER_TEXT = {
    'pobox': _('Post Office Box'),
    'street': _('Street'),
    'ext': _('Extended Address'),
    'locality': _('City'),
    'region': _('State'),
    'code': _('Postal Code'),
    'country': _('Country'),
}


DEFAULT_KWARGS = {
    'fn': {'value': ''},
    'bday': {'value': '', 'value_type': 'date'},
    'gender': {'sex': '', 'identity': ''},
    'adr': {},
    'email': {'value': ''},
    'impp': {'value': ''},
    'tel': {'value': '', 'value_type': 'text'},
    'org': {'values': []},
    'title': {'value': ''},
    'role': {'value': ''},
    'url': {'value': ''},
    'key': {'value': '', 'value_type': 'text'},
    'note': {'value': ''},
}


PROPERTIES_WITH_TYPE = [
    'adr',
    'email',
    'impp',
    'tel',
    'key',
]


ORDER = [
    'fn',
    'gender',
    'bday',
    'adr',
    'email',
    'impp',
    'tel',
    'org',
    'title',
    'role',
    'url',
    'key',
    'note',
]


SEX_VALUES = {
    'M': _('Male'),
    'F': _('Female'),
    'O': Q_('?Gender:Other'),
    'N': Q_('?Gender:None'),
    'U': _('Unknown')
}


TYPE_VALUES = {
    '-': None,
    'home': _('Home'),
    'work': _('Work')
}


class VCardGrid(Gtk.Grid):
    def __init__(self, account):
        Gtk.Grid.__init__(self)

        self._callbacks = {
            'fn': TextEntryProperty,
            'bday': DateProperty,
            'gender': GenderProperty,
            'adr': AdrProperty,
            'tel': TextEntryProperty,
            'email': TextEntryProperty,
            'impp': TextEntryProperty,
            'title': TextEntryProperty,
            'role': TextEntryProperty,
            'org': TextEntryProperty,
            'url': TextEntryProperty,
            'key': KeyProperty,
            'note': MultiLineProperty,
        }

        self.set_column_spacing(12)
        self.set_row_spacing(12)
        self.set_no_show_all(True)
        self.set_visible(True)
        self.set_halign(Gtk.Align.CENTER)

        self._account = account
        self._row_count = 0
        self._vcard = None
        self._props = []

    def set_editable(self, enabled):
        for prop in self._props:
            prop.set_editable(enabled)

    def set_vcard(self, vcard):
        self.clear()
        self._vcard = vcard

        for entry in ORDER:
            for prop in vcard.get_properties():
                if entry != prop.name:
                    continue

                self.add_property(prop)

    def get_vcard(self):
        return self._vcard

    def validate(self):
        for prop in list(self._props):
            base_prop = prop.get_base_property()
            if base_prop.is_empty:
                self.remove_property(prop)

    def add_new_property(self, name):
        kwargs = DEFAULT_KWARGS[name]
        prop = self._vcard.add_property(name, **kwargs)
        self.add_property(prop, editable=True)

    def add_property(self, prop, editable=False):
        prop_class = self._callbacks.get(prop.name)
        if prop_class is None:
            return
        prop_obj = prop_class(prop, self._account)
        prop_obj.set_editable(editable)
        prop_obj.add_to_grid(self, self._row_count)

        self._props.append(prop_obj)
        self._row_count += 1

    def remove_property(self, prop):
        self.remove_row(prop.row_number)
        self._props.remove(prop)
        self._vcard.remove_property(prop.get_base_property())

    def clear(self):
        self._vcard = None
        self._row_count = 0
        for prop in list(self._props):
            self.remove_row(prop.row_number)

        self._props = []

    def sort(self):
        self.set_vcard(self._vcard)


class DescriptionLabel(Gtk.Label):
    def __init__(self, value):
        Gtk.Label.__init__(self, label=LABEL_DICT[value])
        if value == 'adr':
            self.set_valign(Gtk.Align.START)
        else:
            self.set_valign(Gtk.Align.CENTER)
        self.get_style_context().add_class('dim-label')
        self.get_style_context().add_class('margin-right18')
        self.set_visible(True)
        self.set_xalign(1)
        self.set_tooltip_text(FIELD_TOOLTIPS.get(value, ''))


class ValueLabel(Gtk.Label):
    def __init__(self, prop, account):
        Gtk.Label.__init__(self)
        self._prop = prop
        self._uri = None
        self._account = account
        self.set_selectable(True)
        self.set_xalign(0)
        self.set_max_width_chars(50)
        self.set_valign(Gtk.Align.CENTER)
        self.set_halign(Gtk.Align.START)

        self.connect('activate-link', self._on_activate_link)
        if prop.name == 'org':
            self.set_value(prop.values[0] if prop.values else '')
        else:
            self.set_value(prop.value)

    def set_value(self, value):
        if self._prop.name == 'email':
            self._uri = URI(type=URIType.MAIL, data=value)
            self.set_markup(value)

        elif self._prop.name in ('impp', 'tel'):
            self._uri = parse_uri(value)
            self.set_markup(value)

        else:
            self.set_text(value)

    def set_markup(self, text):
        if not text:
            self.set_text('')
            return
        super().set_markup('<a href="{}">{}</a>'.format(
            GLib.markup_escape_text(text),
            GLib.markup_escape_text(text)))

    def _on_activate_link(self, _label, _value):
        open_uri(self._uri, self._account)
        return Gdk.EVENT_STOP


class SexLabel(Gtk.Label):
    def __init__(self, prop):
        Gtk.Label.__init__(self)
        self._prop = prop

        self.set_selectable(True)
        self.set_xalign(0)
        self.set_max_width_chars(50)
        self.set_valign(Gtk.Align.CENTER)
        self.set_halign(Gtk.Align.START)

        self.set_text(prop.sex)

    def set_text(self, value):
        if not value or value == '-':
            super().set_text('')
        else:
            super().set_text(SEX_VALUES[value])


class IdentityLabel(Gtk.Label):
    def __init__(self, prop):
        Gtk.Label.__init__(self)
        self._prop = prop

        self.set_selectable(True)
        self.set_xalign(0)
        self.set_max_width_chars(50)
        self.set_valign(Gtk.Align.CENTER)
        self.set_halign(Gtk.Align.START)

        self.set_text(prop.identity)

    def set_text(self, value):
        super().set_text('' if not value else value)


class ValueEntry(Gtk.Entry):
    def __init__(self, prop):
        Gtk.Entry.__init__(self)
        self.set_valign(Gtk.Align.CENTER)
        self.set_max_width_chars(50)
        if prop.name == 'org':
            self.set_text(prop.values[0] if prop.values else '')
        else:
            self.set_text(prop.value)


class AdrEntry(Gtk.Entry):
    def __init__(self, prop, type_):
        Gtk.Entry.__init__(self)
        self.set_valign(Gtk.Align.CENTER)
        self.set_max_width_chars(50)
        values = getattr(prop, type_)
        if not values:
            value = ''
        else:
            value = values[0]
        self.set_text(value)
        self.set_placeholder_text(ADR_PLACEHOLDER_TEXT.get(type_))


class IdentityEntry(Gtk.Entry):
    def __init__(self, prop):
        Gtk.Entry.__init__(self)
        self.set_valign(Gtk.Align.CENTER)
        self.set_max_width_chars(50)
        self.set_text('' if not prop.identity else prop.identity)


class AdrBox(Gtk.Box):

    __gsignals__ = {
        'field-changed': (
            GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
            None, # return value
            (str, str) # arguments
        )}

    def __init__(self, prop):
        Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=6)

        for field in ADR_FIELDS:
            entry = AdrEntry(prop, field)
            entry.connect('notify::text', self._on_text_changed, field)
            self.add(entry)

        self.show_all()

    def _on_text_changed(self, entry, _param, field):
        self.emit('field-changed', field, entry.get_text())


class AdrLabel(Gtk.Label):
    def __init__(self, prop, type_):
        Gtk.Label.__init__(self)
        self.set_selectable(True)
        self.set_xalign(0)
        self.set_max_width_chars(50)
        self.set_valign(Gtk.Align.CENTER)
        self.set_halign(Gtk.Align.START)
        values = getattr(prop, type_)
        if not values:
            value = ''
        else:
            value = values[0]
        self.set_text(value)

    def set_text(self, value):
        self.set_visible(bool(value))
        super().set_text(value)


class AdrBoxReadOnly(Gtk.Box):
    def __init__(self, prop):
        Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=6)

        self._labels = {}

        for field in ADR_FIELDS:
            label = AdrLabel(prop, field)
            self._labels[field] = label
            self.add(label)

    def set_field(self, field, value):
        self._labels[field].set_text(value)


class ValueTextView(Gtk.TextView):
    def __init__(self, prop):
        Gtk.TextView.__init__(self)
        self.props.right_margin = 8
        self.props.left_margin = 8
        self.props.top_margin = 8
        self.props.bottom_margin = 8

        self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
        self.set_hexpand(True)
        self.set_valign(Gtk.Align.FILL)

        self._prop = prop
        self.get_buffer().set_text(prop.value)
        self.get_buffer().connect('notify::text', self._on_text_changed)

    def get_text(self):
        start_iter, end_iter = self.get_buffer().get_bounds()
        return self.get_buffer().get_text(start_iter, end_iter, False)

    def _on_text_changed(self, _buffer, _param):
        self._prop.value = self.get_text()


class TypeComboBox(Gtk.ComboBoxText):
    def __init__(self, parameters):
        Gtk.ComboBoxText.__init__(self)
        self.set_valign(Gtk.Align.CENTER)
        self._parameters = parameters
        self.append('-', '-')
        self.append('home', _('Home'))
        self.append('work', _('Work'))

        values = self._parameters.get_types()
        if 'home' in values:
            self.set_active_id('home')

        elif 'work' in values:
            self.set_active_id('work')

        else:
            self.set_active_id('-')

        self.connect('notify::active-id', self._on_active_id_changed)

    def _on_active_id_changed(self, _combobox, _param):
        type_ = self.get_active_id()
        if type_ == '-':
            self._parameters.remove_types(['work', 'home'])

        elif type_ == 'work':
            self._parameters.add_types(['work'])
            self._parameters.remove_types(['home'])

        elif type_ == 'home':
            self._parameters.add_types(['home'])
            self._parameters.remove_types(['work'])

    def get_text(self):
        type_value = self.get_active_id()
        if type_value == '-':
            return ''
        return self.get_active_text()


class GenderComboBox(Gtk.ComboBoxText):
    def __init__(self, prop):
        Gtk.ComboBoxText.__init__(self)
        self.set_valign(Gtk.Align.CENTER)
        self.set_halign(Gtk.Align.START)

        self._prop = prop

        self.append('-', '-')
        for key, value in SEX_VALUES.items():
            self.append(key, value)

        if not prop.sex:
            self.set_active_id('-')
        else:
            self.set_active_id(prop.sex)


class RemoveButton(Gtk.Button):
    def __init__(self):
        Gtk.Button.__init__(self)
        self.set_valign(Gtk.Align.CENTER)
        self.set_halign(Gtk.Align.START)
        image = Gtk.Image.new_from_icon_name('user-trash-symbolic',
                                             Gtk.IconSize.MENU)
        self.set_image(image)
        self.set_no_show_all(True)


class VCardProperty:
    def __init__(self, prop):
        self._prop = prop

        self._second_column = []
        self._third_column = []

        self._desc_label = DescriptionLabel(prop.name)
        self._remove_button = RemoveButton()
        self._remove_button.connect('clicked', self._on_remove_clicked)

        self._edit_widgets = [self._remove_button]
        self._read_widgets = []

        if prop.name in PROPERTIES_WITH_TYPE:
            self._type_combobox = TypeComboBox(prop.parameters)
            self._type_combobox.connect('notify::active-id',
                                        self._on_type_changed)
            type_ = self._type_combobox.get_active_id()
            icon_name = self._get_icon_name(type_)
            self._type_image = Gtk.Image.new_from_icon_name(
                icon_name, Gtk.IconSize.MENU)
            self._type_image.set_tooltip_text(TYPE_VALUES[type_])

            if prop.name == 'adr':
                self._type_image.set_valign(Gtk.Align.START)
                self._type_combobox.set_valign(Gtk.Align.START)

            self._edit_widgets.append(self._type_combobox)
            self._read_widgets.append(self._type_image)
            self._second_column.extend([self._type_combobox, self._type_image])

    @staticmethod
    def _get_icon_name(type_):
        if type_ == 'home':
            return 'feather-home'
        if type_ == 'work':
            return 'feather-briefcase'
        return None

    def _on_type_changed(self, _combobox, _param):
        type_ = self._type_combobox.get_active_id()
        icon_name = self._get_icon_name(type_)
        self._type_image.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
        self._type_image.set_tooltip_text(TYPE_VALUES[type_])

    def _on_remove_clicked(self, button):
        button.get_parent().remove_property(self)

    @property
    def row_number(self):
        grid = self._desc_label.get_parent()
        return grid.child_get_property(self._desc_label, 'top-attach')

    def get_base_property(self):
        return self._prop

    def set_editable(self, enabled):
        for widget in self._edit_widgets:
            widget.set_visible(enabled)

        for widget in self._read_widgets:
            widget.set_visible(not enabled)

    def add_to_grid(self, grid, row_number):
        # child, left, top, width, height
        grid.attach(self._desc_label, 0, row_number, 1, 1)

        for widget in self._second_column:
            grid.attach(widget, 1, row_number, 1, 1)

        for widget in self._third_column:
            grid.attach(widget, 2, row_number, 1, 1)

        grid.attach(self._remove_button, 3, row_number, 1, 1)


class TextEntryProperty(VCardProperty):
    def __init__(self, prop, account):
        VCardProperty.__init__(self, prop)

        self._value_entry = ValueEntry(prop)
        self._value_entry.connect('notify::text', self._on_text_changed)

        self._value_label = ValueLabel(prop, account)

        self._edit_widgets.append(self._value_entry)
        self._read_widgets.append(self._value_label)

        self._third_column = [self._value_entry, self._value_label]

    def _on_text_changed(self, entry, _param):
        text = entry.get_text()
        if self._prop.name == 'org':
            self._prop.values = [text]
        else:
            self._prop.value = text
        self._value_label.set_value(text)


class MultiLineProperty(VCardProperty):
    def __init__(self, prop, _account):
        VCardProperty.__init__(self, prop)

        self._edit_text_view = ValueTextView(prop)
        self._edit_text_view.show()

        self._edit_scrolled = Gtk.ScrolledWindow()
        self._edit_scrolled.set_policy(Gtk.PolicyType.NEVER,
                                       Gtk.PolicyType.AUTOMATIC)
        self._edit_scrolled.add(self._edit_text_view)
        self._edit_scrolled.set_valign(Gtk.Align.CENTER)
        self._edit_scrolled.set_size_request(350, 100)
        self._edit_scrolled.get_style_context().add_class('profile-scrolled')

        self._read_text_view = ValueTextView(prop)
        self._read_text_view.set_sensitive(False)
        self._read_text_view.set_left_margin(0)
        self._read_text_view.show()

        self._read_scrolled = Gtk.ScrolledWindow()
        self._read_scrolled.set_policy(Gtk.PolicyType.NEVER,
                                       Gtk.PolicyType.AUTOMATIC)
        self._read_scrolled.add(self._read_text_view)
        self._read_scrolled.set_valign(Gtk.Align.CENTER)
        self._read_scrolled.set_size_request(350, 100)
        self._read_scrolled.get_style_context().add_class(
            'profile-scrolled-read')

        self._edit_widgets.append(self._edit_scrolled)
        self._read_widgets.append(self._read_scrolled)

        self._third_column = [self._edit_scrolled, self._read_scrolled]


class DateProperty(VCardProperty):
    def __init__(self, prop, account):
        VCardProperty.__init__(self, prop)

        self._box = Gtk.Box(spacing=6)
        self._value_entry = ValueEntry(prop)
        self._value_entry.set_placeholder_text(_('YYYY-MM-DD'))
        self._value_entry.connect('notify::text', self._on_text_changed)

        self._calendar_button = Gtk.MenuButton()
        image = Gtk.Image.new_from_icon_name(
            'x-office-calendar-symbolic', Gtk.IconSize.BUTTON)
        self._calendar_button.set_image(image)
        self._calendar_button.connect(
            'clicked', self._on_calendar_button_clicked)
        self._box.add(self._value_entry)
        self._box.add(self._calendar_button)
        self._box.show_all()

        self.calendar = Gtk.Calendar(year=1980, month=5, day=15)
        self.calendar.set_visible(True)
        self.calendar.connect(
            'day-selected', self._on_calendar_day_selected)

        popover = Gtk.Popover()
        popover.add(self.calendar)
        self._calendar_button.set_popover(popover)

        self._value_label = ValueLabel(prop, account)

        self._edit_widgets.append(self._box)
        self._read_widgets.append(self._value_label)

        self._third_column = [self._box, self._value_label]

    def _on_text_changed(self, entry, _param):
        text = entry.get_text()
        self._prop.value = text
        self._value_label.set_value(text)

    def _on_calendar_button_clicked(self, _widget):
        birthday = self._value_entry.get_text()
        if not birthday:
            return
        try:
            date = datetime.datetime.strptime(birthday, '%Y-%m-%d')
        except ValueError:
            return
        month = gtk_month(date.month)
        self.calendar.select_month(month, date.year)
        self.calendar.select_day(date.day)

    def _on_calendar_day_selected(self, _widget):
        year, month, day = self.calendar.get_date()  # Integers
        month = python_month(month)
        date_str = datetime.date(year, month, day).strftime('%Y-%m-%d')
        self._value_entry.set_text(date_str)


class KeyProperty(VCardProperty):
    def __init__(self, prop, _account):
        VCardProperty.__init__(self, prop)

        self._value_text_view = ValueTextView(prop)
        self._value_text_view.show()

        self._scrolled_window = Gtk.ScrolledWindow()
        self._scrolled_window.set_policy(Gtk.PolicyType.NEVER,
                                         Gtk.PolicyType.AUTOMATIC)
        self._scrolled_window.add(self._value_text_view)
        self._scrolled_window.set_valign(Gtk.Align.CENTER)
        self._scrolled_window.set_size_request(350, 200)
        self._scrolled_window.get_style_context().add_class('profile-scrolled')

        self._copy_button = Gtk.Button.new_from_icon_name('edit-copy-symbolic',
                                                          Gtk.IconSize.MENU)
        self._copy_button.connect('clicked', self._on_copy_clicked)
        self._copy_button.set_halign(Gtk.Align.START)
        self._copy_button.set_valign(Gtk.Align.CENTER)

        self._edit_widgets.append(self._scrolled_window)
        self._read_widgets.append(self._copy_button)

        self._third_column = [self._scrolled_window, self._copy_button]

    def _on_copy_clicked(self, _button):
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(self._value_text_view.get_text(), -1)


class GenderProperty(VCardProperty):
    def __init__(self, prop, _account):
        VCardProperty.__init__(self, prop)

        value_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        self._value_combobox = GenderComboBox(prop)
        self._value_combobox.connect('notify::active-id',
                                     self._on_active_id_changed)
        self._value_combobox.show()

        self._value_entry = IdentityEntry(prop)
        self._value_entry.show()
        self._value_entry.connect('notify::text', self._on_text_changed)

        value_box.add(self._value_combobox)
        value_box.add(self._value_entry)

        label_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        self._identity_label = IdentityLabel(prop)
        self._identity_label.show()
        self._sex_label = SexLabel(prop)
        self._sex_label.show()

        label_box.add(self._sex_label)
        label_box.add(self._identity_label)

        self._edit_widgets.append(value_box)
        self._read_widgets.append(label_box)

        self._third_column = [value_box, label_box]

    def _on_text_changed(self, entry, _param):
        text = entry.get_text()
        self._prop.identity = text
        self._identity_label.set_text(text)

    def _on_active_id_changed(self, combobox, _param):
        sex = combobox.get_active_id()
        self._prop.sex = None if sex == '-' else sex
        self._sex_label.set_text(sex)


class AdrProperty(VCardProperty):
    def __init__(self, prop, _account):
        VCardProperty.__init__(self, prop)

        self._entry_box = AdrBox(prop)
        self._entry_box.connect('field-changed', self._on_field_changed)

        self._read_box = AdrBoxReadOnly(prop)

        self._edit_widgets.append(self._entry_box)
        self._read_widgets.append(self._read_box)

        self._third_column = [self._entry_box, self._read_box]

    def _on_field_changed(self, _box, field, value):
        setattr(self._prop, field, [value])
        self._read_box.set_field(field, value)
